diff --git a/app/src/MainWindow.cpp b/app/src/MainWindow.cpp index 05951cb..9b306a3 100644 --- a/app/src/MainWindow.cpp +++ b/app/src/MainWindow.cpp @@ -192,6 +192,10 @@ MainWindow::MainWindow(QWidget *parent) SIGNAL(variableAboutToBeDeleted(std::shared_ptr)), m_Ui->view, SLOT(onVariableAboutToBeDeleted(std::shared_ptr))); + connect(&sqpApp->visualizationController(), + SIGNAL(rangeChanged(std::shared_ptr, const SqpDateTime &)), m_Ui->view, + SLOT(onRangeChanged(std::shared_ptr, const SqpDateTime &))); + // Widgets / widgets connections // For the following connections, we use DirectConnection to allow each widget that can diff --git a/core/include/Data/IDataProvider.h b/core/include/Data/IDataProvider.h index 0f16848..4af05f0 100644 --- a/core/include/Data/IDataProvider.h +++ b/core/include/Data/IDataProvider.h @@ -10,6 +10,8 @@ #include +#include + class DataProviderParameters; class IDataSeries; class QNetworkReply; @@ -34,6 +36,11 @@ public: */ virtual void requestDataLoading(QUuid identifier, const DataProviderParameters ¶meters) = 0; + /** + * @brief requestDataAborting stop data loading of the data identified by identifier + */ + virtual void requestDataAborting(QUuid identifier) = 0; + signals: /** * @brief dataProvided send dataSeries under dateTime and that corresponds of the data @@ -42,6 +49,12 @@ signals: void dataProvided(QUuid identifier, std::shared_ptr dateSerie, const SqpDateTime &dateTime); + /** + * @brief dataProvided send dataSeries under dateTime and that corresponds of the data + * identified by identifier + */ + void dataProvidedProgress(QUuid identifier, double progress); + /** * @brief requestConstructed send a request for the data identified by identifier diff --git a/core/include/DataSource/DataSourceItemAction.h b/core/include/DataSource/DataSourceItemAction.h index 1e64959..1dff4be 100644 --- a/core/include/DataSource/DataSourceItemAction.h +++ b/core/include/DataSource/DataSourceItemAction.h @@ -6,6 +6,8 @@ #include #include +#include + Q_DECLARE_LOGGING_CATEGORY(LOG_DataSourceItemAction) class DataSourceItem; diff --git a/core/include/Variable/VariableController.h b/core/include/Variable/VariableController.h index 2776c18..c896d44 100644 --- a/core/include/Variable/VariableController.h +++ b/core/include/Variable/VariableController.h @@ -50,10 +50,18 @@ public: */ void deleteVariables(const QVector > &variables) noexcept; + /** + * @brief abort the variable retrieve data progression + */ + void abortProgress(std::shared_ptr variable); + signals: /// Signal emitted when a variable is about to be deleted from the controller void variableAboutToBeDeleted(std::shared_ptr variable); + /// Signal emitted when a data acquisition is requested on a range for a variable + void rangeChanged(std::shared_ptr variable, const SqpDateTime &range); + public slots: /// Request the data loading of the variable whithin dateTime void onRequestDataLoading(std::shared_ptr variable, const SqpDateTime &dateTime); @@ -72,6 +80,8 @@ public slots: void onVariableRetrieveDataInProgress(QUuid identifier, double progress); + void onAbortProgressRequested(std::shared_ptr variable); + void initialize(); void finalize(); diff --git a/core/include/Variable/VariableModel.h b/core/include/Variable/VariableModel.h index 8e8f317..21e4edc 100644 --- a/core/include/Variable/VariableModel.h +++ b/core/include/Variable/VariableModel.h @@ -22,6 +22,7 @@ class Variable; * @brief The VariableModel class aims to hold the variables that have been created in SciQlop */ class VariableModel : public QAbstractTableModel { + Q_OBJECT public: explicit VariableModel(QObject *parent = nullptr); @@ -46,6 +47,7 @@ public: void setDataProgress(std::shared_ptr variable, double progress); + // /////////////////////////// // // QAbstractTableModel methods // // /////////////////////////// // @@ -56,6 +58,12 @@ public: virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void abortProgress(const QModelIndex &index); + +signals: + void abortProgessRequested(std::shared_ptr variable); + private: class VariableModelPrivate; spimpl::unique_impl_ptr impl; diff --git a/core/include/Visualization/VisualizationController.h b/core/include/Visualization/VisualizationController.h index fac9491..74ff980 100644 --- a/core/include/Visualization/VisualizationController.h +++ b/core/include/Visualization/VisualizationController.h @@ -1,6 +1,8 @@ #ifndef SCIQLOP_VISUALIZATIONCONTROLLER_H #define SCIQLOP_VISUALIZATIONCONTROLLER_H +#include + #include #include #include @@ -29,6 +31,9 @@ signals: /// Signal emitted when a variable is about to be deleted from SciQlop void variableAboutToBeDeleted(std::shared_ptr variable); + /// Signal emitted when a data acquisition is requested on a range for a variable + void rangeChanged(std::shared_ptr variable, const SqpDateTime &range); + public slots: /// Manage init/end of the controller void initialize(); diff --git a/core/src/Network/NetworkController.cpp b/core/src/Network/NetworkController.cpp index 64c0bb8..ddf4654 100644 --- a/core/src/Network/NetworkController.cpp +++ b/core/src/Network/NetworkController.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -14,8 +15,13 @@ struct NetworkController::NetworkControllerPrivate { explicit NetworkControllerPrivate(NetworkController *parent) : m_WorkingMutex{} {} QMutex m_WorkingMutex; + QReadWriteLock m_Lock; std::unordered_map m_NetworkReplyToVariableId; std::unique_ptr m_AccessManager{nullptr}; + + void lockRead() { m_Lock.lockForRead(); } + void lockWrite() { m_Lock.lockForWrite(); } + void unlock() { m_Lock.unlock(); } }; NetworkController::NetworkController(QObject *parent) @@ -26,35 +32,57 @@ NetworkController::NetworkController(QObject *parent) void NetworkController::onProcessRequested(const QNetworkRequest &request, QUuid identifier, std::function callback) { - qCInfo(LOG_NetworkController()) << tr("NetworkController registered") - << QThread::currentThread(); + qCDebug(LOG_NetworkController()) << tr("NetworkController registered") + << QThread::currentThread()->objectName(); auto reply = impl->m_AccessManager->get(request); // Store the couple reply id + impl->lockWrite(); impl->m_NetworkReplyToVariableId[reply] = identifier; + impl->unlock(); auto onReplyFinished = [reply, this, identifier, callback]() { - qCInfo(LOG_NetworkController()) << tr("NetworkController onReplyFinished") - << QThread::currentThread(); + qCDebug(LOG_NetworkController()) << tr("NetworkController onReplyFinished") + << QThread::currentThread() << reply; + impl->lockRead(); auto it = impl->m_NetworkReplyToVariableId.find(reply); + impl->unlock(); if (it != impl->m_NetworkReplyToVariableId.cend()) { + impl->lockWrite(); + impl->m_NetworkReplyToVariableId.erase(reply); + impl->unlock(); + // Deletes reply callback(reply, identifier); + reply->deleteLater(); + + emit this->replyDownloadProgress(identifier, 0); } + + qCDebug(LOG_NetworkController()) << tr("NetworkController onReplyFinished END") + << QThread::currentThread() << reply; }; auto onReplyProgress = [reply, this](qint64 bytesRead, qint64 totalBytes) { double progress = (bytesRead * 100.0) / totalBytes; + qCDebug(LOG_NetworkController()) << tr("NetworkController onReplyProgress") << progress + << QThread::currentThread() << reply; + impl->lockRead(); auto it = impl->m_NetworkReplyToVariableId.find(reply); + impl->unlock(); if (it != impl->m_NetworkReplyToVariableId.cend()) { emit this->replyDownloadProgress(it->second, progress); } + qCDebug(LOG_NetworkController()) << tr("NetworkController onReplyProgress END") + << QThread::currentThread() << reply; }; connect(reply, &QNetworkReply::finished, this, onReplyFinished); connect(reply, &QNetworkReply::downloadProgress, this, onReplyProgress); + qCDebug(LOG_NetworkController()) << tr("NetworkController registered END") + << QThread::currentThread()->objectName() << reply; } void NetworkController::initialize() @@ -62,6 +90,17 @@ void NetworkController::initialize() qCDebug(LOG_NetworkController()) << tr("NetworkController init") << QThread::currentThread(); impl->m_WorkingMutex.lock(); impl->m_AccessManager = std::make_unique(); + + + auto onReplyErrors = [this](QNetworkReply *reply, const QList &errors) { + + qCCritical(LOG_NetworkController()) << tr("NetworkAcessManager errors: ") << errors; + + }; + + + connect(impl->m_AccessManager.get(), &QNetworkAccessManager::sslErrors, this, onReplyErrors); + qCDebug(LOG_NetworkController()) << tr("NetworkController init END"); } @@ -73,12 +112,19 @@ void NetworkController::finalize() void NetworkController::onReplyCanceled(QUuid identifier) { auto findReply = [identifier](const auto &entry) { return identifier == entry.second; }; + qCDebug(LOG_NetworkController()) << tr("NetworkController onReplyCanceled") + << QThread::currentThread(); + + impl->lockRead(); auto end = impl->m_NetworkReplyToVariableId.cend(); auto it = std::find_if(impl->m_NetworkReplyToVariableId.cbegin(), end, findReply); + impl->unlock(); if (it != end) { it->first->abort(); } + qCDebug(LOG_NetworkController()) << tr("NetworkController onReplyCanceled END") + << QThread::currentThread(); } void NetworkController::waitForFinish() diff --git a/core/src/Variable/Variable.cpp b/core/src/Variable/Variable.cpp index 4a66066..286ef52 100644 --- a/core/src/Variable/Variable.cpp +++ b/core/src/Variable/Variable.cpp @@ -60,7 +60,7 @@ void Variable::setDataSeries(std::shared_ptr dataSeries) noexcept impl->m_DataSeries->merge(dataSeries.get()); impl->m_DataSeries->unlock(); dataSeries->unlock(); - emit updated(); + // emit updated(); } } diff --git a/core/src/Variable/VariableController.cpp b/core/src/Variable/VariableController.cpp index cd5aa59..c439435 100644 --- a/core/src/Variable/VariableController.cpp +++ b/core/src/Variable/VariableController.cpp @@ -38,7 +38,7 @@ struct VariableController::VariableControllerPrivate { std::unordered_map, std::shared_ptr > m_VariableToProviderMap; - std::unordered_map, QUuid> m_VariableToIdentifier; + std::unordered_map, QUuid> m_VariableToIdentifierMap; }; VariableController::VariableController(QObject *parent) @@ -46,6 +46,9 @@ VariableController::VariableController(QObject *parent) { qCDebug(LOG_VariableController()) << tr("VariableController construction") << QThread::currentThread(); + + connect(impl->m_VariableModel, &VariableModel::abortProgessRequested, this, + &VariableController::onAbortProgressRequested); } VariableController::~VariableController() @@ -81,6 +84,9 @@ void VariableController::deleteVariable(std::shared_ptr variable) noex // make some treatments before the deletion emit variableAboutToBeDeleted(variable); + // Deletes identifier + impl->m_VariableToIdentifierMap.erase(variable); + // Deletes provider auto nbProvidersDeleted = impl->m_VariableToProviderMap.erase(variable); qCDebug(LOG_VariableController()) @@ -102,6 +108,10 @@ void VariableController::deleteVariables( } } +void VariableController::abortProgress(std::shared_ptr variable) +{ +} + void VariableController::createVariable(const QString &name, const QVariantHash &metadata, std::shared_ptr provider) noexcept { @@ -119,21 +129,24 @@ void VariableController::createVariable(const QString &name, const QVariantHash // store the provider impl->m_VariableToProviderMap[newVariable] = provider; - impl->m_VariableToIdentifier[newVariable] = identifier; + impl->m_VariableToIdentifierMap[newVariable] = identifier; auto addDateTimeAcquired = [ this, varW = std::weak_ptr{newVariable} ]( QUuid identifier, auto dataSeriesAcquired, auto dateTimeToPutInCache) { if (auto variable = varW.lock()) { - auto varIdentifier = impl->m_VariableToIdentifier.at(variable); + auto varIdentifier = impl->m_VariableToIdentifierMap.at(variable); if (varIdentifier == identifier) { impl->m_VariableCacheController->addDateTime(variable, dateTimeToPutInCache); variable->setDataSeries(dataSeriesAcquired); + emit variable->updated(); } } }; connect(provider.get(), &IDataProvider::dataProvided, addDateTimeAcquired); + connect(provider.get(), &IDataProvider::dataProvidedProgress, this, + &VariableController::onVariableRetrieveDataInProgress); this->onRequestDataLoading(newVariable, dateTime); } } @@ -148,6 +161,9 @@ void VariableController::onDateTimeOnSelection(const SqpDateTime &dateTime) if (auto selectedVariable = impl->m_VariableModel->variable(selectedRow.row())) { selectedVariable->setDateTime(dateTime); this->onRequestDataLoading(selectedVariable, dateTime); + + // notify that rescale operation has to be done + emit rangeChanged(selectedVariable, dateTime); } } } @@ -156,13 +172,29 @@ void VariableController::onVariableRetrieveDataInProgress(QUuid identifier, doub { auto findReply = [identifier](const auto &entry) { return identifier == entry.second; }; - auto end = impl->m_VariableToIdentifier.cend(); - auto it = std::find_if(impl->m_VariableToIdentifier.cbegin(), end, findReply); + auto end = impl->m_VariableToIdentifierMap.cend(); + auto it = std::find_if(impl->m_VariableToIdentifierMap.cbegin(), end, findReply); if (it != end) { impl->m_VariableModel->setDataProgress(it->first, progress); } } +void VariableController::onAbortProgressRequested(std::shared_ptr variable) +{ + qCDebug(LOG_VariableController()) << "TORM: VariableController::onAbortProgressRequested" + << QThread::currentThread()->objectName(); + + auto it = impl->m_VariableToIdentifierMap.find(variable); + if (it != impl->m_VariableToIdentifierMap.cend()) { + impl->m_VariableToProviderMap.at(variable)->requestDataAborting(it->second); + } + else { + qCWarning(LOG_VariableController()) + << tr("Aborting progression of inexistant variable detected !!!") + << QThread::currentThread()->objectName(); + } +} + void VariableController::onRequestDataLoading(std::shared_ptr variable, const SqpDateTime &dateTime) @@ -179,7 +211,7 @@ void VariableController::onRequestDataLoading(std::shared_ptr variable if (!dateTimeListNotInCache.empty()) { // Ask the provider for each data on the dateTimeListNotInCache - auto identifier = impl->m_VariableToIdentifier.at(variable); + auto identifier = impl->m_VariableToIdentifierMap.at(variable); impl->m_VariableToProviderMap.at(variable)->requestDataLoading( identifier, DataProviderParameters{std::move(dateTimeListNotInCache), variable->metadata()}); diff --git a/core/src/Variable/VariableModel.cpp b/core/src/Variable/VariableModel.cpp index 637c4cb..75a3387 100644 --- a/core/src/Variable/VariableModel.cpp +++ b/core/src/Variable/VariableModel.cpp @@ -49,7 +49,6 @@ struct VariableModel::VariableModelPrivate { std::vector > m_Variables; std::unordered_map, double> m_VariableToProgress; - /// Return the row index of the variable. -1 if it's not found int indexOfVariable(Variable *variable) const noexcept; }; @@ -100,6 +99,9 @@ void VariableModel::deleteVariable(std::shared_ptr variable) noexcept << tr("Can't delete variable %1 from the model: the variable is not in the model") .arg(variable->name()); } + + // Removes variable from progress map + impl->m_VariableToProgress.erase(variable); } @@ -110,8 +112,12 @@ std::shared_ptr VariableModel::variable(int index) const void VariableModel::setDataProgress(std::shared_ptr variable, double progress) { - - impl->m_VariableToProgress[variable] = progress; + if (progress > 0.0) { + impl->m_VariableToProgress[variable] = progress; + } + else { + impl->m_VariableToProgress.erase(variable); + } auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN); emit dataChanged(modelIndex, modelIndex); @@ -204,6 +210,13 @@ QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int return QVariant{}; } +void VariableModel::abortProgress(const QModelIndex &index) +{ + if (auto variable = impl->m_Variables.at(index.row())) { + emit abortProgessRequested(variable); + } +} + void VariableModel::onVariableUpdated() noexcept { // Finds variable that has been updated in the model diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index ef0e800..1f31ed2 100644 --- a/gui/include/Visualization/VisualizationGraphWidget.h +++ b/gui/include/Visualization/VisualizationGraphWidget.h @@ -16,6 +16,11 @@ class QCPRange; class SqpDateTime; class Variable; +/** + * Possible types of zoom operation + */ +enum class VisualizationGraphWidgetZoomType { ZoomOut, ZoomIn, PanRight, PanLeft, Unknown }; + namespace Ui { class VisualizationGraphWidget; } // namespace Ui @@ -27,21 +32,28 @@ public: explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0); virtual ~VisualizationGraphWidget(); + void enableSynchronize(bool enable); + void addVariable(std::shared_ptr variable); void addVariableUsingGraph(std::shared_ptr variable); /// Removes a variable from the graph void removeVariable(std::shared_ptr variable) noexcept; + void setRange(std::shared_ptr variable, const SqpDateTime &range); + SqpDateTime graphRange() const noexcept; + void setGraphRange(const SqpDateTime &range); + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; bool contains(const Variable &variable) const override; QString name() const override; - void updateDisplay(std::shared_ptr variable); signals: void requestDataLoading(std::shared_ptr variable, const SqpDateTime &dateTime); + void synchronize(const SqpDateTime &dateTime, const SqpDateTime &oldDateTime, + VisualizationGraphWidgetZoomType zoomType); private: @@ -54,10 +66,15 @@ private slots: /// Slot called when right clicking on the graph (displays a menu) void onGraphMenuRequested(const QPoint &pos) noexcept; - void onRangeChanged(const QCPRange &t1); + /// Rescale the X axe to range parameter + void onRangeChanged(const QCPRange &t1, const QCPRange &t2); /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done void onMouseWheel(QWheelEvent *event) noexcept; + /// Slot called when a mouse press was made, to activate the calibration of a graph + void onMousePress(QMouseEvent *event) noexcept; + /// Slot called when a mouse release was made, to deactivate the calibration of a graph + void onMouseRelease(QMouseEvent *event) noexcept; void onDataCacheVariableUpdated(); }; diff --git a/gui/include/Visualization/VisualizationWidget.h b/gui/include/Visualization/VisualizationWidget.h index 378b3e3..79e2538 100644 --- a/gui/include/Visualization/VisualizationWidget.h +++ b/gui/include/Visualization/VisualizationWidget.h @@ -2,6 +2,7 @@ #define SCIQLOP_VISUALIZATIONWIDGET_H #include "Visualization/IVisualizationWidget.h" +#include #include #include @@ -41,6 +42,8 @@ public slots: /// Slot called when a variable is about to be deleted from SciQlop void onVariableAboutToBeDeleted(std::shared_ptr variable) noexcept; + void onRangeChanged(std::shared_ptr variable, const SqpDateTime &range) noexcept; + private: Ui::VisualizationWidget *ui; }; diff --git a/gui/include/Visualization/operations/RescaleAxeOperation.h b/gui/include/Visualization/operations/RescaleAxeOperation.h new file mode 100644 index 0000000..5e348bb --- /dev/null +++ b/gui/include/Visualization/operations/RescaleAxeOperation.h @@ -0,0 +1,42 @@ +#ifndef SCIQLOP_RESCALEAXEOPERATION_H +#define SCIQLOP_RESCALEAXEOPERATION_H + +#include "Visualization/IVisualizationWidgetVisitor.h" +#include + +#include + +#include + +#include + +class Variable; + +Q_DECLARE_LOGGING_CATEGORY(LOG_RescaleAxeOperation) + +/** + * @brief The RescaleAxeOperation class defines an operation that traverses all of visualization + * widgets to remove a variable if they contain it + */ +class RescaleAxeOperation : public IVisualizationWidgetVisitor { +public: + /** + * Ctor + * @param variable the variable to remove from widgets + */ + explicit RescaleAxeOperation(std::shared_ptr variable, const SqpDateTime &range); + + void visitEnter(VisualizationWidget *widget) override final; + void visitLeave(VisualizationWidget *widget) override final; + void visitEnter(VisualizationTabWidget *tabWidget) override final; + void visitLeave(VisualizationTabWidget *tabWidget) override final; + void visitEnter(VisualizationZoneWidget *zoneWidget) override final; + void visitLeave(VisualizationZoneWidget *zoneWidget) override final; + void visit(VisualizationGraphWidget *graphWidget) override final; + +private: + class RescaleAxeOperationPrivate; + spimpl::unique_impl_ptr impl; +}; + +#endif // SCIQLOP_RESCALEAXEOPERATION_H diff --git a/gui/src/SqpApplication.cpp b/gui/src/SqpApplication.cpp index 04d8858..89e0249 100644 --- a/gui/src/SqpApplication.cpp +++ b/gui/src/SqpApplication.cpp @@ -38,11 +38,20 @@ public: m_VisualizationController.get(), SIGNAL(variableAboutToBeDeleted(std::shared_ptr)), Qt::DirectConnection); + connect(m_VariableController.get(), + SIGNAL(rangeChanged(std::shared_ptr, const SqpDateTime &)), + m_VisualizationController.get(), + SIGNAL(rangeChanged(std::shared_ptr, const SqpDateTime &))); + m_DataSourceController->moveToThread(&m_DataSourceControllerThread); + m_DataSourceControllerThread.setObjectName("DataSourceControllerThread"); m_NetworkController->moveToThread(&m_NetworkControllerThread); + m_NetworkControllerThread.setObjectName("NetworkControllerThread"); m_VariableController->moveToThread(&m_VariableControllerThread); + m_VariableControllerThread.setObjectName("VariableControllerThread"); m_VisualizationController->moveToThread(&m_VisualizationControllerThread); + m_VisualizationControllerThread.setObjectName("VsualizationControllerThread"); // Additionnal init @@ -106,11 +115,6 @@ SqpApplication::SqpApplication(int &argc, char **argv) impl->m_NetworkControllerThread.start(); impl->m_VariableControllerThread.start(); impl->m_VisualizationControllerThread.start(); - - // Core connections: - // NetworkController <-> VariableController - connect(&sqpApp->networkController(), &NetworkController::replyDownloadProgress, - &sqpApp->variableController(), &VariableController::onVariableRetrieveDataInProgress); } SqpApplication::~SqpApplication() diff --git a/gui/src/Variable/VariableInspectorWidget.cpp b/gui/src/Variable/VariableInspectorWidget.cpp index 3b2e9ae..94d06f9 100644 --- a/gui/src/Variable/VariableInspectorWidget.cpp +++ b/gui/src/Variable/VariableInspectorWidget.cpp @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -27,9 +28,12 @@ public: if (data.isValid() && progressData.isValid()) { auto name = data.value(); auto progress = progressData.value(); - if (progress >= 0) { + if (progress > 0) { + auto cancelButtonWidth = 20; auto progressBarOption = QStyleOptionProgressBar{}; - progressBarOption.rect = option.rect; + auto progressRect = option.rect; + progressRect.setWidth(progressRect.width() - cancelButtonWidth); + progressBarOption.rect = progressRect; progressBarOption.minimum = 0; progressBarOption.maximum = 100; progressBarOption.progress = progress; @@ -38,14 +42,69 @@ public: progressBarOption.textVisible = true; progressBarOption.textAlignment = Qt::AlignCenter; + QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); + + // Cancel button + auto buttonRect = QRect(progressRect.right(), option.rect.top(), cancelButtonWidth, + option.rect.height()); + auto buttonOption = QStyleOptionButton{}; + buttonOption.rect = buttonRect; + buttonOption.text = "X"; + + QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter); + } + else { + QStyledItemDelegate::paint(painter, option, index); } } else { QStyledItemDelegate::paint(painter, option, index); } } + + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, + const QModelIndex &index) + { + if (event->type() == QEvent::MouseButtonRelease) { + auto data = index.data(Qt::DisplayRole); + auto progressData = index.data(VariableRoles::ProgressRole); + if (data.isValid() && progressData.isValid()) { + auto cancelButtonWidth = 20; + auto progressRect = option.rect; + progressRect.setWidth(progressRect.width() - cancelButtonWidth); + // Cancel button + auto buttonRect = QRect(progressRect.right(), option.rect.top(), cancelButtonWidth, + option.rect.height()); + + auto e = (QMouseEvent *)event; + auto clickX = e->x(); + auto clickY = e->y(); + + auto x = buttonRect.left(); // the X coordinate + auto y = buttonRect.top(); // the Y coordinate + auto w = buttonRect.width(); // button width + auto h = buttonRect.height(); // button height + + if (clickX > x && clickX < x + w) { + if (clickY > y && clickY < y + h) { + auto variableModel = sqpApp->variableController().variableModel(); + variableModel->abortProgress(index); + } + } + else { + QStyledItemDelegate::editorEvent(event, model, option, index); + } + } + else { + QStyledItemDelegate::editorEvent(event, model, option, index); + } + } + else { + QStyledItemDelegate::editorEvent(event, model, option, index); + } + } }; VariableInspectorWidget::VariableInspectorWidget(QWidget *parent) diff --git a/gui/src/Visualization/VisualizationGraphHelper.cpp b/gui/src/Visualization/VisualizationGraphHelper.cpp index afd2e62..66b9d7c 100644 --- a/gui/src/Visualization/VisualizationGraphHelper.cpp +++ b/gui/src/Visualization/VisualizationGraphHelper.cpp @@ -69,7 +69,6 @@ void updateScalarData(QCPAbstractPlottable *component, ScalarSeries &scalarSerie // Display all data - component->rescaleAxes(); component->parentPlot()->replot(); } else { diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 5af7fa8..bf903ae 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -25,8 +25,17 @@ const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier; struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { + explicit VisualizationGraphWidgetPrivate() : m_DoSynchronize{true}, m_IsCalibration{false} {} + + + // Return the operation when range changed + VisualizationGraphWidgetZoomType getZoomType(const QCPRange &t1, const QCPRange &t2); + // 1 variable -> n qcpplot std::multimap, QCPAbstractPlottable *> m_VariableToPlotMultiMap; + + bool m_DoSynchronize; + bool m_IsCalibration; }; VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent) @@ -48,10 +57,13 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); ui->widget->axisRect()->setRangeDrag(Qt::Horizontal); + connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress); + connect(ui->widget, &QCustomPlot::mouseRelease, this, + &VisualizationGraphWidget::onMouseRelease); connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel); - connect(ui->widget->xAxis, - static_cast(&QCPAxis::rangeChanged), this, - &VisualizationGraphWidget::onRangeChanged); + connect(ui->widget->xAxis, static_cast( + &QCPAxis::rangeChanged), + this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection); // Activates menu when right clicking on the graph ui->widget->setContextMenuPolicy(Qt::CustomContextMenu); @@ -68,6 +80,11 @@ VisualizationGraphWidget::~VisualizationGraphWidget() delete ui; } +void VisualizationGraphWidget::enableSynchronize(bool enable) +{ + impl->m_DoSynchronize = enable; +} + void VisualizationGraphWidget::addVariable(std::shared_ptr variable) { // Uses delegate to create the qcpplot components according to the variable @@ -89,8 +106,8 @@ void VisualizationGraphWidget::addVariableUsingGraph(std::shared_ptr v auto variableDateTimeWithTolerance = dateTime; - // add 10% tolerance for each side - auto tolerance = 0.1 * (dateTime.m_TEnd - dateTime.m_TStart); + // add 20% tolerance for each side + auto tolerance = 0.2 * (dateTime.m_TEnd - dateTime.m_TStart); variableDateTimeWithTolerance.m_TStart -= tolerance; variableDateTimeWithTolerance.m_TEnd += tolerance; @@ -122,6 +139,32 @@ void VisualizationGraphWidget::removeVariable(std::shared_ptr variable ui->widget->replot(); } +void VisualizationGraphWidget::setRange(std::shared_ptr variable, + const SqpDateTime &range) +{ + // Note: in case of different axes that depends on variable, we could start with a code like + // that: + // auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable); + // for (auto it = componentsIt.first; it != componentsIt.second;) { + // } + ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd); + ui->widget->replot(); +} + +SqpDateTime VisualizationGraphWidget::graphRange() const noexcept +{ + auto grapheRange = ui->widget->xAxis->range(); + return SqpDateTime{grapheRange.lower, grapheRange.upper}; +} + +void VisualizationGraphWidget::setGraphRange(const SqpDateTime &range) +{ + qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START"); + ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd); + ui->widget->replot(); + qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END"); +} + void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor) { if (visitor) { @@ -175,62 +218,104 @@ void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept } } -void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1) +void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2) { - qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::onRangeChanged") - << QThread::currentThread()->objectName(); + qCInfo(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::onRangeChanged") + << QThread::currentThread()->objectName(); + + auto dateTimeRange = SqpDateTime{t1.lower, t1.upper}; + auto zoomType = impl->getZoomType(t1, t2); for (auto it = impl->m_VariableToPlotMultiMap.cbegin(); it != impl->m_VariableToPlotMultiMap.cend(); ++it) { auto variable = it->first; - auto dateTime = SqpDateTime{t1.lower, t1.upper}; - - if (!variable->contains(dateTime)) { - - auto variableDateTimeWithTolerance = dateTime; - if (!variable->isInside(dateTime)) { + auto currentDateTime = dateTimeRange; + + auto toleranceFactor = 0.2; + auto tolerance = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart); + auto variableDateTimeWithTolerance = currentDateTime; + variableDateTimeWithTolerance.m_TStart -= tolerance; + variableDateTimeWithTolerance.m_TEnd += tolerance; + + qCDebug(LOG_VisualizationGraphWidget()) << "r" << currentDateTime; + qCDebug(LOG_VisualizationGraphWidget()) << "t" << variableDateTimeWithTolerance; + qCDebug(LOG_VisualizationGraphWidget()) << "v" << variable->dateTime(); + // If new range with tol is upper than variable datetime parameters. we need to request new + // data + if (!variable->contains(variableDateTimeWithTolerance)) { + + auto variableDateTimeWithTolerance = currentDateTime; + if (!variable->isInside(currentDateTime)) { auto variableDateTime = variable->dateTime(); - if (variableDateTime.m_TStart < dateTime.m_TStart) { - qCDebug(LOG_VisualizationGraphWidget()) << tr("TDetection pan to right:"); + if (variable->contains(variableDateTimeWithTolerance)) { + qCDebug(LOG_VisualizationGraphWidget()) + << tr("TORM: Detection zoom in that need request:"); + // add 10% tolerance for each side + tolerance + = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart); + variableDateTimeWithTolerance.m_TStart -= tolerance; + variableDateTimeWithTolerance.m_TEnd += tolerance; + } + else if (variableDateTime.m_TStart < currentDateTime.m_TStart) { + qCInfo(LOG_VisualizationGraphWidget()) << tr("TORM: Detection pan to right:"); - auto diffEndToKeepDelta = dateTime.m_TEnd - variableDateTime.m_TEnd; - dateTime.m_TStart = variableDateTime.m_TStart + diffEndToKeepDelta; + auto diffEndToKeepDelta = currentDateTime.m_TEnd - variableDateTime.m_TEnd; + currentDateTime.m_TStart = variableDateTime.m_TStart + diffEndToKeepDelta; // Tolerance have to be added to the right - // add 10% tolerance for right (end) side - auto tolerance = 0.1 * (dateTime.m_TEnd - dateTime.m_TStart); + // add tolerance for right (end) side + tolerance + = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart); variableDateTimeWithTolerance.m_TEnd += tolerance; } - else if (variableDateTime.m_TEnd > dateTime.m_TEnd) { - qCDebug(LOG_VisualizationGraphWidget()) << tr("Detection pan to left: "); - auto diffStartToKeepDelta = variableDateTime.m_TStart - dateTime.m_TStart; - dateTime.m_TEnd = variableDateTime.m_TEnd - diffStartToKeepDelta; + else if (variableDateTime.m_TEnd > currentDateTime.m_TEnd) { + qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Detection pan to left: "); + auto diffStartToKeepDelta + = variableDateTime.m_TStart - currentDateTime.m_TStart; + currentDateTime.m_TEnd = variableDateTime.m_TEnd - diffStartToKeepDelta; // Tolerance have to be added to the left - // add 10% tolerance for left (start) side - auto tolerance = 0.1 * (dateTime.m_TEnd - dateTime.m_TStart); + // add tolerance for left (start) side + tolerance + = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart); variableDateTimeWithTolerance.m_TStart -= tolerance; } else { - qCWarning(LOG_VisualizationGraphWidget()) + qCCritical(LOG_VisualizationGraphWidget()) << tr("Detection anormal zoom detection: "); } } else { - qCDebug(LOG_VisualizationGraphWidget()) << tr("Detection zoom out: "); + qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Detection zoom out: "); // add 10% tolerance for each side - auto tolerance = 0.1 * (dateTime.m_TEnd - dateTime.m_TStart); + tolerance = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart); variableDateTimeWithTolerance.m_TStart -= tolerance; variableDateTimeWithTolerance.m_TEnd += tolerance; + zoomType = VisualizationGraphWidgetZoomType::ZoomOut; + } + if (!variable->contains(dateTimeRange)) { + qCDebug(LOG_VisualizationGraphWidget()) + << "TORM: Modif on variable datetime detected" << currentDateTime; + variable->setDateTime(currentDateTime); } - variable->setDateTime(dateTime); + qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Request data detection: "); // CHangement detected, we need to ask controller to request data loading emit requestDataLoading(variable, variableDateTimeWithTolerance); } else { - qCDebug(LOG_VisualizationGraphWidget()) << tr("Detection zoom in: "); + qCInfo(LOG_VisualizationGraphWidget()) + << tr("TORM: Detection zoom in that doesn't need request: "); + zoomType = VisualizationGraphWidgetZoomType::ZoomIn; } } + + if (impl->m_DoSynchronize && !impl->m_IsCalibration) { + auto oldDateTime = SqpDateTime{t2.lower, t2.upper}; + qCDebug(LOG_VisualizationGraphWidget()) + << tr("TORM: VisualizationGraphWidget::Synchronize notify !!") + << QThread::currentThread()->objectName(); + emit synchronize(dateTimeRange, oldDateTime, zoomType); + } } void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept @@ -251,6 +336,16 @@ void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept ui->widget->axisRect()->setRangeZoom(zoomOrientations); } +void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept +{ + impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier); +} + +void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept +{ + impl->m_IsCalibration = false; +} + void VisualizationGraphWidget::onDataCacheVariableUpdated() { // NOTE: @@ -262,24 +357,45 @@ void VisualizationGraphWidget::onDataCacheVariableUpdated() // - use an ordered_multimap and the algos of std to group the values by key // - use a map (unique keys) and store as values directly the list of components + auto grapheRange = ui->widget->xAxis->range(); + auto dateTime = SqpDateTime{grapheRange.lower, grapheRange.upper}; + for (auto it = impl->m_VariableToPlotMultiMap.cbegin(); it != impl->m_VariableToPlotMultiMap.cend(); ++it) { auto variable = it->first; - VisualizationGraphHelper::updateData(QVector{} << it->second, - variable->dataSeries(), variable->dateTime()); + qCDebug(LOG_VisualizationGraphWidget()) + << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" + << variable->dateTime(); + qCDebug(LOG_VisualizationGraphWidget()) + << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime; + if (dateTime.contains(variable->dateTime()) || dateTime.intersect(variable->dateTime())) { + + VisualizationGraphHelper::updateData(QVector{} << it->second, + variable->dataSeries(), variable->dateTime()); + } } } -void VisualizationGraphWidget::updateDisplay(std::shared_ptr variable) +VisualizationGraphWidgetZoomType +VisualizationGraphWidget::VisualizationGraphWidgetPrivate::getZoomType(const QCPRange &t1, + const QCPRange &t2) { - auto abstractPlotableItPair = impl->m_VariableToPlotMultiMap.equal_range(variable); - - auto abstractPlotableVect = QVector{}; - - for (auto it = abstractPlotableItPair.first; it != abstractPlotableItPair.second; ++it) { - abstractPlotableVect.push_back(it->second); + // t1.lower <= t2.lower && t2.upper <= t1.upper + auto zoomType = VisualizationGraphWidgetZoomType::Unknown; + if (t1.lower <= t2.lower && t2.upper <= t1.upper) { + zoomType = VisualizationGraphWidgetZoomType::ZoomOut; } - - VisualizationGraphHelper::updateData(abstractPlotableVect, variable->dataSeries(), - variable->dateTime()); + else if (t1.lower > t2.lower && t1.upper > t2.upper) { + zoomType = VisualizationGraphWidgetZoomType::PanRight; + } + else if (t1.lower < t2.lower && t1.upper < t2.upper) { + zoomType = VisualizationGraphWidgetZoomType::PanLeft; + } + else if (t1.lower > t2.lower && t2.upper > t1.upper) { + zoomType = VisualizationGraphWidgetZoomType::ZoomIn; + } + else { + qCCritical(LOG_VisualizationGraphWidget()) << "getZoomType: Unknown type detected"; + } + return zoomType; } diff --git a/gui/src/Visualization/VisualizationWidget.cpp b/gui/src/Visualization/VisualizationWidget.cpp index 2b29114..c5d86c7 100644 --- a/gui/src/Visualization/VisualizationWidget.cpp +++ b/gui/src/Visualization/VisualizationWidget.cpp @@ -5,6 +5,7 @@ #include "Visualization/VisualizationZoneWidget.h" #include "Visualization/operations/GenerateVariableMenuOperation.h" #include "Visualization/operations/RemoveVariableOperation.h" +#include "Visualization/operations/RescaleAxeOperation.h" #include "Visualization/qcustomplot.h" #include "ui_VisualizationWidget.h" @@ -141,3 +142,11 @@ void VisualizationWidget::onVariableAboutToBeDeleted(std::shared_ptr v auto removeVariableOperation = RemoveVariableOperation{variable}; accept(&removeVariableOperation); } + +void VisualizationWidget::onRangeChanged(std::shared_ptr variable, + const SqpDateTime &range) noexcept +{ + // Calls the operation of rescaling all graph that contrains variable in the visualization + auto rescaleVariableOperation = RescaleAxeOperation{variable, range}; + accept(&rescaleVariableOperation); +} diff --git a/gui/src/Visualization/VisualizationZoneWidget.cpp b/gui/src/Visualization/VisualizationZoneWidget.cpp index 355dbf4..293a585 100644 --- a/gui/src/Visualization/VisualizationZoneWidget.cpp +++ b/gui/src/Visualization/VisualizationZoneWidget.cpp @@ -1,8 +1,11 @@ #include "Visualization/VisualizationZoneWidget.h" + +#include "Data/SqpDateTime.h" + #include "Visualization/IVisualizationWidgetVisitor.h" +#include "Visualization/VisualizationGraphWidget.h" #include "ui_VisualizationZoneWidget.h" -#include "Visualization/VisualizationGraphWidget.h" #include @@ -57,6 +60,7 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptrvisualizationZoneFrame->layout()), this}; + // Set graph properties graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT); @@ -65,6 +69,91 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptraddVariable(variable); + // Lambda to synchronize zone widget + auto synchronizeZoneWidget = [this, graphWidget](const SqpDateTime &dateTime, + const SqpDateTime &oldDateTime, + VisualizationGraphWidgetZoomType zoomType) { + auto frameLayout = ui->visualizationZoneFrame->layout(); + for (auto i = 0; i < frameLayout->count(); ++i) { + auto graphChild + = dynamic_cast(frameLayout->itemAt(i)->widget()); + if (graphChild && (graphChild != graphWidget)) { + + auto graphChildRange = graphChild->graphRange(); + switch (zoomType) { + case VisualizationGraphWidgetZoomType::ZoomIn: { + auto deltaLeft = dateTime.m_TStart - oldDateTime.m_TStart; + auto deltaRight = oldDateTime.m_TEnd - dateTime.m_TEnd; + graphChildRange.m_TStart += deltaLeft; + graphChildRange.m_TEnd -= deltaRight; + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn"); + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft") + << deltaLeft; + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight") + << deltaRight; + qCCritical(LOG_VisualizationZoneWidget()) + << tr("TORM: dt") << dateTime.m_TEnd - dateTime.m_TStart; + + break; + } + + case VisualizationGraphWidgetZoomType::ZoomOut: { + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut"); + auto deltaLeft = oldDateTime.m_TStart - dateTime.m_TStart; + auto deltaRight = dateTime.m_TEnd - oldDateTime.m_TEnd; + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft") + << deltaLeft; + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight") + << deltaRight; + qCCritical(LOG_VisualizationZoneWidget()) + << tr("TORM: dt") << dateTime.m_TEnd - dateTime.m_TStart; + graphChildRange.m_TStart -= deltaLeft; + graphChildRange.m_TEnd += deltaRight; + break; + } + case VisualizationGraphWidgetZoomType::PanRight: { + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight"); + auto deltaRight = dateTime.m_TEnd - oldDateTime.m_TEnd; + graphChildRange.m_TStart += deltaRight; + graphChildRange.m_TEnd += deltaRight; + qCCritical(LOG_VisualizationZoneWidget()) + << tr("TORM: dt") << dateTime.m_TEnd - dateTime.m_TStart; + break; + } + case VisualizationGraphWidgetZoomType::PanLeft: { + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft"); + auto deltaLeft = oldDateTime.m_TStart - dateTime.m_TStart; + graphChildRange.m_TStart -= deltaLeft; + graphChildRange.m_TEnd -= deltaLeft; + break; + } + case VisualizationGraphWidgetZoomType::Unknown: { + qCCritical(LOG_VisualizationZoneWidget()) + << 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; + } + graphChild->enableSynchronize(false); + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ") + << graphChild->graphRange(); + qCCritical(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ") + << graphChildRange; + qCCritical(LOG_VisualizationZoneWidget()) + << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart; + graphChild->setGraphRange(graphChildRange); + graphChild->enableSynchronize(true); + } + } + }; + + // connection for synchronization + connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget); + return graphWidget; } diff --git a/gui/src/Visualization/operations/RescaleAxeOperation.cpp b/gui/src/Visualization/operations/RescaleAxeOperation.cpp new file mode 100644 index 0000000..7f8fc66 --- /dev/null +++ b/gui/src/Visualization/operations/RescaleAxeOperation.cpp @@ -0,0 +1,71 @@ +#include "Visualization/operations/RescaleAxeOperation.h" +#include "Visualization/VisualizationGraphWidget.h" + +Q_LOGGING_CATEGORY(LOG_RescaleAxeOperation, "RescaleAxeOperation") + +struct RescaleAxeOperation::RescaleAxeOperationPrivate { + explicit RescaleAxeOperationPrivate(std::shared_ptr variable, + const SqpDateTime &range) + : m_Variable{variable}, m_Range{range} + { + } + + std::shared_ptr m_Variable; + SqpDateTime m_Range; +}; + +RescaleAxeOperation::RescaleAxeOperation(std::shared_ptr variable, + const SqpDateTime &range) + : impl{spimpl::make_unique_impl(variable, range)} +{ +} + +void RescaleAxeOperation::visitEnter(VisualizationWidget *widget) +{ + // VisualizationWidget is not intended to contain a variable + Q_UNUSED(widget) +} + +void RescaleAxeOperation::visitLeave(VisualizationWidget *widget) +{ + // VisualizationWidget is not intended to contain a variable + Q_UNUSED(widget) +} + +void RescaleAxeOperation::visitEnter(VisualizationTabWidget *tabWidget) +{ + // VisualizationTabWidget is not intended to contain a variable + Q_UNUSED(tabWidget) +} + +void RescaleAxeOperation::visitLeave(VisualizationTabWidget *tabWidget) +{ + // VisualizationTabWidget is not intended to contain a variable + Q_UNUSED(tabWidget) +} + +void RescaleAxeOperation::visitEnter(VisualizationZoneWidget *zoneWidget) +{ + // VisualizationZoneWidget is not intended to contain a variable + Q_UNUSED(zoneWidget) +} + +void RescaleAxeOperation::visitLeave(VisualizationZoneWidget *zoneWidget) +{ + // VisualizationZoneWidget is not intended to contain a variable + Q_UNUSED(zoneWidget) +} + +void RescaleAxeOperation::visit(VisualizationGraphWidget *graphWidget) +{ + if (graphWidget) { + // If the widget contains the variable, rescale it + if (impl->m_Variable && graphWidget->contains(*impl->m_Variable)) { + graphWidget->setRange(impl->m_Variable, impl->m_Range); + } + } + else { + qCCritical(LOG_RescaleAxeOperation(), + "Can't visit VisualizationGraphWidget : the widget is null"); + } +} diff --git a/plugins/amda/include/AmdaProvider.h b/plugins/amda/include/AmdaProvider.h index d65573b..6188efe 100644 --- a/plugins/amda/include/AmdaProvider.h +++ b/plugins/amda/include/AmdaProvider.h @@ -21,6 +21,8 @@ public: void requestDataLoading(QUuid token, const DataProviderParameters ¶meters) override; + void requestDataAborting(QUuid identifier) override; + private: void retrieveData(QUuid token, const SqpDateTime &dateTime, const QVariantHash &data); }; diff --git a/plugins/amda/src/AmdaProvider.cpp b/plugins/amda/src/AmdaProvider.cpp index 20be39b..6191106 100644 --- a/plugins/amda/src/AmdaProvider.cpp +++ b/plugins/amda/src/AmdaProvider.cpp @@ -26,7 +26,7 @@ const auto AMDA_URL_FORMAT = QStringLiteral( "timeFormat=ISO8601&gzip=0"); /// Dates format passed in the URL (e.g 2013-09-23T09:00) -const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:ss"); +const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss"); /// Formats a time to a date that can be passed in URL QString dateFormat(double sqpDateTime) noexcept @@ -48,6 +48,10 @@ AmdaProvider::AmdaProvider() &networkController, SLOT(onProcessRequested(QNetworkRequest, QUuid, std::function))); + + + connect(&sqpApp->networkController(), SIGNAL(replyDownloadProgress(QUuid, double)), this, + SIGNAL(dataProvidedProgress(QUuid, double))); } } @@ -61,6 +65,14 @@ void AmdaProvider::requestDataLoading(QUuid token, const DataProviderParameters } } +void AmdaProvider::requestDataAborting(QUuid identifier) +{ + if (auto app = sqpApp) { + auto &networkController = app->networkController(); + networkController.onReplyCanceled(identifier); + } +} + void AmdaProvider::retrieveData(QUuid token, const SqpDateTime &dateTime, const QVariantHash &data) { // Retrieves product ID from data: if the value is invalid, no request is made @@ -69,6 +81,7 @@ void AmdaProvider::retrieveData(QUuid token, const SqpDateTime &dateTime, const qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id"); return; } + qCInfo(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData") << dateTime; // /////////// // // Creates URL // @@ -78,7 +91,7 @@ void AmdaProvider::retrieveData(QUuid token, const SqpDateTime &dateTime, const auto endDate = dateFormat(dateTime.m_TEnd); auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(startDate, endDate, productId)}; - + qCInfo(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData url:") << url; auto tempFile = std::make_shared(); // LAMBDA @@ -86,41 +99,47 @@ void AmdaProvider::retrieveData(QUuid token, const SqpDateTime &dateTime, const = [this, dateTime, tempFile, token](QNetworkReply *reply, QUuid dataId) noexcept { Q_UNUSED(dataId); - if (tempFile) { - auto replyReadAll = reply->readAll(); - if (!replyReadAll.isEmpty()) { - tempFile->write(replyReadAll); - } - tempFile->close(); - - // Parse results file - if (auto dataSeries = AmdaResultParser::readTxt(tempFile->fileName())) { - emit dataProvided(token, dataSeries, dateTime); - } - else { - /// @todo ALX : debug + // Don't do anything if the reply was abort + if (reply->error() != QNetworkReply::OperationCanceledError) { + + if (tempFile) { + auto replyReadAll = reply->readAll(); + if (!replyReadAll.isEmpty()) { + tempFile->write(replyReadAll); + } + tempFile->close(); + + // Parse results file + if (auto dataSeries = AmdaResultParser::readTxt(tempFile->fileName())) { + emit dataProvided(token, dataSeries, dateTime); + } + else { + /// @todo ALX : debug + } } } - // Deletes reply - reply->deleteLater(); - reply = nullptr; }; - auto httpFinishedLambda = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, - QUuid dataId) noexcept { + auto httpFinishedLambda + = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept { - auto downloadFileUrl = QUrl{QString{reply->readAll()}}; - // Deletes old reply - reply->deleteLater(); + // Don't do anything if the reply was abort + if (reply->error() != QNetworkReply::OperationCanceledError) { + auto downloadFileUrl = QUrl{QString{reply->readAll()}}; - // Executes request for downloading file // - // Creates destination file - if (tempFile->open()) { - // Executes request - emit requestConstructed(QNetworkRequest{downloadFileUrl}, dataId, httpDownloadFinished); - } - }; + qCInfo(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData downloadFileUrl:") + << downloadFileUrl; + // Executes request for downloading file // + + // Creates destination file + if (tempFile->open()) { + // Executes request + emit requestConstructed(QNetworkRequest{downloadFileUrl}, dataId, + httpDownloadFinished); + } + } + }; // //////////////// // // Executes request // diff --git a/plugins/amda/src/AmdaResultParser.cpp b/plugins/amda/src/AmdaResultParser.cpp index 668399b..e435dce 100644 --- a/plugins/amda/src/AmdaResultParser.cpp +++ b/plugins/amda/src/AmdaResultParser.cpp @@ -12,6 +12,9 @@ Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser") namespace { +/// Message in result file when the file was not found on server +const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found"); + /// Format for dates in result files const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"); @@ -119,8 +122,16 @@ std::shared_ptr AmdaResultParser::readTxt(const QString &filePath) QTextStream stream{&file}; - // Ignore first two lines (comments lines) - stream.readLine(); + // Checks if the file was found on the server + auto firstLine = stream.readLine(); + if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) { + qCCritical(LOG_AmdaResultParser()) + << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server") + .arg(filePath); + return nullptr; + } + + // Ignore comments lines stream.readLine(); // Reads x-axis unit diff --git a/plugins/amda/tests-resources/TestAmdaResultParser/FileNotFound.txt b/plugins/amda/tests-resources/TestAmdaResultParser/FileNotFound.txt new file mode 100644 index 0000000..bba04b0 --- /dev/null +++ b/plugins/amda/tests-resources/TestAmdaResultParser/FileNotFound.txt @@ -0,0 +1,3 @@ +Not Found + +The requested URL /AMDA/data/WSRESULT/imf(0)-1343153090-1343153092-60.txt was not found on this server. \ No newline at end of file diff --git a/plugins/amda/tests/TestAmdaResultParser.cpp b/plugins/amda/tests/TestAmdaResultParser.cpp index ece3bf5..0501e79 100644 --- a/plugins/amda/tests/TestAmdaResultParser.cpp +++ b/plugins/amda/tests/TestAmdaResultParser.cpp @@ -155,9 +155,12 @@ void TestAmdaResultParser::testReadTxt_data() QVector{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)}, QVector{-2.71850, -2.52150}}; - // Invalid file + // Invalid files QTest::newRow("Invalid file (unexisting file)") << QStringLiteral("UnexistingFile.txt") << ExpectedResults{}; + + QTest::newRow("Invalid file (file not found on server)") << QStringLiteral("FileNotFound.txt") + << ExpectedResults{}; } void TestAmdaResultParser::testReadTxt() diff --git a/plugins/mockplugin/include/CosinusProvider.h b/plugins/mockplugin/include/CosinusProvider.h index 1f04e38..44ae6c6 100644 --- a/plugins/mockplugin/include/CosinusProvider.h +++ b/plugins/mockplugin/include/CosinusProvider.h @@ -6,7 +6,9 @@ #include #include +#include +#include Q_DECLARE_LOGGING_CATEGORY(LOG_CosinusProvider) /** @@ -14,12 +16,18 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_CosinusProvider) */ class SCIQLOP_MOCKPLUGIN_EXPORT CosinusProvider : public IDataProvider { public: + /// @sa IDataProvider::requestDataLoading(). The current impl isn't thread safe. void requestDataLoading(QUuid token, const DataProviderParameters ¶meters) override; + /// @sa IDataProvider::requestDataAborting(). The current impl isn't thread safe. + void requestDataAborting(QUuid identifier) override; + + private: - /// @sa IDataProvider::retrieveData() - std::shared_ptr retrieveData(const SqpDateTime &dateTime) const; + std::shared_ptr retrieveData(QUuid token, const SqpDateTime &dateTime); + + QHash m_VariableToEnableProvider; }; #endif // SCIQLOP_COSINUSPROVIDER_H diff --git a/plugins/mockplugin/src/CosinusProvider.cpp b/plugins/mockplugin/src/CosinusProvider.cpp index 0fd5537..82a8bdd 100644 --- a/plugins/mockplugin/src/CosinusProvider.cpp +++ b/plugins/mockplugin/src/CosinusProvider.cpp @@ -6,12 +6,15 @@ #include #include +#include #include +#include Q_LOGGING_CATEGORY(LOG_CosinusProvider, "CosinusProvider") -std::shared_ptr CosinusProvider::retrieveData(const SqpDateTime &dateTime) const +std::shared_ptr CosinusProvider::retrieveData(QUuid token, const SqpDateTime &dateTime) { + // TODO: Add Mutex auto dataIndex = 0; // Gets the timerange from the parameters @@ -28,21 +31,65 @@ std::shared_ptr CosinusProvider::retrieveData(const SqpDateTime &da auto scalarSeries = std::make_shared(end - start, Unit{QStringLiteral("t"), true}, Unit{}); + + int progress = 0; + auto progressEnd = end - start; for (auto time = start; time < end; ++time, ++dataIndex) { - const auto timeOnFreq = time / freq; - scalarSeries->setData(dataIndex, timeOnFreq, std::cos(timeOnFreq)); + auto it = m_VariableToEnableProvider.find(token); + if (it != m_VariableToEnableProvider.end() && it.value()) { + const auto timeOnFreq = time / freq; + scalarSeries->setData(dataIndex, timeOnFreq, std::cos(timeOnFreq)); + + // progression + int currentProgress = (time - start) * 100.0 / progressEnd; + if (currentProgress != progress) { + progress = currentProgress; + + emit dataProvidedProgress(token, progress); + } + } + else { + if (!it.value()) { + qCDebug(LOG_CosinusProvider()) + << "CosinusProvider::retrieveData: ARRET De l'acquisition detecté" + << end - time; + } + } } + emit dataProvidedProgress(token, 0.0); + + return scalarSeries; } void CosinusProvider::requestDataLoading(QUuid token, const DataProviderParameters ¶meters) { + // TODO: Add Mutex + m_VariableToEnableProvider[token] = true; qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataLoading" << QThread::currentThread()->objectName(); // NOTE: Try to use multithread if possible const auto times = parameters.m_Times; + for (const auto &dateTime : qAsConst(times)) { - auto scalarSeries = this->retrieveData(dateTime); - emit dataProvided(token, scalarSeries, dateTime); + if (m_VariableToEnableProvider[token]) { + auto scalarSeries = this->retrieveData(token, dateTime); + emit dataProvided(token, scalarSeries, dateTime); + } + } +} + +void CosinusProvider::requestDataAborting(QUuid identifier) +{ + // TODO: Add Mutex + qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataAborting" << identifier + << QThread::currentThread()->objectName(); + auto it = m_VariableToEnableProvider.find(identifier); + if (it != m_VariableToEnableProvider.end()) { + it.value() = false; + } + else { + qCWarning(LOG_CosinusProvider()) + << tr("Aborting progression of inexistant identifier detected !!!"); } }