##// END OF EJS Templates
Add Catalogue methods
perrinel -
r1191:4c27015bd9c6
parent child
Show More
@@ -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,74
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) const;
56 void updateCatalogue(std::shared_ptr<DBCatalogue> catalogue);
57 void removeCatalogue(std::shared_ptr<DBCatalogue> catalogue);
58 void saveCatalogue(std::shared_ptr<DBCatalogue> catalogue);
59
60 void saveAll();
56 61
57 62 public slots:
58 63 /// Manage init/end of the controller
59 64 void initialize();
60 65 void finalize();
61 66
62 67 private:
63 68 void waitForFinish();
64 69
65 70 class CatalogueControllerPrivate;
66 71 spimpl::unique_impl_ptr<CatalogueControllerPrivate> impl;
67 72 };
68 73
69 74 #endif // SCIQLOP_CATALOGUECONTROLLER_H
@@ -1,161 +1,287
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->toSyncRepository(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 catalogues = m_Q->retrieveCatalogues(dbFrom);
259 auto events = m_Q->retrieveEvents(dbFrom);
260
261 for (auto catalogue : catalogues) {
262 m_CatalogueDao.copyCatalogue(*catalogue, dbTo, true);
263 }
264
265 for (auto event : events) {
266 m_CatalogueDao.copyEvent(*event, dbTo, true);
267 }
268 }
269
270 QString CatalogueController::CatalogueControllerPrivate::toWorkRepository(QString repository)
271 {
272 auto syncRepository = toSyncRepository(repository);
273
274 return QString("%1_%2").arg(syncRepository, REPOSITORY_WORK_SUFFIX);
275 }
276
277 QString CatalogueController::CatalogueControllerPrivate::toSyncRepository(QString repository)
278 {
279 auto syncRepository = repository;
280 if (repository.endsWith(REPOSITORY_WORK_SUFFIX)) {
281 syncRepository.remove(REPOSITORY_WORK_SUFFIX);
282 }
283 else if (repository.endsWith(REPOSITORY_TRASH_SUFFIX)) {
284 syncRepository.remove(REPOSITORY_TRASH_SUFFIX);
285 }
286 return syncRepository;
287 }
@@ -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,247 +1,247
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 179 auto db = addDatabaseItem("Default", treeWidget);
180 180
181 auto catalogues = sqpApp->catalogueController().getCatalogues("Default");
181 auto catalogues = sqpApp->catalogueController().retrieveCatalogues("Default");
182 182 for (auto catalogue : catalogues) {
183 183 addCatalogueItem(catalogue, db);
184 184 }
185 185
186 186 treeWidget->expandAll();
187 187 }
188 188
189 189 QTreeWidgetItem *
190 190 CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::addDatabaseItem(const QString &name,
191 191 QTreeWidget *treeWidget)
192 192 {
193 193 auto databaseItem = new QTreeWidgetItem{{name}, DATABASE_ITEM_TYPE};
194 194 databaseItem->setIcon(0, QIcon{":/icones/database.png"});
195 195 treeWidget->addTopLevelItem(databaseItem);
196 196
197 197 return databaseItem;
198 198 }
199 199
200 200 QTreeWidgetItem *
201 201 CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::getDatabaseItem(const QString &name,
202 202 QTreeWidget *treeWidget)
203 203 {
204 204 for (auto i = 0; i < treeWidget->topLevelItemCount(); ++i) {
205 205 auto item = treeWidget->topLevelItem(i);
206 206 if (item->type() == DATABASE_ITEM_TYPE && item->text(0) == name) {
207 207 return item;
208 208 }
209 209 }
210 210
211 211 return nullptr;
212 212 }
213 213
214 214 void CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::addCatalogueItem(
215 215 const std::shared_ptr<DBCatalogue> &catalogue, QTreeWidgetItem *parentDatabaseItem)
216 216 {
217 217 auto catalogueItem = new CatalogueTreeWidgetItem{catalogue, CATALOGUE_ITEM_TYPE};
218 218 catalogueItem->setIcon(0, QIcon{":/icones/catalogue.png"});
219 219 parentDatabaseItem->addChild(catalogueItem);
220 220 }
221 221
222 222 CatalogueTreeWidgetItem *CatalogueSideBarWidget::CatalogueSideBarWidgetPrivate::getCatalogueItem(
223 223 const std::shared_ptr<DBCatalogue> &catalogue, QTreeWidget *treeWidget) const
224 224 {
225 225 for (auto i = 0; i < treeWidget->topLevelItemCount(); ++i) {
226 226 auto item = treeWidget->topLevelItem(i);
227 227 if (item->type() == DATABASE_ITEM_TYPE) {
228 228 for (auto j = 0; j < item->childCount(); ++j) {
229 229 auto childItem = item->child(j);
230 230 if (childItem->type() == CATALOGUE_ITEM_TYPE) {
231 231 auto catalogueItem = static_cast<CatalogueTreeWidgetItem *>(childItem);
232 232 if (catalogueItem->catalogue() == catalogue) {
233 233 return catalogueItem;
234 234 }
235 235 }
236 236 else {
237 237 qCWarning(LOG_CatalogueSideBarWidget()) << "getCatalogueItem: Invalid tree "
238 238 "structure. A database item should "
239 239 "only contain catalogues.";
240 240 Q_ASSERT(false);
241 241 }
242 242 }
243 243 }
244 244 }
245 245
246 246 return nullptr;
247 247 }
@@ -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),
247 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
244 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
245 &QCPAxis::rangeChanged),
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 4
Under Review
author

Pull request updated. Auto status change to "Under Review"

Changed commits:
  * 1 added
  * 0 removed

Changed files:
  * M app/src/Main.cpp
  * M core/include/Catalogue/CatalogueController.h
  * M core/src/Catalogue/CatalogueController.cpp
  * M core/src/Plugin/PluginManager.cpp
  * M core/tests/Data/TestDataSeriesUtils.cpp
  * M gui/include/Catalogue/CatalogueEventsModel.h
  * M gui/include/Catalogue/CatalogueEventsWidget.h
  * M gui/include/Catalogue/CatalogueTreeWidgetItem.h
  * M gui/meson.build
  * M gui/src/Catalogue/CatalogueEventsModel.cpp
  * M gui/src/Catalogue/CatalogueEventsWidget.cpp
  * M gui/src/Catalogue/CatalogueExplorer.cpp
  * M gui/src/Catalogue/CatalogueSideBarWidget.cpp
  * M gui/src/Catalogue/CatalogueTreeWidgetItem.cpp
  * M gui/src/Visualization/VisualizationActionManager.cpp
  * M gui/src/Visualization/VisualizationGraphWidget.cpp
  * M gui/src/Visualization/VisualizationSelectionZoneItem.cpp
  * M plugins/amda/tests/TestAmdaResultParser.cpp
  * R cmake/FindCatalogueAPI.cmake
  * R core/include/Common/MimeTypesDef.h
  * R core/include/Data/DataSeriesUtils.h
  * R core/include/Data/OptionalAxis.h
  * R core/include/Data/SpectrogramSeries.h
  * R core/include/Data/Unit.h
  * R core/include/DataSource/DataSourceItemMergeHelper.h
  * R core/include/Variable/VariableCacheStrategyFactory.h
  * R core/include/Variable/VariableSingleThresholdCacheStrategy.h
  * R core/src/Common/MimeTypesDef.cpp
  * R core/src/Data/DataSeriesUtils.cpp
  * R core/src/Data/OptionalAxis.cpp
  * R core/src/Data/SpectrogramSeries.cpp
  * R core/src/DataSource/DataSourceItemMergeHelper.cpp
  * R core/tests-resources/TestDataSeriesUtils/TestThresholds.txt
  * R core/tests/Data/DataSeriesBuilders.cpp
  * R core/tests/Data/DataSeriesBuilders.h
  * R core/tests/Data/DataSeriesTestsUtils.cpp
  * R core/tests/Data/DataSeriesTestsUtils.h
  * R core/tests/Data/TestOptionalAxis.cpp
  * R core/tests/Data/TestScalarSeries.cpp
  * R core/tests/Data/TestSpectrogramSeries.cpp
  * R core/tests/Data/TestVectorSeries.cpp
  * R core/tests/DataSource/DataSourceItemBuilder.cpp
  * R core/tests/DataSource/DataSourceItemBuilder.h
  * R core/tests/DataSource/TestDataSourceItem.cpp
  * R core/tests/Variable/TestVariableSync.cpp
  * R extern/CatalogueAPI.cmake
  * R gui/include/Actions/ActionsGuiController.h
  * R gui/include/Actions/SelectionZoneAction.h
  * R gui/include/Catalogue/CatalogueExplorer.h
  * R gui/include/Catalogue/CatalogueInspectorWidget.h
  * R gui/include/Catalogue/CatalogueSideBarWidget.h
  * R gui/include/Common/VisualizationDef.h
  * R gui/include/DataSource/DataSourceTreeWidget.h
  * R gui/include/DragAndDrop/DragDropGuiController.h
  * R gui/include/DragAndDrop/DragDropScroller.h
  * R gui/include/DragAndDrop/DragDropTabSwitcher.h
  * R gui/include/Variable/VariableInspectorTableView.h
  * R gui/include/Visualization/AxisRenderingUtils.h
  * R gui/include/Visualization/ColorScaleEditor.h
  * R gui/include/Visualization/MacScrollBarStyle.h
  * R gui/include/Visualization/PlottablesRenderingUtils.h
  * R gui/include/Visualization/QCPColorMapIterator.h
  * R gui/include/Visualization/SqpColorScale.h
  * R gui/include/Visualization/VisualizationActionManager.h
  * R gui/include/Visualization/VisualizationCursorItem.h
  * R gui/include/Visualization/VisualizationDragDropContainer.h
  * R gui/include/Visualization/VisualizationDragWidget.h
  * R gui/include/Visualization/VisualizationMultiZoneSelectionDialog.h
  * R gui/include/Visualization/VisualizationSelectionZoneItem.h
  * R gui/include/Visualization/VisualizationSelectionZoneManager.h
  * R gui/resources/icones/add.png
  * R gui/resources/icones/allEvents.png
  * R gui/resources/icones/catalogue.png
  * R gui/resources/icones/chart.png
  * R gui/resources/icones/cursor.png
  * R gui/resources/icones/database.png
  * R gui/resources/icones/discard.png
  * R gui/resources/icones/drag.png
  * R gui/resources/icones/pointer.png
  * R gui/resources/icones/rectangle.png
  * R gui/resources/icones/remove.png
  * R gui/resources/icones/save.png
  * R gui/resources/icones/time.png
  * R gui/resources/icones/trash.png
  * R gui/resources/icones/zoom.png
  * R gui/src/Actions/ActionsGuiController.cpp
  * R gui/src/Actions/SelectionZoneAction.cpp
  * R gui/src/Catalogue/CatalogueInspectorWidget.cpp
  * R gui/src/Common/VisualizationDef.cpp
  * R gui/src/DataSource/DataSourceTreeWidget.cpp
  * R gui/src/DragAndDrop/DragDropGuiController.cpp
  * R gui/src/DragAndDrop/DragDropScroller.cpp
  * R gui/src/DragAndDrop/DragDropTabSwitcher.cpp
  * R gui/src/Variable/VariableInspectorTableView.cpp
  * R gui/src/Visualization/AxisRenderingUtils.cpp
  * R gui/src/Visualization/ColorScaleEditor.cpp
  * R gui/src/Visualization/MacScrollBarStyle.cpp
  * R gui/src/Visualization/PlottablesRenderingUtils.cpp
  * R gui/src/Visualization/QCPColorMapIterator.cpp
  * R gui/src/Visualization/SqpColorScale.cpp
  * R gui/src/Visualization/VisualizationCursorItem.cpp
  * R gui/src/Visualization/VisualizationDragDropContainer.cpp
  * R gui/src/Visualization/VisualizationDragWidget.cpp
  * R gui/src/Visualization/VisualizationMultiZoneSelectionDialog.cpp
  * R gui/src/Visualization/VisualizationSelectionZoneManager.cpp
  * R gui/ui/Catalogue/CatalogueEventsWidget.ui
  * R gui/ui/Catalogue/CatalogueExplorer.ui
  * R gui/ui/Catalogue/CatalogueInspectorWidget.ui
  * R gui/ui/Catalogue/CatalogueSideBarWidget.ui
  * R gui/ui/Visualization/ColorScaleEditor.ui
  * R gui/ui/Visualization/VisualizationMultiZoneSelectionDialog.ui
  * R plugins/amda/include/AmdaResultParserDefs.h
  * R plugins/amda/include/AmdaResultParserHelper.h
  * R plugins/amda/include/AmdaServer.h
  * R plugins/amda/resources/samples/AmdaSampleV3.json
  * R plugins/amda/src/AmdaResultParserDefs.cpp
  * R plugins/amda/src/AmdaResultParserHelper.cpp
  * R plugins/amda/src/AmdaServer.cpp
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/FileNotFound.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/NaNValue.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/NaNX.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/NoUnit.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/TooManyValues.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/ValidScalar1.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/ValidVector1.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/WrongDate.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/WrongUnit.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/amdaV2/WrongValue.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/InvalidSpectrogramWrongBands.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogram1.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogram2.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogram3.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogramDataHoles.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogramDataHoles2.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogramFillValues.txt
  * R plugins/amda/tests-resources/TestAmdaResultParser/spectro/ValidSpectrogramNaNValues.txt
  * R plugins/amda/vera-exclusions/exclusions.txt
  * R plugins/mockplugin/include/MockDefs.h
  * R plugins/mockplugin/src/MockDefs.cpp
  * R subprojects/CatalogueAPI.wrap
  * R subprojects/QxOrm.wrap
  * R .gitignore
  * R CMakeLists.txt
  * R app/src/MainWindow.cpp
  * R app/ui/MainWindow.ui
  * R cmake/sciqlop_applications.cmake
  * R cmake/sciqlop_package_qt.cmake
  * R core/CMakeLists.txt
  * R core/cmake/Findsciqlop-core.cmake
  * R core/include/Common/SortUtils.h
  * R core/include/Data/ArrayData.h
  * R core/include/Data/DataSeries.h
  * R core/include/Data/DataSeriesIterator.h
  * R core/include/Data/IDataSeries.h
  * R core/include/Data/VariableRequest.h
  * R core/include/DataSource/DataSourceController.h
  * R core/include/DataSource/DataSourceItem.h
  * R core/include/DataSource/DataSourceItemAction.h
  * R core/include/Plugin/PluginManager.h
  * R core/include/Time/TimeController.h
  * R core/include/Variable/Variable.h
  * R core/include/Variable/VariableCacheStrategy.h
  * R core/include/Variable/VariableController.h
  * R core/include/Variable/VariableModel.h
  * R core/meson.build
  * R core/src/Data/DataSeriesIterator.cpp
  * R core/src/Data/VectorSeries.cpp
  * R core/src/DataSource/DataSourceController.cpp
  * R core/src/DataSource/DataSourceItem.cpp
  * R core/src/DataSource/DataSourceItemAction.cpp
  * R core/src/Time/TimeController.cpp
  * R core/src/Variable/Variable.cpp
  * R core/src/Variable/VariableAcquisitionWorker.cpp
  * R core/src/Variable/VariableController.cpp
  * R core/src/Variable/VariableModel.cpp
  * R core/tests/Data/TestTwoDimArrayData.cpp
  * R core/tests/Variable/TestVariable.cpp
  * R core/tests/meson.build
  * R gui/CMakeLists.txt
  * R gui/cmake/Findsciqlop-gui.cmake
  * R gui/include/DataSource/DataSourceWidget.h
  * R gui/include/SqpApplication.h
  * R gui/include/TimeWidget/TimeWidget.h
  * R gui/include/Visualization/VisualizationGraphHelper.h
  * R gui/include/Visualization/VisualizationGraphRenderingDelegate.h
  * R gui/include/Visualization/VisualizationGraphWidget.h
  * R gui/include/Visualization/VisualizationTabWidget.h
  * R gui/include/Visualization/VisualizationWidget.h
  * R gui/include/Visualization/VisualizationZoneWidget.h
  * R gui/resources/sqpguiresources.qrc
  * R gui/src/DataSource/DataSourceTreeWidgetItem.cpp
  * R gui/src/DataSource/DataSourceWidget.cpp
  * R gui/src/SqpApplication.cpp
  * R gui/src/TimeWidget/TimeWidget.cpp
  * R gui/src/Variable/VariableInspectorWidget.cpp
  * R gui/src/Visualization/VisualizationGraphHelper.cpp
  * R gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp
  * R gui/src/Visualization/VisualizationTabWidget.cpp
  * R gui/src/Visualization/VisualizationWidget.cpp
  * R gui/src/Visualization/VisualizationZoneWidget.cpp
  * R gui/src/Visualization/operations/GenerateVariableMenuOperation.cpp
  * R gui/src/Visualization/operations/RescaleAxeOperation.cpp
  * R gui/ui/DataSource/DataSourceWidget.ui
  * R gui/ui/TimeWidget/TimeWidget.ui
  * R gui/ui/Variable/VariableInspectorWidget.ui
  * R gui/ui/Visualization/VisualizationTabWidget.ui
  * R gui/ui/Visualization/VisualizationZoneWidget.ui
  * R gui/vera-exclusions/exclusions.txt
  * R meson.build
  * R plugins/amda/CMakeLists.txt
  * R plugins/amda/cmake/Findsciqlop-amda.cmake
  * R plugins/amda/include/AmdaDefs.h
  * R plugins/amda/include/AmdaPlugin.h
  * R plugins/amda/include/AmdaProvider.h
  * R plugins/amda/include/AmdaResultParser.h
  * R plugins/amda/meson.build
  * R plugins/amda/resources/amdaresources.qrc
  * R plugins/amda/src/AmdaDefs.cpp
  * R plugins/amda/src/AmdaPlugin.cpp
  * R plugins/amda/src/AmdaProvider.cpp
  * R plugins/amda/src/AmdaResultParser.cpp
  * R plugins/amda/tests/TestAmdaAcquisition.cpp
  * R plugins/mockplugin/cmake/Findsciqlop-mockplugin.cmake
  * R plugins/mockplugin/include/CosinusProvider.h
  * R plugins/mockplugin/meson.build
  * R plugins/mockplugin/src/CosinusProvider.cpp
  * R plugins/mockplugin/src/MockPlugin.cpp
  * R plugins/mockplugin/tests/TestCosinusAcquisition.cpp
  * R core/src/Variable/VariableCacheStrategy.cpp
  * R core/tests/Data/TestDataSeries.cpp
Approved

Status change > Approved

You need to be logged in to leave comments. Login now