VisualizationGraphWidget.cpp
1083 lines
| 39.3 KiB
| text/x-c
|
CppLexer
r95 | #include "Visualization/VisualizationGraphWidget.h" | |||
|
r207 | #include "Visualization/IVisualizationWidgetVisitor.h" | ||
r960 | #include "Visualization/VisualizationCursorItem.h" | |||
|
r582 | #include "Visualization/VisualizationDefs.h" | ||
r243 | #include "Visualization/VisualizationGraphHelper.h" | |||
|
r480 | #include "Visualization/VisualizationGraphRenderingDelegate.h" | ||
r1085 | #include "Visualization/VisualizationMultiZoneSelectionDialog.h" | |||
r1044 | #include "Visualization/VisualizationSelectionZoneItem.h" | |||
r1049 | #include "Visualization/VisualizationSelectionZoneManager.h" | |||
#include "Visualization/VisualizationWidget.h" | ||||
r839 | #include "Visualization/VisualizationZoneWidget.h" | |||
r58 | #include "ui_VisualizationGraphWidget.h" | |||
r1077 | #include <Actions/ActionsGuiController.h> | |||
r1328 | #include <Actions/FilteringAction.h> | |||
r848 | #include <Common/MimeTypesDef.h> | |||
r235 | #include <Data/ArrayData.h> | |||
#include <Data/IDataSeries.h> | ||||
|
r1022 | #include <Data/SpectrogramSeries.h> | ||
r1075 | #include <DragAndDrop/DragDropGuiController.h> | |||
|
r470 | #include <Settings/SqpSettingsDefs.h> | ||
r235 | #include <SqpApplication.h> | |||
r878 | #include <Time/TimeController.h> | |||
r118 | #include <Variable/Variable.h> | |||
r1349 | #include <Variable/VariableController2.h> | |||
r235 | ||||
r118 | #include <unordered_map> | |||
|
r219 | Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget") | ||
|
r179 | namespace { | ||
r1047 | /// Key pressed to enable drag&drop in all modes | |||
const auto DRAG_DROP_MODIFIER = Qt::AltModifier; | ||||
|
r179 | /// Key pressed to enable zoom on horizontal axis | ||
r958 | const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier; | |||
|
r179 | |||
/// Key pressed to enable zoom on vertical axis | ||||
r958 | 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 | |||
r1049 | /// Key pressed to enable multi selection of selection zones | |||
const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier; | ||||
r959 | /// Minimum size for the zoom box, in percentage of the axis range | |||
const auto ZOOM_BOX_MIN_SIZE = 0.8; | ||||
r960 | /// Format of the dates appearing in the label of a cursor | |||
const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz"); | ||||
|
r179 | |||
} // namespace | ||||
r118 | struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { | |||
|
r724 | explicit VisualizationGraphWidgetPrivate(const QString &name) | ||
: m_Name{name}, | ||||
|
r1271 | m_Flags{GraphFlag::EnableAll}, | ||
|
r724 | m_IsCalibration{false}, | ||
m_RenderingDelegate{nullptr} | ||||
|
r480 | { | ||
} | ||||
r445 | ||||
|
r1280 | void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable, | ||
r1346 | const DateTimeRange &range) | |||
|
r1020 | { | ||
|
r1280 | VisualizationGraphHelper::updateData(plottables, variable, range); | ||
|
r1020 | |||
// Prevents that data has changed to update rendering | ||||
m_RenderingDelegate->onPlotUpdated(); | ||||
} | ||||
|
r724 | QString m_Name; | ||
r118 | // 1 variable -> n qcpplot | |||
|
r582 | std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap; | ||
|
r1271 | GraphFlags m_Flags; | ||
r445 | bool m_IsCalibration; | |||
|
r480 | /// Delegate used to attach rendering features to the plot | ||
std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate; | ||||
r959 | ||||
r1044 | QCPItemRect *m_DrawingZoomRect = nullptr; | |||
r1046 | QStack<QPair<QCPRange, QCPRange> > m_ZoomStack; | |||
r1044 | ||||
r960 | std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr; | |||
std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr; | ||||
r959 | ||||
r1044 | VisualizationSelectionZoneItem *m_DrawingZone = nullptr; | |||
VisualizationSelectionZoneItem *m_HoveredZone = nullptr; | ||||
QVector<VisualizationSelectionZoneItem *> m_SelectionZones; | ||||
r959 | ||||
r1051 | bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even | |||
r1050 | ||||
r1292 | bool m_VariableAutoRangeOnInit = true; | |||
r959 | void startDrawingRect(const QPoint &pos, QCustomPlot &plot) | |||
{ | ||||
removeDrawingRect(plot); | ||||
auto axisPos = posToAxisPos(pos, plot); | ||||
r1044 | m_DrawingZoomRect = new QCPItemRect{&plot}; | |||
QPen p; | ||||
p.setWidth(2); | ||||
m_DrawingZoomRect->setPen(p); | ||||
r959 | ||||
r1044 | m_DrawingZoomRect->topLeft->setCoords(axisPos); | |||
m_DrawingZoomRect->bottomRight->setCoords(axisPos); | ||||
r959 | } | |||
void removeDrawingRect(QCustomPlot &plot) | ||||
{ | ||||
r1044 | if (m_DrawingZoomRect) { | |||
plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot | ||||
m_DrawingZoomRect = nullptr; | ||||
r959 | plot.replot(QCustomPlot::rpQueuedReplot); | |||
} | ||||
} | ||||
r1049 | void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph) | |||
r1044 | { | |||
r1049 | endDrawingZone(graph); | |||
r1044 | ||||
r1049 | auto axisPos = posToAxisPos(pos, graph->plot()); | |||
r1044 | ||||
r1049 | m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()}; | |||
r1044 | m_DrawingZone->setRange(axisPos.x(), axisPos.x()); | |||
m_DrawingZone->setEditionEnabled(false); | ||||
} | ||||
r1049 | void endDrawingZone(VisualizationGraphWidget *graph) | |||
r1044 | { | |||
if (m_DrawingZone) { | ||||
auto drawingZoneRange = m_DrawingZone->range(); | ||||
if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) { | ||||
m_DrawingZone->setEditionEnabled(true); | ||||
r1049 | addSelectionZone(m_DrawingZone); | |||
r1044 | } | |||
else { | ||||
r1049 | graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot | |||
r1044 | } | |||
r1049 | graph->plot().replot(QCustomPlot::rpQueuedReplot); | |||
r1044 | m_DrawingZone = nullptr; | |||
} | ||||
} | ||||
void setSelectionZonesEditionEnabled(bool value) | ||||
{ | ||||
for (auto s : m_SelectionZones) { | ||||
s->setEditionEnabled(value); | ||||
} | ||||
} | ||||
r1049 | void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; } | |||
r1047 | VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos, | |||
const QCustomPlot &plot) const | ||||
{ | ||||
VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr; | ||||
auto minDistanceToZone = -1; | ||||
for (auto zone : m_SelectionZones) { | ||||
auto distanceToZone = zone->selectTest(pos, false); | ||||
if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone) | ||||
&& distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) { | ||||
selectionZoneItemUnderCursor = zone; | ||||
} | ||||
} | ||||
return selectionZoneItemUnderCursor; | ||||
} | ||||
r1085 | QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos, | |||
const QCustomPlot &plot) const | ||||
{ | ||||
QVector<VisualizationSelectionZoneItem *> zones; | ||||
for (auto zone : m_SelectionZones) { | ||||
auto distanceToZone = zone->selectTest(pos, false); | ||||
if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) { | ||||
zones << zone; | ||||
} | ||||
} | ||||
return zones; | ||||
} | ||||
void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot) | ||||
{ | ||||
if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) { | ||||
zone->moveToTop(); | ||||
m_SelectionZones.removeAll(zone); | ||||
m_SelectionZones.append(zone); | ||||
} | ||||
} | ||||
r959 | QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const | |||
{ | ||||
auto axisX = plot.axisRect()->axis(QCPAxis::atBottom); | ||||
auto axisY = plot.axisRect()->axis(QCPAxis::atLeft); | ||||
return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())}; | ||||
} | ||||
r960 | ||||
bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const | ||||
{ | ||||
auto axisX = plot.axisRect()->axis(QCPAxis::atBottom); | ||||
auto axisY = plot.axisRect()->axis(QCPAxis::atLeft); | ||||
return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y()); | ||||
} | ||||
r118 | }; | |||
|
r205 | VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent) | ||
r839 | : 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 : | ||
r1044 | // - zoom is enabled | |||
|
r179 | // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation | ||
r1054 | ui->widget->setInteractions(QCP::iRangeZoom); | |||
r1045 | ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical); | |||
|
r481 | |||
|
r724 | // The delegate must be initialized after the ui as it uses the plot | ||
impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this); | ||||
r960 | // Init the cursors | |||
impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot()); | ||||
impl->m_HorizontalCursor->setOrientation(Qt::Horizontal); | ||||
impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot()); | ||||
impl->m_VerticalCursor->setOrientation(Qt::Vertical); | ||||
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); | ||
|
r1002 | connect(ui->widget, &QCustomPlot::mouseDoubleClick, this, | ||
&VisualizationGraphWidget::onMouseDoubleClick); | ||||
r1159 | connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>( | |||
&QCPAxis::rangeChanged), | ||||
r1328 | 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 | ||||
r1348 | //@TODO implement this :) | |||
// connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(), | ||||
// &VariableController::onRequestDataLoading); | ||||
r571 | ||||
r1348 | // connect(&sqpApp->variableController(), &VariableController2::updateVarDisplaying, this, | |||
// &VisualizationGraphWidget::onUpdateVarDisplaying); | ||||
|
r963 | |||
r1320 | // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable. | |||
|
r963 | plot().setPlottingHint(QCP::phFastPolylines, true); | ||
r58 | } | |||
|
r227 | |||
r58 | VisualizationGraphWidget::~VisualizationGraphWidget() | |||
{ | ||||
delete ui; | ||||
} | ||||
r118 | ||||
r839 | VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept | |||
{ | ||||
auto parent = parentWidget(); | ||||
r846 | while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) { | |||
r839 | parent = parent->parentWidget(); | |||
r846 | } | |||
r839 | ||||
r844 | return qobject_cast<VisualizationZoneWidget *>(parent); | |||
r839 | } | |||
r1049 | VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const | |||
{ | ||||
auto parent = parentWidget(); | ||||
while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) { | ||||
parent = parent->parentWidget(); | ||||
} | ||||
return qobject_cast<VisualizationWidget *>(parent); | ||||
} | ||||
|
r1271 | void VisualizationGraphWidget::setFlags(GraphFlags flags) | ||
r444 | { | |||
|
r1271 | impl->m_Flags = std::move(flags); | ||
r444 | } | |||
r1346 | void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range) | |||
r118 | { | |||
|
r1284 | /// Lambda used to set graph's units and range according to the variable passed in parameter | ||
r1346 | auto loadRange = [this](std::shared_ptr<Variable> variable, const DateTimeRange &range) { | |||
|
r1284 | impl->m_RenderingDelegate->setAxesUnits(*variable); | ||
|
r919 | |||
|
r1284 | this->setFlags(GraphFlag::DisableAll); | ||
setGraphRange(range); | ||||
this->setFlags(GraphFlag::EnableAll); | ||||
emit requestDataLoading({variable}, range, false); | ||||
}; | ||||
|
r729 | |||
r298 | connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated())); | |||
r540 | ||||
|
r1284 | // Calls update of graph's range and units when the data of the variable have been initialized. | ||
// Note: we use QueuedConnection here as the update event must be called in the UI thread | ||||
connect(variable.get(), &Variable::dataInitialized, this, | ||||
r1289 | [ varW = std::weak_ptr<Variable>{variable}, range, loadRange, this ]() { | |||
|
r1284 | if (auto var = varW.lock()) { | ||
// If the variable is the first added in the graph, we load its range | ||||
auto firstVariableInGraph = range == INVALID_RANGE; | ||||
r1292 | auto loadedRange = graphRange(); | |||
if (impl->m_VariableAutoRangeOnInit) { | ||||
loadedRange = firstVariableInGraph ? var->range() : range; | ||||
} | ||||
|
r1284 | loadRange(var, loadedRange); | ||
r1289 | setYRange(var); | |||
|
r1284 | } | ||
}, | ||||
Qt::QueuedConnection); | ||||
// Uses delegate to create the qcpplot components according to the variable | ||||
auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget); | ||||
// Sets graph properties | ||||
impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables); | ||||
r314 | ||||
|
r1284 | impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)}); | ||
// If the variable already has its data loaded, load its units and its range in the graph | ||||
if (variable->dataSeries() != nullptr) { | ||||
loadRange(variable, range); | ||||
} | ||||
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 | } | ||
r1348 | std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const | |||
r839 | { | |||
r1348 | auto variables = std::vector<std::shared_ptr<Variable> >{}; | |||
r844 | for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap); | |||
it != std::cend(impl->m_VariableToPlotMultiMap); ++it) { | ||||
r1348 | variables.push_back (it->first); | |||
r839 | } | |||
return variables; | ||||
} | ||||
|
r900 | void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable) | ||
r548 | { | |||
|
r900 | if (!variable) { | ||
qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null"; | ||||
return; | ||||
} | ||||
VisualizationGraphHelper::setYAxisRange(variable, *ui->widget); | ||||
r548 | } | |||
r1346 | DateTimeRange VisualizationGraphWidget::graphRange() const noexcept | |||
r444 | { | |||
r545 | auto graphRange = ui->widget->xAxis->range(); | |||
r1346 | return DateTimeRange{graphRange.lower, graphRange.upper}; | |||
r444 | } | |||
r1346 | void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool calibration) | |||
r444 | { | |||
r445 | qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START"); | |||
r1291 | ||||
if (calibration) { | ||||
impl->m_IsCalibration = true; | ||||
} | ||||
r444 | ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd); | |||
ui->widget->replot(); | ||||
r1291 | ||||
if (calibration) { | ||||
impl->m_IsCalibration = false; | ||||
} | ||||
r444 | qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END"); | |||
r438 | } | |||
r1292 | void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value) | |||
{ | ||||
impl->m_VariableAutoRangeOnInit = value; | ||||
} | ||||
r1346 | QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const | |||
r1048 | { | |||
r1346 | QVector<DateTimeRange> ranges; | |||
r1048 | for (auto zone : impl->m_SelectionZones) { | |||
ranges << zone->range(); | ||||
} | ||||
return ranges; | ||||
} | ||||
r1346 | void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges) | |||
r1048 | { | |||
for (const auto &range : ranges) { | ||||
r1049 | // note: ownership is transfered to QCustomPlot | |||
r1048 | auto zone = new VisualizationSelectionZoneItem(&plot()); | |||
zone->setRange(range.m_TStart, range.m_TEnd); | ||||
r1049 | impl->addSelectionZone(zone); | |||
r1048 | } | |||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
r1293 | VisualizationSelectionZoneItem *VisualizationGraphWidget::addSelectionZone(const QString &name, | |||
r1346 | const DateTimeRange &range) | |||
r1293 | { | |||
// note: ownership is transfered to QCustomPlot | ||||
auto zone = new VisualizationSelectionZoneItem(&plot()); | ||||
zone->setName(name); | ||||
zone->setRange(range.m_TStart, range.m_TEnd); | ||||
impl->addSelectionZone(zone); | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
return zone; | ||||
} | ||||
r1079 | void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone) | |||
{ | ||||
r1082 | parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false); | |||
if (impl->m_HoveredZone == selectionZone) { | ||||
impl->m_HoveredZone = nullptr; | ||||
setCursor(Qt::ArrowCursor); | ||||
} | ||||
r1079 | impl->m_SelectionZones.removeAll(selectionZone); | |||
plot().removeItem(selectionZone); | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
r1046 | void VisualizationGraphWidget::undoZoom() | |||
{ | ||||
auto zoom = impl->m_ZoomStack.pop(); | ||||
auto axisX = plot().axisRect()->axis(QCPAxis::atBottom); | ||||
auto axisY = plot().axisRect()->axis(QCPAxis::atLeft); | ||||
axisX->setRange(zoom.first); | ||||
axisY->setRange(zoom.second); | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
|
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 | ||
{ | ||||
|
r1022 | auto isSpectrogram = [](const auto &variable) { | ||
return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr; | ||||
}; | ||||
// - A spectrogram series can't be dropped on graph with existing plottables | ||||
// - No data series can be dropped on graph with existing spectrogram series | ||||
return isSpectrogram(variable) | ||||
? impl->m_VariableToPlotMultiMap.empty() | ||||
: std::none_of( | ||||
impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(), | ||||
[isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); }); | ||||
|
r209 | } | ||
|
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 | } | |||
r1047 | QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const | |||
r839 | { | |||
r846 | auto mimeData = new QMimeData; | |||
r839 | ||||
r1047 | auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot()); | |||
if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones | ||||
&& selectionZoneItemUnderCursor) { | ||||
mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange( | ||||
selectionZoneItemUnderCursor->range())); | ||||
mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange( | ||||
selectionZoneItemUnderCursor->range())); | ||||
} | ||||
else { | ||||
mimeData->setData(MIME_TYPE_GRAPH, QByteArray{}); | ||||
auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange()); | ||||
mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData); | ||||
} | ||||
r878 | ||||
r844 | return mimeData; | |||
r839 | } | |||
r1047 | QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition) | |||
{ | ||||
auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot()); | ||||
if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones | ||||
&& selectionZoneItemUnderCursor) { | ||||
auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition(); | ||||
auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition(); | ||||
auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()), | ||||
qAbs(zoneBottomRight.y() - zoneTopLeft.y())} | ||||
.toSize(); | ||||
auto pixmap = QPixmap(zoneSize); | ||||
render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}}); | ||||
return pixmap; | ||||
} | ||||
return QPixmap(); | ||||
} | ||||
r839 | bool VisualizationGraphWidget::isDragAllowed() const | |||
{ | ||||
return true; | ||||
} | ||||
r873 | void VisualizationGraphWidget::highlightForMerge(bool highlighted) | |||
{ | ||||
if (highlighted) { | ||||
plot().setBackground(QBrush(QColor("#BBD5EE"))); | ||||
} | ||||
else { | ||||
plot().setBackground(QBrush(Qt::white)); | ||||
} | ||||
plot().update(); | ||||
} | ||||
r960 | void VisualizationGraphWidget::addVerticalCursor(double time) | |||
{ | ||||
impl->m_VerticalCursor->setPosition(time); | ||||
impl->m_VerticalCursor->setVisible(true); | ||||
auto text | ||||
= DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n'); | ||||
impl->m_VerticalCursor->setLabelText(text); | ||||
} | ||||
void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position) | ||||
{ | ||||
impl->m_VerticalCursor->setAbsolutePosition(position); | ||||
impl->m_VerticalCursor->setVisible(true); | ||||
auto axis = plot().axisRect()->axis(QCPAxis::atBottom); | ||||
auto text | ||||
= DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT); | ||||
impl->m_VerticalCursor->setLabelText(text); | ||||
} | ||||
void VisualizationGraphWidget::removeVerticalCursor() | ||||
{ | ||||
impl->m_VerticalCursor->setVisible(false); | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
void VisualizationGraphWidget::addHorizontalCursor(double value) | ||||
{ | ||||
impl->m_HorizontalCursor->setPosition(value); | ||||
impl->m_HorizontalCursor->setVisible(true); | ||||
impl->m_HorizontalCursor->setLabelText(QString::number(value)); | ||||
} | ||||
void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position) | ||||
{ | ||||
impl->m_HorizontalCursor->setAbsolutePosition(position); | ||||
impl->m_HorizontalCursor->setVisible(true); | ||||
auto axis = plot().axisRect()->axis(QCPAxis::atLeft); | ||||
impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position))); | ||||
} | ||||
void VisualizationGraphWidget::removeHorizontalCursor() | ||||
{ | ||||
impl->m_HorizontalCursor->setVisible(false); | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
|
r738 | void VisualizationGraphWidget::closeEvent(QCloseEvent *event) | ||
{ | ||||
Q_UNUSED(event); | ||||
r1324 | for (auto i : impl->m_SelectionZones) { | |||
parentVisualizationWidget()->selectionZoneManager().setSelected(i, false); | ||||
} | ||||
|
r738 | // 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); | ||||
r960 | ||||
if (auto parentZone = parentZoneWidget()) { | ||||
parentZone->notifyMouseLeaveGraph(this); | ||||
} | ||||
else { | ||||
qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget"; | ||||
} | ||||
r1044 | ||||
if (impl->m_HoveredZone) { | ||||
impl->m_HoveredZone->setHovered(false); | ||||
impl->m_HoveredZone = nullptr; | ||||
} | ||||
|
r728 | } | ||
r1047 | QCustomPlot &VisualizationGraphWidget::plot() const noexcept | |||
|
r725 | { | ||
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 | |||
r1046 | if (!impl->m_ZoomStack.isEmpty()) { | |||
if (!graphMenu.isEmpty()) { | ||||
graphMenu.addSeparator(); | ||||
} | ||||
graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); }); | ||||
} | ||||
r1083 | // Selection Zone Actions | |||
r1077 | auto selectionZoneItem = impl->selectionZoneAt(pos, plot()); | |||
if (selectionZoneItem) { | ||||
auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems(); | ||||
r1081 | selectedItems.removeAll(selectionZoneItem); | |||
selectedItems.prepend(selectionZoneItem); // Put the current selection zone first | ||||
r1077 | auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions(); | |||
if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) { | ||||
graphMenu.addSeparator(); | ||||
} | ||||
r1083 | QHash<QString, QMenu *> subMenus; | |||
QHash<QString, bool> subMenusEnabled; | ||||
r1328 | QHash<QString, FilteringAction *> filteredMenu; | |||
r1083 | ||||
r1077 | for (auto zoneAction : zoneActions) { | |||
r1083 | ||||
auto isEnabled = zoneAction->isEnabled(selectedItems); | ||||
auto menu = &graphMenu; | ||||
r1328 | QString menuPath; | |||
r1083 | for (auto subMenuName : zoneAction->subMenuList()) { | |||
r1328 | menuPath += '/'; | |||
menuPath += subMenuName; | ||||
if (!subMenus.contains(menuPath)) { | ||||
r1083 | menu = menu->addMenu(subMenuName); | |||
r1328 | subMenus[menuPath] = menu; | |||
subMenusEnabled[menuPath] = isEnabled; | ||||
r1083 | } | |||
else { | ||||
r1328 | menu = subMenus.value(menuPath); | |||
r1083 | if (isEnabled) { | |||
// The sub menu is enabled if at least one of its actions is enabled | ||||
r1328 | subMenusEnabled[menuPath] = true; | |||
r1083 | } | |||
} | ||||
} | ||||
r1328 | FilteringAction *filterAction = nullptr; | |||
if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) { | ||||
filterAction = filteredMenu.value(menuPath); | ||||
if (!filterAction) { | ||||
filterAction = new FilteringAction{this}; | ||||
filteredMenu[menuPath] = filterAction; | ||||
menu->addAction(filterAction); | ||||
} | ||||
} | ||||
r1083 | auto action = menu->addAction(zoneAction->name()); | |||
action->setEnabled(isEnabled); | ||||
r1082 | action->setShortcut(zoneAction->displayedShortcut()); | |||
r1081 | QObject::connect(action, &QAction::triggered, | |||
[zoneAction, selectedItems]() { zoneAction->execute(selectedItems); }); | ||||
r1328 | ||||
if (filterAction && zoneAction->isFilteringAllowed()) { | ||||
filterAction->addActionToFilter(action); | ||||
} | ||||
r1077 | } | |||
r1083 | ||||
for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) { | ||||
it.value()->setEnabled(subMenusEnabled[it.key()]); | ||||
} | ||||
r1077 | } | |||
|
r269 | if (!graphMenu.isEmpty()) { | ||
|
r655 | graphMenu.exec(QCursor::pos()); | ||
|
r196 | } | ||
r118 | } | |||
|
r179 | |||
r444 | void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2) | |||
|
r227 | { | ||
r1159 | qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged") | |||
<< QThread::currentThread()->objectName() << "DoAcqui" | ||||
|
r1271 | << impl->m_Flags.testFlag(GraphFlag::EnableAcquisition); | ||
r444 | ||||
r1346 | auto graphRange = DateTimeRange{t1.lower, t1.upper}; | |||
auto oldGraphRange = DateTimeRange{t2.lower, t2.upper}; | ||||
r235 | ||||
|
r1271 | if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) { | ||
r539 | 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 | } | |||
r811 | emit requestDataLoading(std::move(variableUnderGraphVector), graphRange, | |||
r539 | !impl->m_IsCalibration); | |||
|
r1272 | } | ||
r539 | ||||
|
r1272 | if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) { | ||
qCDebug(LOG_VisualizationGraphWidget()) | ||||
<< tr("TORM: VisualizationGraphWidget::Synchronize notify !!") | ||||
<< QThread::currentThread()->objectName() << graphRange << oldGraphRange; | ||||
emit synchronize(graphRange, oldGraphRange); | ||||
|
r227 | } | ||
r960 | ||||
auto pos = mapFromGlobal(QCursor::pos()); | ||||
auto axisPos = impl->posToAxisPos(pos, plot()); | ||||
if (auto parentZone = parentZoneWidget()) { | ||||
if (impl->pointIsInAxisRect(axisPos, plot())) { | ||||
parentZone->notifyMouseMoveInGraph(pos, axisPos, this); | ||||
} | ||||
else { | ||||
parentZone->notifyMouseLeaveGraph(this); | ||||
} | ||||
} | ||||
else { | ||||
qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget"; | ||||
} | ||||
|
r1272 | |||
// Quits calibration | ||||
impl->m_IsCalibration = false; | ||||
|
r227 | } | ||
|
r1002 | void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept | ||
{ | ||||
impl->m_RenderingDelegate->onMouseDoubleClick(event); | ||||
} | ||||
|
r481 | void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept | ||
{ | ||||
// Handles plot rendering when mouse is moving | ||||
impl->m_RenderingDelegate->onMouseMove(event); | ||||
r839 | ||||
r960 | auto axisPos = impl->posToAxisPos(event->pos(), plot()); | |||
r1044 | // Zoom box and zone drawing | |||
if (impl->m_DrawingZoomRect) { | ||||
impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos); | ||||
} | ||||
else if (impl->m_DrawingZone) { | ||||
impl->m_DrawingZone->setEnd(axisPos.x()); | ||||
r959 | } | |||
r1044 | // Cursor | |||
r960 | if (auto parentZone = parentZoneWidget()) { | |||
if (impl->pointIsInAxisRect(axisPos, plot())) { | ||||
parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this); | ||||
} | ||||
else { | ||||
parentZone->notifyMouseLeaveGraph(this); | ||||
} | ||||
} | ||||
else { | ||||
qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget"; | ||||
} | ||||
r1044 | // Search for the selection zone under the mouse | |||
r1047 | auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot()); | |||
r1044 | if (selectionZoneItemUnderCursor && !impl->m_DrawingZone | |||
&& sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) { | ||||
// Sets the appropriate cursor shape | ||||
auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos()); | ||||
setCursor(cursorShape); | ||||
// Manages the hovered zone | ||||
if (selectionZoneItemUnderCursor != impl->m_HoveredZone) { | ||||
if (impl->m_HoveredZone) { | ||||
impl->m_HoveredZone->setHovered(false); | ||||
} | ||||
selectionZoneItemUnderCursor->setHovered(true); | ||||
impl->m_HoveredZone = selectionZoneItemUnderCursor; | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
} | ||||
else { | ||||
// There is no zone under the mouse or the interaction mode is not "selection zones" | ||||
if (impl->m_HoveredZone) { | ||||
impl->m_HoveredZone->setHovered(false); | ||||
impl->m_HoveredZone = nullptr; | ||||
} | ||||
setCursor(Qt::ArrowCursor); | ||||
} | ||||
r1050 | impl->m_HasMovedMouse = true; | |||
r839 | VisualizationDragWidget::mouseMoveEvent(event); | |||
|
r481 | } | ||
|
r179 | void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept | ||
{ | ||||
|
r1330 | // Processes event only if the wheel occurs on axis rect | ||
if (!dynamic_cast<QCPAxisRect *>(ui->widget->layoutElementAt(event->posF()))) { | ||||
return; | ||||
} | ||||
r958 | 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); | ||||
|
r179 | |||
r958 | if (!isZoomX && !isZoomY) { | |||
auto axis = plot().axisRect()->axis(QCPAxis::atBottom); | ||||
auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0)); | ||||
|
r179 | |||
r958 | 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 | |||
{ | ||||
|
r1052 | auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER); | ||
r1049 | auto isSelectionZoneMode | |||
= sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones; | ||||
|
r1052 | auto isLeftClick = event->buttons().testFlag(Qt::LeftButton); | ||
r1047 | ||||
|
r1052 | if (!isDragDropClick && isLeftClick) { | ||
r1047 | if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) { | |||
// Starts a zoom box | ||||
impl->startDrawingRect(event->pos(), plot()); | ||||
} | ||||
r1049 | else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) { | |||
r1047 | // Starts a new selection zone | |||
auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot()); | ||||
if (!zoneAtPos) { | ||||
r1049 | impl->startDrawingZone(event->pos(), this); | |||
r1047 | } | |||
r1044 | } | |||
} | ||||
// Allows mouse panning only in default mode | ||||
plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode() | ||||
r1047 | == SqpApplication::PlotsInteractionMode::None | |||
&& !isDragDropClick); | ||||
r1044 | ||||
r1049 | // Allows zone edition only in selection zone mode without drag&drop | |||
impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick); | ||||
r1051 | // Selection / Deselection | |||
r1049 | if (isSelectionZoneMode) { | |||
auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER); | ||||
auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot()); | ||||
r1084 | ||||
if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected() | ||||
&& !isMultiSelectionClick) { | ||||
parentVisualizationWidget()->selectionZoneManager().select( | ||||
{selectionZoneItemUnderCursor}); | ||||
r1049 | } | |||
r1084 | else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) { | |||
r1049 | parentVisualizationWidget()->selectionZoneManager().clearSelection(); | |||
} | ||||
else { | ||||
// No selection change | ||||
} | ||||
r1084 | ||||
if (selectionZoneItemUnderCursor && isLeftClick) { | ||||
selectionZoneItemUnderCursor->setAssociatedEditedZones( | ||||
parentVisualizationWidget()->selectionZoneManager().selectedItems()); | ||||
} | ||||
r1049 | } | |||
r839 | ||||
r1050 | ||||
impl->m_HasMovedMouse = false; | ||||
r839 | VisualizationDragWidget::mousePressEvent(event); | |||
r445 | } | |||
void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept | ||||
{ | ||||
r1044 | if (impl->m_DrawingZoomRect) { | |||
r959 | ||||
auto axisX = plot().axisRect()->axis(QCPAxis::atBottom); | ||||
auto axisY = plot().axisRect()->axis(QCPAxis::atLeft); | ||||
r1044 | auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(), | |||
impl->m_DrawingZoomRect->bottomRight->coords().x()}; | ||||
r959 | ||||
r1044 | auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(), | |||
impl->m_DrawingZoomRect->bottomRight->coords().y()}; | ||||
r959 | ||||
impl->removeDrawingRect(plot()); | ||||
if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0) | ||||
&& newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) { | ||||
r1046 | impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range())); | |||
r959 | axisX->setRange(newAxisXRange); | |||
axisY->setRange(newAxisYRange); | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
} | ||||
r1049 | impl->endDrawingZone(this); | |||
r1044 | ||||
r1050 | // Selection / Deselection | |||
auto isSelectionZoneMode | ||||
= sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones; | ||||
if (isSelectionZoneMode) { | ||||
auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER); | ||||
auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot()); | ||||
r1085 | if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton | |||
&& !impl->m_HasMovedMouse) { | ||||
auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot()); | ||||
if (zonesUnderCursor.count() > 1) { | ||||
// There are multiple zones under the mouse. | ||||
// Performs the selection with a selection dialog. | ||||
VisualizationMultiZoneSelectionDialog dialog{this}; | ||||
dialog.setZones(zonesUnderCursor); | ||||
dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20))); | ||||
dialog.activateWindow(); | ||||
dialog.raise(); | ||||
if (dialog.exec() == QDialog::Accepted) { | ||||
auto selection = dialog.selectedZones(); | ||||
if (!isMultiSelectionClick) { | ||||
parentVisualizationWidget()->selectionZoneManager().clearSelection(); | ||||
} | ||||
for (auto it = selection.cbegin(); it != selection.cend(); ++it) { | ||||
auto zone = it.key(); | ||||
auto isSelected = it.value(); | ||||
parentVisualizationWidget()->selectionZoneManager().setSelected(zone, | ||||
isSelected); | ||||
if (isSelected) { | ||||
// Puts the zone on top of the stack so it can be moved or resized | ||||
impl->moveSelectionZoneOnTop(zone, plot()); | ||||
} | ||||
} | ||||
} | ||||
r1050 | } | |||
r1085 | else { | |||
if (!isMultiSelectionClick) { | ||||
parentVisualizationWidget()->selectionZoneManager().select( | ||||
{selectionZoneItemUnderCursor}); | ||||
impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot()); | ||||
} | ||||
else { | ||||
parentVisualizationWidget()->selectionZoneManager().setSelected( | ||||
selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected() | ||||
|| event->button() == Qt::RightButton); | ||||
} | ||||
r1050 | } | |||
} | ||||
else { | ||||
// No selection change | ||||
} | ||||
} | ||||
r445 | } | |||
r235 | void VisualizationGraphWidget::onDataCacheVariableUpdated() | |||
{ | ||||
r545 | auto graphRange = ui->widget->xAxis->range(); | |||
r1346 | auto dateTime = DateTimeRange{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())) { | |||
|
r1280 | impl->updateData(variableEntry.second, variable, variable->range()); | ||
r433 | } | |||
r235 | } | |||
} | ||||
r571 | ||||
void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable, | ||||
r1346 | const DateTimeRange &range) | |||
r571 | { | |||
|
r582 | auto it = impl->m_VariableToPlotMultiMap.find(variable); | ||
if (it != impl->m_VariableToPlotMultiMap.end()) { | ||||
|
r1280 | impl->updateData(it->second, variable, range); | ||
r571 | } | |||
} | ||||