##// END OF EJS Templates
Merge pull request 392 from SciQLop-fork develop...
perrinel -
r1167:9af4de2afc8d merge
parent child
Show More
@@ -0,0 +1,17
1 #ifndef SCIQLOP_CATALOGUEACTIONMANAGER_H
2 #define SCIQLOP_CATALOGUEACTIONMANAGER_H
3
4 #include <Common/spimpl.h>
5
6 class CatalogueActionManager {
7 public:
8 CatalogueActionManager();
9
10 void installSelectionZoneActions();
11
12 private:
13 class CatalogueActionManagerPrivate;
14 spimpl::unique_impl_ptr<CatalogueActionManagerPrivate> impl;
15 };
16
17 #endif // SCIQLOP_CATALOGUEACTIONMANAGER_H
@@ -0,0 +1,13
1 #ifndef SCIQLOP_CATALOGUEEXPLORERHELPER_H
2 #define SCIQLOP_CATALOGUEEXPLORERHELPER_H
3
4 #include <QWidget>
5
6 #include <functional>
7
8 struct CatalogueExplorerHelper {
9 static QWidget *buildValidationWidget(QWidget *parent, std::function<void()> save,
10 std::function<void()> discard);
11 };
12
13 #endif // SCIQLOP_CATALOGUEEXPLORERHELPER_H
@@ -0,0 +1,35
1 #ifndef SCIQLOP_CREATEEVENTDIALOG_H
2 #define SCIQLOP_CREATEEVENTDIALOG_H
3
4 #include <Common/spimpl.h>
5 #include <QDialog>
6 #include <memory>
7
8 namespace Ui {
9 class CreateEventDialog;
10 }
11
12 class DBCatalogue;
13
14 class CreateEventDialog : public QDialog {
15 Q_OBJECT
16
17 public:
18 explicit CreateEventDialog(QWidget *parent = 0);
19 virtual ~CreateEventDialog();
20
21 void hideCatalogueChoice();
22
23 QString eventName() const;
24
25 std::shared_ptr<DBCatalogue> selectedCatalogue() const;
26 QString catalogueName() const;
27
28 private:
29 Ui::CreateEventDialog *ui;
30
31 class CreateEventDialogPrivate;
32 spimpl::unique_impl_ptr<CreateEventDialogPrivate> impl;
33 };
34
35 #endif // SCIQLOP_CREATEEVENTDIALOG_H
@@ -0,0 +1,107
1 #include "Catalogue/CatalogueActionManager.h"
2
3 #include <Actions/ActionsGuiController.h>
4 #include <Catalogue/CatalogueController.h>
5 #include <SqpApplication.h>
6 #include <Variable/Variable.h>
7 #include <Visualization/VisualizationGraphWidget.h>
8 #include <Visualization/VisualizationSelectionZoneItem.h>
9
10 #include <Catalogue/CreateEventDialog.h>
11
12 #include <DBCatalogue.h>
13 #include <DBEvent.h>
14 #include <DBEventProduct.h>
15
16 #include <QBoxLayout>
17 #include <QComboBox>
18 #include <QDialog>
19 #include <QDialogButtonBox>
20 #include <QLineEdit>
21 #include <memory>
22
23 struct CatalogueActionManager::CatalogueActionManagerPrivate {
24 void createEventFromZones(const QString &eventName,
25 const QVector<VisualizationSelectionZoneItem *> &zones,
26 const std::shared_ptr<DBCatalogue> &catalogue = nullptr)
27 {
28 auto event = std::make_shared<DBEvent>();
29 event->setName(eventName);
30
31 std::list<DBEventProduct> productList;
32 for (auto zone : zones) {
33 auto graph = zone->parentGraphWidget();
34 for (auto var : graph->variables()) {
35 auto eventProduct = std::make_shared<DBEventProduct>();
36 eventProduct->setEvent(*event);
37
38 auto zoneRange = zone->range();
39 eventProduct->setTStart(zoneRange.m_TStart);
40 eventProduct->setTEnd(zoneRange.m_TEnd);
41
42 eventProduct->setProductId(var->metadata().value("id", "TODO").toString()); // todo
43
44 productList.push_back(*eventProduct);
45 }
46 }
47
48 event->setEventProducts(productList);
49
50 sqpApp->catalogueController().addEvent(event);
51
52 if (catalogue) {
53 // TODO
54 // catalogue->addEvent(event);
55 }
56 }
57 };
58
59 CatalogueActionManager::CatalogueActionManager()
60 : impl{spimpl::make_unique_impl<CatalogueActionManagerPrivate>()}
61 {
62 }
63
64 void CatalogueActionManager::installSelectionZoneActions()
65 {
66 auto &actionController = sqpApp->actionsGuiController();
67
68 auto createEventEnableFuntion = [](auto zones) {
69 QSet<VisualizationGraphWidget *> usedGraphs;
70 for (auto zone : zones) {
71 auto graph = zone->parentGraphWidget();
72 if (!usedGraphs.contains(graph)) {
73 usedGraphs.insert(graph);
74 }
75 else {
76 return false;
77 }
78 }
79
80 return true;
81 };
82
83 auto createEventAction = actionController.addSectionZoneAction(
84 {QObject::tr("Catalogues")}, QObject::tr("New Event..."), [this](auto zones) {
85 CreateEventDialog dialog;
86 dialog.hideCatalogueChoice();
87 if (dialog.exec() == QDialog::Accepted) {
88 impl->createEventFromZones(dialog.eventName(), zones);
89 }
90 });
91 createEventAction->setEnableFunction(createEventEnableFuntion);
92
93 auto createEventInCatalogueAction = actionController.addSectionZoneAction(
94 {QObject::tr("Catalogues")}, QObject::tr("New Event in Catalogue..."), [this](auto zones) {
95 CreateEventDialog dialog;
96 if (dialog.exec() == QDialog::Accepted) {
97 auto selectedCatalogue = dialog.selectedCatalogue();
98 if (!selectedCatalogue) {
99 selectedCatalogue = std::make_shared<DBCatalogue>();
100 selectedCatalogue->setName(dialog.catalogueName());
101 }
102
103 impl->createEventFromZones(dialog.eventName(), zones, selectedCatalogue);
104 }
105 });
106 createEventInCatalogueAction->setEnableFunction(createEventEnableFuntion);
107 }
@@ -0,0 +1,32
1 #include "Catalogue/CatalogueExplorerHelper.h"
2
3 #include <QBoxLayout>
4 #include <QToolButton>
5
6 const auto VALIDATION_BUTTON_ICON_SIZE = 12;
7
8 QWidget *CatalogueExplorerHelper::buildValidationWidget(QWidget *parent, std::function<void()> save,
9 std::function<void()> discard)
10 {
11 auto widget = new QWidget{parent};
12
13 auto layout = new QHBoxLayout{widget};
14 layout->setContentsMargins(0, 0, 0, 0);
15 layout->setSpacing(0);
16
17 auto btnValid = new QToolButton{widget};
18 btnValid->setIcon(QIcon{":/icones/save"});
19 btnValid->setIconSize(QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE});
20 btnValid->setAutoRaise(true);
21 QObject::connect(btnValid, &QToolButton::clicked, save);
22 layout->addWidget(btnValid);
23
24 auto btnDiscard = new QToolButton{widget};
25 btnDiscard->setIcon(QIcon{":/icones/discard"});
26 btnDiscard->setIconSize(QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE});
27 btnDiscard->setAutoRaise(true);
28 QObject::connect(btnDiscard, &QToolButton::clicked, discard);
29 layout->addWidget(btnDiscard);
30
31 return widget;
32 }
@@ -0,0 +1,59
1 #include "Catalogue/CreateEventDialog.h"
2 #include "ui_CreateEventDialog.h"
3
4 #include <Catalogue/CatalogueController.h>
5 #include <SqpApplication.h>
6
7 #include <DBCatalogue.h>
8
9 struct CreateEventDialog::CreateEventDialogPrivate {
10 QVector<std::shared_ptr<DBCatalogue> > m_DisplayedCatalogues;
11 };
12
13 CreateEventDialog::CreateEventDialog(QWidget *parent)
14 : QDialog(parent),
15 ui(new Ui::CreateEventDialog),
16 impl{spimpl::make_unique_impl<CreateEventDialogPrivate>()}
17 {
18 ui->setupUi(this);
19
20 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
21 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
22
23 auto catalogues = sqpApp->catalogueController().retrieveCatalogues();
24 for (auto cat : catalogues) {
25 ui->cbCatalogue->addItem(cat->getName());
26 impl->m_DisplayedCatalogues << cat;
27 }
28 }
29
30 CreateEventDialog::~CreateEventDialog()
31 {
32 delete ui;
33 }
34
35 void CreateEventDialog::hideCatalogueChoice()
36 {
37 ui->cbCatalogue->hide();
38 ui->lblCatalogue->hide();
39 }
40
41 QString CreateEventDialog::eventName() const
42 {
43 return ui->leEvent->text();
44 }
45
46 std::shared_ptr<DBCatalogue> CreateEventDialog::selectedCatalogue() const
47 {
48 auto catalogue = impl->m_DisplayedCatalogues.value(ui->cbCatalogue->currentIndex());
49 if (!catalogue || catalogue->getName() != catalogueName()) {
50 return nullptr;
51 }
52
53 return catalogue;
54 }
55
56 QString CreateEventDialog::catalogueName() const
57 {
58 return ui->cbCatalogue->currentText();
59 }
@@ -0,0 +1,55
1 <?xml version="1.0" encoding="UTF-8"?>
2 <ui version="4.0">
3 <class>CreateEventDialog</class>
4 <widget class="QDialog" name="CreateEventDialog">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>324</width>
10 <height>93</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>New Event</string>
15 </property>
16 <layout class="QGridLayout" name="gridLayout">
17 <item row="0" column="0">
18 <widget class="QLabel" name="label">
19 <property name="text">
20 <string>Event Name</string>
21 </property>
22 </widget>
23 </item>
24 <item row="0" column="1">
25 <widget class="QLineEdit" name="leEvent"/>
26 </item>
27 <item row="1" column="0">
28 <widget class="QLabel" name="lblCatalogue">
29 <property name="text">
30 <string>Catalogue</string>
31 </property>
32 </widget>
33 </item>
34 <item row="1" column="1">
35 <widget class="QComboBox" name="cbCatalogue">
36 <property name="editable">
37 <bool>true</bool>
38 </property>
39 <property name="insertPolicy">
40 <enum>QComboBox::NoInsert</enum>
41 </property>
42 </widget>
43 </item>
44 <item row="2" column="0" colspan="2">
45 <widget class="QDialogButtonBox" name="buttonBox">
46 <property name="standardButtons">
47 <set>QDialogButtonBox::Ok</set>
48 </property>
49 </widget>
50 </item>
51 </layout>
52 </widget>
53 <resources/>
54 <connections/>
55 </ui>
@@ -1,89 +1,89
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the QLop Software
3 3 -- Copyright (C) 2015, Plasma Physics Laboratory - CNRS
4 4 --
5 5 -- This program is free software; you can redistribute it and/or modify
6 6 -- it under the terms of the GNU General Public License as published by
7 7 -- the Free Software Foundation; either version 2 of the License, or
8 8 -- (at your option) any later version.
9 9 --
10 10 -- This program is distributed in the hope that it will be useful,
11 11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 -- GNU General Public License for more details.
14 14 --
15 15 -- You should have received a copy of the GNU General Public License
16 16 -- along with this program; if not, write to the Free Software
17 17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 18 -------------------------------------------------------------------------------*/
19 19 /*-- Author : Alexis Jeandet
20 20 -- Mail : alexis.jeandet@member.fsf.org
21 21 ----------------------------------------------------------------------------*/
22 22 #include "MainWindow.h"
23 23 #include <QProcessEnvironment>
24 24 #include <QThread>
25 25 #include <SqpApplication.h>
26 26 #include <qglobal.h>
27 27
28 #include <QtPlugin>
29 28 #include <Plugin/PluginManager.h>
30 29 #include <QDir>
30 #include <QtPlugin>
31 31
32 32 #include <QLoggingCategory>
33 33
34 34 Q_LOGGING_CATEGORY(LOG_Main, "Main")
35 35
36 36 namespace {
37 37
38 38 const auto PLUGIN_DIRECTORY_NAME = QStringLiteral("plugins");
39 39
40 40
41 41 } // namespace
42 42
43 43 int main(int argc, char *argv[])
44 44 {
45 45 #ifdef QT_STATICPLUGIN
46 46 Q_IMPORT_PLUGIN(MockPlugin)
47 47 Q_IMPORT_PLUGIN(AmdaPlugin)
48 48 Q_INIT_RESOURCE(amdaresources);
49 49 #endif
50 50 Q_INIT_RESOURCE(sqpguiresources);
51 51
52 52 SqpApplication a{argc, argv};
53 53 SqpApplication::setOrganizationName("LPP");
54 54 SqpApplication::setOrganizationDomain("lpp.fr");
55 55 SqpApplication::setApplicationName("SciQLop");
56 56 MainWindow w;
57 57 w.show();
58 58
59 59 // Loads plugins
60 60 auto pluginDir = QDir{a.applicationDirPath()};
61 61 auto pluginLookupPath = {
62 62 a.applicationDirPath(),
63 63 a.applicationDirPath() + "/" + PLUGIN_DIRECTORY_NAME,
64 64 a.applicationDirPath() + "/../lib64/SciQlop",
65 65 a.applicationDirPath() + "/../lib64/sciqlop",
66 66 a.applicationDirPath() + "/../lib/SciQlop",
67 67 a.applicationDirPath() + "/../lib/sciqlop",
68 68 a.applicationDirPath() + "/../plugins",
69 69 };
70 70
71 71 #if _WIN32 || _WIN64
72 72 pluginDir.mkdir(PLUGIN_DIRECTORY_NAME);
73 73 pluginDir.cd(PLUGIN_DIRECTORY_NAME);
74 74 #endif
75 75
76 76 PluginManager pluginManager{};
77 77
78 78 for (auto &&path : pluginLookupPath) {
79 79 QDir directory{path};
80 80 if (directory.exists()) {
81 81 qCDebug(LOG_Main())
82 82 << QObject::tr("Plugin directory: %1").arg(directory.absolutePath());
83 83 pluginManager.loadPlugins(directory);
84 84 }
85 85 }
86 86 pluginManager.loadStaticPlugins();
87 87
88 88 return a.exec();
89 89 }
@@ -1,69 +1,75
1 1 #ifndef SCIQLOP_CATALOGUECONTROLLER_H
2 2 #define SCIQLOP_CATALOGUECONTROLLER_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/SqpRange.h>
7 7
8 8 #include <QLoggingCategory>
9 9 #include <QObject>
10 10 #include <QUuid>
11 11
12 12 #include <Common/spimpl.h>
13 13
14 14 #include <memory>
15 15
16 16 class DBCatalogue;
17 17 class DBEvent;
18 18
19 19 Q_DECLARE_LOGGING_CATEGORY(LOG_CatalogueController)
20 20
21 21 class DataSourceItem;
22 22 class Variable;
23 23
24 24 /**
25 25 * @brief The CatalogueController class aims to handle catalogues and event using the CatalogueAPI
26 26 * library.
27 27 */
28 28 class SCIQLOP_CORE_EXPORT CatalogueController : public QObject {
29 29 Q_OBJECT
30 30 public:
31 31 explicit CatalogueController(QObject *parent = 0);
32 32 virtual ~CatalogueController();
33 33
34 34 // DB
35 // QStringList getRepositories() const;
35 QStringList getRepositories() const;
36 36 void addDB(const QString &dbPath);
37 37 void saveDB(const QString &destinationPath, const QString &repository);
38 38
39 39 // Event
40 // bool createEvent(const QString &name);
40 /// retrieveEvents with empty repository retrieve them from the default repository
41 41 std::list<std::shared_ptr<DBEvent> > retrieveEvents(const QString &repository) const;
42 42 std::list<std::shared_ptr<DBEvent> > retrieveAllEvents() const;
43 43 std::list<std::shared_ptr<DBEvent> >
44 44 retrieveEventsFromCatalogue(std::shared_ptr<DBCatalogue> catalogue) const;
45 // void updateEvent(std::shared_ptr<DBEvent> event);
45 void addEvent(std::shared_ptr<DBEvent> event);
46 void updateEvent(std::shared_ptr<DBEvent> event);
47 void removeEvent(std::shared_ptr<DBEvent> event);
46 48 // void trashEvent(std::shared_ptr<DBEvent> event);
47 // void removeEvent(std::shared_ptr<DBEvent> event);
48 49 // void restore(QUuid eventId);
49 // void saveEvent(std::shared_ptr<DBEvent> event);
50 void saveEvent(std::shared_ptr<DBEvent> event);
50 51
51 52 // Catalogue
52 53 // bool createCatalogue(const QString &name, QVector<QUuid> eventList);
53 std::list<std::shared_ptr<DBCatalogue> > getCatalogues(const QString &repository) const;
54 // void removeEvent(QUuid catalogueId, const QString &repository);
55 // void saveCatalogue(std::shared_ptr<DBEvent> event);
54 /// retrieveEvents with empty repository retrieve them from the default repository
55 std::list<std::shared_ptr<DBCatalogue> > retrieveCatalogues(const QString &repository
56 = QString()) const;
57 void updateCatalogue(std::shared_ptr<DBCatalogue> catalogue);
58 void removeCatalogue(std::shared_ptr<DBCatalogue> catalogue);
59 void saveCatalogue(std::shared_ptr<DBCatalogue> catalogue);
60
61 void saveAll();
56 62
57 63 public slots:
58 64 /// Manage init/end of the controller
59 65 void initialize();
60 66 void finalize();
61 67
62 68 private:
63 69 void waitForFinish();
64 70
65 71 class CatalogueControllerPrivate;
66 72 spimpl::unique_impl_ptr<CatalogueControllerPrivate> impl;
67 73 };
68 74
69 75 #endif // SCIQLOP_CATALOGUECONTROLLER_H
@@ -1,161 +1,296
1 1 #include <Catalogue/CatalogueController.h>
2 2
3 3 #include <Variable/Variable.h>
4 4
5 5 #include <CatalogueDao.h>
6 6
7 7 #include <ComparaisonPredicate.h>
8 8 #include <CompoundPredicate.h>
9 9 #include <DBCatalogue.h>
10 10 #include <DBEvent.h>
11 #include <DBEventProduct.h>
11 12 #include <DBTag.h>
12 13 #include <IRequestPredicate.h>
13 14
14 15 #include <QMutex>
15 16 #include <QThread>
16 17
17 18 #include <QDir>
18 19 #include <QStandardPaths>
19 20
20 21 Q_LOGGING_CATEGORY(LOG_CatalogueController, "CatalogueController")
21 22
22 23 namespace {
23 24
24 static QString REPOSITORY_WORK_SUFFIX = QString{"Work"};
25
25 static QString REPOSITORY_WORK_SUFFIX = QString{"work"};
26 static QString REPOSITORY_TRASH_SUFFIX = QString{"trash"};
26 27 }
27 28
28 29 class CatalogueController::CatalogueControllerPrivate {
30
29 31 public:
32 explicit CatalogueControllerPrivate(CatalogueController *parent) : m_Q{parent} {}
33
30 34 QMutex m_WorkingMutex;
31 35 CatalogueDao m_CatalogueDao;
32 36
33 std::list<QString> m_RepositoryList;
37 QStringList m_RepositoryList;
38 CatalogueController *m_Q;
39
40 void copyDBtoDB(const QString &dbFrom, const QString &dbTo);
41 QString toWorkRepository(QString repository);
42 QString toSyncRepository(QString repository);
34 43 };
35 44
36 45 CatalogueController::CatalogueController(QObject *parent)
37 : impl{spimpl::make_unique_impl<CatalogueControllerPrivate>()}
46 : impl{spimpl::make_unique_impl<CatalogueControllerPrivate>(this)}
38 47 {
39 48 qCDebug(LOG_CatalogueController()) << tr("CatalogueController construction")
40 49 << QThread::currentThread();
41 50 }
42 51
43 52 CatalogueController::~CatalogueController()
44 53 {
45 54 qCDebug(LOG_CatalogueController()) << tr("CatalogueController destruction")
46 55 << QThread::currentThread();
47 56 this->waitForFinish();
48 57 }
49 58
59 QStringList CatalogueController::getRepositories() const
60 {
61 return impl->m_RepositoryList;
62 }
63
50 64 void CatalogueController::addDB(const QString &dbPath)
51 65 {
52 66 QDir dbDir(dbPath);
53 67 if (dbDir.exists()) {
54 68 auto dirName = dbDir.dirName();
55 69
56 70 if (std::find(impl->m_RepositoryList.cbegin(), impl->m_RepositoryList.cend(), dirName)
57 71 != impl->m_RepositoryList.cend()) {
58 72 qCCritical(LOG_CatalogueController())
59 73 << tr("Impossible to addDB that is already loaded");
60 74 }
61 75
62 76 if (!impl->m_CatalogueDao.addDB(dbPath, dirName)) {
63 77 qCCritical(LOG_CatalogueController())
64 78 << tr("Impossible to addDB %1 from %2 ").arg(dirName, dbPath);
65 79 }
66 80 else {
67 impl->m_RepositoryList.push_back(dirName);
81 impl->m_RepositoryList << dirName;
82 impl->copyDBtoDB(dirName, impl->toWorkRepository(dirName));
68 83 }
69 84 }
70 85 else {
71 86 qCCritical(LOG_CatalogueController()) << tr("Impossible to addDB that not exists: ")
72 87 << dbPath;
73 88 }
74 89 }
75 90
76 91 void CatalogueController::saveDB(const QString &destinationPath, const QString &repository)
77 92 {
78 93 if (!impl->m_CatalogueDao.saveDB(destinationPath, repository)) {
79 94 qCCritical(LOG_CatalogueController())
80 95 << tr("Impossible to saveDB %1 from %2 ").arg(repository, destinationPath);
81 96 }
82 97 }
83 98
84 99 std::list<std::shared_ptr<DBEvent> >
85 100 CatalogueController::retrieveEvents(const QString &repository) const
86 101 {
102 QString dbDireName = repository.isEmpty() ? REPOSITORY_DEFAULT : repository;
103
87 104 auto eventsShared = std::list<std::shared_ptr<DBEvent> >{};
88 auto events = impl->m_CatalogueDao.getEvents(repository);
105 auto events = impl->m_CatalogueDao.getEvents(impl->toWorkRepository(dbDireName));
89 106 for (auto event : events) {
90 107 eventsShared.push_back(std::make_shared<DBEvent>(event));
91 108 }
92 109 return eventsShared;
93 110 }
94 111
95 112 std::list<std::shared_ptr<DBEvent> > CatalogueController::retrieveAllEvents() const
96 113 {
97 114 auto eventsShared = std::list<std::shared_ptr<DBEvent> >{};
98 115 for (auto repository : impl->m_RepositoryList) {
99 116 eventsShared.splice(eventsShared.end(), retrieveEvents(repository));
100 117 }
101 118
102 119 return eventsShared;
103 120 }
104 121
105 122 std::list<std::shared_ptr<DBEvent> >
106 123 CatalogueController::retrieveEventsFromCatalogue(std::shared_ptr<DBCatalogue> catalogue) const
107 124 {
108 125 auto eventsShared = std::list<std::shared_ptr<DBEvent> >{};
109 126 auto events = impl->m_CatalogueDao.getCatalogueEvents(*catalogue);
110 127 for (auto event : events) {
111 128 eventsShared.push_back(std::make_shared<DBEvent>(event));
112 129 }
113 130 return eventsShared;
114 131 }
115 132
133 void CatalogueController::updateEvent(std::shared_ptr<DBEvent> event)
134 {
135 event->setRepository(impl->toSyncRepository(event->getRepository()));
136
137 impl->m_CatalogueDao.updateEvent(*event);
138 }
139
140 void CatalogueController::removeEvent(std::shared_ptr<DBEvent> event)
141 {
142 // Remove it from both repository and repository_work
143 event->setRepository(impl->toWorkRepository(event->getRepository()));
144 impl->m_CatalogueDao.removeEvent(*event);
145 event->setRepository(impl->toSyncRepository(event->getRepository()));
146 impl->m_CatalogueDao.removeEvent(*event);
147 }
148
149 void CatalogueController::addEvent(std::shared_ptr<DBEvent> event)
150 {
151 event->setRepository(impl->toWorkRepository(event->getRepository()));
152
153 impl->m_CatalogueDao.addEvent(*event);
154
155 // Call update is necessary at the creation of add Event if it has some tags or some event
156 // products
157 if (!event->getEventProducts().empty() || !event->getTags().empty()) {
158 impl->m_CatalogueDao.updateEvent(*event);
159 }
160 }
161
162 void CatalogueController::saveEvent(std::shared_ptr<DBEvent> event)
163 {
164 impl->m_CatalogueDao.moveEvent(*event, impl->toSyncRepository(event->getRepository()), true);
165 }
166
116 167 std::list<std::shared_ptr<DBCatalogue> >
117 CatalogueController::getCatalogues(const QString &repository) const
168 CatalogueController::retrieveCatalogues(const QString &repository) const
118 169 {
170 QString dbDireName = repository.isEmpty() ? REPOSITORY_DEFAULT : repository;
171
119 172 auto cataloguesShared = std::list<std::shared_ptr<DBCatalogue> >{};
120 auto catalogues = impl->m_CatalogueDao.getCatalogues(repository);
173 auto catalogues = impl->m_CatalogueDao.getCatalogues(impl->toWorkRepository(dbDireName));
121 174 for (auto catalogue : catalogues) {
122 175 cataloguesShared.push_back(std::make_shared<DBCatalogue>(catalogue));
123 176 }
124 177 return cataloguesShared;
125 178 }
126 179
180 void CatalogueController::updateCatalogue(std::shared_ptr<DBCatalogue> catalogue)
181 {
182 catalogue->setRepository(impl->toSyncRepository(catalogue->getRepository()));
183
184 impl->m_CatalogueDao.updateCatalogue(*catalogue);
185 }
186
187 void CatalogueController::removeCatalogue(std::shared_ptr<DBCatalogue> catalogue)
188 {
189 // Remove it from both repository and repository_work
190 catalogue->setRepository(impl->toWorkRepository(catalogue->getRepository()));
191 impl->m_CatalogueDao.removeCatalogue(*catalogue);
192 catalogue->setRepository(impl->toSyncRepository(catalogue->getRepository()));
193 impl->m_CatalogueDao.removeCatalogue(*catalogue);
194 }
195
196 void CatalogueController::saveCatalogue(std::shared_ptr<DBCatalogue> catalogue)
197 {
198 impl->m_CatalogueDao.moveCatalogue(*catalogue,
199 impl->toSyncRepository(catalogue->getRepository()), true);
200 }
201
202 void CatalogueController::saveAll()
203 {
204 for (auto repository : impl->m_RepositoryList) {
205 // Save Event
206 auto events = this->retrieveEvents(repository);
207 for (auto event : events) {
208 this->saveEvent(event);
209 }
210
211 // Save Catalogue
212 auto catalogues = this->retrieveCatalogues(repository);
213 for (auto catalogue : catalogues) {
214 this->saveCatalogue(catalogue);
215 }
216 }
217 }
218
127 219 void CatalogueController::initialize()
128 220 {
129 221 qCDebug(LOG_CatalogueController()) << tr("CatalogueController init")
130 222 << QThread::currentThread();
131 223 impl->m_WorkingMutex.lock();
132 224 impl->m_CatalogueDao.initialize();
133 225 auto defaultRepositoryLocation
134 226 = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
135 227
136 228 QDir defaultRepositoryLocationDir;
137 229 if (defaultRepositoryLocationDir.mkpath(defaultRepositoryLocation)) {
138 230 defaultRepositoryLocationDir.cd(defaultRepositoryLocation);
139 231 auto defaultRepository = defaultRepositoryLocationDir.absoluteFilePath(REPOSITORY_DEFAULT);
140 qCInfo(LOG_CatalogueController())
141 << tr("Persistant data loading from: ") << defaultRepository;
232 qCInfo(LOG_CatalogueController()) << tr("Persistant data loading from: ")
233 << defaultRepository;
142 234 this->addDB(defaultRepository);
143 235 }
144 236 else {
145 237 qCWarning(LOG_CatalogueController())
146 238 << tr("Cannot load the persistent default repository from ")
147 239 << defaultRepositoryLocation;
148 240 }
149 241
150 242 qCDebug(LOG_CatalogueController()) << tr("CatalogueController init END");
151 243 }
152 244
153 245 void CatalogueController::finalize()
154 246 {
155 247 impl->m_WorkingMutex.unlock();
156 248 }
157 249
158 250 void CatalogueController::waitForFinish()
159 251 {
160 252 QMutexLocker locker{&impl->m_WorkingMutex};
161 253 }
254
255 void CatalogueController::CatalogueControllerPrivate::copyDBtoDB(const QString &dbFrom,
256 const QString &dbTo)
257 {
258 auto cataloguesShared = std::list<std::shared_ptr<DBCatalogue> >{};
259 auto catalogues = m_CatalogueDao.getCatalogues(dbFrom);
260 for (auto catalogue : catalogues) {
261 cataloguesShared.push_back(std::make_shared<DBCatalogue>(catalogue));
262 }
263
264 auto eventsShared = std::list<std::shared_ptr<DBEvent> >{};
265 auto events = m_CatalogueDao.getEvents(dbFrom);
266 for (auto event : events) {
267 eventsShared.push_back(std::make_shared<DBEvent>(event));
268 }
269
270 for (auto catalogue : cataloguesShared) {
271 m_CatalogueDao.copyCatalogue(*catalogue, dbTo, true);
272 }
273
274 for (auto event : eventsShared) {
275 m_CatalogueDao.copyEvent(*event, dbTo, true);
276 }
277 }
278
279 QString CatalogueController::CatalogueControllerPrivate::toWorkRepository(QString repository)
280 {
281 auto syncRepository = toSyncRepository(repository);
282
283 return QString("%1_%2").arg(syncRepository, REPOSITORY_WORK_SUFFIX);
284 }
285
286 QString CatalogueController::CatalogueControllerPrivate::toSyncRepository(QString repository)
287 {
288 auto syncRepository = repository;
289 if (repository.endsWith(REPOSITORY_WORK_SUFFIX)) {
290 syncRepository.remove(REPOSITORY_WORK_SUFFIX);
291 }
292 else if (repository.endsWith(REPOSITORY_TRASH_SUFFIX)) {
293 syncRepository.remove(REPOSITORY_TRASH_SUFFIX);
294 }
295 return syncRepository;
296 }
@@ -1,138 +1,137
1 1 #include <Plugin/PluginManager.h>
2 2
3 3 #include <Plugin/IPlugin.h>
4 4
5 5 #include <QDir>
6 6 #include <QLibrary>
7 7 #include <QPluginLoader>
8 8
9 9 Q_LOGGING_CATEGORY(LOG_PluginManager, "PluginManager")
10 10
11 11 namespace {
12 12
13 13 /// Key for retrieving metadata of the plugin
14 14 const auto PLUGIN_METADATA_KEY = QStringLiteral("MetaData");
15 15
16 16 /// Key for retrieving the name of the plugin in its metadata
17 17 const auto PLUGIN_NAME_KEY = QStringLiteral("name");
18 18
19 19 /// Helper to state the plugin loading operation
20 20 struct LoadPluginState {
21 21 explicit LoadPluginState(const QString &pluginPath)
22 22 : m_PluginPath{pluginPath}, m_Valid{true}, m_ErrorMessage{}
23 23 {
24 24 }
25 25
26 26 void log() const
27 27 {
28 28 if (m_Valid) {
29 29 qCDebug(LOG_PluginManager())
30 30 << QObject::tr("File '%1' has been loaded as a plugin").arg(m_PluginPath);
31 31 }
32 32 else {
33 33 qCWarning(LOG_PluginManager())
34 34 << QObject::tr("File '%1' can't be loaded as a plugin: %2")
35 35 .arg(m_PluginPath)
36 36 .arg(m_ErrorMessage);
37 37 }
38 38 }
39 39
40 40 void setError(const QString &errorMessage)
41 41 {
42 42 m_Valid = false;
43 43 m_ErrorMessage = errorMessage;
44 44 }
45 45
46 46 QString m_PluginPath;
47 47 bool m_Valid;
48 48 QString m_ErrorMessage;
49 49 };
50 50
51 51 } // namespace
52 52
53 53 struct PluginManager::PluginManagerPrivate {
54 54 /**
55 55 * Loads a single plugin into SciQlop. The method has no effect if the plugin is malformed (e.g.
56 56 * wrong library type, missing metadata, etc.)
57 57 * @param pluginPath the path to the plugin library.
58 58 */
59 59 void loadPlugin(const QString &pluginPath)
60 60 {
61 61 qCDebug(LOG_PluginManager())
62 62 << QObject::tr("Attempting to load file '%1' as a plugin").arg(pluginPath);
63 63
64 64 LoadPluginState loadState{pluginPath};
65 65
66 66 if (QLibrary::isLibrary(pluginPath)) {
67 67 QPluginLoader pluginLoader{pluginPath};
68 68
69 69 // Retrieving the plugin name to check if it can be loaded (i.e. no plugin with the same
70 70 // name has been registered yet)
71 71 auto metadata = pluginLoader.metaData().value(PLUGIN_METADATA_KEY).toObject();
72 72 auto pluginName = metadata.value(PLUGIN_NAME_KEY).toString();
73 73
74 74 if (pluginName.isEmpty()) {
75 75 loadState.setError(QObject::tr("empty file name"));
76 76 }
77 77 else if (m_RegisteredPlugins.contains(pluginName)) {
78 78 loadState.setError(QObject::tr("name '%1' already registered").arg(pluginName));
79 79 }
80 80 else {
81 81 if (auto pluginInstance = qobject_cast<IPlugin *>(pluginLoader.instance())) {
82 82 pluginInstance->initialize();
83 83 m_RegisteredPlugins.insert(pluginName, pluginPath);
84 84 }
85 85 else {
86 86 loadState.setError(QObject::tr("the file is not a Sciqlop plugin"));
87 87 }
88 88 }
89 89 }
90 90 else {
91 91 loadState.setError(QObject::tr("the file is not a library"));
92 92 }
93 93
94 94 // Log loading result
95 95 loadState.log();
96 96 }
97 97
98 98 void loadStaticPlugins()
99 99 {
100 for (QObject *plugin : QPluginLoader::staticInstances())
101 {
100 for (QObject *plugin : QPluginLoader::staticInstances()) {
102 101 qobject_cast<IPlugin *>(plugin)->initialize();
103 102 m_RegisteredPlugins.insert(plugin->metaObject()->className(), "StaticPlugin");
104 103 }
105 104 }
106 105
107 106 /// Registered plugins (key: plugin name, value: plugin path)
108 107 QHash<QString, QString> m_RegisteredPlugins;
109 108 };
110 109
111 110 PluginManager::PluginManager() : impl{spimpl::make_unique_impl<PluginManagerPrivate>()}
112 111 {
113 112 }
114 113
115 114 void PluginManager::loadPlugins(const QDir &pluginDir)
116 115 {
117 116 // Load plugins
118 117 auto pluginInfoList
119 118 = pluginDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
120 119 for (auto entryInfo : qAsConst(pluginInfoList)) {
121 120 if (entryInfo.isDir()) {
122 121 this->loadPlugins(QDir{entryInfo.absoluteFilePath()});
123 122 }
124 123 else if (QLibrary::isLibrary(entryInfo.absoluteFilePath())) {
125 124 impl->loadPlugin(entryInfo.absoluteFilePath());
126 125 }
127 126 }
128 127 }
129 128
130 129 void PluginManager::loadStaticPlugins()
131 130 {
132 131 impl->loadStaticPlugins();
133 132 }
134 133
135 134 int PluginManager::nbPluginsLoaded() const noexcept
136 135 {
137 136 return impl->m_RegisteredPlugins.size();
138 137 }
@@ -1,206 +1,206
1 1 #include <Data/DataSeriesUtils.h>
2 2
3 3 #include <QObject>
4 4 #include <QtTest>
5 5
6 6 namespace {
7 7
8 8 /// Path for resources
9 9 const auto TESTS_RESOURCES_PATH
10 10 = QFileInfo{QString{CORE_TESTS_RESOURCES_DIR}, "TestDataSeriesUtils"}.absoluteFilePath();
11 11
12 12 QString inputFilePath(const QString &inputFileName)
13 13 {
14 14 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
15 15 }
16 16
17 17 } // namespace
18 18
19 19 class TestDataSeriesUtils : public QObject {
20 20 Q_OBJECT
21 21
22 22 private slots:
23 23 /// Tests @sa DataSeriesUtils::thresholds() method
24 24 void testThresholds_data();
25 25 void testThresholds();
26 26
27 27 /// Tests @sa DataSeriesUtils::fillDataHoles() method
28 28 void testFillDataHoles_data();
29 29 void testFillDataHoles();
30 30 };
31 31
32 32 void TestDataSeriesUtils::testFillDataHoles_data()
33 33 {
34 34 QTest::addColumn<std::vector<double> >("xAxisData");
35 35 QTest::addColumn<std::vector<double> >("valuesData");
36 36 QTest::addColumn<double>("resolution");
37 37 QTest::addColumn<double>("fillValue");
38 38 QTest::addColumn<double>("minBound");
39 39 QTest::addColumn<double>("maxBound");
40 40 QTest::addColumn<std::vector<double> >(
41 41 "expectedXAxisData"); // expected x-axis data after filling holes
42 42 QTest::addColumn<std::vector<double> >(
43 43 "expectedValuesData"); // expected values data after filling holes
44 44
45 45 auto nan = std::numeric_limits<double>::quiet_NaN();
46 46
47 47 QTest::newRow("fillDataHoles (basic case)")
48 48 << std::vector<double>{0., 1., 5., 7., 14.} << std::vector<double>{0., 1., 2., 3., 4.} << 2.
49 49 << nan << nan << nan << std::vector<double>{0., 1., 3., 5., 7., 9., 11., 13., 14.}
50 50 << std::vector<double>{0., 1., nan, 2., 3., nan, nan, nan, 4.};
51 51
52 52 QTest::newRow("fillDataHoles (change nb components)")
53 53 << std::vector<double>{0., 1., 5., 7., 14.}
54 54 << std::vector<double>{0., 1., 2., 3., 4., 5., 6., 7., 8., 9.} << 2. << nan << nan << nan
55 55 << std::vector<double>{0., 1., 3., 5., 7., 9., 11., 13., 14.}
56 56 << std::vector<double>{0., 1., 2., 3., nan, nan, 4., 5., 6.,
57 57 7., nan, nan, nan, nan, nan, nan, 8., 9.};
58 58
59 59 QTest::newRow("fillDataHoles (change resolution)")
60 60 << std::vector<double>{0., 1., 5., 7., 14.} << std::vector<double>{0., 1., 2., 3., 4.}
61 61 << 1.5 << nan << nan << nan
62 62 << std::vector<double>{0., 1., 2.5, 4., 5., 6.5, 7., 8.5, 10., 11.5, 13., 14.}
63 63 << std::vector<double>{0., 1., nan, nan, 2., nan, 3., nan, nan, nan, nan, 4.};
64 64
65 65 QTest::newRow("fillDataHoles (with no data (no changes made))")
66 66 << std::vector<double>{} << std::vector<double>{} << 2. << nan << nan << nan
67 67 << std::vector<double>{} << std::vector<double>{};
68 68
69 69 QTest::newRow("fillDataHoles (with no resolution (no changes made))")
70 70 << std::vector<double>{0., 1., 5., 7., 14.} << std::vector<double>{0., 1., 2., 3., 4.} << 0.
71 71 << nan << nan << nan << std::vector<double>{0., 1., 5., 7., 14.}
72 72 << std::vector<double>{0., 1., 2., 3., 4.};
73 73
74 74 QTest::newRow("fillDataHoles (change fill value)")
75 75 << std::vector<double>{0., 1., 5., 7., 14.} << std::vector<double>{0., 1., 2., 3., 4.} << 2.
76 76 << -1. << nan << nan << std::vector<double>{0., 1., 3., 5., 7., 9., 11., 13., 14.}
77 77 << std::vector<double>{0., 1., -1., 2., 3., -1., -1., -1., 4.};
78 78
79 79 QTest::newRow("fillDataHoles (add data holes to the beginning)")
80 80 << std::vector<double>{5., 7., 9., 11., 13.} << std::vector<double>{0., 1., 2., 3., 4.}
81 81 << 2. << nan << 0. << nan << std::vector<double>{1., 3., 5., 7., 9., 11., 13.}
82 82 << std::vector<double>{nan, nan, 0., 1., 2., 3., 4.};
83 83
84 84 QTest::newRow("fillDataHoles (add data holes to the end)")
85 85 << std::vector<double>{5., 7., 9., 11., 13.} << std::vector<double>{0., 1., 2., 3., 4.}
86 86 << 2. << nan << nan << 21. << std::vector<double>{5., 7., 9., 11., 13., 15., 17., 19., 21.}
87 87 << std::vector<double>{0., 1., 2., 3., 4., nan, nan, nan, nan};
88 88
89 89 QTest::newRow("fillDataHoles (invalid min/max bounds (no changes made))")
90 90 << std::vector<double>{5., 7., 9., 11., 13.} << std::vector<double>{0., 1., 2., 3., 4.}
91 91 << 2. << nan << 8. << 13. << std::vector<double>{5., 7., 9., 11., 13.}
92 92 << std::vector<double>{0., 1., 2., 3., 4.};
93 93 }
94 94
95 95 void TestDataSeriesUtils::testFillDataHoles()
96 96 {
97 97 QFETCH(std::vector<double>, xAxisData);
98 98 QFETCH(std::vector<double>, valuesData);
99 99 QFETCH(double, resolution);
100 100 QFETCH(double, fillValue);
101 101 QFETCH(double, minBound);
102 102 QFETCH(double, maxBound);
103 103
104 104 QFETCH(std::vector<double>, expectedXAxisData);
105 105 QFETCH(std::vector<double>, expectedValuesData);
106 106
107 107 // Executes method (xAxisData and valuesData are modified)
108 108 DataSeriesUtils::fillDataHoles(xAxisData, valuesData, resolution, fillValue, minBound,
109 109 maxBound);
110 110
111 111 // Checks results
112 112 auto equal = [](const auto &data, const auto &expectedData) {
113 113 // Compares with NaN values
114 114 return std::equal(data.begin(), data.end(), expectedData.begin(), expectedData.end(),
115 115 [](const auto &val, const auto &expectedVal) {
116 116 return (std::isnan(val) && std::isnan(expectedVal))
117 117 || val == expectedVal;
118 118 });
119 119 };
120 120 QVERIFY(equal(xAxisData, expectedXAxisData));
121 121 QVERIFY(equal(valuesData, expectedValuesData));
122 122 }
123 123
124 124 namespace {
125 125
126 126 const auto LINE_SEP = QRegularExpression{QStringLiteral("\\s+")};
127 127
128 128 std::vector<double> fromFile(const QString &filePath)
129 129 {
130 130 QFile file{filePath};
131 131
132 132 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
133 133 return {};
134 134 }
135 135
136 136 std::vector<double> result{};
137 137
138 138 QTextStream stream{&file};
139 139 QString line{};
140 140
141 141 while (stream.readLineInto(&line)) {
142 142 auto lineData = line.split(LINE_SEP, QString::SkipEmptyParts);
143 143
144 144 for (auto data : lineData) {
145 145 bool valueOk;
146 146 auto value = data.toDouble(&valueOk);
147 147
148 148 result.push_back(valueOk ? value : std::numeric_limits<double>::quiet_NaN());
149 149 }
150 150 }
151 151
152 152 return result;
153 153 }
154 154
155 155 } // namespace
156 156
157 157 void TestDataSeriesUtils::testThresholds_data()
158 158 {
159 159 QTest::addColumn<std::vector<double> >("input");
160 160 QTest::addColumn<bool>("logarithmic");
161 161 QTest::addColumn<double>("expectedMinThreshold");
162 162 QTest::addColumn<double>("expectedMaxThreshold");
163 163
164 164 auto nan = std::numeric_limits<double>::quiet_NaN();
165 165
166 166 QTest::newRow("thresholds (basic case)")
167 167 << std::vector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << false << 1. << 10.;
168 168
169 169 QTest::newRow("thresholds (with nan values)")
170 170 << std::vector<double>{nan, 2., 3., 4., 5., 6., 7., 8., 9., nan} << false << 2. << 9.;
171 171
172 172 QTest::newRow("thresholds (case with low values and aberrant value)")
173 173 << std::vector<double>{1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2.,
174 174 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 3., 3.,
175 175 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 100.}
176 176 << false << 1. << 47.073;
177 177
178 178 QTest::newRow("thresholds (empty data)") << std::vector<double>{} << false << nan << nan;
179 QTest::newRow("thresholds (only nan values)")
180 << std::vector<double>{nan, nan, nan, nan, nan} << false << nan << nan;
179 QTest::newRow("thresholds (only nan values)") << std::vector<double>{nan, nan, nan, nan, nan}
180 << false << nan << nan;
181 181
182 182 QTest::newRow("thresholds (from file with logarithmic scale)")
183 183 << fromFile(inputFilePath("TestThresholds.txt")) << true << 832.005 << 17655064.730;
184 184 }
185 185
186 186 void TestDataSeriesUtils::testThresholds()
187 187 {
188 188 QFETCH(std::vector<double>, input);
189 189 QFETCH(bool, logarithmic);
190 190 QFETCH(double, expectedMinThreshold);
191 191 QFETCH(double, expectedMaxThreshold);
192 192
193 193 double minThreshold, maxThreshold;
194 194 std::tie(minThreshold, maxThreshold)
195 195 = DataSeriesUtils::thresholds(input.begin(), input.end(), logarithmic);
196 196
197 197 auto compareWithNaN = [](const auto &v1, const auto &v2) {
198 198 return (std::isnan(v1) && std::isnan(v2)) || std::abs(v1 - v2) < 1e-3;
199 199 };
200 200
201 201 QVERIFY(compareWithNaN(minThreshold, expectedMinThreshold));
202 202 QVERIFY(compareWithNaN(maxThreshold, expectedMaxThreshold));
203 203 }
204 204
205 205 QTEST_MAIN(TestDataSeriesUtils)
206 206 #include "TestDataSeriesUtils.moc"
@@ -1,46 +1,68
1 1 #ifndef SCIQLOP_CATALOGUEEVENTSMODEL_H
2 2 #define SCIQLOP_CATALOGUEEVENTSMODEL_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <QAbstractItemModel>
6 #include <QLoggingCategory>
7 #include <unordered_set>
6 8
7 9 class DBEvent;
8 10 class DBEventProduct;
9 11
12 Q_DECLARE_LOGGING_CATEGORY(LOG_CatalogueEventsModel)
13
10 14 class CatalogueEventsModel : public QAbstractItemModel {
15 Q_OBJECT
16
17 signals:
18 void modelSorted();
19
11 20 public:
12 21 CatalogueEventsModel(QObject *parent = nullptr);
13 22
23 enum class Column { Name, TStart, TEnd, Tags, Product, Validation, NbColumn };
24
14 25 void setEvents(const QVector<std::shared_ptr<DBEvent> > &events);
15 26 void addEvent(const std::shared_ptr<DBEvent> &event);
16 27 void removeEvent(const std::shared_ptr<DBEvent> &event);
28 QVector<std::shared_ptr<DBEvent> > events() const;
17 29
18 30 enum class ItemType { Root, Event, EventProduct };
19 31 ItemType itemTypeOf(const QModelIndex &index) const;
20 32 std::shared_ptr<DBEvent> getEvent(const QModelIndex &index) const;
21 33 std::shared_ptr<DBEvent> getParentEvent(const QModelIndex &index) const;
22 34 std::shared_ptr<DBEventProduct> getEventProduct(const QModelIndex &index) const;
23 35
36 /// Refresh the data for the specified event
24 37 void refreshEvent(const std::shared_ptr<DBEvent> &event);
25 38
39 /// Returns a QModelIndex which represent the specified event
40 QModelIndex indexOf(const std::shared_ptr<DBEvent> &event) const;
41
42 /// Marks a change flag on the specified event to allow sorting on the validation column
43 void setEventHasChanges(const std::shared_ptr<DBEvent> &event, bool hasChanges);
44
45 /// Returns true if the specified event has unsaved changes
46 bool eventsHasChanges(const std::shared_ptr<DBEvent> &event) const;
47
26 48 // Model
27 49 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
28 50 QModelIndex parent(const QModelIndex &index) const;
29 51 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
30 52 int columnCount(const QModelIndex &parent = QModelIndex()) const override;
31 53 Qt::ItemFlags flags(const QModelIndex &index) const override;
32 54 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
33 55 QVariant headerData(int section, Qt::Orientation orientation,
34 56 int role = Qt::DisplayRole) const override;
35 57 void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
36 58
37 59 Qt::DropActions supportedDragActions() const override;
38 60 QStringList mimeTypes() const override;
39 61 QMimeData *mimeData(const QModelIndexList &indexes) const override;
40 62
41 63 private:
42 64 class CatalogueEventsModelPrivate;
43 65 spimpl::unique_impl_ptr<CatalogueEventsModelPrivate> impl;
44 66 };
45 67
46 68 #endif // SCIQLOP_CATALOGUEEVENTSMODEL_H
@@ -1,47 +1,48
1 1 #ifndef SCIQLOP_CATALOGUEEVENTSWIDGET_H
2 2 #define SCIQLOP_CATALOGUEEVENTSWIDGET_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <QLoggingCategory>
6 6 #include <QWidget>
7 7
8 8 class DBCatalogue;
9 9 class DBEvent;
10 10 class DBEventProduct;
11 11 class VisualizationWidget;
12 12
13 13 namespace Ui {
14 14 class CatalogueEventsWidget;
15 15 }
16 16
17 17 Q_DECLARE_LOGGING_CATEGORY(LOG_CatalogueEventsWidget)
18 18
19 19 class CatalogueEventsWidget : public QWidget {
20 20 Q_OBJECT
21 21
22 22 signals:
23 23 void eventsSelected(const QVector<std::shared_ptr<DBEvent> > &event);
24 24 void eventProductsSelected(
25 25 const QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > >
26 26 &eventproducts);
27 27 void selectionCleared();
28 28
29 29 public:
30 30 explicit CatalogueEventsWidget(QWidget *parent = 0);
31 31 virtual ~CatalogueEventsWidget();
32 32
33 33 void setVisualizationWidget(VisualizationWidget *visualization);
34 34
35 35 void setEventChanges(const std::shared_ptr<DBEvent> &event, bool hasChanges);
36 36
37 37 public slots:
38 38 void populateWithCatalogues(const QVector<std::shared_ptr<DBCatalogue> > &catalogues);
39 void populateWithAllEvents();
39 40
40 41 private:
41 42 Ui::CatalogueEventsWidget *ui;
42 43
43 44 class CatalogueEventsWidgetPrivate;
44 45 spimpl::unique_impl_ptr<CatalogueEventsWidgetPrivate> impl;
45 46 };
46 47
47 48 #endif // SCIQLOP_CATALOGUEEVENTSWIDGET_H
@@ -1,33 +1,36
1 1 #ifndef SCIQLOP_CATALOGUETREEWIDGETITEM_H
2 2 #define SCIQLOP_CATALOGUETREEWIDGETITEM_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <QTreeWidgetItem>
6 6
7 7 class DBCatalogue;
8 8
9 9
10 10 class CatalogueTreeWidgetItem : public QTreeWidgetItem {
11 11 public:
12 12 CatalogueTreeWidgetItem(std::shared_ptr<DBCatalogue> catalogue,
13 13 int type = QTreeWidgetItem::Type);
14 14
15 15 QVariant data(int column, int role) const override;
16 16 void setData(int column, int role, const QVariant &value) override;
17 17
18 18 /// Returns the catalogue represented by the item
19 19 std::shared_ptr<DBCatalogue> catalogue() const;
20 20
21 21 /// Displays or hides the save and cancel buttons indicating that the catalogue has unsaved
22 22 /// changes
23 23 void setHasChanges(bool value);
24 24
25 /// Returns true if the widget indicating the event has unsaved changes is displayed
26 bool hasChanges();
27
25 28 /// Refreshes the data displayed by the item from the catalogue
26 29 void refresh();
27 30
28 31 private:
29 32 class CatalogueTreeWidgetItemPrivate;
30 33 spimpl::unique_impl_ptr<CatalogueTreeWidgetItemPrivate> impl;
31 34 };
32 35
33 36 #endif // SCIQLOP_CATALOGUETREEWIDGETITEM_H
@@ -1,127 +1,143
1
2 1 qxorm_dep = dependency('QxOrm', required : true, fallback:['QxOrm','qxorm_dep'])
3 2 catalogueapi_dep = dependency('CatalogueAPI', required : true, fallback:['CatalogueAPI','CatalogueAPI_dep'])
4 3
5 4 gui_moc_headers = [
6 5 'include/DataSource/DataSourceWidget.h',
7 6 'include/Settings/SqpSettingsDialog.h',
8 7 'include/Settings/SqpSettingsGeneralWidget.h',
9 8 'include/SidePane/SqpSidePane.h',
10 9 'include/SqpApplication.h',
11 10 'include/DragAndDrop/DragDropScroller.h',
12 11 'include/DragAndDrop/DragDropTabSwitcher.h',
13 12 'include/TimeWidget/TimeWidget.h',
14 13 'include/Variable/VariableInspectorWidget.h',
15 14 'include/Variable/RenameVariableDialog.h',
16 15 'include/Visualization/qcustomplot.h',
17 16 'include/Visualization/VisualizationGraphWidget.h',
18 17 'include/Visualization/VisualizationTabWidget.h',
19 18 'include/Visualization/VisualizationWidget.h',
20 19 'include/Visualization/VisualizationZoneWidget.h',
21 20 'include/Visualization/VisualizationDragDropContainer.h',
22 21 'include/Visualization/VisualizationDragWidget.h',
23 22 'include/Visualization/ColorScaleEditor.h',
24 23 'include/Actions/SelectionZoneAction.h',
25 24 'include/Visualization/VisualizationMultiZoneSelectionDialog.h',
26 25 'include/Catalogue/CatalogueExplorer.h',
27 26 'include/Catalogue/CatalogueEventsWidget.h',
28 27 'include/Catalogue/CatalogueSideBarWidget.h',
29 'include/Catalogue/CatalogueInspectorWidget.h'
28 'include/Catalogue/CatalogueInspectorWidget.h',
29 'include/Catalogue/CatalogueEventsModel.h',
30 'include/Catalogue/CreateEventDialog.h'
30 31 ]
31 32
32 33 gui_ui_files = [
33 34 'ui/DataSource/DataSourceWidget.ui',
34 35 'ui/Settings/SqpSettingsDialog.ui',
35 36 'ui/Settings/SqpSettingsGeneralWidget.ui',
36 37 'ui/SidePane/SqpSidePane.ui',
37 38 'ui/TimeWidget/TimeWidget.ui',
38 39 'ui/Variable/VariableInspectorWidget.ui',
39 40 'ui/Variable/RenameVariableDialog.ui',
40 41 'ui/Variable/VariableMenuHeaderWidget.ui',
41 42 'ui/Visualization/VisualizationGraphWidget.ui',
42 43 'ui/Visualization/VisualizationTabWidget.ui',
43 44 'ui/Visualization/VisualizationWidget.ui',
44 45 'ui/Visualization/VisualizationZoneWidget.ui',
45 46 'ui/Visualization/ColorScaleEditor.ui',
46 47 'ui/Visualization/VisualizationMultiZoneSelectionDialog.ui',
47 48 'ui/Catalogue/CatalogueExplorer.ui',
48 49 'ui/Catalogue/CatalogueEventsWidget.ui',
49 50 'ui/Catalogue/CatalogueSideBarWidget.ui',
50 'ui/Catalogue/CatalogueInspectorWidget.ui'
51 'ui/Catalogue/CatalogueInspectorWidget.ui',
52 'ui/Catalogue/CreateEventDialog.ui'
51 53 ]
52 54
53 55 gui_qresources = ['resources/sqpguiresources.qrc']
54 56
57 rcc_gen = generator(rcc,
58 output : 'qrc_@BASENAME@.cpp',
59 arguments : [
60 '--output',
61 '@OUTPUT@',
62 '@INPUT@',
63 '@EXTRA_ARGS@'])
64
65 rcc_files = rcc_gen.process(gui_qresources, extra_args : ['-name', 'sqpguiresources'])
66
55 67 gui_moc_files = qt5.preprocess(moc_headers : gui_moc_headers,
56 ui_files : gui_ui_files,
57 qresources : gui_qresources)
68 ui_files : gui_ui_files)
58 69
59 70 gui_sources = [
60 71 'src/SqpApplication.cpp',
61 72 'src/DragAndDrop/DragDropGuiController.cpp',
62 73 'src/DragAndDrop/DragDropScroller.cpp',
63 74 'src/DragAndDrop/DragDropTabSwitcher.cpp',
64 75 'src/Common/ColorUtils.cpp',
65 76 'src/Common/VisualizationDef.cpp',
66 77 'src/DataSource/DataSourceTreeWidgetItem.cpp',
67 78 'src/DataSource/DataSourceTreeWidgetHelper.cpp',
68 79 'src/DataSource/DataSourceWidget.cpp',
69 80 'src/DataSource/DataSourceTreeWidget.cpp',
70 81 'src/Settings/SqpSettingsDialog.cpp',
71 82 'src/Settings/SqpSettingsGeneralWidget.cpp',
72 83 'src/SidePane/SqpSidePane.cpp',
73 84 'src/TimeWidget/TimeWidget.cpp',
74 85 'src/Variable/VariableInspectorWidget.cpp',
75 86 'src/Variable/VariableInspectorTableView.cpp',
76 87 'src/Variable/VariableMenuHeaderWidget.cpp',
77 88 'src/Variable/RenameVariableDialog.cpp',
78 89 'src/Visualization/VisualizationGraphHelper.cpp',
79 90 'src/Visualization/VisualizationGraphRenderingDelegate.cpp',
80 91 'src/Visualization/VisualizationGraphWidget.cpp',
81 92 'src/Visualization/VisualizationTabWidget.cpp',
82 93 'src/Visualization/VisualizationWidget.cpp',
83 94 'src/Visualization/VisualizationZoneWidget.cpp',
84 95 'src/Visualization/qcustomplot.cpp',
85 96 'src/Visualization/QCustomPlotSynchronizer.cpp',
86 97 'src/Visualization/operations/FindVariableOperation.cpp',
87 98 'src/Visualization/operations/GenerateVariableMenuOperation.cpp',
88 99 'src/Visualization/operations/MenuBuilder.cpp',
89 100 'src/Visualization/operations/RemoveVariableOperation.cpp',
90 101 'src/Visualization/operations/RescaleAxeOperation.cpp',
91 102 'src/Visualization/VisualizationDragDropContainer.cpp',
92 103 'src/Visualization/VisualizationDragWidget.cpp',
93 104 'src/Visualization/AxisRenderingUtils.cpp',
94 105 'src/Visualization/PlottablesRenderingUtils.cpp',
95 106 'src/Visualization/MacScrollBarStyle.cpp',
96 107 'src/Visualization/VisualizationCursorItem.cpp',
97 108 'src/Visualization/ColorScaleEditor.cpp',
98 109 'src/Visualization/SqpColorScale.cpp',
99 110 'src/Visualization/QCPColorMapIterator.cpp',
100 111 'src/Visualization/VisualizationSelectionZoneItem.cpp',
101 112 'src/Visualization/VisualizationSelectionZoneManager.cpp',
102 113 'src/Actions/SelectionZoneAction.cpp',
103 114 'src/Actions/ActionsGuiController.cpp',
104 115 'src/Visualization/VisualizationActionManager.cpp',
105 116 'src/Visualization/VisualizationMultiZoneSelectionDialog.cpp',
106 117 'src/Catalogue/CatalogueExplorer.cpp',
107 118 'src/Catalogue/CatalogueEventsWidget.cpp',
108 119 'src/Catalogue/CatalogueSideBarWidget.cpp',
109 120 'src/Catalogue/CatalogueInspectorWidget.cpp',
110 121 'src/Catalogue/CatalogueTreeWidgetItem.cpp',
111 'src/Catalogue/CatalogueEventsModel.cpp'
122 'src/Catalogue/CatalogueEventsModel.cpp',
123 'src/Catalogue/CatalogueExplorerHelper.cpp',
124 'src/Catalogue/CatalogueActionManager.cpp',
125 'src/Catalogue/CreateEventDialog.cpp'
112 126 ]
113 127
114 128 gui_inc = include_directories(['include'])
115 129
116 130 sciqlop_gui_lib = library('sciqlopgui',
117 131 gui_sources,
118 132 gui_moc_files,
133 rcc_files,
119 134 include_directories : [gui_inc],
120 135 dependencies : [ qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core, catalogueapi_dep],
121 136 install : true
122 137 )
123 138
124 139 sciqlop_gui = declare_dependency(link_with : sciqlop_gui_lib,
125 140 include_directories : gui_inc,
126 141 dependencies : [qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core, catalogueapi_dep])
127 142
143
@@ -1,381 +1,442
1 1 #include "Catalogue/CatalogueEventsModel.h"
2 2
3 3 #include <Common/DateUtils.h>
4 4 #include <Common/MimeTypesDef.h>
5 5 #include <DBEvent.h>
6 6 #include <DBEventProduct.h>
7 7 #include <DBTag.h>
8 8 #include <Data/SqpRange.h>
9 9 #include <SqpApplication.h>
10 10 #include <Time/TimeController.h>
11 11
12 12 #include <list>
13 13 #include <unordered_map>
14 14
15 15 #include <QHash>
16 16 #include <QMimeData>
17 17
18 Q_LOGGING_CATEGORY(LOG_CatalogueEventsModel, "CatalogueEventsModel")
19
18 20 const auto EVENT_ITEM_TYPE = 1;
19 21 const auto EVENT_PRODUCT_ITEM_TYPE = 2;
20 22
21 23 struct CatalogueEventsModel::CatalogueEventsModelPrivate {
22 24 QVector<std::shared_ptr<DBEvent> > m_Events;
23 25 std::unordered_map<DBEvent *, QVector<std::shared_ptr<DBEventProduct> > > m_EventProducts;
26 std::unordered_set<std::shared_ptr<DBEvent> > m_EventsWithChanges;
24 27
25 enum class Column { Name, TStart, TEnd, Tags, Product, NbColumn };
26 28 QStringList columnNames()
27 29 {
28 return QStringList{tr("Event"), tr("TStart"), tr("TEnd"), tr("Tags"), tr("Product")};
30 return QStringList{tr("Event"), tr("TStart"), tr("TEnd"),
31 tr("Tags"), tr("Product"), tr("")};
32 }
33
34 QVariant sortData(int col, const std::shared_ptr<DBEvent> &event) const
35 {
36 if (col == (int)CatalogueEventsModel::Column::Validation) {
37 return m_EventsWithChanges.find(event) != m_EventsWithChanges.cend() ? true
38 : QVariant();
39 }
40
41 return eventData(col, event);
29 42 }
30 43
31 44 QVariant eventData(int col, const std::shared_ptr<DBEvent> &event) const
32 45 {
33 46 switch (static_cast<Column>(col)) {
34 case Column::Name:
47 case CatalogueEventsModel::Column::Name:
35 48 return event->getName();
36 case Column::TStart:
37 return DateUtils::dateTime(event->getTStart());
38 case Column::TEnd:
39 return DateUtils::dateTime(event->getTEnd());
40 case Column::Product: {
41 auto eventProductsIt = m_EventProducts.find(event.get());
42 if (eventProductsIt != m_EventProducts.cend()) {
43 return QString::number(m_EventProducts.at(event.get()).count()) + " product(s)";
44 }
45 else {
46 return "0 product";
47 }
48 }
49 case Column::Tags: {
49 case CatalogueEventsModel::Column::TStart:
50 return nbEventProducts(event) > 0 ? DateUtils::dateTime(event->getTStart())
51 : QVariant{};
52 case CatalogueEventsModel::Column::TEnd:
53 return nbEventProducts(event) > 0 ? DateUtils::dateTime(event->getTEnd())
54 : QVariant{};
55 case CatalogueEventsModel::Column::Product:
56 return QString::number(nbEventProducts(event)) + " product(s)";
57 case CatalogueEventsModel::Column::Tags: {
50 58 QString tagList;
51 59 auto tags = event->getTags();
52 60 for (auto tag : tags) {
53 61 tagList += tag.getName();
54 62 tagList += ' ';
55 63 }
56 64
57 65 return tagList;
58 66 }
67 case CatalogueEventsModel::Column::Validation:
68 return QVariant();
59 69 default:
60 70 break;
61 71 }
62 72
63 73 Q_ASSERT(false);
64 74 return QStringLiteral("Unknown Data");
65 75 }
66 76
67 77 void parseEventProduct(const std::shared_ptr<DBEvent> &event)
68 78 {
69 79 for (auto product : event->getEventProducts()) {
70 80 m_EventProducts[event.get()].append(std::make_shared<DBEventProduct>(product));
71 81 }
72 82 }
73 83
84 int nbEventProducts(const std::shared_ptr<DBEvent> &event) const
85 {
86 auto eventProductsIt = m_EventProducts.find(event.get());
87 if (eventProductsIt != m_EventProducts.cend()) {
88 return m_EventProducts.at(event.get()).count();
89 }
90 else {
91 return 0;
92 }
93 }
94
74 95 QVariant eventProductData(int col, const std::shared_ptr<DBEventProduct> &eventProduct) const
75 96 {
76 97 switch (static_cast<Column>(col)) {
77 case Column::Name:
98 case CatalogueEventsModel::Column::Name:
78 99 return eventProduct->getProductId();
79 case Column::TStart:
100 case CatalogueEventsModel::Column::TStart:
80 101 return DateUtils::dateTime(eventProduct->getTStart());
81 case Column::TEnd:
102 case CatalogueEventsModel::Column::TEnd:
82 103 return DateUtils::dateTime(eventProduct->getTEnd());
83 case Column::Product:
104 case CatalogueEventsModel::Column::Product:
84 105 return eventProduct->getProductId();
85 case Column::Tags: {
106 case CatalogueEventsModel::Column::Tags:
86 107 return QString();
87 }
108 case CatalogueEventsModel::Column::Validation:
109 return QVariant();
88 110 default:
89 111 break;
90 112 }
91 113
92 114 Q_ASSERT(false);
93 115 return QStringLiteral("Unknown Data");
94 116 }
95 117 };
96 118
97 119 CatalogueEventsModel::CatalogueEventsModel(QObject *parent)
98 120 : QAbstractItemModel(parent), impl{spimpl::make_unique_impl<CatalogueEventsModelPrivate>()}
99 121 {
100 122 }
101 123
102 124 void CatalogueEventsModel::setEvents(const QVector<std::shared_ptr<DBEvent> > &events)
103 125 {
104 126 beginResetModel();
105 127
106 128 impl->m_Events = events;
107 129 impl->m_EventProducts.clear();
130 impl->m_EventsWithChanges.clear();
108 131 for (auto event : events) {
109 132 impl->parseEventProduct(event);
110 133 }
111 134
112 135 endResetModel();
113 136 }
114 137
115 138 std::shared_ptr<DBEvent> CatalogueEventsModel::getEvent(const QModelIndex &index) const
116 139 {
117 140 if (itemTypeOf(index) == CatalogueEventsModel::ItemType::Event) {
118 141 return impl->m_Events.value(index.row());
119 142 }
120 143 else {
121 144 return nullptr;
122 145 }
123 146 }
124 147
125 148 std::shared_ptr<DBEvent> CatalogueEventsModel::getParentEvent(const QModelIndex &index) const
126 149 {
127 150 if (itemTypeOf(index) == CatalogueEventsModel::ItemType::EventProduct) {
128 151 return getEvent(index.parent());
129 152 }
130 153 else {
131 154 return nullptr;
132 155 }
133 156 }
134 157
135 158 std::shared_ptr<DBEventProduct>
136 159 CatalogueEventsModel::getEventProduct(const QModelIndex &index) const
137 160 {
138 161 if (itemTypeOf(index) == CatalogueEventsModel::ItemType::EventProduct) {
139 162 auto event = static_cast<DBEvent *>(index.internalPointer());
140 163 return impl->m_EventProducts.at(event).value(index.row());
141 164 }
142 165 else {
143 166 return nullptr;
144 167 }
145 168 }
146 169
147 170 void CatalogueEventsModel::addEvent(const std::shared_ptr<DBEvent> &event)
148 171 {
149 172 beginInsertRows(QModelIndex(), impl->m_Events.count() - 1, impl->m_Events.count() - 1);
150 173 impl->m_Events.append(event);
151 174 impl->parseEventProduct(event);
152 175 endInsertRows();
153 176 }
154 177
155 178 void CatalogueEventsModel::removeEvent(const std::shared_ptr<DBEvent> &event)
156 179 {
157 180 auto index = impl->m_Events.indexOf(event);
158 181 if (index >= 0) {
159 182 beginRemoveRows(QModelIndex(), index, index);
160 183 impl->m_Events.removeAt(index);
161 184 impl->m_EventProducts.erase(event.get());
185 impl->m_EventsWithChanges.erase(event);
162 186 endRemoveRows();
163 187 }
164 188 }
165 189
190 QVector<std::shared_ptr<DBEvent> > CatalogueEventsModel::events() const
191 {
192 return impl->m_Events;
193 }
194
166 195 void CatalogueEventsModel::refreshEvent(const std::shared_ptr<DBEvent> &event)
167 196 {
168 auto i = impl->m_Events.indexOf(event);
169 if (i >= 0) {
170 auto eventIndex = index(i, 0);
197 auto eventIndex = indexOf(event);
198 if (eventIndex.isValid()) {
199
200 // Refreshes the event line
171 201 auto colCount = columnCount();
172 emit dataChanged(eventIndex, index(i, colCount));
202 emit dataChanged(eventIndex, index(eventIndex.row(), colCount));
173 203
204 // Also refreshes its children event products
174 205 auto childCount = rowCount(eventIndex);
175 206 emit dataChanged(index(0, 0, eventIndex), index(childCount, colCount, eventIndex));
176 207 }
208 else {
209 qCWarning(LOG_CatalogueEventsModel()) << "refreshEvent: event not found.";
210 }
211 }
212
213 QModelIndex CatalogueEventsModel::indexOf(const std::shared_ptr<DBEvent> &event) const
214 {
215 auto row = impl->m_Events.indexOf(event);
216 if (row >= 0) {
217 return index(row, 0);
218 }
219
220 return QModelIndex();
221 }
222
223 void CatalogueEventsModel::setEventHasChanges(const std::shared_ptr<DBEvent> &event,
224 bool hasChanges)
225 {
226 if (hasChanges) {
227 impl->m_EventsWithChanges.insert(event);
228 }
229 else {
230 impl->m_EventsWithChanges.erase(event);
231 }
232 }
233
234 bool CatalogueEventsModel::eventsHasChanges(const std::shared_ptr<DBEvent> &event) const
235 {
236 return impl->m_EventsWithChanges.find(event) != impl->m_EventsWithChanges.cend();
177 237 }
178 238
179 239 QModelIndex CatalogueEventsModel::index(int row, int column, const QModelIndex &parent) const
180 240 {
181 241 if (!hasIndex(row, column, parent)) {
182 242 return QModelIndex();
183 243 }
184 244
185 245 switch (itemTypeOf(parent)) {
186 246 case CatalogueEventsModel::ItemType::Root:
187 247 return createIndex(row, column);
188 248 case CatalogueEventsModel::ItemType::Event: {
189 249 auto event = getEvent(parent);
190 250 return createIndex(row, column, event.get());
191 251 }
192 252 case CatalogueEventsModel::ItemType::EventProduct:
193 253 break;
194 254 default:
195 255 break;
196 256 }
197 257
198 258 return QModelIndex();
199 259 }
200 260
201 261 QModelIndex CatalogueEventsModel::parent(const QModelIndex &index) const
202 262 {
203 263 switch (itemTypeOf(index)) {
204 264 case CatalogueEventsModel::ItemType::EventProduct: {
205 265 auto parentEvent = static_cast<DBEvent *>(index.internalPointer());
206 266 auto it
207 267 = std::find_if(impl->m_Events.cbegin(), impl->m_Events.cend(),
208 268 [parentEvent](auto event) { return event.get() == parentEvent; });
209 269
210 270 if (it != impl->m_Events.cend()) {
211 271 return createIndex(it - impl->m_Events.cbegin(), 0);
212 272 }
213 273 else {
214 274 return QModelIndex();
215 275 }
216 276 }
217 277 case CatalogueEventsModel::ItemType::Root:
218 278 break;
219 279 case CatalogueEventsModel::ItemType::Event:
220 280 break;
221 281 default:
222 282 break;
223 283 }
224 284
225 285 return QModelIndex();
226 286 }
227 287
228 288 int CatalogueEventsModel::rowCount(const QModelIndex &parent) const
229 289 {
230 290 if (parent.column() > 0) {
231 291 return 0;
232 292 }
233 293
234 294 switch (itemTypeOf(parent)) {
235 295 case CatalogueEventsModel::ItemType::Root:
236 296 return impl->m_Events.count();
237 297 case CatalogueEventsModel::ItemType::Event: {
238 298 auto event = getEvent(parent);
239 299 return impl->m_EventProducts[event.get()].count();
240 300 }
241 301 case CatalogueEventsModel::ItemType::EventProduct:
242 302 break;
243 303 default:
244 304 break;
245 305 }
246 306
247 307 return 0;
248 308 }
249 309
250 310 int CatalogueEventsModel::columnCount(const QModelIndex &parent) const
251 311 {
252 return static_cast<int>(CatalogueEventsModelPrivate::Column::NbColumn);
312 return static_cast<int>(CatalogueEventsModel::Column::NbColumn);
253 313 }
254 314
255 315 Qt::ItemFlags CatalogueEventsModel::flags(const QModelIndex &index) const
256 316 {
257 317 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
258 318 }
259 319
260 320 QVariant CatalogueEventsModel::data(const QModelIndex &index, int role) const
261 321 {
262 322 if (index.isValid()) {
263 323
264 324 auto type = itemTypeOf(index);
265 325 if (type == CatalogueEventsModel::ItemType::Event) {
266 326 auto event = getEvent(index);
267 327 switch (role) {
268 328 case Qt::DisplayRole:
269 329 return impl->eventData(index.column(), event);
270 330 break;
271 331 }
272 332 }
273 333 else if (type == CatalogueEventsModel::ItemType::EventProduct) {
274 334 auto product = getEventProduct(index);
275 335 switch (role) {
276 336 case Qt::DisplayRole:
277 337 return impl->eventProductData(index.column(), product);
278 338 break;
279 339 }
280 340 }
281 341 }
282 342
283 343 return QVariant{};
284 344 }
285 345
286 346 QVariant CatalogueEventsModel::headerData(int section, Qt::Orientation orientation, int role) const
287 347 {
288 348 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
289 349 return impl->columnNames().value(section);
290 350 }
291 351
292 352 return QVariant();
293 353 }
294 354
295 355 void CatalogueEventsModel::sort(int column, Qt::SortOrder order)
296 356 {
297 357 std::sort(impl->m_Events.begin(), impl->m_Events.end(),
298 358 [this, column, order](auto e1, auto e2) {
299 auto data1 = impl->eventData(column, e1);
300 auto data2 = impl->eventData(column, e2);
359 auto data1 = impl->sortData(column, e1);
360 auto data2 = impl->sortData(column, e2);
301 361
302 362 auto result = data1.toString() < data2.toString();
303 363
304 364 return order == Qt::AscendingOrder ? result : !result;
305 365 });
306 366
307 367 emit dataChanged(QModelIndex(), QModelIndex());
368 emit modelSorted();
308 369 }
309 370
310 371 Qt::DropActions CatalogueEventsModel::supportedDragActions() const
311 372 {
312 373 return Qt::CopyAction | Qt::MoveAction;
313 374 }
314 375
315 376 QStringList CatalogueEventsModel::mimeTypes() const
316 377 {
317 378 return {MIME_TYPE_EVENT_LIST, MIME_TYPE_TIME_RANGE};
318 379 }
319 380
320 381 QMimeData *CatalogueEventsModel::mimeData(const QModelIndexList &indexes) const
321 382 {
322 383 auto mimeData = new QMimeData;
323 384
324 385 bool isFirst = true;
325 386
326 387 QVector<std::shared_ptr<DBEvent> > eventList;
327 388 QVector<std::shared_ptr<DBEventProduct> > eventProductList;
328 389
329 390 SqpRange firstTimeRange;
330 391 for (const auto &index : indexes) {
331 392 if (index.column() == 0) { // only the first column
332 393
333 394 auto type = itemTypeOf(index);
334 395 if (type == ItemType::Event) {
335 396 auto event = getEvent(index);
336 397 eventList << event;
337 398
338 399 if (isFirst) {
339 400 isFirst = false;
340 401 firstTimeRange.m_TStart = event->getTStart();
341 402 firstTimeRange.m_TEnd = event->getTEnd();
342 403 }
343 404 }
344 405 else if (type == ItemType::EventProduct) {
345 406 auto product = getEventProduct(index);
346 407 eventProductList << product;
347 408
348 409 if (isFirst) {
349 410 isFirst = false;
350 411 firstTimeRange.m_TStart = product->getTStart();
351 412 firstTimeRange.m_TEnd = product->getTEnd();
352 413 }
353 414 }
354 415 }
355 416 }
356 417
357 418 auto eventsEncodedData
358 419 = QByteArray{}; // sqpApp->catalogueController().->mimeDataForEvents(eventList); //TODO
359 420 mimeData->setData(MIME_TYPE_EVENT_LIST, eventsEncodedData);
360 421
361 422 if (eventList.count() + eventProductList.count() == 1) {
362 423 // No time range MIME data if multiple events are dragged
363 424 auto timeEncodedData = TimeController::mimeDataForTimeRange(firstTimeRange);
364 425 mimeData->setData(MIME_TYPE_TIME_RANGE, timeEncodedData);
365 426 }
366 427
367 428 return mimeData;
368 429 }
369 430
370 431 CatalogueEventsModel::ItemType CatalogueEventsModel::itemTypeOf(const QModelIndex &index) const
371 432 {
372 433 if (!index.isValid()) {
373 434 return ItemType::Root;
374 435 }
375 436 else if (index.internalPointer() == nullptr) {
376 437 return ItemType::Event;
377 438 }
378 439 else {
379 440 return ItemType::EventProduct;
380 441 }
381 442 }
@@ -1,316 +1,360
1 1 #include "Catalogue/CatalogueEventsWidget.h"
2 2 #include "ui_CatalogueEventsWidget.h"
3 3
4 4 #include <Catalogue/CatalogueController.h>
5 5 #include <Catalogue/CatalogueEventsModel.h>
6 #include <Catalogue/CatalogueExplorerHelper.h>
6 7 #include <CatalogueDao.h>
7 8 #include <DBCatalogue.h>
8 9 #include <SqpApplication.h>
9 10 #include <Visualization/VisualizationTabWidget.h>
10 11 #include <Visualization/VisualizationWidget.h>
11 12 #include <Visualization/VisualizationZoneWidget.h>
12 13
13 14 #include <QDialog>
14 15 #include <QDialogButtonBox>
15 16 #include <QListWidget>
16 17
17 18 Q_LOGGING_CATEGORY(LOG_CatalogueEventsWidget, "CatalogueEventsWidget")
18 19
19 /// Format of the dates appearing in the label of a cursor
20 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss");
20 /// Fixed size of the validation column
21 const auto VALIDATION_COLUMN_SIZE = 35;
21 22
22 23 struct CatalogueEventsWidget::CatalogueEventsWidgetPrivate {
23 24
24 25 CatalogueEventsModel *m_Model = nullptr;
25 26 QStringList m_ZonesForTimeMode;
26 27 QString m_ZoneForGraphMode;
27 28
28 29 VisualizationWidget *m_VisualizationWidget = nullptr;
29 30
30 31 void setEvents(const QVector<std::shared_ptr<DBEvent> > &events, QTreeView *treeView)
31 32 {
32 33 treeView->setSortingEnabled(false);
33 34 m_Model->setEvents(events);
34 35 treeView->setSortingEnabled(true);
35 36 }
36 37
37 38 void addEvent(const std::shared_ptr<DBEvent> &event, QTreeView *treeView)
38 39 {
39 40 treeView->setSortingEnabled(false);
40 41 m_Model->addEvent(event);
41 42 treeView->setSortingEnabled(true);
42 43 }
43 44
44 45 void removeEvent(const std::shared_ptr<DBEvent> &event, QTreeView *treeView)
45 46 {
46 47 treeView->setSortingEnabled(false);
47 48 m_Model->removeEvent(event);
48 49 treeView->setSortingEnabled(true);
49 50 }
50 51
51 52 QStringList getAvailableVisualizationZoneList() const
52 53 {
53 54 if (m_VisualizationWidget) {
54 55 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
55 56 return tab->availableZoneWidgets();
56 57 }
57 58 }
58 59
59 60 return QStringList{};
60 61 }
61 62
62 63 QStringList selectZone(QWidget *parent, const QStringList &selectedZones,
63 64 bool allowMultiSelection, const QPoint &location)
64 65 {
65 66 auto availableZones = getAvailableVisualizationZoneList();
66 67 if (availableZones.isEmpty()) {
67 68 return QStringList{};
68 69 }
69 70
70 71 QDialog d(parent, Qt::Tool);
71 72 d.setWindowTitle("Choose a zone");
72 73 auto layout = new QVBoxLayout{&d};
73 74 layout->setContentsMargins(0, 0, 0, 0);
74 75 auto listWidget = new QListWidget{&d};
75 76 layout->addWidget(listWidget);
76 77
77 78 QSet<QListWidgetItem *> checkedItems;
78 79 for (auto zone : availableZones) {
79 80 auto item = new QListWidgetItem{zone};
80 81 item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
81 82 if (selectedZones.contains(zone)) {
82 83 item->setCheckState(Qt::Checked);
83 84 checkedItems << item;
84 85 }
85 86 else {
86 87 item->setCheckState(Qt::Unchecked);
87 88 }
88 89
89 90 listWidget->addItem(item);
90 91 }
91 92
92 93 auto buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok, &d};
93 94 layout->addWidget(buttonBox);
94 95
95 96 QObject::connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
96 97 QObject::connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
97 98
98 99 QObject::connect(listWidget, &QListWidget::itemChanged,
99 100 [&checkedItems, allowMultiSelection, listWidget](auto item) {
100 101 if (item->checkState() == Qt::Checked) {
101 102 if (!allowMultiSelection) {
102 103 for (auto checkedItem : checkedItems) {
103 104 listWidget->blockSignals(true);
104 105 checkedItem->setCheckState(Qt::Unchecked);
105 106 listWidget->blockSignals(false);
106 107 }
107 108
108 109 checkedItems.clear();
109 110 }
110 111 checkedItems << item;
111 112 }
112 113 else {
113 114 checkedItems.remove(item);
114 115 }
115 116 });
116 117
117 118 QStringList result;
118 119
119 120 d.setMinimumWidth(120);
120 121 d.resize(d.minimumSizeHint());
121 122 d.move(location);
122 123 if (d.exec() == QDialog::Accepted) {
123 124 for (auto item : checkedItems) {
124 125 result += item->text();
125 126 }
126 127 }
127 128 else {
128 129 result = selectedZones;
129 130 }
130 131
131 132 return result;
132 133 }
133 134
134 135 void updateForTimeMode(QTreeView *treeView)
135 136 {
136 137 auto selectedRows = treeView->selectionModel()->selectedRows();
137 138
138 139 if (selectedRows.count() == 1) {
139 140 auto event = m_Model->getEvent(selectedRows.first());
140 141 if (event) {
141 142 if (m_VisualizationWidget) {
142 143 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
143 144
144 145 for (auto zoneName : m_ZonesForTimeMode) {
145 146 if (auto zone = tab->getZoneWithName(zoneName)) {
146 147 SqpRange eventRange;
147 148 eventRange.m_TStart = event->getTStart();
148 149 eventRange.m_TEnd = event->getTEnd();
149 150 zone->setZoneRange(eventRange);
150 151 }
151 152 }
152 153 }
153 154 else {
154 155 qCWarning(LOG_CatalogueEventsWidget())
155 156 << "updateTimeZone: no tab found in the visualization";
156 157 }
157 158 }
158 159 else {
159 160 qCWarning(LOG_CatalogueEventsWidget())
160 161 << "updateTimeZone: visualization widget not found";
161 162 }
162 163 }
163 164 }
164 165 else {
165 166 qCWarning(LOG_CatalogueEventsWidget())
166 167 << "updateTimeZone: not compatible with multiple events selected";
167 168 }
168 169 }
169 170
170 171 void updateForGraphMode(QTreeView *treeView)
171 172 {
172 173 auto selectedRows = treeView->selectionModel()->selectedRows();
173 174
174 175 if (selectedRows.count() == 1) {
175 176 auto event = m_Model->getEvent(selectedRows.first());
176 177 if (m_VisualizationWidget) {
177 178 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
178 179 if (auto zone = tab->getZoneWithName(m_ZoneForGraphMode)) {
179 180 // TODO
180 181 }
181 182 }
182 183 else {
183 184 qCWarning(LOG_CatalogueEventsWidget())
184 185 << "updateGraphMode: no tab found in the visualization";
185 186 }
186 187 }
187 188 else {
188 189 qCWarning(LOG_CatalogueEventsWidget())
189 190 << "updateGraphMode: visualization widget not found";
190 191 }
191 192 }
192 193 else {
193 194 qCWarning(LOG_CatalogueEventsWidget())
194 195 << "updateGraphMode: not compatible with multiple events selected";
195 196 }
196 197 }
197 198 };
198 199
199 200 CatalogueEventsWidget::CatalogueEventsWidget(QWidget *parent)
200 201 : QWidget(parent),
201 202 ui(new Ui::CatalogueEventsWidget),
202 203 impl{spimpl::make_unique_impl<CatalogueEventsWidgetPrivate>()}
203 204 {
204 205 ui->setupUi(this);
205 206
206 207 impl->m_Model = new CatalogueEventsModel{this};
207 208 ui->treeView->setModel(impl->m_Model);
208 209
209 210 ui->treeView->setSortingEnabled(true);
210 211 ui->treeView->setDragDropMode(QAbstractItemView::DragDrop);
211 212 ui->treeView->setDragEnabled(true);
212 213
213 214 connect(ui->btnTime, &QToolButton::clicked, [this](auto checked) {
214 215 if (checked) {
215 216 ui->btnChart->setChecked(false);
216 217 impl->m_ZonesForTimeMode
217 218 = impl->selectZone(this, impl->m_ZonesForTimeMode, true,
218 219 this->mapToGlobal(ui->btnTime->frameGeometry().center()));
219 220
220 221 impl->updateForTimeMode(ui->treeView);
221 222 }
222 223 });
223 224
224 225 connect(ui->btnChart, &QToolButton::clicked, [this](auto checked) {
225 226 if (checked) {
226 227 ui->btnTime->setChecked(false);
227 228 impl->m_ZoneForGraphMode
228 229 = impl->selectZone(this, {impl->m_ZoneForGraphMode}, false,
229 230 this->mapToGlobal(ui->btnChart->frameGeometry().center()))
230 231 .value(0);
231 232
232 233 impl->updateForGraphMode(ui->treeView);
233 234 }
234 235 });
235 236
236 237 auto emitSelection = [this]() {
237 238 QVector<std::shared_ptr<DBEvent> > events;
238 239 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
239 240
240 241 for (auto rowIndex : ui->treeView->selectionModel()->selectedRows()) {
241 242
242 243 auto itemType = impl->m_Model->itemTypeOf(rowIndex);
243 244 if (itemType == CatalogueEventsModel::ItemType::Event) {
244 245 events << impl->m_Model->getEvent(rowIndex);
245 246 }
246 247 else if (itemType == CatalogueEventsModel::ItemType::EventProduct) {
247 248 eventProducts << qMakePair(impl->m_Model->getParentEvent(rowIndex),
248 249 impl->m_Model->getEventProduct(rowIndex));
249 250 }
250 251 }
251 252
252 253 if (!events.isEmpty() && eventProducts.isEmpty()) {
253 254 emit this->eventsSelected(events);
254 255 }
255 256 else if (events.isEmpty() && !eventProducts.isEmpty()) {
256 257 emit this->eventProductsSelected(eventProducts);
257 258 }
258 259 else {
259 260 emit this->selectionCleared();
260 261 }
261 262 };
262 263
263 264 connect(ui->treeView, &QTreeView::clicked, emitSelection);
264 265 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, emitSelection);
265 266
266 267 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, [this]() {
267 268 auto isNotMultiSelection = ui->treeView->selectionModel()->selectedRows().count() <= 1;
268 269 ui->btnChart->setEnabled(isNotMultiSelection);
269 270 ui->btnTime->setEnabled(isNotMultiSelection);
270 271
271 272 if (isNotMultiSelection && ui->btnTime->isChecked()) {
272 273 impl->updateForTimeMode(ui->treeView);
273 274 }
274 275 else if (isNotMultiSelection && ui->btnChart->isChecked()) {
275 276 impl->updateForGraphMode(ui->treeView);
276 277 }
277 278 });
278 279
279 280 ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
280 ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
281 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Name,
282 QHeaderView::Stretch);
283 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Validation,
284 QHeaderView::Fixed);
285 ui->treeView->header()->resizeSection((int)CatalogueEventsModel::Column::Validation,
286 VALIDATION_COLUMN_SIZE);
281 287 ui->treeView->header()->setSortIndicatorShown(true);
288
289 connect(impl->m_Model, &CatalogueEventsModel::modelSorted, [this]() {
290 auto allEvents = impl->m_Model->events();
291 for (auto event : allEvents) {
292 setEventChanges(event, impl->m_Model->eventsHasChanges(event));
293 }
294 });
282 295 }
283 296
284 297 CatalogueEventsWidget::~CatalogueEventsWidget()
285 298 {
286 299 delete ui;
287 300 }
288 301
289 302 void CatalogueEventsWidget::setVisualizationWidget(VisualizationWidget *visualization)
290 303 {
291 304 impl->m_VisualizationWidget = visualization;
292 305 }
293 306
294 307 void CatalogueEventsWidget::setEventChanges(const std::shared_ptr<DBEvent> &event, bool hasChanges)
295 308 {
296 309 impl->m_Model->refreshEvent(event);
310
311 auto eventIndex = impl->m_Model->indexOf(event);
312 auto validationIndex
313 = eventIndex.sibling(eventIndex.row(), (int)CatalogueEventsModel::Column::Validation);
314
315 if (hasChanges) {
316 if (ui->treeView->indexWidget(validationIndex) == nullptr) {
317 auto widget = CatalogueExplorerHelper::buildValidationWidget(
318 ui->treeView, [this, event]() { setEventChanges(event, false); },
319 [this, event]() { setEventChanges(event, false); });
320 ui->treeView->setIndexWidget(validationIndex, widget);
321 }
322 }
323 else {
324 // Note: the widget is destroyed
325 ui->treeView->setIndexWidget(validationIndex, nullptr);
326 }
327
328 impl->m_Model->setEventHasChanges(event, hasChanges);
297 329 }
298 330
299 331 void CatalogueEventsWidget::populateWithCatalogues(
300 332 const QVector<std::shared_ptr<DBCatalogue> > &catalogues)
301 333 {
302 334 QSet<QUuid> eventIds;
303 335 QVector<std::shared_ptr<DBEvent> > events;
304 336
305 337 for (auto catalogue : catalogues) {
306 338 auto catalogueEvents = sqpApp->catalogueController().retrieveEventsFromCatalogue(catalogue);
307 339 for (auto event : catalogueEvents) {
308 340 if (!eventIds.contains(event->getUniqId())) {
309 341 events << event;
310 342 eventIds.insert(event->getUniqId());
311 343 }
312 344 }
313 345 }
314 346
315 347 impl->setEvents(events, ui->treeView);
316 348 }
349
350 void CatalogueEventsWidget::populateWithAllEvents()
351 {
352 auto allEvents = sqpApp->catalogueController().retrieveAllEvents();
353
354 QVector<std::shared_ptr<DBEvent> > events;
355 for (auto event : allEvents) {
356 events << event;
357 }
358
359 impl->setEvents(events, ui->treeView);
360 }
@@ -1,83 +1,95
1 1 #include "Catalogue/CatalogueExplorer.h"
2 2 #include "ui_CatalogueExplorer.h"
3 3
4 #include <Catalogue/CatalogueActionManager.h>
5 #include <Catalogue/CatalogueController.h>
6 #include <SqpApplication.h>
4 7 #include <Visualization/VisualizationWidget.h>
5 8
6 9 #include <DBCatalogue.h>
7 10 #include <DBEvent.h>
8 11
9 12 struct CatalogueExplorer::CatalogueExplorerPrivate {
13 CatalogueActionManager m_ActionManager;
10 14 };
11 15
12 16 CatalogueExplorer::CatalogueExplorer(QWidget *parent)
13 17 : QDialog(parent, Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint),
14 18 ui(new Ui::CatalogueExplorer),
15 19 impl{spimpl::make_unique_impl<CatalogueExplorerPrivate>()}
16 20 {
17 21 ui->setupUi(this);
18 22
23 impl->m_ActionManager.installSelectionZoneActions();
24
19 25 connect(ui->catalogues, &CatalogueSideBarWidget::catalogueSelected, [this](auto catalogues) {
20 26 if (catalogues.count() == 1) {
21 27 ui->inspector->setCatalogue(catalogues.first());
22 28 }
23 29 else {
24 30 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
25 31 }
26 32
27 33 ui->events->populateWithCatalogues(catalogues);
28 34 });
29 35
30 36 connect(ui->catalogues, &CatalogueSideBarWidget::databaseSelected, [this](auto databases) {
31 37 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
32 38 });
33 39
34 40 connect(ui->catalogues, &CatalogueSideBarWidget::trashSelected,
35 41 [this]() { ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty); });
36 42
37 connect(ui->catalogues, &CatalogueSideBarWidget::allEventsSelected,
38 [this]() { ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty); });
43 connect(ui->catalogues, &CatalogueSideBarWidget::allEventsSelected, [this]() {
44 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
45 ui->events->populateWithAllEvents();
46 });
39 47
40 48 connect(ui->catalogues, &CatalogueSideBarWidget::selectionCleared,
41 49 [this]() { ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty); });
42 50
43 51 connect(ui->events, &CatalogueEventsWidget::eventsSelected, [this](auto events) {
44 52 if (events.count() == 1) {
45 53 ui->inspector->setEvent(events.first());
46 54 }
47 55 else {
48 56 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
49 57 }
50 58 });
51 59
52 60 connect(ui->events, &CatalogueEventsWidget::eventProductsSelected, [this](auto eventProducts) {
53 61 if (eventProducts.count() == 1) {
54 62 ui->inspector->setEventProduct(eventProducts.first().first,
55 63 eventProducts.first().second);
56 64 }
57 65 else {
58 66 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
59 67 }
60 68 });
61 69
62 70 connect(ui->events, &CatalogueEventsWidget::selectionCleared,
63 71 [this]() { ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty); });
64 72
65 connect(ui->inspector, &CatalogueInspectorWidget::catalogueUpdated,
66 [this](auto catalogue) { ui->catalogues->setCatalogueChanges(catalogue, true); });
73 connect(ui->inspector, &CatalogueInspectorWidget::catalogueUpdated, [this](auto catalogue) {
74 sqpApp->catalogueController().updateCatalogue(catalogue);
75 ui->catalogues->setCatalogueChanges(catalogue, true);
76 });
67 77
68 connect(ui->inspector, &CatalogueInspectorWidget::eventUpdated,
69 [this](auto event) { ui->events->setEventChanges(event, true); });
78 connect(ui->inspector, &CatalogueInspectorWidget::eventUpdated, [this](auto event) {
79 sqpApp->catalogueController().updateEvent(event);
80 ui->events->setEventChanges(event, true);
81 });
70 82
71 83 connect(ui->inspector, &CatalogueInspectorWidget::eventProductUpdated,
72 84 [this](auto event, auto eventProduct) { ui->events->setEventChanges(event, true); });
73 85 }
74 86
75 87 CatalogueExplorer::~CatalogueExplorer()
76 88 {
77 89 delete ui;
78 90 }
79 91
80 92 void CatalogueExplorer::setVisualizationWidget(VisualizationWidget *visualization)
81 93 {
82 94 ui->events->setVisualizationWidget(visualization);
83 95 }
@@ -1,247 +1,250
1 1 #include "Catalogue/CatalogueSideBarWidget.h"
2 2 #include "ui_CatalogueSideBarWidget.h"
3 3 #include <SqpApplication.h>
4 4
5 5 #include <Catalogue/CatalogueController.h>
6 6 #include <Catalogue/CatalogueTreeWidgetItem.h>
7 7 #include <CatalogueDao.h>
8 8 #include <ComparaisonPredicate.h>
9 9 #include <DBCatalogue.h>
10 10
11 11 #include <QMenu>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_CatalogueSideBarWidget, "CatalogueSideBarWidget")
14 14
15 15
16 16 constexpr auto ALL_EVENT_ITEM_TYPE = QTreeWidgetItem::UserType;
17 17 constexpr auto TRASH_ITEM_TYPE = QTreeWidgetItem::UserType + 1;
18 18 constexpr auto CATALOGUE_ITEM_TYPE = QTreeWidgetItem::UserType + 2;
19 19 constexpr auto DATABASE_ITEM_TYPE = QTreeWidgetItem::UserType + 3;
20 20
21 21
22 22 struct CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate {
23 23
24 24 void configureTreeWidget(QTreeWidget *treeWidget);
25 25 QTreeWidgetItem *addDatabaseItem(const QString &name, QTreeWidget *treeWidget);
26 26 QTreeWidgetItem *getDatabaseItem(const QString &name, QTreeWidget *treeWidget);
27 27 void addCatalogueItem(const std::shared_ptr<DBCatalogue> &catalogue,
28 28 QTreeWidgetItem *parentDatabaseItem);
29 29
30 30 CatalogueTreeWidgetItem *getCatalogueItem(const std::shared_ptr<DBCatalogue> &catalogue,
31 31 QTreeWidget *treeWidget) const;
32 32 };
33 33
34 34 CatalogueSideBarWidget::CatalogueSideBarWidget(QWidget *parent)
35 35 : QWidget(parent),
36 36 ui(new Ui::CatalogueSideBarWidget),
37 37 impl{spimpl::make_unique_impl<CatalogueSideBarWidgetPrivate>()}
38 38 {
39 39 ui->setupUi(this);
40 40 impl->configureTreeWidget(ui->treeWidget);
41 41
42 42 ui->treeWidget->setColumnCount(2);
43 43 ui->treeWidget->header()->setStretchLastSection(false);
44 44 ui->treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
45 45 ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
46 46
47 47 auto emitSelection = [this]() {
48 48
49 49 auto selectedItems = ui->treeWidget->selectedItems();
50 50 if (selectedItems.isEmpty()) {
51 51 emit this->selectionCleared();
52 52 }
53 53 else {
54 54 QVector<std::shared_ptr<DBCatalogue> > catalogues;
55 55 QStringList databases;
56 56 int selectionType = selectedItems.first()->type();
57 57
58 58 for (auto item : ui->treeWidget->selectedItems()) {
59 59 if (item->type() == selectionType) {
60 60 switch (selectionType) {
61 61 case CATALOGUE_ITEM_TYPE:
62 62 catalogues.append(
63 63 static_cast<CatalogueTreeWidgetItem *>(item)->catalogue());
64 64 break;
65 65 case DATABASE_ITEM_TYPE:
66 66 selectionType = DATABASE_ITEM_TYPE;
67 67 databases.append(item->text(0));
68 68 case ALL_EVENT_ITEM_TYPE: // fallthrough
69 69 case TRASH_ITEM_TYPE: // fallthrough
70 70 default:
71 71 break;
72 72 }
73 73 }
74 74 else {
75 75 // Incoherent multi selection
76 76 selectionType = -1;
77 77 break;
78 78 }
79 79 }
80 80
81 81 switch (selectionType) {
82 82 case CATALOGUE_ITEM_TYPE:
83 83 emit this->catalogueSelected(catalogues);
84 84 break;
85 85 case DATABASE_ITEM_TYPE:
86 86 emit this->databaseSelected(databases);
87 87 break;
88 88 case ALL_EVENT_ITEM_TYPE:
89 89 emit this->allEventsSelected();
90 90 break;
91 91 case TRASH_ITEM_TYPE:
92 92 emit this->trashSelected();
93 93 break;
94 94 default:
95 95 emit this->selectionCleared();
96 96 break;
97 97 }
98 98 }
99 99
100 100
101 101 };
102 102
103 103 connect(ui->treeWidget, &QTreeWidget::itemClicked, emitSelection);
104 104 connect(ui->treeWidget, &QTreeWidget::currentItemChanged, emitSelection);
105 105 connect(ui->treeWidget, &QTreeWidget::itemChanged,
106 106 [emitSelection, this](auto item, auto column) {
107 107 auto selectedItems = ui->treeWidget->selectedItems();
108 108 qDebug() << "ITEM CHANGED" << column;
109 109 if (selectedItems.contains(item) && column == 0) {
110 110 emitSelection();
111 111 }
112 112 });
113 113
114 114 ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
115 115 connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this,
116 116 &CatalogueSideBarWidget::onContextMenuRequested);
117 117 }
118 118
119 119 CatalogueSideBarWidget::~CatalogueSideBarWidget()
120 120 {
121 121 delete ui;
122 122 }
123 123
124 124 void CatalogueSideBarWidget::setCatalogueChanges(const std::shared_ptr<DBCatalogue> &catalogue,
125 125 bool hasChanges)
126 126 {
127 127 if (auto catalogueItem = impl->getCatalogueItem(catalogue, ui->treeWidget)) {
128 128 catalogueItem->setHasChanges(hasChanges);
129 129 catalogueItem->refresh();
130 130 }
131 131 }
132 132
133 133 void CatalogueSideBarWidget::onContextMenuRequested(const QPoint &pos)
134 134 {
135 135 QMenu menu{this};
136 136
137 137 auto currentItem = ui->treeWidget->currentItem();
138 138 switch (currentItem->type()) {
139 139 case CATALOGUE_ITEM_TYPE:
140 140 menu.addAction("Rename",
141 141 [this, currentItem]() { ui->treeWidget->editItem(currentItem); });
142 142 break;
143 143 case DATABASE_ITEM_TYPE:
144 144 break;
145 145 case ALL_EVENT_ITEM_TYPE:
146 146 break;
147 147 case TRASH_ITEM_TYPE:
148 148 menu.addAction("Empty Trash", []() {
149 149 // TODO
150 150 });
151 151 break;
152 152 default:
153 153 break;
154 154 }
155 155
156 156 if (!menu.isEmpty()) {
157 157 menu.exec(ui->treeWidget->mapToGlobal(pos));
158 158 }
159 159 }
160 160
161 161 void CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::configureTreeWidget(
162 162 QTreeWidget *treeWidget)
163 163 {
164 164 auto allEventsItem = new QTreeWidgetItem{{"All Events"}, ALL_EVENT_ITEM_TYPE};
165 165 allEventsItem->setIcon(0, QIcon(":/icones/allEvents.png"));
166 166 treeWidget->addTopLevelItem(allEventsItem);
167 167
168 168 auto trashItem = new QTreeWidgetItem{{"Trash"}, TRASH_ITEM_TYPE};
169 169 trashItem->setIcon(0, QIcon(":/icones/trash.png"));
170 170 treeWidget->addTopLevelItem(trashItem);
171 171
172 172 auto separator = new QFrame{treeWidget};
173 173 separator->setFrameShape(QFrame::HLine);
174 174 auto separatorItem = new QTreeWidgetItem{};
175 175 separatorItem->setFlags(Qt::NoItemFlags);
176 176 treeWidget->addTopLevelItem(separatorItem);
177 177 treeWidget->setItemWidget(separatorItem, 0, separator);
178 178
179 auto db = addDatabaseItem("Default", treeWidget);
179 auto repositories = sqpApp->catalogueController().getRepositories();
180 for (auto dbname : repositories) {
181 auto db = addDatabaseItem(dbname, treeWidget);
180 182
181 auto catalogues = sqpApp->catalogueController().getCatalogues("Default");
183 auto catalogues = sqpApp->catalogueController().retrieveCatalogues(dbname);
182 184 for (auto catalogue : catalogues) {
183 185 addCatalogueItem(catalogue, db);
184 186 }
187 }
185 188
186 189 treeWidget->expandAll();
187 190 }
188 191
189 192 QTreeWidgetItem *
190 193 CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::addDatabaseItem(const QString &name,
191 194 QTreeWidget *treeWidget)
192 195 {
193 196 auto databaseItem = new QTreeWidgetItem{{name}, DATABASE_ITEM_TYPE};
194 197 databaseItem->setIcon(0, QIcon{":/icones/database.png"});
195 198 treeWidget->addTopLevelItem(databaseItem);
196 199
197 200 return databaseItem;
198 201 }
199 202
200 203 QTreeWidgetItem *
201 204 CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::getDatabaseItem(const QString &name,
202 205 QTreeWidget *treeWidget)
203 206 {
204 207 for (auto i = 0; i < treeWidget->topLevelItemCount(); ++i) {
205 208 auto item = treeWidget->topLevelItem(i);
206 209 if (item->type() == DATABASE_ITEM_TYPE && item->text(0) == name) {
207 210 return item;
208 211 }
209 212 }
210 213
211 214 return nullptr;
212 215 }
213 216
214 217 void CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::addCatalogueItem(
215 218 const std::shared_ptr<DBCatalogue> &catalogue, QTreeWidgetItem *parentDatabaseItem)
216 219 {
217 220 auto catalogueItem = new CatalogueTreeWidgetItem{catalogue, CATALOGUE_ITEM_TYPE};
218 221 catalogueItem->setIcon(0, QIcon{":/icones/catalogue.png"});
219 222 parentDatabaseItem->addChild(catalogueItem);
220 223 }
221 224
222 225 CatalogueTreeWidgetItem *CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::getCatalogueItem(
223 226 const std::shared_ptr<DBCatalogue> &catalogue, QTreeWidget *treeWidget) const
224 227 {
225 228 for (auto i = 0; i < treeWidget->topLevelItemCount(); ++i) {
226 229 auto item = treeWidget->topLevelItem(i);
227 230 if (item->type() == DATABASE_ITEM_TYPE) {
228 231 for (auto j = 0; j < item->childCount(); ++j) {
229 232 auto childItem = item->child(j);
230 233 if (childItem->type() == CATALOGUE_ITEM_TYPE) {
231 234 auto catalogueItem = static_cast<CatalogueTreeWidgetItem *>(childItem);
232 235 if (catalogueItem->catalogue() == catalogue) {
233 236 return catalogueItem;
234 237 }
235 238 }
236 239 else {
237 240 qCWarning(LOG_CatalogueSideBarWidget()) << "getCatalogueItem: Invalid tree "
238 241 "structure. A database item should "
239 242 "only contain catalogues.";
240 243 Q_ASSERT(false);
241 244 }
242 245 }
243 246 }
244 247 }
245 248
246 249 return nullptr;
247 250 }
@@ -1,103 +1,91
1 1 #include "Catalogue/CatalogueTreeWidgetItem.h"
2 #include <Catalogue/CatalogueExplorerHelper.h>
3
4 #include <Catalogue/CatalogueController.h>
5 #include <SqpApplication.h>
2 6
3 7 #include <memory>
4 8
5 9 #include <DBCatalogue.h>
6 #include <QBoxLayout>
7 #include <QToolButton>
8
9 const auto VALIDATION_BUTTON_ICON_SIZE = 12;
10 10
11 11 /// Column in the tree widget where the apply and cancel buttons must appear
12 12 const auto APPLY_CANCEL_BUTTONS_COLUMN = 1;
13 13
14 14 struct CatalogueTreeWidgetItem::CatalogueTreeWidgetItemPrivate {
15 15
16 16 std::shared_ptr<DBCatalogue> m_Catalogue;
17 17
18 18 CatalogueTreeWidgetItemPrivate(std::shared_ptr<DBCatalogue> catalogue) : m_Catalogue(catalogue)
19 19 {
20 20 }
21 21 };
22 22
23 23
24 24 CatalogueTreeWidgetItem::CatalogueTreeWidgetItem(std::shared_ptr<DBCatalogue> catalogue, int type)
25 25 : QTreeWidgetItem(type),
26 26 impl{spimpl::make_unique_impl<CatalogueTreeWidgetItemPrivate>(catalogue)}
27 27 {
28 28 setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
29 29 }
30 30
31 31 QVariant CatalogueTreeWidgetItem::data(int column, int role) const
32 32 {
33 33 if (column == 0) {
34 34 switch (role) {
35 35 case Qt::EditRole: // fallthrough
36 36 case Qt::DisplayRole:
37 37 return impl->m_Catalogue->getName();
38 38 default:
39 39 break;
40 40 }
41 41 }
42 42
43 43 return QTreeWidgetItem::data(column, role);
44 44 }
45 45
46 46 void CatalogueTreeWidgetItem::setData(int column, int role, const QVariant &value)
47 47 {
48 48 if (role == Qt::EditRole && column == 0) {
49 49 auto newName = value.toString();
50 50 if (newName != impl->m_Catalogue->getName()) {
51 51 setText(0, newName);
52 52 impl->m_Catalogue->setName(newName);
53 sqpApp->catalogueController().updateCatalogue(impl->m_Catalogue);
53 54 setHasChanges(true);
54 55 }
55 56 }
56 57 else {
57 58 QTreeWidgetItem::setData(column, role, value);
58 59 }
59 60 }
60 61
61 62 std::shared_ptr<DBCatalogue> CatalogueTreeWidgetItem::catalogue() const
62 63 {
63 64 return impl->m_Catalogue;
64 65 }
65 66
66 67 void CatalogueTreeWidgetItem::setHasChanges(bool value)
67 68 {
68 69 if (value) {
69 if (treeWidget()->itemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN) == nullptr) {
70 auto widet = new QWidget{treeWidget()};
71
72 auto layout = new QHBoxLayout{widet};
73 layout->setContentsMargins(0, 0, 0, 0);
74 layout->setSpacing(0);
75
76 auto btnValid = new QToolButton{widet};
77 btnValid->setIcon(QIcon{":/icones/save"});
78 btnValid->setIconSize(QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE});
79 btnValid->setAutoRaise(true);
80 QObject::connect(btnValid, &QToolButton::clicked, [this]() { setHasChanges(false); });
81 layout->addWidget(btnValid);
82
83 auto btnDiscard = new QToolButton{widet};
84 btnDiscard->setIcon(QIcon{":/icones/discard"});
85 btnDiscard->setIconSize(
86 QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE});
87 btnDiscard->setAutoRaise(true);
88 QObject::connect(btnDiscard, &QToolButton::clicked, [this]() { setHasChanges(false); });
89 layout->addWidget(btnDiscard);
90
91 treeWidget()->setItemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN, {widet});
70 if (!hasChanges()) {
71 auto widget = CatalogueExplorerHelper::buildValidationWidget(
72 treeWidget(), [this]() { setHasChanges(false); },
73 [this]() { setHasChanges(false); });
74 treeWidget()->setItemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN, widget);
92 75 }
93 76 }
94 77 else {
95 78 // Note: the widget is destroyed
96 79 treeWidget()->setItemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN, nullptr);
97 80 }
98 81 }
99 82
83 bool CatalogueTreeWidgetItem::hasChanges()
84 {
85 return treeWidget()->itemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN) != nullptr;
86 }
87
100 88 void CatalogueTreeWidgetItem::refresh()
101 89 {
102 90 emitDataChanged();
103 91 }
@@ -1,109 +1,111
1 1 #include "Visualization/VisualizationActionManager.h"
2 2 #include "Visualization/VisualizationGraphWidget.h"
3 3 #include "Visualization/VisualizationSelectionZoneItem.h"
4 4
5 5 #include <Actions/ActionsGuiController.h>
6 6 #include <SqpApplication.h>
7 7
8 VisualizationActionManager::VisualizationActionManager() {}
8 VisualizationActionManager::VisualizationActionManager()
9 {
10 }
9 11
10 12 void VisualizationActionManager::installSelectionZoneActions()
11 13 {
12 14 auto &actionController = sqpApp->actionsGuiController();
13 15
14 16 auto removeZonesAction
15 17 = actionController.addSectionZoneAction("Remove Selected Zone(s)", [](auto zones) {
16 18 for (auto selectionZone : zones) {
17 19 if (auto graph = selectionZone->parentGraphWidget()) {
18 20 graph->removeSelectionZone(selectionZone);
19 21 }
20 22 }
21 23 });
22 24 removeZonesAction->setDisplayedShortcut(QKeySequence::Delete);
23 25
24 26 auto alignEnableFuntion = [](auto items) { return items.count() > 1; };
25 27
26 28 // Vertical alignment actions
27 29 auto alignLeftAction = actionController.addSectionZoneAction(
28 30 QStringList{"Align Vertically"}, "Left", [](auto zones) {
29 31 Q_ASSERT(zones.count() > 1);
30 32 auto ref = zones.takeFirst();
31 33 ref->alignZonesVerticallyOnLeft(zones, false);
32 34 });
33 35 alignLeftAction->setEnableFunction(alignEnableFuntion);
34 36
35 37 auto alignLeftBorderAction = actionController.addSectionZoneAction(
36 38 QStringList{"Align Vertically"}, "Left Borders", [](auto zones) {
37 39 Q_ASSERT(zones.count() > 1);
38 40 auto ref = zones.takeFirst();
39 41 ref->alignZonesVerticallyOnLeft(zones, true);
40 42 });
41 43 alignLeftBorderAction->setEnableFunction(alignEnableFuntion);
42 44
43 45 auto alignRightAction = actionController.addSectionZoneAction(
44 46 QStringList{"Align Vertically"}, "Right", [](auto zones) {
45 47 Q_ASSERT(zones.count() > 1);
46 48 auto ref = zones.takeFirst();
47 49 ref->alignZonesVerticallyOnRight(zones, false);
48 50 });
49 51 alignRightAction->setEnableFunction(alignEnableFuntion);
50 52
51 53 auto alignRightBorderAction = actionController.addSectionZoneAction(
52 54 QStringList{"Align Vertically"}, "Right Borders", [](auto zones) {
53 55 Q_ASSERT(zones.count() > 1);
54 56 auto ref = zones.takeFirst();
55 57 ref->alignZonesVerticallyOnRight(zones, true);
56 58 });
57 59 alignRightBorderAction->setEnableFunction(alignEnableFuntion);
58 60
59 61 auto alignLeftAndRightAction = actionController.addSectionZoneAction(
60 62 QStringList{"Align Vertically"}, "Left and Right", [](auto zones) {
61 63 Q_ASSERT(zones.count() > 1);
62 64 auto ref = zones.takeFirst();
63 65 ref->alignZonesVerticallyOnLeft(zones, false);
64 66 ref->alignZonesVerticallyOnRight(zones, true);
65 67 });
66 68 alignLeftAndRightAction->setEnableFunction(alignEnableFuntion);
67 69
68 70 // Temporal alignment actions
69 71 auto alignLeftTemporallyAction = actionController.addSectionZoneAction(
70 72 QStringList{"Align Temporally"}, "Left", [](auto zones) {
71 73 Q_ASSERT(zones.count() > 1);
72 74 auto ref = zones.takeFirst();
73 75 ref->alignZonesTemporallyOnLeft(zones, false);
74 76 });
75 77 alignLeftTemporallyAction->setEnableFunction(alignEnableFuntion);
76 78
77 79 auto alignLeftBorderTemporallyAction = actionController.addSectionZoneAction(
78 80 QStringList{"Align Temporally"}, "Left Borders", [](auto zones) {
79 81 Q_ASSERT(zones.count() > 1);
80 82 auto ref = zones.takeFirst();
81 83 ref->alignZonesTemporallyOnLeft(zones, true);
82 84 });
83 85 alignLeftBorderTemporallyAction->setEnableFunction(alignEnableFuntion);
84 86
85 87 auto alignRightTemporallyAction = actionController.addSectionZoneAction(
86 88 QStringList{"Align Temporally"}, "Right", [](auto zones) {
87 89 Q_ASSERT(zones.count() > 1);
88 90 auto ref = zones.takeFirst();
89 91 ref->alignZonesTemporallyOnRight(zones, false);
90 92 });
91 93 alignRightTemporallyAction->setEnableFunction(alignEnableFuntion);
92 94
93 95 auto alignRightBorderTemporallyAction = actionController.addSectionZoneAction(
94 96 QStringList{"Align Temporally"}, "Right Borders", [](auto zones) {
95 97 Q_ASSERT(zones.count() > 1);
96 98 auto ref = zones.takeFirst();
97 99 ref->alignZonesTemporallyOnRight(zones, true);
98 100 });
99 101 alignRightBorderTemporallyAction->setEnableFunction(alignEnableFuntion);
100 102
101 103 auto alignLeftAndRightTemporallyAction = actionController.addSectionZoneAction(
102 104 QStringList{"Align Temporally"}, "Left and Right", [](auto zones) {
103 105 Q_ASSERT(zones.count() > 1);
104 106 auto ref = zones.takeFirst();
105 107 ref->alignZonesTemporallyOnLeft(zones, false);
106 108 ref->alignZonesTemporallyOnRight(zones, true);
107 109 });
108 110 alignLeftAndRightTemporallyAction->setEnableFunction(alignEnableFuntion);
109 111 }
@@ -1,1004 +1,1003
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationCursorItem.h"
4 4 #include "Visualization/VisualizationDefs.h"
5 5 #include "Visualization/VisualizationGraphHelper.h"
6 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 10 #include "Visualization/VisualizationWidget.h"
11 11 #include "Visualization/VisualizationZoneWidget.h"
12 12 #include "ui_VisualizationGraphWidget.h"
13 13
14 14 #include <Actions/ActionsGuiController.h>
15 15 #include <Common/MimeTypesDef.h>
16 16 #include <Data/ArrayData.h>
17 17 #include <Data/IDataSeries.h>
18 18 #include <Data/SpectrogramSeries.h>
19 19 #include <DragAndDrop/DragDropGuiController.h>
20 20 #include <Settings/SqpSettingsDefs.h>
21 21 #include <SqpApplication.h>
22 22 #include <Time/TimeController.h>
23 23 #include <Variable/Variable.h>
24 24 #include <Variable/VariableController.h>
25 25
26 26 #include <unordered_map>
27 27
28 28 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29 29
30 30 namespace {
31 31
32 32 /// Key pressed to enable drag&drop in all modes
33 33 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
34 34
35 35 /// Key pressed to enable zoom on horizontal axis
36 36 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
37 37
38 38 /// Key pressed to enable zoom on vertical axis
39 39 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
40 40
41 41 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
42 42 const auto PAN_SPEED = 5;
43 43
44 44 /// Key pressed to enable a calibration pan
45 45 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
46 46
47 47 /// Key pressed to enable multi selection of selection zones
48 48 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
49 49
50 50 /// Minimum size for the zoom box, in percentage of the axis range
51 51 const auto ZOOM_BOX_MIN_SIZE = 0.8;
52 52
53 53 /// Format of the dates appearing in the label of a cursor
54 54 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
55 55
56 56 } // namespace
57 57
58 58 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
59 59
60 60 explicit VisualizationGraphWidgetPrivate(const QString &name)
61 61 : m_Name{name},
62 62 m_DoAcquisition{true},
63 63 m_IsCalibration{false},
64 64 m_RenderingDelegate{nullptr}
65 65 {
66 66 }
67 67
68 68 void updateData(PlottablesMap &plottables, std::shared_ptr<IDataSeries> dataSeries,
69 69 const SqpRange &range)
70 70 {
71 71 VisualizationGraphHelper::updateData(plottables, dataSeries, range);
72 72
73 73 // Prevents that data has changed to update rendering
74 74 m_RenderingDelegate->onPlotUpdated();
75 75 }
76 76
77 77 QString m_Name;
78 78 // 1 variable -> n qcpplot
79 79 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
80 80 bool m_DoAcquisition;
81 81 bool m_IsCalibration;
82 82 /// Delegate used to attach rendering features to the plot
83 83 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
84 84
85 85 QCPItemRect *m_DrawingZoomRect = nullptr;
86 86 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
87 87
88 88 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
89 89 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
90 90
91 91 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
92 92 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
93 93 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
94 94
95 95 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
96 96
97 97 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
98 98 {
99 99 removeDrawingRect(plot);
100 100
101 101 auto axisPos = posToAxisPos(pos, plot);
102 102
103 103 m_DrawingZoomRect = new QCPItemRect{&plot};
104 104 QPen p;
105 105 p.setWidth(2);
106 106 m_DrawingZoomRect->setPen(p);
107 107
108 108 m_DrawingZoomRect->topLeft->setCoords(axisPos);
109 109 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
110 110 }
111 111
112 112 void removeDrawingRect(QCustomPlot &plot)
113 113 {
114 114 if (m_DrawingZoomRect) {
115 115 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
116 116 m_DrawingZoomRect = nullptr;
117 117 plot.replot(QCustomPlot::rpQueuedReplot);
118 118 }
119 119 }
120 120
121 121 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
122 122 {
123 123 endDrawingZone(graph);
124 124
125 125 auto axisPos = posToAxisPos(pos, graph->plot());
126 126
127 127 m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()};
128 128 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
129 129 m_DrawingZone->setEditionEnabled(false);
130 130 }
131 131
132 132 void endDrawingZone(VisualizationGraphWidget *graph)
133 133 {
134 134 if (m_DrawingZone) {
135 135 auto drawingZoneRange = m_DrawingZone->range();
136 136 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
137 137 m_DrawingZone->setEditionEnabled(true);
138 138 addSelectionZone(m_DrawingZone);
139 139 }
140 140 else {
141 141 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
142 142 }
143 143
144 144 graph->plot().replot(QCustomPlot::rpQueuedReplot);
145 145 m_DrawingZone = nullptr;
146 146 }
147 147 }
148 148
149 149 void setSelectionZonesEditionEnabled(bool value)
150 150 {
151 151 for (auto s : m_SelectionZones) {
152 152 s->setEditionEnabled(value);
153 153 }
154 154 }
155 155
156 156 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
157 157
158 158 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos,
159 159 const QCustomPlot &plot) const
160 160 {
161 161 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
162 162 auto minDistanceToZone = -1;
163 163 for (auto zone : m_SelectionZones) {
164 164 auto distanceToZone = zone->selectTest(pos, false);
165 165 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
166 166 && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
167 167 selectionZoneItemUnderCursor = zone;
168 168 }
169 169 }
170 170
171 171 return selectionZoneItemUnderCursor;
172 172 }
173 173
174 174 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
175 175 const QCustomPlot &plot) const
176 176 {
177 177 QVector<VisualizationSelectionZoneItem *> zones;
178 178 for (auto zone : m_SelectionZones) {
179 179 auto distanceToZone = zone->selectTest(pos, false);
180 180 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
181 181 zones << zone;
182 182 }
183 183 }
184 184
185 185 return zones;
186 186 }
187 187
188 188 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
189 189 {
190 190 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
191 191 zone->moveToTop();
192 192 m_SelectionZones.removeAll(zone);
193 193 m_SelectionZones.append(zone);
194 194 }
195 195 }
196 196
197 197 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
198 198 {
199 199 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
200 200 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
201 201 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
202 202 }
203 203
204 204 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
205 205 {
206 206 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
207 207 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
208 208 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
209 209 }
210 210 };
211 211
212 212 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
213 213 : VisualizationDragWidget{parent},
214 214 ui{new Ui::VisualizationGraphWidget},
215 215 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
216 216 {
217 217 ui->setupUi(this);
218 218
219 219 // 'Close' options : widget is deleted when closed
220 220 setAttribute(Qt::WA_DeleteOnClose);
221 221
222 222 // Set qcpplot properties :
223 223 // - zoom is enabled
224 224 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
225 225 ui->widget->setInteractions(QCP::iRangeZoom);
226 226 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
227 227
228 228 // The delegate must be initialized after the ui as it uses the plot
229 229 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
230 230
231 231 // Init the cursors
232 232 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
233 233 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
234 234 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
235 235 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
236 236
237 237 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
238 238 connect(ui->widget, &QCustomPlot::mouseRelease, this,
239 239 &VisualizationGraphWidget::onMouseRelease);
240 240 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
241 241 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
242 242 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
243 243 &VisualizationGraphWidget::onMouseDoubleClick);
244 connect(
245 ui->widget->xAxis,
246 static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(&QCPAxis::rangeChanged),
244 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
245 &QCPAxis::rangeChanged),
247 246 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
248 247
249 248 // Activates menu when right clicking on the graph
250 249 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
251 250 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
252 251 &VisualizationGraphWidget::onGraphMenuRequested);
253 252
254 253 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
255 254 &VariableController::onRequestDataLoading);
256 255
257 256 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
258 257 &VisualizationGraphWidget::onUpdateVarDisplaying);
259 258
260 259 #ifdef Q_OS_MAC
261 260 plot().setPlottingHint(QCP::phFastPolylines, true);
262 261 #endif
263 262 }
264 263
265 264
266 265 VisualizationGraphWidget::~VisualizationGraphWidget()
267 266 {
268 267 delete ui;
269 268 }
270 269
271 270 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
272 271 {
273 272 auto parent = parentWidget();
274 273 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
275 274 parent = parent->parentWidget();
276 275 }
277 276
278 277 return qobject_cast<VisualizationZoneWidget *>(parent);
279 278 }
280 279
281 280 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
282 281 {
283 282 auto parent = parentWidget();
284 283 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
285 284 parent = parent->parentWidget();
286 285 }
287 286
288 287 return qobject_cast<VisualizationWidget *>(parent);
289 288 }
290 289
291 290 void VisualizationGraphWidget::enableAcquisition(bool enable)
292 291 {
293 292 impl->m_DoAcquisition = enable;
294 293 }
295 294
296 295 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
297 296 {
298 297 // Uses delegate to create the qcpplot components according to the variable
299 298 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
300 299
301 300 if (auto dataSeries = variable->dataSeries()) {
302 301 // Set axes properties according to the units of the data series
303 302 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
304 303
305 304 // Sets rendering properties for the new plottables
306 305 // Warning: this method must be called after setAxesProperties(), as it can access to some
307 306 // axes properties that have to be initialized
308 307 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
309 308 }
310 309
311 310 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
312 311
313 312 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
314 313
315 314 this->enableAcquisition(false);
316 315 this->setGraphRange(range);
317 316 this->enableAcquisition(true);
318 317
319 318 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
320 319
321 320 emit variableAdded(variable);
322 321 }
323 322
324 323 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
325 324 {
326 325 // Each component associated to the variable :
327 326 // - is removed from qcpplot (which deletes it)
328 327 // - is no longer referenced in the map
329 328 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
330 329 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
331 330 emit variableAboutToBeRemoved(variable);
332 331
333 332 auto &plottablesMap = variableIt->second;
334 333
335 334 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
336 335 plottableIt != plottableEnd;) {
337 336 ui->widget->removePlottable(plottableIt->second);
338 337 plottableIt = plottablesMap.erase(plottableIt);
339 338 }
340 339
341 340 impl->m_VariableToPlotMultiMap.erase(variableIt);
342 341 }
343 342
344 343 // Updates graph
345 344 ui->widget->replot();
346 345 }
347 346
348 347 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
349 348 {
350 349 auto variables = QList<std::shared_ptr<Variable> >{};
351 350 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
352 351 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
353 352 variables << it->first;
354 353 }
355 354
356 355 return variables;
357 356 }
358 357
359 358 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
360 359 {
361 360 if (!variable) {
362 361 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
363 362 return;
364 363 }
365 364
366 365 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
367 366 }
368 367
369 368 SqpRange VisualizationGraphWidget::graphRange() const noexcept
370 369 {
371 370 auto graphRange = ui->widget->xAxis->range();
372 371 return SqpRange{graphRange.lower, graphRange.upper};
373 372 }
374 373
375 374 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
376 375 {
377 376 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
378 377 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
379 378 ui->widget->replot();
380 379 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
381 380 }
382 381
383 382 QVector<SqpRange> VisualizationGraphWidget::selectionZoneRanges() const
384 383 {
385 384 QVector<SqpRange> ranges;
386 385 for (auto zone : impl->m_SelectionZones) {
387 386 ranges << zone->range();
388 387 }
389 388
390 389 return ranges;
391 390 }
392 391
393 392 void VisualizationGraphWidget::addSelectionZones(const QVector<SqpRange> &ranges)
394 393 {
395 394 for (const auto &range : ranges) {
396 395 // note: ownership is transfered to QCustomPlot
397 396 auto zone = new VisualizationSelectionZoneItem(&plot());
398 397 zone->setRange(range.m_TStart, range.m_TEnd);
399 398 impl->addSelectionZone(zone);
400 399 }
401 400
402 401 plot().replot(QCustomPlot::rpQueuedReplot);
403 402 }
404 403
405 404 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
406 405 {
407 406 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
408 407
409 408 if (impl->m_HoveredZone == selectionZone) {
410 409 impl->m_HoveredZone = nullptr;
411 410 setCursor(Qt::ArrowCursor);
412 411 }
413 412
414 413 impl->m_SelectionZones.removeAll(selectionZone);
415 414 plot().removeItem(selectionZone);
416 415 plot().replot(QCustomPlot::rpQueuedReplot);
417 416 }
418 417
419 418 void VisualizationGraphWidget::undoZoom()
420 419 {
421 420 auto zoom = impl->m_ZoomStack.pop();
422 421 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
423 422 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
424 423
425 424 axisX->setRange(zoom.first);
426 425 axisY->setRange(zoom.second);
427 426
428 427 plot().replot(QCustomPlot::rpQueuedReplot);
429 428 }
430 429
431 430 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
432 431 {
433 432 if (visitor) {
434 433 visitor->visit(this);
435 434 }
436 435 else {
437 436 qCCritical(LOG_VisualizationGraphWidget())
438 437 << tr("Can't visit widget : the visitor is null");
439 438 }
440 439 }
441 440
442 441 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
443 442 {
444 443 auto isSpectrogram = [](const auto &variable) {
445 444 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
446 445 };
447 446
448 447 // - A spectrogram series can't be dropped on graph with existing plottables
449 448 // - No data series can be dropped on graph with existing spectrogram series
450 449 return isSpectrogram(variable)
451 450 ? impl->m_VariableToPlotMultiMap.empty()
452 451 : std::none_of(
453 452 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
454 453 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
455 454 }
456 455
457 456 bool VisualizationGraphWidget::contains(const Variable &variable) const
458 457 {
459 458 // Finds the variable among the keys of the map
460 459 auto variablePtr = &variable;
461 460 auto findVariable
462 461 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
463 462
464 463 auto end = impl->m_VariableToPlotMultiMap.cend();
465 464 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
466 465 return it != end;
467 466 }
468 467
469 468 QString VisualizationGraphWidget::name() const
470 469 {
471 470 return impl->m_Name;
472 471 }
473 472
474 473 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
475 474 {
476 475 auto mimeData = new QMimeData;
477 476
478 477 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot());
479 478 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
480 479 && selectionZoneItemUnderCursor) {
481 480 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
482 481 selectionZoneItemUnderCursor->range()));
483 482 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
484 483 selectionZoneItemUnderCursor->range()));
485 484 }
486 485 else {
487 486 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
488 487
489 488 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
490 489 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
491 490 }
492 491
493 492 return mimeData;
494 493 }
495 494
496 495 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
497 496 {
498 497 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot());
499 498 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
500 499 && selectionZoneItemUnderCursor) {
501 500
502 501 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
503 502 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
504 503
505 504 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
506 505 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
507 506 .toSize();
508 507
509 508 auto pixmap = QPixmap(zoneSize);
510 509 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
511 510
512 511 return pixmap;
513 512 }
514 513
515 514 return QPixmap();
516 515 }
517 516
518 517 bool VisualizationGraphWidget::isDragAllowed() const
519 518 {
520 519 return true;
521 520 }
522 521
523 522 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
524 523 {
525 524 if (highlighted) {
526 525 plot().setBackground(QBrush(QColor("#BBD5EE")));
527 526 }
528 527 else {
529 528 plot().setBackground(QBrush(Qt::white));
530 529 }
531 530
532 531 plot().update();
533 532 }
534 533
535 534 void VisualizationGraphWidget::addVerticalCursor(double time)
536 535 {
537 536 impl->m_VerticalCursor->setPosition(time);
538 537 impl->m_VerticalCursor->setVisible(true);
539 538
540 539 auto text
541 540 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
542 541 impl->m_VerticalCursor->setLabelText(text);
543 542 }
544 543
545 544 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
546 545 {
547 546 impl->m_VerticalCursor->setAbsolutePosition(position);
548 547 impl->m_VerticalCursor->setVisible(true);
549 548
550 549 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
551 550 auto text
552 551 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
553 552 impl->m_VerticalCursor->setLabelText(text);
554 553 }
555 554
556 555 void VisualizationGraphWidget::removeVerticalCursor()
557 556 {
558 557 impl->m_VerticalCursor->setVisible(false);
559 558 plot().replot(QCustomPlot::rpQueuedReplot);
560 559 }
561 560
562 561 void VisualizationGraphWidget::addHorizontalCursor(double value)
563 562 {
564 563 impl->m_HorizontalCursor->setPosition(value);
565 564 impl->m_HorizontalCursor->setVisible(true);
566 565 impl->m_HorizontalCursor->setLabelText(QString::number(value));
567 566 }
568 567
569 568 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
570 569 {
571 570 impl->m_HorizontalCursor->setAbsolutePosition(position);
572 571 impl->m_HorizontalCursor->setVisible(true);
573 572
574 573 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
575 574 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
576 575 }
577 576
578 577 void VisualizationGraphWidget::removeHorizontalCursor()
579 578 {
580 579 impl->m_HorizontalCursor->setVisible(false);
581 580 plot().replot(QCustomPlot::rpQueuedReplot);
582 581 }
583 582
584 583 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
585 584 {
586 585 Q_UNUSED(event);
587 586
588 587 // Prevents that all variables will be removed from graph when it will be closed
589 588 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
590 589 emit variableAboutToBeRemoved(variableEntry.first);
591 590 }
592 591 }
593 592
594 593 void VisualizationGraphWidget::enterEvent(QEvent *event)
595 594 {
596 595 Q_UNUSED(event);
597 596 impl->m_RenderingDelegate->showGraphOverlay(true);
598 597 }
599 598
600 599 void VisualizationGraphWidget::leaveEvent(QEvent *event)
601 600 {
602 601 Q_UNUSED(event);
603 602 impl->m_RenderingDelegate->showGraphOverlay(false);
604 603
605 604 if (auto parentZone = parentZoneWidget()) {
606 605 parentZone->notifyMouseLeaveGraph(this);
607 606 }
608 607 else {
609 608 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
610 609 }
611 610
612 611 if (impl->m_HoveredZone) {
613 612 impl->m_HoveredZone->setHovered(false);
614 613 impl->m_HoveredZone = nullptr;
615 614 }
616 615 }
617 616
618 617 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
619 618 {
620 619 return *ui->widget;
621 620 }
622 621
623 622 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
624 623 {
625 624 QMenu graphMenu{};
626 625
627 626 // Iterates on variables (unique keys)
628 627 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
629 628 end = impl->m_VariableToPlotMultiMap.cend();
630 629 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
631 630 // 'Remove variable' action
632 631 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
633 632 [ this, var = it->first ]() { removeVariable(var); });
634 633 }
635 634
636 635 if (!impl->m_ZoomStack.isEmpty()) {
637 636 if (!graphMenu.isEmpty()) {
638 637 graphMenu.addSeparator();
639 638 }
640 639
641 640 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
642 641 }
643 642
644 643 // Selection Zone Actions
645 644 auto selectionZoneItem = impl->selectionZoneAt(pos, plot());
646 645 if (selectionZoneItem) {
647 646 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
648 647 selectedItems.removeAll(selectionZoneItem);
649 648 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
650 649
651 650 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
652 651 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
653 652 graphMenu.addSeparator();
654 653 }
655 654
656 655 QHash<QString, QMenu *> subMenus;
657 656 QHash<QString, bool> subMenusEnabled;
658 657
659 658 for (auto zoneAction : zoneActions) {
660 659
661 660 auto isEnabled = zoneAction->isEnabled(selectedItems);
662 661
663 662 auto menu = &graphMenu;
664 663 for (auto subMenuName : zoneAction->subMenuList()) {
665 664 if (!subMenus.contains(subMenuName)) {
666 665 menu = menu->addMenu(subMenuName);
667 666 subMenus[subMenuName] = menu;
668 667 subMenusEnabled[subMenuName] = isEnabled;
669 668 }
670 669 else {
671 670 menu = subMenus.value(subMenuName);
672 671 if (isEnabled) {
673 672 // The sub menu is enabled if at least one of its actions is enabled
674 673 subMenusEnabled[subMenuName] = true;
675 674 }
676 675 }
677 676 }
678 677
679 678 auto action = menu->addAction(zoneAction->name());
680 679 action->setEnabled(isEnabled);
681 680 action->setShortcut(zoneAction->displayedShortcut());
682 681 QObject::connect(action, &QAction::triggered,
683 682 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
684 683 }
685 684
686 685 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
687 686 it.value()->setEnabled(subMenusEnabled[it.key()]);
688 687 }
689 688 }
690 689
691 690 if (!graphMenu.isEmpty()) {
692 691 graphMenu.exec(QCursor::pos());
693 692 }
694 693 }
695 694
696 695 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
697 696 {
698 qCDebug(LOG_VisualizationGraphWidget())
699 << tr("TORM: VisualizationGraphWidget::onRangeChanged")
700 << QThread::currentThread()->objectName() << "DoAcqui" << impl->m_DoAcquisition;
697 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
698 << QThread::currentThread()->objectName() << "DoAcqui"
699 << impl->m_DoAcquisition;
701 700
702 701 auto graphRange = SqpRange{t1.lower, t1.upper};
703 702 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
704 703
705 704 if (impl->m_DoAcquisition) {
706 705 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
707 706
708 707 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
709 708 end = impl->m_VariableToPlotMultiMap.end();
710 709 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
711 710 variableUnderGraphVector.push_back(it->first);
712 711 }
713 712 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
714 713 !impl->m_IsCalibration);
715 714
716 715 if (!impl->m_IsCalibration) {
717 716 qCDebug(LOG_VisualizationGraphWidget())
718 717 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
719 718 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
720 719 emit synchronize(graphRange, oldGraphRange);
721 720 }
722 721 }
723 722
724 723 auto pos = mapFromGlobal(QCursor::pos());
725 724 auto axisPos = impl->posToAxisPos(pos, plot());
726 725 if (auto parentZone = parentZoneWidget()) {
727 726 if (impl->pointIsInAxisRect(axisPos, plot())) {
728 727 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
729 728 }
730 729 else {
731 730 parentZone->notifyMouseLeaveGraph(this);
732 731 }
733 732 }
734 733 else {
735 734 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
736 735 }
737 736 }
738 737
739 738 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
740 739 {
741 740 impl->m_RenderingDelegate->onMouseDoubleClick(event);
742 741 }
743 742
744 743 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
745 744 {
746 745 // Handles plot rendering when mouse is moving
747 746 impl->m_RenderingDelegate->onMouseMove(event);
748 747
749 748 auto axisPos = impl->posToAxisPos(event->pos(), plot());
750 749
751 750 // Zoom box and zone drawing
752 751 if (impl->m_DrawingZoomRect) {
753 752 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
754 753 }
755 754 else if (impl->m_DrawingZone) {
756 755 impl->m_DrawingZone->setEnd(axisPos.x());
757 756 }
758 757
759 758 // Cursor
760 759 if (auto parentZone = parentZoneWidget()) {
761 760 if (impl->pointIsInAxisRect(axisPos, plot())) {
762 761 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
763 762 }
764 763 else {
765 764 parentZone->notifyMouseLeaveGraph(this);
766 765 }
767 766 }
768 767 else {
769 768 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
770 769 }
771 770
772 771 // Search for the selection zone under the mouse
773 772 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
774 773 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
775 774 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
776 775
777 776 // Sets the appropriate cursor shape
778 777 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
779 778 setCursor(cursorShape);
780 779
781 780 // Manages the hovered zone
782 781 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
783 782 if (impl->m_HoveredZone) {
784 783 impl->m_HoveredZone->setHovered(false);
785 784 }
786 785 selectionZoneItemUnderCursor->setHovered(true);
787 786 impl->m_HoveredZone = selectionZoneItemUnderCursor;
788 787 plot().replot(QCustomPlot::rpQueuedReplot);
789 788 }
790 789 }
791 790 else {
792 791 // There is no zone under the mouse or the interaction mode is not "selection zones"
793 792 if (impl->m_HoveredZone) {
794 793 impl->m_HoveredZone->setHovered(false);
795 794 impl->m_HoveredZone = nullptr;
796 795 }
797 796
798 797 setCursor(Qt::ArrowCursor);
799 798 }
800 799
801 800 impl->m_HasMovedMouse = true;
802 801 VisualizationDragWidget::mouseMoveEvent(event);
803 802 }
804 803
805 804 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
806 805 {
807 806 auto value = event->angleDelta().x() + event->angleDelta().y();
808 807 if (value != 0) {
809 808
810 809 auto direction = value > 0 ? 1.0 : -1.0;
811 810 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
812 811 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
813 812 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
814 813
815 814 auto zoomOrientations = QFlags<Qt::Orientation>{};
816 815 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
817 816 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
818 817
819 818 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
820 819
821 820 if (!isZoomX && !isZoomY) {
822 821 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
823 822 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
824 823
825 824 axis->setRange(axis->range() + diff);
826 825
827 826 if (plot().noAntialiasingOnDrag()) {
828 827 plot().setNotAntialiasedElements(QCP::aeAll);
829 828 }
830 829
831 830 plot().replot(QCustomPlot::rpQueuedReplot);
832 831 }
833 832 }
834 833 }
835 834
836 835 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
837 836 {
838 837 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
839 838 auto isSelectionZoneMode
840 839 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
841 840 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
842 841
843 842 if (!isDragDropClick && isLeftClick) {
844 843 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
845 844 // Starts a zoom box
846 845 impl->startDrawingRect(event->pos(), plot());
847 846 }
848 847 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
849 848 // Starts a new selection zone
850 849 auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot());
851 850 if (!zoneAtPos) {
852 851 impl->startDrawingZone(event->pos(), this);
853 852 }
854 853 }
855 854 }
856 855
857 856 // Allows mouse panning only in default mode
858 857 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
859 858 == SqpApplication::PlotsInteractionMode::None
860 859 && !isDragDropClick);
861 860
862 861 // Allows zone edition only in selection zone mode without drag&drop
863 862 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
864 863
865 864 // Selection / Deselection
866 865 if (isSelectionZoneMode) {
867 866 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
868 867 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
869 868
870 869
871 870 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
872 871 && !isMultiSelectionClick) {
873 872 parentVisualizationWidget()->selectionZoneManager().select(
874 873 {selectionZoneItemUnderCursor});
875 874 }
876 875 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
877 876 parentVisualizationWidget()->selectionZoneManager().clearSelection();
878 877 }
879 878 else {
880 879 // No selection change
881 880 }
882 881
883 882 if (selectionZoneItemUnderCursor && isLeftClick) {
884 883 selectionZoneItemUnderCursor->setAssociatedEditedZones(
885 884 parentVisualizationWidget()->selectionZoneManager().selectedItems());
886 885 }
887 886 }
888 887
889 888
890 889 impl->m_HasMovedMouse = false;
891 890 VisualizationDragWidget::mousePressEvent(event);
892 891 }
893 892
894 893 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
895 894 {
896 895 if (impl->m_DrawingZoomRect) {
897 896
898 897 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
899 898 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
900 899
901 900 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
902 901 impl->m_DrawingZoomRect->bottomRight->coords().x()};
903 902
904 903 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
905 904 impl->m_DrawingZoomRect->bottomRight->coords().y()};
906 905
907 906 impl->removeDrawingRect(plot());
908 907
909 908 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
910 909 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
911 910 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
912 911 axisX->setRange(newAxisXRange);
913 912 axisY->setRange(newAxisYRange);
914 913
915 914 plot().replot(QCustomPlot::rpQueuedReplot);
916 915 }
917 916 }
918 917
919 918 impl->endDrawingZone(this);
920 919
921 920 impl->m_IsCalibration = false;
922 921
923 922 // Selection / Deselection
924 923 auto isSelectionZoneMode
925 924 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
926 925 if (isSelectionZoneMode) {
927 926 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
928 927 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
929 928 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
930 929 && !impl->m_HasMovedMouse) {
931 930
932 931 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
933 932 if (zonesUnderCursor.count() > 1) {
934 933 // There are multiple zones under the mouse.
935 934 // Performs the selection with a selection dialog.
936 935 VisualizationMultiZoneSelectionDialog dialog{this};
937 936 dialog.setZones(zonesUnderCursor);
938 937 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
939 938 dialog.activateWindow();
940 939 dialog.raise();
941 940 if (dialog.exec() == QDialog::Accepted) {
942 941 auto selection = dialog.selectedZones();
943 942
944 943 if (!isMultiSelectionClick) {
945 944 parentVisualizationWidget()->selectionZoneManager().clearSelection();
946 945 }
947 946
948 947 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
949 948 auto zone = it.key();
950 949 auto isSelected = it.value();
951 950 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
952 951 isSelected);
953 952
954 953 if (isSelected) {
955 954 // Puts the zone on top of the stack so it can be moved or resized
956 955 impl->moveSelectionZoneOnTop(zone, plot());
957 956 }
958 957 }
959 958 }
960 959 }
961 960 else {
962 961 if (!isMultiSelectionClick) {
963 962 parentVisualizationWidget()->selectionZoneManager().select(
964 963 {selectionZoneItemUnderCursor});
965 964 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
966 965 }
967 966 else {
968 967 parentVisualizationWidget()->selectionZoneManager().setSelected(
969 968 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
970 969 || event->button() == Qt::RightButton);
971 970 }
972 971 }
973 972 }
974 973 else {
975 974 // No selection change
976 975 }
977 976 }
978 977 }
979 978
980 979 void VisualizationGraphWidget::onDataCacheVariableUpdated()
981 980 {
982 981 auto graphRange = ui->widget->xAxis->range();
983 982 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
984 983
985 984 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
986 985 auto variable = variableEntry.first;
987 986 qCDebug(LOG_VisualizationGraphWidget())
988 987 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
989 988 qCDebug(LOG_VisualizationGraphWidget())
990 989 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
991 990 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
992 991 impl->updateData(variableEntry.second, variable->dataSeries(), variable->range());
993 992 }
994 993 }
995 994 }
996 995
997 996 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
998 997 const SqpRange &range)
999 998 {
1000 999 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1001 1000 if (it != impl->m_VariableToPlotMultiMap.end()) {
1002 1001 impl->updateData(it->second, variable->dataSeries(), range);
1003 1002 }
1004 1003 }
@@ -1,438 +1,440
1 1 #include "Visualization/VisualizationSelectionZoneItem.h"
2 2 #include "Visualization/VisualizationGraphWidget.h"
3 3 #include "Visualization/VisualizationSelectionZoneManager.h"
4 4 #include "Visualization/VisualizationWidget.h"
5 5
6 6 const QString &DEFAULT_COLOR = QStringLiteral("#E79D41");
7 7
8 8 struct VisualizationSelectionZoneItem::VisualizationSelectionZoneItemPrivate {
9 9
10 10 QCustomPlot *m_Plot;
11 11 double m_T1 = 0;
12 12 double m_T2 = 0;
13 13 QColor m_Color;
14 14
15 15 bool m_IsEditionEnabled = true;
16 16 double m_MovedOrinalT1 = 0;
17 17 double m_MovedOrinalT2 = 0;
18 18
19 19 QCPItemStraightLine *m_LeftLine;
20 20 QCPItemStraightLine *m_RightLine;
21 21 QCPItemText *m_NameLabelItem = nullptr;
22 22
23 23 enum class EditionMode { NoEdition, ResizeLeft, ResizeRight, Move };
24 24 EditionMode m_CurrentEditionMode;
25 25
26 26 QVector<VisualizationSelectionZoneItem *> m_AssociatedEditedZones;
27 27
28 28 VisualizationSelectionZoneItemPrivate(QCustomPlot *plot)
29 29 : m_Plot(plot), m_Color(Qt::blue), m_CurrentEditionMode(EditionMode::NoEdition)
30 30 {
31 31 }
32 32
33 33 void updatePosition(VisualizationSelectionZoneItem *item)
34 34 {
35 35 item->topLeft->setCoords(m_T1, 0);
36 36 item->bottomRight->setCoords(m_T2, 1);
37 37 }
38 38
39 39 EditionMode getEditionMode(const QPoint &pos, const VisualizationSelectionZoneItem *zoneItem)
40 40 {
41 41 auto distanceLeft = m_LeftLine->selectTest(pos, false);
42 42 auto distanceRight = m_RightLine->selectTest(pos, false);
43 43 auto distance = zoneItem->selectTest(pos, false);
44 44
45 45 if (distanceRight <= distance) {
46 46 return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight;
47 47 }
48 48 else if (distanceLeft <= distance) {
49 49 return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft;
50 50 }
51 51
52 52 return VisualizationSelectionZoneItemPrivate::EditionMode::Move;
53 53 }
54 54
55 55 double pixelSizeToAxisXSize(double pixels)
56 56 {
57 57 auto axis = m_Plot->axisRect()->axis(QCPAxis::atBottom);
58 58 return axis->pixelToCoord(pixels) - axis->pixelToCoord(0);
59 59 }
60 60
61 61 bool alignZones(VisualizationSelectionZoneItem *referenceZone,
62 62 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool alignOnLeft,
63 63 bool allowResize, bool vertically)
64 64 {
65 65 auto result = false;
66 66
67 67 auto referenceTime
68 68 = alignOnLeft ? referenceZone->range().m_TStart : referenceZone->range().m_TEnd;
69 69
70 70 auto referenceBottomAxis = m_Plot->axisRect()->axis(QCPAxis::atBottom);
71 71 auto referenceVerticalPosition = referenceBottomAxis->coordToPixel(referenceTime);
72 72
73 73 for (auto otherZone : zonesToAlign) {
74 74
75 75 auto otherZoneRange = otherZone->range();
76 76 auto newZoneStart = otherZoneRange.m_TStart;
77 77 auto newZoneEnd = otherZoneRange.m_TEnd;
78 78
79 79 auto alignedTime = referenceTime;
80 80 if (vertically) {
81 81 auto otherZoneAxis = otherZone->parentPlot()->axisRect()->axis(QCPAxis::atBottom);
82 82 alignedTime = otherZoneAxis->pixelToCoord(referenceVerticalPosition);
83 83 }
84 84
85 85 if (alignOnLeft) {
86 86 newZoneStart = alignedTime;
87 87 if (!allowResize) {
88 88 newZoneEnd = alignedTime + (otherZoneRange.m_TEnd - otherZoneRange.m_TStart);
89 89 }
90 90 }
91 91 else { // align on right
92 92 newZoneEnd = alignedTime;
93 93 if (!allowResize) {
94 94 newZoneStart = alignedTime - (otherZoneRange.m_TEnd - otherZoneRange.m_TStart);
95 95 }
96 96 }
97 97
98 98 if (newZoneStart < newZoneEnd) {
99 99 result = true;
100 100 otherZone->setRange(newZoneStart, newZoneEnd);
101 101 otherZone->parentPlot()->replot();
102 102 }
103 103 }
104 104
105 105 return result;
106 106 }
107 107 };
108 108
109 109 VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot)
110 110 : QCPItemRect(plot),
111 111 impl{spimpl::make_unique_impl<VisualizationSelectionZoneItemPrivate>(plot)}
112 112 {
113 113 topLeft->setTypeX(QCPItemPosition::ptPlotCoords);
114 114 topLeft->setTypeY(QCPItemPosition::ptAxisRectRatio);
115 115 bottomRight->setTypeX(QCPItemPosition::ptPlotCoords);
116 116 bottomRight->setTypeY(QCPItemPosition::ptAxisRectRatio);
117 117 setSelectable(false);
118 118
119 119 impl->m_RightLine = new QCPItemStraightLine(plot);
120 120 impl->m_RightLine->point1->setParentAnchor(topRight);
121 121 impl->m_RightLine->point2->setParentAnchor(bottomRight);
122 122 impl->m_RightLine->point1->setTypeX(QCPItemPosition::ptAbsolute);
123 123 impl->m_RightLine->point1->setTypeY(QCPItemPosition::ptAbsolute);
124 124 impl->m_RightLine->point2->setTypeX(QCPItemPosition::ptAbsolute);
125 125 impl->m_RightLine->point2->setTypeY(QCPItemPosition::ptAbsolute);
126 126 impl->m_RightLine->setSelectable(false);
127 127
128 128 impl->m_LeftLine = new QCPItemStraightLine(plot);
129 129 impl->m_LeftLine->point1->setParentAnchor(topLeft);
130 130 impl->m_LeftLine->point2->setParentAnchor(bottomLeft);
131 131 impl->m_LeftLine->point1->setTypeX(QCPItemPosition::ptAbsolute);
132 132 impl->m_LeftLine->point1->setTypeY(QCPItemPosition::ptAbsolute);
133 133 impl->m_LeftLine->point2->setTypeX(QCPItemPosition::ptAbsolute);
134 134 impl->m_LeftLine->point2->setTypeY(QCPItemPosition::ptAbsolute);
135 135 impl->m_LeftLine->setSelectable(false);
136 136
137 137 connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_RightLine,
138 138 &QCPItemStraightLine::setSelected);
139 139 connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_LeftLine,
140 140 &QCPItemStraightLine::setSelected);
141 141
142 142 setColor(QColor(DEFAULT_COLOR));
143 143 }
144 144
145 VisualizationSelectionZoneItem::~VisualizationSelectionZoneItem() {}
145 VisualizationSelectionZoneItem::~VisualizationSelectionZoneItem()
146 {
147 }
146 148
147 149 VisualizationGraphWidget *VisualizationSelectionZoneItem::parentGraphWidget() const noexcept
148 150 {
149 151 auto parent = impl->m_Plot->parentWidget();
150 152 while (parent != nullptr && !qobject_cast<VisualizationGraphWidget *>(parent)) {
151 153 parent = parent->parentWidget();
152 154 }
153 155
154 156 return qobject_cast<VisualizationGraphWidget *>(parent);
155 157 }
156 158
157 159 void VisualizationSelectionZoneItem::setName(const QString &name)
158 160 {
159 161 if (name.isEmpty() && impl->m_NameLabelItem) {
160 162 impl->m_Plot->removeItem(impl->m_NameLabelItem);
161 163 impl->m_NameLabelItem = nullptr;
162 164 }
163 165 else if (!impl->m_NameLabelItem) {
164 166 impl->m_NameLabelItem = new QCPItemText(impl->m_Plot);
165 167 impl->m_NameLabelItem->setText(name);
166 168 impl->m_NameLabelItem->setPositionAlignment(Qt::AlignHCenter | Qt::AlignTop);
167 169 impl->m_NameLabelItem->setColor(impl->m_Color);
168 170 impl->m_NameLabelItem->position->setParentAnchor(top);
169 171 }
170 172 }
171 173
172 174 QString VisualizationSelectionZoneItem::name() const
173 175 {
174 176 if (!impl->m_NameLabelItem) {
175 177 return QString();
176 178 }
177 179
178 180 return impl->m_NameLabelItem->text();
179 181 }
180 182
181 183 SqpRange VisualizationSelectionZoneItem::range() const
182 184 {
183 185 SqpRange range;
184 186 range.m_TStart = impl->m_T1 <= impl->m_T2 ? impl->m_T1 : impl->m_T2;
185 187 range.m_TEnd = impl->m_T1 > impl->m_T2 ? impl->m_T1 : impl->m_T2;
186 188 return range;
187 189 }
188 190
189 191 void VisualizationSelectionZoneItem::setRange(double tstart, double tend)
190 192 {
191 193 impl->m_T1 = tstart;
192 194 impl->m_T2 = tend;
193 195 impl->updatePosition(this);
194 196 }
195 197
196 198 void VisualizationSelectionZoneItem::setStart(double tstart)
197 199 {
198 200 impl->m_T1 = tstart;
199 201 impl->updatePosition(this);
200 202 }
201 203
202 204 void VisualizationSelectionZoneItem::setEnd(double tend)
203 205 {
204 206 impl->m_T2 = tend;
205 207 impl->updatePosition(this);
206 208 }
207 209
208 210 void VisualizationSelectionZoneItem::setColor(const QColor &color)
209 211 {
210 212 impl->m_Color = color;
211 213
212 214 auto brushColor = color;
213 215 brushColor.setAlpha(80);
214 216 setBrush(QBrush(brushColor));
215 217 setPen(QPen(Qt::NoPen));
216 218
217 219 auto selectedBrushColor = brushColor;
218 220 selectedBrushColor.setAlpha(150);
219 221 setSelectedBrush(QBrush(selectedBrushColor));
220 222 setSelectedPen(QPen(Qt::NoPen));
221 223
222 224 auto linePen = QPen(color);
223 225 linePen.setStyle(Qt::SolidLine);
224 226 linePen.setWidth(4);
225 227
226 228 auto selectedLinePen = linePen;
227 229 selectedLinePen.setColor(color.darker(120));
228 230 selectedLinePen.setWidth(4);
229 231
230 232 impl->m_LeftLine->setPen(linePen);
231 233 impl->m_RightLine->setPen(linePen);
232 234
233 235 impl->m_LeftLine->setSelectedPen(selectedLinePen);
234 236 impl->m_RightLine->setSelectedPen(selectedLinePen);
235 237 }
236 238
237 239 void VisualizationSelectionZoneItem::setEditionEnabled(bool value)
238 240 {
239 241 impl->m_IsEditionEnabled = value;
240 242 setSelectable(value);
241 243 if (!value) {
242 244 setSelected(false);
243 245 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
244 246 }
245 247 }
246 248
247 249 bool VisualizationSelectionZoneItem::isEditionEnabled() const
248 250 {
249 251 return impl->m_IsEditionEnabled;
250 252 }
251 253
252 254 void VisualizationSelectionZoneItem::moveToTop()
253 255 {
254 256 moveToLayer(layer(), false);
255 257 }
256 258
257 259 Qt::CursorShape
258 260 VisualizationSelectionZoneItem::curshorShapeForPosition(const QPoint &position) const
259 261 {
260 262 auto mode = impl->m_CurrentEditionMode
261 263 == VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition
262 264 ? impl->getEditionMode(position, this)
263 265 : impl->m_CurrentEditionMode;
264 266 switch (mode) {
265 267 case VisualizationSelectionZoneItemPrivate::EditionMode::Move:
266 268 return Qt::SizeAllCursor;
267 269 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft:
268 270 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight: // fallthrough
269 271 return Qt::SizeHorCursor;
270 272 default:
271 273 return Qt::ArrowCursor;
272 274 }
273 275 }
274 276
275 277 void VisualizationSelectionZoneItem::setHovered(bool value)
276 278 {
277 279 if (value) {
278 280 auto linePen = impl->m_LeftLine->pen();
279 281 linePen.setStyle(Qt::DotLine);
280 282 linePen.setWidth(3);
281 283
282 284 auto selectedLinePen = impl->m_LeftLine->selectedPen();
283 285 ;
284 286 selectedLinePen.setStyle(Qt::DotLine);
285 287 selectedLinePen.setWidth(3);
286 288
287 289 impl->m_LeftLine->setPen(linePen);
288 290 impl->m_RightLine->setPen(linePen);
289 291
290 292 impl->m_LeftLine->setSelectedPen(selectedLinePen);
291 293 impl->m_RightLine->setSelectedPen(selectedLinePen);
292 294 }
293 295 else {
294 296 setColor(impl->m_Color);
295 297 }
296 298 }
297 299
298 300 void VisualizationSelectionZoneItem::setAssociatedEditedZones(
299 301 const QVector<VisualizationSelectionZoneItem *> &associatedZones)
300 302 {
301 303 impl->m_AssociatedEditedZones = associatedZones;
302 304 impl->m_AssociatedEditedZones.removeAll(this);
303 305 }
304 306
305 307 bool VisualizationSelectionZoneItem::alignZonesVerticallyOnLeft(
306 308 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
307 309 {
308 310 return impl->alignZones(this, zonesToAlign, true, allowResize, true);
309 311 }
310 312
311 313 bool VisualizationSelectionZoneItem::alignZonesVerticallyOnRight(
312 314 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
313 315 {
314 316 return impl->alignZones(this, zonesToAlign, false, allowResize, true);
315 317 }
316 318
317 319 bool VisualizationSelectionZoneItem::alignZonesTemporallyOnLeft(
318 320 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
319 321 {
320 322 return impl->alignZones(this, zonesToAlign, true, allowResize, false);
321 323 }
322 324
323 325 bool VisualizationSelectionZoneItem::alignZonesTemporallyOnRight(
324 326 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
325 327 {
326 328 return impl->alignZones(this, zonesToAlign, false, allowResize, false);
327 329 }
328 330
329 331 void VisualizationSelectionZoneItem::mousePressEvent(QMouseEvent *event, const QVariant &details)
330 332 {
331 333 Q_UNUSED(details);
332 334
333 335 if (isEditionEnabled() && event->button() == Qt::LeftButton) {
334 336 impl->m_CurrentEditionMode = impl->getEditionMode(event->pos(), this);
335 337
336 338 impl->m_MovedOrinalT1 = impl->m_T1;
337 339 impl->m_MovedOrinalT2 = impl->m_T2;
338 340 for (auto associatedZone : impl->m_AssociatedEditedZones) {
339 341 associatedZone->impl->m_MovedOrinalT1 = associatedZone->impl->m_T1;
340 342 associatedZone->impl->m_MovedOrinalT2 = associatedZone->impl->m_T2;
341 343 }
342 344 }
343 345 else {
344 346 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
345 347 event->ignore();
346 348 }
347 349 }
348 350
349 351 void VisualizationSelectionZoneItem::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
350 352 {
351 353 if (isEditionEnabled()) {
352 354 if (!selected()) {
353 355 // Force the item to be selected during the edition
354 356 parentGraphWidget()->parentVisualizationWidget()->selectionZoneManager().setSelected(
355 357 this, true);
356 358 }
357 359
358 360 auto axis = impl->m_Plot->axisRect()->axis(QCPAxis::atBottom);
359 361 auto pixelDiff = event->pos().x() - startPos.x();
360 362 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
361 363
362 364 switch (impl->m_CurrentEditionMode) {
363 365 case VisualizationSelectionZoneItemPrivate::EditionMode::Move:
364 366 setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff);
365 367 for (auto associatedZone : impl->m_AssociatedEditedZones) {
366 368 associatedZone->move(pixelDiff);
367 369 }
368 370 break;
369 371 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft:
370 372 setStart(impl->m_MovedOrinalT1 + diff);
371 373 for (auto associatedZone : impl->m_AssociatedEditedZones) {
372 374 impl->m_MovedOrinalT1 < impl->m_MovedOrinalT2
373 375 ? associatedZone->resizeLeft(pixelDiff)
374 376 : associatedZone->resizeRight(pixelDiff);
375 377 }
376 378 break;
377 379 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight:
378 380 setEnd(impl->m_MovedOrinalT2 + diff);
379 381 for (auto associatedZone : impl->m_AssociatedEditedZones) {
380 382 impl->m_MovedOrinalT1 < impl->m_MovedOrinalT2
381 383 ? associatedZone->resizeRight(pixelDiff)
382 384 : associatedZone->resizeLeft(pixelDiff);
383 385 }
384 386 break;
385 387 default:
386 388 break;
387 389 }
388 390
389 391 for (auto associatedZone : impl->m_AssociatedEditedZones) {
390 392 associatedZone->parentPlot()->replot();
391 393 }
392 394 }
393 395 else {
394 396 event->ignore();
395 397 }
396 398 }
397 399
398 400 void VisualizationSelectionZoneItem::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
399 401 {
400 402 Q_UNUSED(startPos);
401 403
402 404 if (isEditionEnabled()) {
403 405 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
404 406 }
405 407 else {
406 408 event->ignore();
407 409 }
408 410
409 411 impl->m_AssociatedEditedZones.clear();
410 412 }
411 413
412 414 void VisualizationSelectionZoneItem::resizeLeft(double pixelDiff)
413 415 {
414 416 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
415 417 if (impl->m_MovedOrinalT1 <= impl->m_MovedOrinalT2) {
416 418 setStart(impl->m_MovedOrinalT1 + diff);
417 419 }
418 420 else {
419 421 setEnd(impl->m_MovedOrinalT2 + diff);
420 422 }
421 423 }
422 424
423 425 void VisualizationSelectionZoneItem::resizeRight(double pixelDiff)
424 426 {
425 427 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
426 428 if (impl->m_MovedOrinalT1 > impl->m_MovedOrinalT2) {
427 429 setStart(impl->m_MovedOrinalT1 + diff);
428 430 }
429 431 else {
430 432 setEnd(impl->m_MovedOrinalT2 + diff);
431 433 }
432 434 }
433 435
434 436 void VisualizationSelectionZoneItem::move(double pixelDiff)
435 437 {
436 438 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
437 439 setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff);
438 440 }
@@ -1,579 +1,579
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/SpectrogramSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 7 #include <QObject>
8 8 #include <QtTest>
9 9
10 10 namespace {
11 11
12 12 /// Path for the tests
13 13 const auto TESTS_RESOURCES_PATH
14 14 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
15 15
16 16 QDateTime dateTime(int year, int month, int day, int hours, int minutes, int seconds)
17 17 {
18 18 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
19 19 }
20 20
21 21 QString inputFilePath(const QString &inputFileName)
22 22 {
23 23 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
24 24 }
25 25
26 26 template <typename T>
27 27 struct ExpectedResults {
28 28
29 29 ExpectedResults &setParsingOK(bool parsingOK)
30 30 {
31 31 m_ParsingOK = parsingOK;
32 32 return *this;
33 33 }
34 34
35 35 ExpectedResults &setXAxisUnit(Unit xAxisUnit)
36 36 {
37 37 m_XAxisUnit = std::move(xAxisUnit);
38 38 return *this;
39 39 }
40 40
41 41 ExpectedResults &setXAxisData(const QVector<QDateTime> &xAxisData)
42 42 {
43 43 m_XAxisData.clear();
44 44
45 45 // Converts QVector<QDateTime> to QVector<double>
46 46 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
47 47 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
48 48
49 49 return *this;
50 50 }
51 51
52 52 ExpectedResults &setValuesUnit(Unit valuesUnit)
53 53 {
54 54 m_ValuesUnit = std::move(valuesUnit);
55 55 return *this;
56 56 }
57 57
58 58 ExpectedResults &setValuesData(QVector<double> valuesData)
59 59 {
60 60 m_ValuesData.clear();
61 61 m_ValuesData.push_back(std::move(valuesData));
62 62 return *this;
63 63 }
64 64
65 65 ExpectedResults &setValuesData(QVector<QVector<double> > valuesData)
66 66 {
67 67 m_ValuesData = std::move(valuesData);
68 68 return *this;
69 69 }
70 70
71 71 ExpectedResults &setYAxisEnabled(bool yAxisEnabled)
72 72 {
73 73 m_YAxisEnabled = yAxisEnabled;
74 74 return *this;
75 75 }
76 76
77 77 ExpectedResults &setYAxisUnit(Unit yAxisUnit)
78 78 {
79 79 m_YAxisUnit = std::move(yAxisUnit);
80 80 return *this;
81 81 }
82 82
83 83 ExpectedResults &setYAxisData(QVector<double> yAxisData)
84 84 {
85 85 m_YAxisData = std::move(yAxisData);
86 86 return *this;
87 87 }
88 88
89 89 /**
90 90 * Validates a DataSeries compared to the expected results
91 91 * @param results the DataSeries to validate
92 92 */
93 93 void validate(std::shared_ptr<IDataSeries> results)
94 94 {
95 95 if (m_ParsingOK) {
96 96 auto dataSeries = dynamic_cast<T *>(results.get());
97 97 if (dataSeries == nullptr) {
98 98
99 99 // No unit detected, parsink ok but data is nullptr
100 100 // TODO, improve the test to verify that the data is null
101 101 return;
102 102 }
103 103
104 104 // Checks units
105 105 QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit);
106 106 QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit);
107 107
108 108 auto verifyRange = [dataSeries](const auto &expectedData, const auto &equalFun) {
109 109 QVERIFY(std::equal(dataSeries->cbegin(), dataSeries->cend(), expectedData.cbegin(),
110 110 expectedData.cend(),
111 111 [&equalFun](const auto &dataSeriesIt, const auto &expectedX) {
112 112 return equalFun(dataSeriesIt, expectedX);
113 113 }));
114 114 };
115 115
116 116 // Checks x-axis data
117 117 verifyRange(m_XAxisData, [](const auto &seriesIt, const auto &value) {
118 118 return seriesIt.x() == value;
119 119 });
120 120
121 121 // Checks values data of each component
122 122 for (auto i = 0; i < m_ValuesData.size(); ++i) {
123 123 verifyRange(m_ValuesData.at(i), [i](const auto &seriesIt, const auto &value) {
124 124 auto itValue = seriesIt.value(i);
125 125 return (std::isnan(itValue) && std::isnan(value)) || seriesIt.value(i) == value;
126 126 });
127 127 }
128 128
129 129 // Checks y-axis (if defined)
130 130 auto yAxis = dataSeries->yAxis();
131 131 QCOMPARE(yAxis.isDefined(), m_YAxisEnabled);
132 132
133 133 if (m_YAxisEnabled) {
134 134 // Unit
135 135 QCOMPARE(yAxis.unit(), m_YAxisUnit);
136 136
137 137 // Data
138 138 QVERIFY(std::equal(yAxis.cbegin(), yAxis.cend(), m_YAxisData.cbegin(),
139 139 m_YAxisData.cend(), [](const auto &it, const auto &expectedVal) {
140 140 return it.first() == expectedVal;
141 141 }));
142 142 }
143 143 }
144 144 else {
145 145 QVERIFY(results == nullptr);
146 146 }
147 147 }
148 148
149 149 // Parsing was successfully completed
150 150 bool m_ParsingOK{false};
151 151 // Expected x-axis unit
152 152 Unit m_XAxisUnit{};
153 153 // Expected x-axis data
154 154 QVector<double> m_XAxisData{};
155 155 // Expected values unit
156 156 Unit m_ValuesUnit{};
157 157 // Expected values data
158 158 QVector<QVector<double> > m_ValuesData{};
159 159 // Expected data series has y-axis
160 160 bool m_YAxisEnabled{false};
161 161 // Expected y-axis unit (if axis defined)
162 162 Unit m_YAxisUnit{};
163 163 // Expected y-axis data (if axis defined)
164 164 QVector<double> m_YAxisData{};
165 165 };
166 166
167 167 } // namespace
168 168
169 169 Q_DECLARE_METATYPE(ExpectedResults<ScalarSeries>)
170 170 Q_DECLARE_METATYPE(ExpectedResults<SpectrogramSeries>)
171 171 Q_DECLARE_METATYPE(ExpectedResults<VectorSeries>)
172 172
173 173 class TestAmdaResultParser : public QObject {
174 174 Q_OBJECT
175 175 private:
176 176 template <typename T>
177 177 void testReadDataStructure()
178 178 {
179 179 // ////////////// //
180 180 // Test structure //
181 181 // ////////////// //
182 182
183 183 // Name of TXT file to read
184 184 QTest::addColumn<QString>("inputFileName");
185 185 // Expected results
186 186 QTest::addColumn<ExpectedResults<T> >("expectedResults");
187 187 }
188 188
189 189 template <typename T>
190 190 void testRead(AmdaResultParser::ValueType valueType)
191 191 {
192 192 QFETCH(QString, inputFileName);
193 193 QFETCH(ExpectedResults<T>, expectedResults);
194 194
195 195 // Parses file
196 196 auto filePath = inputFilePath(inputFileName);
197 197 auto results = AmdaResultParser::readTxt(filePath, valueType);
198 198
199 199 // ///////////////// //
200 200 // Validates results //
201 201 // ///////////////// //
202 202 expectedResults.validate(results);
203 203 }
204 204
205 205 private slots:
206 206 /// Input test data
207 207 /// @sa testReadScalarTxt()
208 208 void testReadScalarTxt_data();
209 209
210 210 /// Tests parsing scalar series of a TXT file
211 211 void testReadScalarTxt();
212 212
213 213 /// Input test data
214 214 /// @sa testReadSpectrogramTxt()
215 215 void testReadSpectrogramTxt_data();
216 216
217 217 /// Tests parsing spectrogram series of a TXT file
218 218 void testReadSpectrogramTxt();
219 219
220 220 /// Input test data
221 221 /// @sa testReadVectorTxt()
222 222 void testReadVectorTxt_data();
223 223
224 224 /// Tests parsing vector series of a TXT file
225 225 void testReadVectorTxt();
226 226 };
227 227
228 228 void TestAmdaResultParser::testReadScalarTxt_data()
229 229 {
230 230 testReadDataStructure<ScalarSeries>();
231 231
232 232 // ////////// //
233 233 // Test cases //
234 234 // ////////// //
235 235
236 236 // Valid files
237 237 QTest::newRow("Valid file")
238 238 << QStringLiteral("ValidScalar1.txt")
239 239 << ExpectedResults<ScalarSeries>{}
240 240 .setParsingOK(true)
241 241 .setXAxisUnit(Unit{"nT", true})
242 242 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
243 243 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
244 244 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
245 245 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
246 246 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)})
247 247 .setValuesData({-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
248 248 -2.55800, -2.43250, -2.42200});
249 249
250 250 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
251 251 << QStringLiteral("WrongValue.txt")
252 252 << ExpectedResults<ScalarSeries>{}
253 253 .setParsingOK(true)
254 254 .setXAxisUnit(Unit{"nT", true})
255 255 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
256 256 dateTime(2013, 9, 23, 9, 2, 30)})
257 257 .setValuesData({std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150});
258 258
259 259 QTest::newRow("Valid file that contains NaN values")
260 260 << QStringLiteral("NaNValue.txt")
261 261 << ExpectedResults<ScalarSeries>{}
262 262 .setParsingOK(true)
263 263 .setXAxisUnit(Unit{("nT"), true})
264 264 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
265 265 dateTime(2013, 9, 23, 9, 2, 30)})
266 266 .setValuesData({std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150});
267 267
268 268 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
269 269 QTest::newRow("No unit file")
270 270 << QStringLiteral("NoUnit.txt")
271 271 << ExpectedResults<ScalarSeries>{}.setParsingOK(true).setXAxisUnit(Unit{"", true});
272 272
273 273 QTest::newRow("Wrong unit file")
274 274 << QStringLiteral("WrongUnit.txt")
275 275 << ExpectedResults<ScalarSeries>{}
276 276 .setParsingOK(true)
277 277 .setXAxisUnit(Unit{"", true})
278 278 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
279 279 dateTime(2013, 9, 23, 9, 2, 30)})
280 280 .setValuesData({-2.83950, -2.71850, -2.52150});
281 281
282 282 QTest::newRow("Wrong results file (date of first line is invalid")
283 283 << QStringLiteral("WrongDate.txt")
284 284 << ExpectedResults<ScalarSeries>{}
285 285 .setParsingOK(true)
286 286 .setXAxisUnit(Unit{"nT", true})
287 287 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
288 288 .setValuesData({-2.71850, -2.52150});
289 289
290 290 QTest::newRow("Wrong results file (too many values for first line")
291 291 << QStringLiteral("TooManyValues.txt")
292 292 << ExpectedResults<ScalarSeries>{}
293 293 .setParsingOK(true)
294 294 .setXAxisUnit(Unit{"nT", true})
295 295 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
296 296 .setValuesData({-2.71850, -2.52150});
297 297
298 298 QTest::newRow("Wrong results file (x of first line is NaN")
299 299 << QStringLiteral("NaNX.txt")
300 300 << ExpectedResults<ScalarSeries>{}
301 301 .setParsingOK(true)
302 302 .setXAxisUnit(Unit{"nT", true})
303 303 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
304 304 .setValuesData({-2.71850, -2.52150});
305 305
306 306 QTest::newRow("Invalid file type (vector)")
307 307 << QStringLiteral("ValidVector1.txt")
308 308 << ExpectedResults<ScalarSeries>{}.setParsingOK(true).setXAxisUnit(Unit{"nT", true});
309 309
310 310 // Invalid files
311 311 QTest::newRow("Invalid file (unexisting file)")
312 312 << QStringLiteral("UnexistingFile.txt")
313 313 << ExpectedResults<ScalarSeries>{}.setParsingOK(false);
314 314
315 315 QTest::newRow("Invalid file (file not found on server)")
316 316 << QStringLiteral("FileNotFound.txt")
317 317 << ExpectedResults<ScalarSeries>{}.setParsingOK(false);
318 318 }
319 319
320 320 void TestAmdaResultParser::testReadScalarTxt()
321 321 {
322 322 testRead<ScalarSeries>(AmdaResultParser::ValueType::SCALAR);
323 323 }
324 324
325 325 void TestAmdaResultParser::testReadSpectrogramTxt_data()
326 326 {
327 327 testReadDataStructure<SpectrogramSeries>();
328 328
329 329 // ////////// //
330 330 // Test cases //
331 331 // ////////// //
332 332
333 333 // Valid files
334 334 QTest::newRow("Valid file (three bands)")
335 335 << QStringLiteral("spectro/ValidSpectrogram1.txt")
336 336 << ExpectedResults<SpectrogramSeries>{}
337 337 .setParsingOK(true)
338 338 .setXAxisUnit(Unit{"t", true})
339 339 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
340 340 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
341 341 dateTime(2012, 11, 6, 9, 20, 55)})
342 342 .setYAxisEnabled(true)
343 343 .setYAxisUnit(Unit{"eV"})
344 344 .setYAxisData({5.75, 7.6, 10.05}) // middle of the intervals of each band
345 345 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
346 346 .setValuesData(QVector<QVector<double> >{
347 347 {16313.780, 12631.465, 8223.368, 27595.301, 12820.613},
348 348 {15405.838, 11957.925, 15026.249, 25617.533, 11179.109},
349 349 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221}});
350 350
351 351 auto fourBandsResult
352 352 = ExpectedResults<SpectrogramSeries>{}
353 353 .setParsingOK(true)
354 354 .setXAxisUnit(Unit{"t", true})
355 355 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
356 356 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
357 357 dateTime(2012, 11, 6, 9, 20, 55)})
358 358 .setYAxisEnabled(true)
359 359 .setYAxisUnit(Unit{"eV"})
360 360 .setYAxisData({5.75, 7.6, 10.05, 13.}) // middle of the intervals of each band
361 361 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
362 362 .setValuesData(QVector<QVector<double> >{
363 363 {16313.780, 12631.465, 8223.368, 27595.301, 12820.613},
364 364 {15405.838, 11957.925, 15026.249, 25617.533, 11179.109},
365 365 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221},
366 366 {20907.664, 32076.725, 13008.381, 13142.759, 23226.998}});
367 367
368 QTest::newRow("Valid file (four bands)")
369 << QStringLiteral("spectro/ValidSpectrogram2.txt") << fourBandsResult;
368 QTest::newRow("Valid file (four bands)") << QStringLiteral("spectro/ValidSpectrogram2.txt")
369 << fourBandsResult;
370 370 QTest::newRow("Valid file (four unsorted bands)")
371 371 << QStringLiteral("spectro/ValidSpectrogram3.txt")
372 372 << fourBandsResult; // Bands and values are sorted
373 373
374 374 auto nan = std::numeric_limits<double>::quiet_NaN();
375 375
376 376 auto nanValuesResult
377 377 = ExpectedResults<SpectrogramSeries>{}
378 378 .setParsingOK(true)
379 379 .setXAxisUnit(Unit{"t", true})
380 380 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
381 381 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
382 382 dateTime(2012, 11, 6, 9, 20, 55)})
383 383 .setYAxisEnabled(true)
384 384 .setYAxisUnit(Unit{"eV"})
385 385 .setYAxisData({5.75, 7.6, 10.05, 13.}) // middle of the intervals of each band
386 386 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
387 387 .setValuesData(
388 388 QVector<QVector<double> >{{nan, 12631.465, 8223.368, 27595.301, 12820.613},
389 389 {15405.838, nan, nan, 25617.533, 11179.109},
390 390 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221},
391 391 {nan, nan, nan, nan, nan}});
392 392
393 393 QTest::newRow("Valid file (containing NaN values)")
394 394 << QStringLiteral("spectro/ValidSpectrogramNaNValues.txt") << nanValuesResult;
395 395 QTest::newRow("Valid file (containing fill values)")
396 396 << QStringLiteral("spectro/ValidSpectrogramFillValues.txt")
397 397 << nanValuesResult; // Fill values are replaced by NaN values in the data series
398 398
399 399 QTest::newRow("Valid file (containing data holes, resolution = 3 minutes)")
400 400 << QStringLiteral("spectro/ValidSpectrogramDataHoles.txt")
401 401 << ExpectedResults<SpectrogramSeries>{}
402 402 .setParsingOK(true)
403 403 .setXAxisUnit(Unit{"t", true})
404 404 .setXAxisData({dateTime(2011, 12, 10, 12, 10, 54), //
405 405 dateTime(2011, 12, 10, 12, 13, 54), // Data hole
406 406 dateTime(2011, 12, 10, 12, 16, 54), // Data hole
407 407 dateTime(2011, 12, 10, 12, 17, 23), //
408 408 dateTime(2011, 12, 10, 12, 20, 23), // Data hole
409 409 dateTime(2011, 12, 10, 12, 23, 23), // Data hole
410 410 dateTime(2011, 12, 10, 12, 23, 51), //
411 411 dateTime(2011, 12, 10, 12, 26, 51), // Data hole
412 412 dateTime(2011, 12, 10, 12, 29, 51), // Data hole
413 413 dateTime(2011, 12, 10, 12, 30, 19), //
414 414 dateTime(2011, 12, 10, 12, 33, 19), // Data hole
415 415 dateTime(2011, 12, 10, 12, 35, 04), //
416 416 dateTime(2011, 12, 10, 12, 36, 41), //
417 417 dateTime(2011, 12, 10, 12, 38, 18), //
418 418 dateTime(2011, 12, 10, 12, 39, 55)})
419 419 .setYAxisEnabled(true)
420 420 .setYAxisUnit(Unit{"eV"})
421 421 .setYAxisData({16485.85, 20996.1}) // middle of the intervals of each band
422 422 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
423 423 .setValuesData(QVector<QVector<double> >{{2577578.000, //
424 424 nan, // Data hole
425 425 nan, // Data hole
426 426 2314121.500, //
427 427 nan, // Data hole
428 428 nan, // Data hole
429 429 2063608.750, //
430 430 nan, // Data hole
431 431 nan, // Data hole
432 432 2234525.500, //
433 433 nan, // Data hole
434 434 1670215.250, //
435 435 1689243.250, //
436 436 1654617.125, //
437 437 1504983.750},
438 438 {2336016.000, //
439 439 nan, // Data hole
440 440 nan, // Data hole
441 441 1712093.125, //
442 442 nan, // Data hole
443 443 nan, // Data hole
444 444 1614491.625, //
445 445 nan, // Data hole
446 446 nan, // Data hole
447 447 1764516.500, //
448 448 nan, // Data hole
449 449 1688078.500, //
450 450 1743183.500, //
451 451 1733603.250, //
452 452 1708356.500}});
453 453
454 454 QTest::newRow(
455 455 "Valid file (containing data holes at the beginning and the end, resolution = 4 minutes)")
456 456 << QStringLiteral("spectro/ValidSpectrogramDataHoles2.txt")
457 457 << ExpectedResults<SpectrogramSeries>{}
458 458 .setParsingOK(true)
459 459 .setXAxisUnit(Unit{"t", true})
460 460 .setXAxisData({
461 461 dateTime(2011, 12, 10, 12, 2, 54), // Data hole
462 462 dateTime(2011, 12, 10, 12, 6, 54), // Data hole
463 463 dateTime(2011, 12, 10, 12, 10, 54), //
464 464 dateTime(2011, 12, 10, 12, 14, 54), // Data hole
465 465 dateTime(2011, 12, 10, 12, 17, 23), //
466 466 dateTime(2011, 12, 10, 12, 21, 23), // Data hole
467 467 dateTime(2011, 12, 10, 12, 23, 51), //
468 468 dateTime(2011, 12, 10, 12, 27, 51), // Data hole
469 469 dateTime(2011, 12, 10, 12, 30, 19), //
470 470 dateTime(2011, 12, 10, 12, 34, 19), // Data hole
471 471 dateTime(2011, 12, 10, 12, 35, 04), //
472 472 dateTime(2011, 12, 10, 12, 36, 41), //
473 473 dateTime(2011, 12, 10, 12, 38, 18), //
474 474 dateTime(2011, 12, 10, 12, 39, 55),
475 475 dateTime(2011, 12, 10, 12, 43, 55), // Data hole
476 476 dateTime(2011, 12, 10, 12, 47, 55), // Data hole
477 477 dateTime(2011, 12, 10, 12, 51, 55), // Data hole
478 478 dateTime(2011, 12, 10, 12, 55, 55), // Data hole
479 479 dateTime(2011, 12, 10, 12, 59, 55) // Data hole
480 480 })
481 481 .setYAxisEnabled(true)
482 482 .setYAxisUnit(Unit{"eV"})
483 483 .setYAxisData({16485.85, 20996.1}) // middle of the intervals of each band
484 484 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
485 485 .setValuesData(QVector<QVector<double> >{{
486 486 nan, // Data hole
487 487 nan, // Data hole
488 488 2577578.000, //
489 489 nan, // Data hole
490 490 2314121.500, //
491 491 nan, // Data hole
492 492 2063608.750, //
493 493 nan, // Data hole
494 494 2234525.500, //
495 495 nan, // Data hole
496 496 1670215.250, //
497 497 1689243.250, //
498 498 1654617.125, //
499 499 1504983.750, //
500 500 nan, // Data hole
501 501 nan, // Data hole
502 502 nan, // Data hole
503 503 nan, // Data hole
504 504 nan // Data hole
505 505 },
506 506 {
507 507 nan, // Data hole
508 508 nan, // Data hole
509 509 2336016.000, //
510 510 nan, // Data hole
511 511 1712093.125, //
512 512 nan, // Data hole
513 513 1614491.625, //
514 514 nan, // Data hole
515 515 1764516.500, //
516 516 nan, // Data hole
517 517 1688078.500, //
518 518 1743183.500, //
519 519 1733603.250, //
520 520 1708356.500, //
521 521 nan, // Data hole
522 522 nan, // Data hole
523 523 nan, // Data hole
524 524 nan, // Data hole
525 525 nan // Data hole
526 526 }});
527 527
528 528 // Invalid files
529 529 QTest::newRow("Invalid file (inconsistent bands)")
530 530 << QStringLiteral("spectro/InvalidSpectrogramWrongBands.txt")
531 531 << ExpectedResults<SpectrogramSeries>{}.setParsingOK(false);
532 532 }
533 533
534 534 void TestAmdaResultParser::testReadSpectrogramTxt()
535 535 {
536 536 testRead<SpectrogramSeries>(AmdaResultParser::ValueType::SPECTROGRAM);
537 537 }
538 538
539 539 void TestAmdaResultParser::testReadVectorTxt_data()
540 540 {
541 541 testReadDataStructure<VectorSeries>();
542 542
543 543 // ////////// //
544 544 // Test cases //
545 545 // ////////// //
546 546
547 547 // Valid files
548 548 QTest::newRow("Valid file")
549 549 << QStringLiteral("ValidVector1.txt")
550 550 << ExpectedResults<VectorSeries>{}
551 551 .setParsingOK(true)
552 552 .setXAxisUnit(Unit{"nT", true})
553 553 .setXAxisData({dateTime(2013, 7, 2, 9, 13, 50), dateTime(2013, 7, 2, 9, 14, 6),
554 554 dateTime(2013, 7, 2, 9, 14, 22), dateTime(2013, 7, 2, 9, 14, 38),
555 555 dateTime(2013, 7, 2, 9, 14, 54), dateTime(2013, 7, 2, 9, 15, 10),
556 556 dateTime(2013, 7, 2, 9, 15, 26), dateTime(2013, 7, 2, 9, 15, 42),
557 557 dateTime(2013, 7, 2, 9, 15, 58), dateTime(2013, 7, 2, 9, 16, 14)})
558 558 .setValuesData(
559 559 {{-0.332, -1.011, -1.457, -1.293, -1.217, -1.443, -1.278, -1.202, -1.22, -1.259},
560 560 {3.206, 2.999, 2.785, 2.736, 2.612, 2.564, 2.892, 2.862, 2.859, 2.764},
561 561 {0.058, 0.496, 1.018, 1.485, 1.662, 1.505, 1.168, 1.244, 1.15, 1.358}});
562 562
563 563 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
564 564 QTest::newRow("Invalid file type (scalar)")
565 565 << QStringLiteral("ValidScalar1.txt")
566 566 << ExpectedResults<VectorSeries>{}
567 567 .setParsingOK(true)
568 568 .setXAxisUnit(Unit{"nT", true})
569 569 .setXAxisData({})
570 570 .setValuesData(QVector<QVector<double> >{{}, {}, {}});
571 571 }
572 572
573 573 void TestAmdaResultParser::testReadVectorTxt()
574 574 {
575 575 testRead<VectorSeries>(AmdaResultParser::ValueType::VECTOR);
576 576 }
577 577
578 578 QTEST_MAIN(TestAmdaResultParser)
579 579 #include "TestAmdaResultParser.moc"
General Comments 0
You need to be logged in to leave comments. Login now