##// END OF EJS Templates
Switched to cpp_utils package...
jeandet -
r1486:8539a975891c
parent child
Show More
@@ -0,0 +1,5
1 [wrap-git]
2 directory = cpp_utils
3 url = https://github.com/jeandet/cpp_utils.git
4 revision = master
5
@@ -1,1 +1,1
1 Subproject commit 63c6ae3895dda76aabbc560a636a1f3d8d4d95bf
1 Subproject commit 29637e951955d1747d325407e2d833646c98f1c2
@@ -1,74 +1,74
1 #include "Catalogue2/eventeditor.h"
1 #include "Catalogue2/eventeditor.h"
2 #include "ui_eventeditor.h"
2 #include "ui_eventeditor.h"
3 #include <Common/DateUtils.h>
3 #include <Common/DateUtils.h>
4 #include <Common/StringUtils.h>
4 #include <containers/algorithms.hpp>
5
5
6 EventEditor::EventEditor(QWidget* parent) : QWidget(parent), ui(new Ui::EventEditor)
6 EventEditor::EventEditor(QWidget* parent) : QWidget(parent), ui(new Ui::EventEditor)
7 {
7 {
8 ui->setupUi(this);
8 ui->setupUi(this);
9 }
9 }
10
10
11 EventEditor::~EventEditor()
11 EventEditor::~EventEditor()
12 {
12 {
13 delete ui;
13 delete ui;
14 }
14 }
15
15
16 void EventEditor::setEvent(const CatalogueController::Event_ptr& event)
16 void EventEditor::setEvent(const CatalogueController::Event_ptr& event)
17 {
17 {
18 _setEventName(event, mode::editable);
18 _setEventName(event, mode::editable);
19 _setTags(event, mode::readonly);
19 _setTags(event, mode::readonly);
20 _setProducts(event, mode::readonly);
20 _setProducts(event, mode::readonly);
21 _setDates(event->startTime(), event->stopTime(), mode::readonly);
21 _setDates(event->startTime(), event->stopTime(), mode::readonly);
22 }
22 }
23
23
24 void EventEditor::setProduct(
24 void EventEditor::setProduct(
25 const CatalogueController::Product_t& product, const CatalogueController::Event_ptr& event)
25 const CatalogueController::Product_t& product, const CatalogueController::Event_ptr& event)
26 {
26 {
27 _setEventName(event, mode::readonly);
27 _setEventName(event, mode::readonly);
28 _setTags(event, mode::readonly);
28 _setTags(event, mode::readonly);
29 _setDates(product.startTime, product.stopTime, mode::editable);
29 _setDates(product.startTime, product.stopTime, mode::editable);
30 _setProducts(product, mode::readonly);
30 _setProducts(product, mode::readonly);
31 }
31 }
32
32
33 void EventEditor::_setEventName(const CatalogueController::Event_ptr& event, mode is_editable)
33 void EventEditor::_setEventName(const CatalogueController::Event_ptr& event, mode is_editable)
34 {
34 {
35 this->ui->EventName->setText(QString::fromStdString(event->name));
35 this->ui->EventName->setText(QString::fromStdString(event->name));
36 this->ui->EventName->setEnabled(bool(is_editable));
36 this->ui->EventName->setEnabled(bool(is_editable));
37 }
37 }
38
38
39 void EventEditor::_setTags(const CatalogueController::Event_ptr& event, mode is_editable)
39 void EventEditor::_setTags(const CatalogueController::Event_ptr& event, mode is_editable)
40 {
40 {
41 this->ui->Tags->setText(StringUtils::join(event->tags, ", "));
41 this->ui->Tags->setText(QString::fromStdString(cpp_utils::containers::join(event->tags, ',')));
42 this->ui->Tags->setEnabled(bool(is_editable));
42 this->ui->Tags->setEnabled(bool(is_editable));
43 }
43 }
44
44
45 void EventEditor::_setProducts(const CatalogueController::Event_ptr& event, mode is_editable)
45 void EventEditor::_setProducts(const CatalogueController::Event_ptr& event, mode is_editable)
46 {
46 {
47 QStringList products;
47 QStringList products;
48 this->ui->Products->setText(StringUtils::join(event->products, ", ",
48 std::transform(std::cbegin(event->products),std::cend(event->products),std::begin(products),[](const auto& product) { return QString::fromStdString(product.name); });
49 [](const auto& product) { return QString::fromStdString(product.name); }));
49 this->ui->Products->setText(cpp_utils::containers::join(products, QString(", ")));
50 this->ui->Products->setEnabled(bool(is_editable));
50 this->ui->Products->setEnabled(bool(is_editable));
51 }
51 }
52
52
53 void EventEditor::_setProducts(const CatalogueController::Product_t& product, mode is_editable)
53 void EventEditor::_setProducts(const CatalogueController::Product_t& product, mode is_editable)
54 {
54 {
55 this->ui->Products->setText(QString::fromStdString(product.name));
55 this->ui->Products->setText(QString::fromStdString(product.name));
56 this->ui->Products->setEnabled(bool(is_editable));
56 this->ui->Products->setEnabled(bool(is_editable));
57 }
57 }
58
58
59 void EventEditor::_setDates(double startDate, double stopDate, mode is_editable)
59 void EventEditor::_setDates(double startDate, double stopDate, mode is_editable)
60 {
60 {
61 this->ui->StartTime->setDateTime(DateUtils::dateTime(startDate));
61 this->ui->StartTime->setDateTime(DateUtils::dateTime(startDate));
62 this->ui->StopTime->setDateTime(DateUtils::dateTime(stopDate));
62 this->ui->StopTime->setDateTime(DateUtils::dateTime(stopDate));
63 this->ui->StartTime->setEnabled(bool(is_editable));
63 this->ui->StartTime->setEnabled(bool(is_editable));
64 this->ui->StopTime->setEnabled(bool(is_editable));
64 this->ui->StopTime->setEnabled(bool(is_editable));
65 }
65 }
66
66
67 void EventEditor::_setDates(
67 void EventEditor::_setDates(
68 std::optional<double> startDate, std::optional<double> stopDate, mode is_editable)
68 std::optional<double> startDate, std::optional<double> stopDate, mode is_editable)
69 {
69 {
70 if (startDate && stopDate)
70 if (startDate && stopDate)
71 _setDates(*startDate, *stopDate, is_editable);
71 _setDates(*startDate, *stopDate, is_editable);
72 else
72 else
73 _setDates(0., 0., is_editable);
73 _setDates(0., 0., is_editable);
74 }
74 }
@@ -1,152 +1,153
1 /*
1 /*
2 This file is part of SciQLop.
2 This file is part of SciQLop.
3
3
4 SciQLop is free software: you can redistribute it and/or modify
4 SciQLop is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
7 (at your option) any later version.
8
8
9 SciQLop is distributed in the hope that it will be useful,
9 SciQLop is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
12 GNU General Public License for more details.
13
13
14 You should have received a copy of the GNU General Public License
14 You should have received a copy of the GNU General Public License
15 along with SciQLop. If not, see <https://www.gnu.org/licenses/>.
15 along with SciQLop. If not, see <https://www.gnu.org/licenses/>.
16 */
16 */
17 #include "Catalogue2/eventsmodel.h"
17 #include "Catalogue2/eventsmodel.h"
18 #include <Common/containers.h>
19 #include <SqpApplication.h>
18 #include <SqpApplication.h>
19 #include <containers/algorithms.hpp>
20
20
21
21 EventsModel::EventsModel(QObject* parent) : QAbstractItemModel(parent) {}
22 EventsModel::EventsModel(QObject* parent) : QAbstractItemModel(parent) {}
22
23
23 EventsModel::ItemType EventsModel::type(const QModelIndex& index) const
24 EventsModel::ItemType EventsModel::type(const QModelIndex& index) const
24 {
25 {
25 if (EventsModelItem* item = to_item(index))
26 if (EventsModelItem* item = to_item(index))
26 {
27 {
27 return item->type;
28 return item->type;
28 }
29 }
29 return ItemType::None;
30 return ItemType::None;
30 }
31 }
31
32
32 QVariant EventsModel::data(const QModelIndex& index, int role) const
33 QVariant EventsModel::data(const QModelIndex& index, int role) const
33 {
34 {
34 if (index.isValid())
35 if (index.isValid())
35 {
36 {
36 return to_item(index)->data(index.column(), role);
37 return to_item(index)->data(index.column(), role);
37 }
38 }
38 return QVariant {};
39 return QVariant {};
39 }
40 }
40
41
41 QModelIndex EventsModel::index(int row, int column, const QModelIndex& parent) const
42 QModelIndex EventsModel::index(int row, int column, const QModelIndex& parent) const
42 {
43 {
43 if (!hasIndex(row, column, parent))
44 if (!hasIndex(row, column, parent))
44 {
45 {
45 return QModelIndex();
46 return QModelIndex();
46 }
47 }
47
48
48 switch (type(parent))
49 switch (type(parent))
49 {
50 {
50 case ItemType::None: // is an event
51 case ItemType::None: // is an event
51 return createIndex(row, column, _items[row].get());
52 return createIndex(row, column, _items[row].get());
52 case ItemType::Event: // is a product
53 case ItemType::Event: // is a product
53 return createIndex(row, column, to_item(parent)->children[row].get());
54 return createIndex(row, column, to_item(parent)->children[row].get());
54 case ItemType::Product:
55 case ItemType::Product:
55 QModelIndex();
56 QModelIndex();
56 }
57 }
57
58
58 return QModelIndex();
59 return QModelIndex();
59 }
60 }
60
61
61 QModelIndex EventsModel::parent(const QModelIndex& index) const
62 QModelIndex EventsModel::parent(const QModelIndex& index) const
62 {
63 {
63 auto item = to_item(index);
64 auto item = to_item(index);
64 if (item->type == ItemType::Product)
65 if (item->type == ItemType::Product)
65 {
66 {
66 auto repoIndex = SciQLop::containers::index_of(_items, item->parent);
67 auto repoIndex = cpp_utils::containers::index_of(_items, item->parent);
67 return createIndex(repoIndex, 0, item->parent);
68 return createIndex(repoIndex, 0, item->parent);
68 }
69 }
69 return QModelIndex();
70 return QModelIndex();
70 }
71 }
71
72
72 int EventsModel::rowCount(const QModelIndex& parent) const
73 int EventsModel::rowCount(const QModelIndex& parent) const
73 {
74 {
74 if (parent.column() > 0)
75 if (parent.column() > 0)
75 {
76 {
76 return 0;
77 return 0;
77 }
78 }
78 switch (type(parent))
79 switch (type(parent))
79 {
80 {
80 case ItemType::None:
81 case ItemType::None:
81 return _items.size();
82 return _items.size();
82 case ItemType::Event:
83 case ItemType::Event:
83 return to_item(parent)->children.size();
84 return to_item(parent)->children.size();
84 case ItemType::Product:
85 case ItemType::Product:
85 break;
86 break;
86 }
87 }
87 return 0;
88 return 0;
88 }
89 }
89
90
90 int EventsModel::columnCount(const QModelIndex& parent) const
91 int EventsModel::columnCount(const QModelIndex& parent) const
91 {
92 {
92 return static_cast<int>(EventsModel::Columns::NbColumn);
93 return static_cast<int>(EventsModel::Columns::NbColumn);
93 }
94 }
94
95
95 QVariant EventsModel::headerData(int section, Qt::Orientation orientation, int role) const
96 QVariant EventsModel::headerData(int section, Qt::Orientation orientation, int role) const
96 {
97 {
97 if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section < ColumnsNames.size())
98 if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section < ColumnsNames.size())
98 {
99 {
99 return ColumnsNames[section];
100 return ColumnsNames[section];
100 }
101 }
101
102
102 return QVariant();
103 return QVariant();
103 }
104 }
104
105
105 void EventsModel::sort(int column, Qt::SortOrder order)
106 void EventsModel::sort(int column, Qt::SortOrder order)
106 {
107 {
107 beginResetModel();
108 beginResetModel();
108 switch (static_cast<Columns>(column))
109 switch (static_cast<Columns>(column))
109 {
110 {
110 case EventsModel::Columns::Name:
111 case EventsModel::Columns::Name:
111 std::sort(std::begin(_items), std::end(_items),
112 std::sort(std::begin(_items), std::end(_items),
112 [inverse = order != Qt::SortOrder::AscendingOrder](
113 [inverse = order != Qt::SortOrder::AscendingOrder](
113 const std::unique_ptr<EventsModelItem>& a,
114 const std::unique_ptr<EventsModelItem>& a,
114 const std::unique_ptr<EventsModelItem>& b) {
115 const std::unique_ptr<EventsModelItem>& b) {
115 return (a->event()->name < b->event()->name) xor inverse;
116 return (a->event()->name < b->event()->name) xor inverse;
116 });
117 });
117 break;
118 break;
118 case EventsModel::Columns::TStart:
119 case EventsModel::Columns::TStart:
119 std::sort(std::begin(_items), std::end(_items),
120 std::sort(std::begin(_items), std::end(_items),
120 [inverse = order != Qt::SortOrder::AscendingOrder](
121 [inverse = order != Qt::SortOrder::AscendingOrder](
121 const std::unique_ptr<EventsModelItem>& a,
122 const std::unique_ptr<EventsModelItem>& a,
122 const std::unique_ptr<EventsModelItem>& b) {
123 const std::unique_ptr<EventsModelItem>& b) {
123 if (auto t1 = a->event()->startTime(); auto t2 = b->event()->startTime())
124 if (auto t1 = a->event()->startTime(); auto t2 = b->event()->startTime())
124 {
125 {
125 if (t1 and t2)
126 if (t1 and t2)
126 return bool((t1.value() < t2.value()) xor inverse);
127 return bool((t1.value() < t2.value()) xor inverse);
127 }
128 }
128 return true;
129 return true;
129 });
130 });
130 break;
131 break;
131 case EventsModel::Columns::TEnd:
132 case EventsModel::Columns::TEnd:
132 std::sort(std::begin(_items), std::end(_items),
133 std::sort(std::begin(_items), std::end(_items),
133 [inverse = order != Qt::SortOrder::AscendingOrder](
134 [inverse = order != Qt::SortOrder::AscendingOrder](
134 const std::unique_ptr<EventsModelItem>& a,
135 const std::unique_ptr<EventsModelItem>& a,
135 const std::unique_ptr<EventsModelItem>& b) {
136 const std::unique_ptr<EventsModelItem>& b) {
136 if (auto t1 = a->event()->stopTime(); auto t2 = b->event()->stopTime())
137 if (auto t1 = a->event()->stopTime(); auto t2 = b->event()->stopTime())
137 {
138 {
138 if (t1 and t2)
139 if (t1 and t2)
139 return bool((t1.value() < t2.value()) xor inverse);
140 return bool((t1.value() < t2.value()) xor inverse);
140 }
141 }
141 return true;
142 return true;
142 });
143 });
143 break;
144 break;
144 case EventsModel::Columns::Product:
145 case EventsModel::Columns::Product:
145 break;
146 break;
146 case EventsModel::Columns::Tags:
147 case EventsModel::Columns::Tags:
147 break;
148 break;
148 default:
149 default:
149 break;
150 break;
150 }
151 }
151 endResetModel();
152 endResetModel();
152 }
153 }
@@ -1,127 +1,127
1 /*
1 /*
2 This file is part of SciQLop.
2 This file is part of SciQLop.
3
3
4 SciQLop is free software: you can redistribute it and/or modify
4 SciQLop is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
7 (at your option) any later version.
8
8
9 SciQLop is distributed in the hope that it will be useful,
9 SciQLop is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
12 GNU General Public License for more details.
13
13
14 You should have received a copy of the GNU General Public License
14 You should have received a copy of the GNU General Public License
15 along with SciQLop. If not, see <https://www.gnu.org/licenses/>.
15 along with SciQLop. If not, see <https://www.gnu.org/licenses/>.
16 */
16 */
17 #include <Catalogue2/repositoriesmodel.h>
17 #include <Catalogue2/repositoriesmodel.h>
18 #include <Common/containers.h>
18 #include <containers/algorithms.hpp>
19 #include <SqpApplication.h>
19 #include <SqpApplication.h>
20
20
21
21
22 RepositoriesModel::RepositoriesModel(QObject* parent) : QAbstractItemModel(parent)
22 RepositoriesModel::RepositoriesModel(QObject* parent) : QAbstractItemModel(parent)
23 {
23 {
24 refresh();
24 refresh();
25 connect(&(sqpApp->catalogueController()), &CatalogueController::repositoryAdded, this,
25 connect(&(sqpApp->catalogueController()), &CatalogueController::repositoryAdded, this,
26 [this](const QString&) { this->refresh(); });
26 [this](const QString&) { this->refresh(); });
27 connect(&(sqpApp->catalogueController()), &CatalogueController::catalogueAdded, this,
27 connect(&(sqpApp->catalogueController()), &CatalogueController::catalogueAdded, this,
28 [this](const CatalogueController::Catalogue_ptr&, const QString&) { this->refresh(); });
28 [this](const CatalogueController::Catalogue_ptr&, const QString&) { this->refresh(); });
29 }
29 }
30
30
31 RepositoriesModel::ItemType RepositoriesModel::type(const QModelIndex& index) const
31 RepositoriesModel::ItemType RepositoriesModel::type(const QModelIndex& index) const
32 {
32 {
33 if (RepoModelItem* item = to_item(index))
33 if (RepoModelItem* item = to_item(index))
34 {
34 {
35 return item->type;
35 return item->type;
36 }
36 }
37 return ItemType::None;
37 return ItemType::None;
38 }
38 }
39
39
40 void RepositoriesModel::refresh()
40 void RepositoriesModel::refresh()
41 {
41 {
42 beginResetModel();
42 beginResetModel();
43 _items.clear();
43 _items.clear();
44 _items.push_back(std::make_unique<RepoModelItem>("All"));
44 _items.push_back(std::make_unique<RepoModelItem>("All"));
45 _items.push_back(std::make_unique<RepoModelItem>("Trash"));
45 _items.push_back(std::make_unique<RepoModelItem>("Trash"));
46 auto repo_list = sqpApp->catalogueController().repositories();
46 auto repo_list = sqpApp->catalogueController().repositories();
47 std::transform(std::begin(repo_list), std::end(repo_list), std::back_inserter(_items),
47 std::transform(std::begin(repo_list), std::end(repo_list), std::back_inserter(_items),
48 [](const auto& repo_name) { return std::make_unique<RepoModelItem>(repo_name); });
48 [](const auto& repo_name) { return std::make_unique<RepoModelItem>(repo_name); });
49 endResetModel();
49 endResetModel();
50 }
50 }
51
51
52 QVariant RepositoriesModel::data(const QModelIndex& index, int role) const
52 QVariant RepositoriesModel::data(const QModelIndex& index, int role) const
53 {
53 {
54 if (index.isValid() && index.column() == 0)
54 if (index.isValid() && index.column() == 0)
55 {
55 {
56 return to_item(index)->data(role);
56 return to_item(index)->data(role);
57 }
57 }
58 return QVariant {};
58 return QVariant {};
59 }
59 }
60
60
61 QModelIndex RepositoriesModel::index(int row, int column, const QModelIndex& parent) const
61 QModelIndex RepositoriesModel::index(int row, int column, const QModelIndex& parent) const
62 {
62 {
63 if (!hasIndex(row, column, parent))
63 if (!hasIndex(row, column, parent))
64 {
64 {
65 return QModelIndex();
65 return QModelIndex();
66 }
66 }
67
67
68 switch (type(parent))
68 switch (type(parent))
69 {
69 {
70 case RepositoriesModel::ItemType::None: // is a repo
70 case RepositoriesModel::ItemType::None: // is a repo
71 return createIndex(row, column, _items[row].get());
71 return createIndex(row, column, _items[row].get());
72 case RepositoriesModel::ItemType::Repository: // is a catalogue
72 case RepositoriesModel::ItemType::Repository: // is a catalogue
73 return createIndex(row, column, to_item(parent)->children[row].get());
73 return createIndex(row, column, to_item(parent)->children[row].get());
74 case RepositoriesModel::ItemType::Catalogue:
74 case RepositoriesModel::ItemType::Catalogue:
75 return createIndex(row, column, new RepoModelItem());
75 return createIndex(row, column, new RepoModelItem());
76 }
76 }
77
77
78 return QModelIndex();
78 return QModelIndex();
79 }
79 }
80
80
81 QModelIndex RepositoriesModel::parent(const QModelIndex& index) const
81 QModelIndex RepositoriesModel::parent(const QModelIndex& index) const
82 {
82 {
83 auto item = to_item(index);
83 auto item = to_item(index);
84 if (item->type == ItemType::Catalogue)
84 if (item->type == ItemType::Catalogue)
85 {
85 {
86 auto repoIndex = SciQLop::containers::index_of(_items, item->parent);
86 auto repoIndex = cpp_utils::containers::index_of(_items, item->parent);
87 return createIndex(repoIndex, 0, item->parent);
87 return createIndex(repoIndex, 0, item->parent);
88 }
88 }
89 return QModelIndex();
89 return QModelIndex();
90 }
90 }
91
91
92 int RepositoriesModel::rowCount(const QModelIndex& parent) const
92 int RepositoriesModel::rowCount(const QModelIndex& parent) const
93 {
93 {
94 switch (type(parent))
94 switch (type(parent))
95 {
95 {
96 case RepositoriesModel::ItemType::None:
96 case RepositoriesModel::ItemType::None:
97 return _items.size();
97 return _items.size();
98 case RepositoriesModel::ItemType::Repository:
98 case RepositoriesModel::ItemType::Repository:
99 return to_item(parent)->children.size();
99 return to_item(parent)->children.size();
100 case RepositoriesModel::ItemType::Catalogue:
100 case RepositoriesModel::ItemType::Catalogue:
101 break;
101 break;
102 }
102 }
103 return 0;
103 return 0;
104 }
104 }
105
105
106 RepositoriesModel::RepoModelItem::RepoModelItem(const QString& repo)
106 RepositoriesModel::RepoModelItem::RepoModelItem(const QString& repo)
107 : type { ItemType::Repository }, item { repo }, icon { ":/icones/database.png" }
107 : type { ItemType::Repository }, item { repo }, icon { ":/icones/database.png" }
108 {
108 {
109 auto catalogues = sqpApp->catalogueController().catalogues(repo);
109 auto catalogues = sqpApp->catalogueController().catalogues(repo);
110 std::transform(std::begin(catalogues), std::end(catalogues), std::back_inserter(children),
110 std::transform(std::begin(catalogues), std::end(catalogues), std::back_inserter(children),
111 [this](auto& catalogue) { return std::make_unique<RepoModelItem>(catalogue, this); });
111 [this](auto& catalogue) { return std::make_unique<RepoModelItem>(catalogue, this); });
112 }
112 }
113
113
114 QVariant RepositoriesModel::RepoModelItem::data(int role) const
114 QVariant RepositoriesModel::RepoModelItem::data(int role) const
115 {
115 {
116 switch (role)
116 switch (role)
117 {
117 {
118 case Qt::EditRole:
118 case Qt::EditRole:
119 case Qt::DisplayRole:
119 case Qt::DisplayRole:
120 return text();
120 return text();
121 case Qt::DecorationRole:
121 case Qt::DecorationRole:
122 return QVariant { icon };
122 return QVariant { icon };
123 default:
123 default:
124 break;
124 break;
125 }
125 }
126 return QVariant {};
126 return QVariant {};
127 }
127 }
@@ -1,638 +1,638
1 #include "Visualization/VisualizationGraphHelper.h"
1 #include "Visualization/VisualizationGraphHelper.h"
2 #include "Visualization/qcustomplot.h"
2 #include "Visualization/qcustomplot.h"
3
3
4 #include <Data/ScalarTimeSerie.h>
4 #include <Data/ScalarTimeSerie.h>
5 #include <Data/SpectrogramTimeSerie.h>
5 #include <Data/SpectrogramTimeSerie.h>
6 #include <Data/TimeSeriesUtils.h>
6 #include <Data/TimeSeriesUtils.h>
7 #include <Data/VectorTimeSerie.h>
7 #include <Data/VectorTimeSerie.h>
8
8
9 #include <Common/cpp_utils.h>
9 #include <cpp_utils.hpp>
10 #include <Variable/Variable2.h>
10 #include <Variable/Variable2.h>
11 #include <algorithm>
11 #include <algorithm>
12 #include <cmath>
12 #include <cmath>
13
13
14 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
14 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
15
15
16 namespace
16 namespace
17 {
17 {
18
18
19 class SqpDataContainer : public QCPGraphDataContainer
19 class SqpDataContainer : public QCPGraphDataContainer
20 {
20 {
21 public:
21 public:
22 void appendGraphData(const QCPGraphData& data) { mData.append(data); }
22 void appendGraphData(const QCPGraphData& data) { mData.append(data); }
23 };
23 };
24
24
25 /**
25 /**
26 * Struct used to create plottables, depending on the type of the data series from which to create
26 * Struct used to create plottables, depending on the type of the data series from which to create
27 * them
27 * them
28 * @tparam T the data series' type
28 * @tparam T the data series' type
29 * @remarks Default implementation can't create plottables
29 * @remarks Default implementation can't create plottables
30 */
30 */
31 template <typename T, typename Enabled = void>
31 template <typename T, typename Enabled = void>
32 struct PlottablesCreator
32 struct PlottablesCreator
33 {
33 {
34 static PlottablesMap createPlottables(QCustomPlot&, const std::shared_ptr<T>& dataSeries)
34 static PlottablesMap createPlottables(QCustomPlot&, const std::shared_ptr<T>& dataSeries)
35 {
35 {
36 return {};
36 return {};
37 }
37 }
38 };
38 };
39
39
40 PlottablesMap createGraphs(QCustomPlot& plot, int nbGraphs)
40 PlottablesMap createGraphs(QCustomPlot& plot, int nbGraphs)
41 {
41 {
42 PlottablesMap result {};
42 PlottablesMap result {};
43
43
44 // Creates {nbGraphs} QCPGraph to add to the plot
44 // Creates {nbGraphs} QCPGraph to add to the plot
45 for (auto i = 0; i < nbGraphs; ++i)
45 for (auto i = 0; i < nbGraphs; ++i)
46 {
46 {
47 auto graph = plot.addGraph();
47 auto graph = plot.addGraph();
48 result.insert({ i, graph });
48 result.insert({ i, graph });
49 }
49 }
50
50
51 plot.replot();
51 plot.replot();
52
52
53 return result;
53 return result;
54 }
54 }
55
55
56 /**
56 /**
57 * Specialization of PlottablesCreator for scalars
57 * Specialization of PlottablesCreator for scalars
58 * @sa ScalarSeries
58 * @sa ScalarSeries
59 */
59 */
60 template <typename T>
60 template <typename T>
61 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<ScalarTimeSerie, T>::value>>
61 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<ScalarTimeSerie, T>::value>>
62 {
62 {
63 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
63 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
64 {
64 {
65 return createGraphs(plot, 1);
65 return createGraphs(plot, 1);
66 }
66 }
67 };
67 };
68
68
69 /**
69 /**
70 * Specialization of PlottablesCreator for vectors
70 * Specialization of PlottablesCreator for vectors
71 * @sa VectorSeries
71 * @sa VectorSeries
72 */
72 */
73 template <typename T>
73 template <typename T>
74 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<VectorTimeSerie, T>::value>>
74 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<VectorTimeSerie, T>::value>>
75 {
75 {
76 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
76 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
77 {
77 {
78 return createGraphs(plot, 3);
78 return createGraphs(plot, 3);
79 }
79 }
80 };
80 };
81
81
82 /**
82 /**
83 * Specialization of PlottablesCreator for MultiComponentTimeSeries
83 * Specialization of PlottablesCreator for MultiComponentTimeSeries
84 * @sa VectorSeries
84 * @sa VectorSeries
85 */
85 */
86 template <typename T>
86 template <typename T>
87 struct PlottablesCreator<T,
87 struct PlottablesCreator<T,
88 typename std::enable_if_t<std::is_base_of<MultiComponentTimeSerie, T>::value>>
88 typename std::enable_if_t<std::is_base_of<MultiComponentTimeSerie, T>::value>>
89 {
89 {
90 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
90 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
91 {
91 {
92 return createGraphs(plot, dataSeries->size(1));
92 return createGraphs(plot, dataSeries->size(1));
93 }
93 }
94 };
94 };
95
95
96 /**
96 /**
97 * Specialization of PlottablesCreator for spectrograms
97 * Specialization of PlottablesCreator for spectrograms
98 * @sa SpectrogramSeries
98 * @sa SpectrogramSeries
99 */
99 */
100 template <typename T>
100 template <typename T>
101 struct PlottablesCreator<T,
101 struct PlottablesCreator<T,
102 typename std::enable_if_t<std::is_base_of<SpectrogramTimeSerie, T>::value>>
102 typename std::enable_if_t<std::is_base_of<SpectrogramTimeSerie, T>::value>>
103 {
103 {
104 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
104 static PlottablesMap createPlottables(QCustomPlot& plot, const std::shared_ptr<T>& dataSeries)
105 {
105 {
106 PlottablesMap result {};
106 PlottablesMap result {};
107 result.insert({ 0, new QCPColorMap { plot.xAxis, plot.yAxis } });
107 result.insert({ 0, new QCPColorMap { plot.xAxis, plot.yAxis } });
108
108
109 plot.replot();
109 plot.replot();
110
110
111 return result;
111 return result;
112 }
112 }
113 };
113 };
114
114
115 /**
115 /**
116 * Struct used to update plottables, depending on the type of the data series from which to update
116 * Struct used to update plottables, depending on the type of the data series from which to update
117 * them
117 * them
118 * @tparam T the data series' type
118 * @tparam T the data series' type
119 * @remarks Default implementation can't update plottables
119 * @remarks Default implementation can't update plottables
120 */
120 */
121 template <typename T, typename Enabled = void>
121 template <typename T, typename Enabled = void>
122 struct PlottablesUpdater
122 struct PlottablesUpdater
123 {
123 {
124 static void setPlotYAxisRange(T&, const DateTimeRange&, QCustomPlot&)
124 static void setPlotYAxisRange(T&, const DateTimeRange&, QCustomPlot&)
125 {
125 {
126 qCCritical(LOG_VisualizationGraphHelper())
126 qCCritical(LOG_VisualizationGraphHelper())
127 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
127 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
128 }
128 }
129
129
130 static void updatePlottables(T&, PlottablesMap&, const DateTimeRange&, bool)
130 static void updatePlottables(T&, PlottablesMap&, const DateTimeRange&, bool)
131 {
131 {
132 qCCritical(LOG_VisualizationGraphHelper())
132 qCCritical(LOG_VisualizationGraphHelper())
133 << QObject::tr("Can't update plottables: unmanaged data series type");
133 << QObject::tr("Can't update plottables: unmanaged data series type");
134 }
134 }
135 };
135 };
136
136
137 /**
137 /**
138 * Specialization of PlottablesUpdater for scalars and vectors
138 * Specialization of PlottablesUpdater for scalars and vectors
139 * @sa ScalarSeries
139 * @sa ScalarSeries
140 * @sa VectorSeries
140 * @sa VectorSeries
141 */
141 */
142 template <typename T>
142 template <typename T>
143 struct PlottablesUpdater<T, typename std::enable_if_t<std::is_base_of<ScalarTimeSerie, T>::value>>
143 struct PlottablesUpdater<T, typename std::enable_if_t<std::is_base_of<ScalarTimeSerie, T>::value>>
144 {
144 {
145 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
145 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
146 {
146 {
147 auto minValue = 0., maxValue = 0.;
147 auto minValue = 0., maxValue = 0.;
148 if (auto serie = dynamic_cast<ScalarTimeSerie*>(&dataSeries))
148 if (auto serie = dynamic_cast<ScalarTimeSerie*>(&dataSeries))
149 {
149 {
150 if (serie->size())
150 if (serie->size())
151 {
151 {
152 maxValue = (*std::max_element(std::begin(*serie), std::end(*serie))).v();
152 maxValue = (*std::max_element(std::begin(*serie), std::end(*serie))).v();
153 minValue = (*std::min_element(std::begin(*serie), std::end(*serie))).v();
153 minValue = (*std::min_element(std::begin(*serie), std::end(*serie))).v();
154 }
154 }
155 }
155 }
156 plot.yAxis->setRange(QCPRange { minValue, maxValue });
156 plot.yAxis->setRange(QCPRange { minValue, maxValue });
157 }
157 }
158
158
159 static void updatePlottables(
159 static void updatePlottables(
160 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
160 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
161 {
161 {
162
162
163 // For each plottable to update, resets its data
163 // For each plottable to update, resets its data
164 for (const auto& plottable : plottables)
164 for (const auto& plottable : plottables)
165 {
165 {
166 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
166 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
167 {
167 {
168 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
168 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
169 if (auto serie = dynamic_cast<ScalarTimeSerie*>(&dataSeries))
169 if (auto serie = dynamic_cast<ScalarTimeSerie*>(&dataSeries))
170 {
170 {
171 std::for_each(
171 std::for_each(
172 std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) {
172 std::begin(*serie), std::end(*serie), [&dataContainer](const auto& value) {
173 dataContainer->appendGraphData(QCPGraphData(value.t(), value.v()));
173 dataContainer->appendGraphData(QCPGraphData(value.t(), value.v()));
174 });
174 });
175 }
175 }
176 graph->setData(dataContainer);
176 graph->setData(dataContainer);
177 }
177 }
178 }
178 }
179
179
180 if (!plottables.empty())
180 if (!plottables.empty())
181 {
181 {
182 auto plot = plottables.begin()->second->parentPlot();
182 auto plot = plottables.begin()->second->parentPlot();
183
183
184 if (rescaleAxes)
184 if (rescaleAxes)
185 {
185 {
186 plot->rescaleAxes();
186 plot->rescaleAxes();
187 }
187 }
188 }
188 }
189 }
189 }
190 };
190 };
191
191
192
192
193 template <typename T>
193 template <typename T>
194 struct PlottablesUpdater<T, typename std::enable_if_t<std::is_base_of<VectorTimeSerie, T>::value>>
194 struct PlottablesUpdater<T, typename std::enable_if_t<std::is_base_of<VectorTimeSerie, T>::value>>
195 {
195 {
196 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
196 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
197 {
197 {
198 double minValue = 0., maxValue = 0.;
198 double minValue = 0., maxValue = 0.;
199 if (auto serie = dynamic_cast<VectorTimeSerie*>(&dataSeries))
199 if (auto serie = dynamic_cast<VectorTimeSerie*>(&dataSeries))
200 {
200 {
201 std::for_each(
201 std::for_each(
202 std::begin(*serie), std::end(*serie), [&minValue, &maxValue](const auto& v) {
202 std::begin(*serie), std::end(*serie), [&minValue, &maxValue](const auto& v) {
203 minValue = std::min({ minValue, v.v().x, v.v().y, v.v().z });
203 minValue = std::min({ minValue, v.v().x, v.v().y, v.v().z });
204 maxValue = std::max({ maxValue, v.v().x, v.v().y, v.v().z });
204 maxValue = std::max({ maxValue, v.v().x, v.v().y, v.v().z });
205 });
205 });
206 }
206 }
207
207
208 plot.yAxis->setRange(QCPRange { minValue, maxValue });
208 plot.yAxis->setRange(QCPRange { minValue, maxValue });
209 }
209 }
210
210
211 static void updatePlottables(
211 static void updatePlottables(
212 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
212 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
213 {
213 {
214
214
215 // For each plottable to update, resets its data
215 // For each plottable to update, resets its data
216 for (const auto& plottable : plottables)
216 for (const auto& plottable : plottables)
217 {
217 {
218 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
218 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
219 {
219 {
220 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
220 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
221 if (auto serie = dynamic_cast<VectorTimeSerie*>(&dataSeries))
221 if (auto serie = dynamic_cast<VectorTimeSerie*>(&dataSeries))
222 {
222 {
223 switch (plottable.first)
223 switch (plottable.first)
224 {
224 {
225 case 0:
225 case 0:
226 std::for_each(std::begin(*serie), std::end(*serie),
226 std::for_each(std::begin(*serie), std::end(*serie),
227 [&dataContainer](const auto& value) {
227 [&dataContainer](const auto& value) {
228 dataContainer->appendGraphData(
228 dataContainer->appendGraphData(
229 QCPGraphData(value.t(), value.v().x));
229 QCPGraphData(value.t(), value.v().x));
230 });
230 });
231 break;
231 break;
232 case 1:
232 case 1:
233 std::for_each(std::begin(*serie), std::end(*serie),
233 std::for_each(std::begin(*serie), std::end(*serie),
234 [&dataContainer](const auto& value) {
234 [&dataContainer](const auto& value) {
235 dataContainer->appendGraphData(
235 dataContainer->appendGraphData(
236 QCPGraphData(value.t(), value.v().y));
236 QCPGraphData(value.t(), value.v().y));
237 });
237 });
238 break;
238 break;
239 case 2:
239 case 2:
240 std::for_each(std::begin(*serie), std::end(*serie),
240 std::for_each(std::begin(*serie), std::end(*serie),
241 [&dataContainer](const auto& value) {
241 [&dataContainer](const auto& value) {
242 dataContainer->appendGraphData(
242 dataContainer->appendGraphData(
243 QCPGraphData(value.t(), value.v().z));
243 QCPGraphData(value.t(), value.v().z));
244 });
244 });
245 break;
245 break;
246 default:
246 default:
247 break;
247 break;
248 }
248 }
249 }
249 }
250 graph->setData(dataContainer);
250 graph->setData(dataContainer);
251 }
251 }
252 }
252 }
253
253
254 if (!plottables.empty())
254 if (!plottables.empty())
255 {
255 {
256 auto plot = plottables.begin()->second->parentPlot();
256 auto plot = plottables.begin()->second->parentPlot();
257
257
258 if (rescaleAxes)
258 if (rescaleAxes)
259 {
259 {
260 plot->rescaleAxes();
260 plot->rescaleAxes();
261 }
261 }
262 }
262 }
263 }
263 }
264 };
264 };
265
265
266
266
267 template <typename T>
267 template <typename T>
268 struct PlottablesUpdater<T,
268 struct PlottablesUpdater<T,
269 typename std::enable_if_t<std::is_base_of<MultiComponentTimeSerie, T>::value>>
269 typename std::enable_if_t<std::is_base_of<MultiComponentTimeSerie, T>::value>>
270 {
270 {
271 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
271 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
272 {
272 {
273 double minValue = 0., maxValue = 0.;
273 double minValue = 0., maxValue = 0.;
274 if (auto serie = dynamic_cast<MultiComponentTimeSerie*>(&dataSeries))
274 if (auto serie = dynamic_cast<MultiComponentTimeSerie*>(&dataSeries))
275 {
275 {
276 std::for_each(std::begin(*serie), std::end(*serie), [&minValue, &maxValue](auto& v) {
276 std::for_each(std::begin(*serie), std::end(*serie), [&minValue, &maxValue](auto& v) {
277 minValue = std::min(minValue, std::min_element(v.begin(), v.end())->v());
277 minValue = std::min(minValue, std::min_element(v.begin(), v.end())->v());
278 maxValue = std::max(maxValue, std::max_element(v.begin(), v.end())->v());
278 maxValue = std::max(maxValue, std::max_element(v.begin(), v.end())->v());
279 });
279 });
280 }
280 }
281 plot.yAxis->setRange(QCPRange { minValue, maxValue });
281 plot.yAxis->setRange(QCPRange { minValue, maxValue });
282 }
282 }
283
283
284 static void updatePlottables(
284 static void updatePlottables(
285 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
285 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
286 {
286 {
287 for (const auto& plottable : plottables)
287 for (const auto& plottable : plottables)
288 {
288 {
289 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
289 if (auto graph = dynamic_cast<QCPGraph*>(plottable.second))
290 {
290 {
291 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
291 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
292 if (auto serie = dynamic_cast<MultiComponentTimeSerie*>(&dataSeries))
292 if (auto serie = dynamic_cast<MultiComponentTimeSerie*>(&dataSeries))
293 {
293 {
294 // TODO
294 // TODO
295 std::for_each(std::begin(*serie), std::end(*serie),
295 std::for_each(std::begin(*serie), std::end(*serie),
296 [&dataContainer, component = plottable.first](const auto& value) {
296 [&dataContainer, component = plottable.first](const auto& value) {
297 dataContainer->appendGraphData(
297 dataContainer->appendGraphData(
298 QCPGraphData(value.t(), value[component]));
298 QCPGraphData(value.t(), value[component]));
299 });
299 });
300 }
300 }
301 graph->setData(dataContainer);
301 graph->setData(dataContainer);
302 }
302 }
303 }
303 }
304
304
305 if (!plottables.empty())
305 if (!plottables.empty())
306 {
306 {
307 auto plot = plottables.begin()->second->parentPlot();
307 auto plot = plottables.begin()->second->parentPlot();
308
308
309 if (rescaleAxes)
309 if (rescaleAxes)
310 {
310 {
311 plot->rescaleAxes();
311 plot->rescaleAxes();
312 }
312 }
313 }
313 }
314 }
314 }
315 };
315 };
316
316
317 /*=============================================================*/
317 /*=============================================================*/
318 // TODO move this to dedicated srcs
318 // TODO move this to dedicated srcs
319 /*=============================================================*/
319 /*=============================================================*/
320 struct ColomapProperties
320 struct ColomapProperties
321 {
321 {
322 int h_size_px;
322 int h_size_px;
323 int v_size_px;
323 int v_size_px;
324 double h_resolutuon;
324 double h_resolutuon;
325 double v_resolutuon;
325 double v_resolutuon;
326 };
326 };
327
327
328 inline ColomapProperties CMAxisAnalysis(const TimeSeriesUtils::axis_properties& xAxisProperties,
328 inline ColomapProperties CMAxisAnalysis(const TimeSeriesUtils::axis_properties& xAxisProperties,
329 const TimeSeriesUtils::axis_properties& yAxisProperties)
329 const TimeSeriesUtils::axis_properties& yAxisProperties)
330 {
330 {
331 int colormap_h_size
331 int colormap_h_size
332 = std::min(32000, static_cast<int>(xAxisProperties.range / xAxisProperties.max_resolution));
332 = std::min(32000, static_cast<int>(xAxisProperties.range / xAxisProperties.max_resolution));
333 int colormap_v_size = static_cast<int>(yAxisProperties.range / yAxisProperties.max_resolution);
333 int colormap_v_size = static_cast<int>(yAxisProperties.range / yAxisProperties.max_resolution);
334 double colormap_h_resolution = xAxisProperties.range / static_cast<double>(colormap_h_size);
334 double colormap_h_resolution = xAxisProperties.range / static_cast<double>(colormap_h_size);
335 double colormap_v_resolution = yAxisProperties.range / static_cast<double>(colormap_v_size);
335 double colormap_v_resolution = yAxisProperties.range / static_cast<double>(colormap_v_size);
336 return ColomapProperties { colormap_h_size, colormap_v_size, colormap_h_resolution,
336 return ColomapProperties { colormap_h_size, colormap_v_size, colormap_h_resolution,
337 colormap_v_resolution };
337 colormap_v_resolution };
338 }
338 }
339
339
340
340
341 template <bool condition, typename T, typename U>
341 template <bool condition, typename T, typename U>
342 std::enable_if_t<condition, T> constexpr conditional_v(T first, U second)
342 std::enable_if_t<condition, T> constexpr conditional_v(T first, U second)
343 {
343 {
344 return first;
344 return first;
345 }
345 }
346
346
347 template <bool condition, typename T, typename U>
347 template <bool condition, typename T, typename U>
348 std::enable_if_t<!condition, U> constexpr conditional_v(T first, U second)
348 std::enable_if_t<!condition, U> constexpr conditional_v(T first, U second)
349 {
349 {
350 return second;
350 return second;
351 }
351 }
352
352
353 template <bool reversedAxis = true, bool reversedData = true>
353 template <bool reversedAxis = true, bool reversedData = true>
354 inline std::vector<std::pair<int, int>> build_access_pattern(const std::vector<double>& axis,
354 inline std::vector<std::pair<int, int>> build_access_pattern(const std::vector<double>& axis,
355 const TimeSeriesUtils::axis_properties& axisProperties,
355 const TimeSeriesUtils::axis_properties& axisProperties,
356 const ColomapProperties& colormap_properties)
356 const ColomapProperties& colormap_properties)
357 {
357 {
358 std::vector<std::pair<int, int>> access_pattern;
358 std::vector<std::pair<int, int>> access_pattern;
359 for (int index = 0, axis_index = conditional_v<reversedAxis>(axis.size() - 1, 0),
359 for (int index = 0, axis_index = conditional_v<reversedAxis>(axis.size() - 1, 0),
360 data_index = conditional_v<reversedData>(axis.size() - 1, 0);
360 data_index = conditional_v<reversedData>(axis.size() - 1, 0);
361 index < colormap_properties.v_size_px; index++)
361 index < colormap_properties.v_size_px; index++)
362 {
362 {
363 double current_y = (axisProperties.max_resolution * index) + axisProperties.min;
363 double current_y = (axisProperties.max_resolution * index) + axisProperties.min;
364 if (current_y > axis[axis_index])
364 if (current_y > axis[axis_index])
365 {
365 {
366 conditional_v<reversedAxis>(
366 conditional_v<reversedAxis>(
367 [&axis_index]() { axis_index--; }, [&axis_index]() { axis_index++; })();
367 [&axis_index]() { axis_index--; }, [&axis_index]() { axis_index++; })();
368 conditional_v<reversedData>(
368 conditional_v<reversedData>(
369 [&data_index]() { data_index--; }, [&data_index]() { data_index++; })();
369 [&data_index]() { data_index--; }, [&data_index]() { data_index++; })();
370 }
370 }
371 access_pattern.push_back({ index, data_index });
371 access_pattern.push_back({ index, data_index });
372 }
372 }
373 return access_pattern;
373 return access_pattern;
374 }
374 }
375
375
376 inline bool is_log(const std::vector<double>& axis)
376 inline bool is_log(const std::vector<double>& axis)
377 {
377 {
378 if (axis.size() > 2)
378 if (axis.size() > 2)
379 {
379 {
380 auto first = axis.front(), midle = axis[axis.size() / 2], last = axis.back();
380 auto first = axis.front(), midle = axis[axis.size() / 2], last = axis.back();
381 auto error_linear = (midle - (last + first) / 2) / midle;
381 auto error_linear = (midle - (last + first) / 2) / midle;
382 first = log10(first);
382 first = log10(first);
383 midle = log10(midle);
383 midle = log10(midle);
384 last = log10(last);
384 last = log10(last);
385 auto error_log = (midle - (last + first) / 2) / midle;
385 auto error_log = (midle - (last + first) / 2) / midle;
386 return error_log < error_linear;
386 return error_log < error_linear;
387 }
387 }
388 return false;
388 return false;
389 }
389 }
390
390
391 template <typename accessPattern_t, typename colomapT, typename axProp_t, typename cmProp_t>
391 template <typename accessPattern_t, typename colomapT, typename axProp_t, typename cmProp_t>
392 inline void fill_data(SpectrogramTimeSerie* serie, colomapT* colormap,
392 inline void fill_data(SpectrogramTimeSerie* serie, colomapT* colormap,
393 const accessPattern_t& y_access_pattern, const axProp_t& xAxisProperties,
393 const accessPattern_t& y_access_pattern, const axProp_t& xAxisProperties,
394 const cmProp_t& colormap_properties)
394 const cmProp_t& colormap_properties)
395 {
395 {
396 auto line = serie->begin();
396 auto line = serie->begin();
397 auto next_line = line + 1;
397 auto next_line = line + 1;
398 double current_time = xAxisProperties.min;
398 double current_time = xAxisProperties.min;
399 int x_index = 0;
399 int x_index = 0;
400 auto x_min_resolution
400 auto x_min_resolution
401 = std::fmin(2. * serie->max_sampling, xAxisProperties.max_resolution * 100.);
401 = std::fmin(2. * serie->max_sampling, xAxisProperties.max_resolution * 100.);
402 std::vector<double> line_values(serie->size(1));
402 std::vector<double> line_values(serie->size(1));
403 double avg_coef = 0.;
403 double avg_coef = 0.;
404 while (x_index < colormap_properties.h_size_px)
404 while (x_index < colormap_properties.h_size_px)
405 {
405 {
406 if (next_line != std::end(*serie) and current_time >= next_line->t())
406 if (next_line != std::end(*serie) and current_time >= next_line->t())
407 {
407 {
408 line = next_line;
408 line = next_line;
409 next_line++;
409 next_line++;
410 }
410 }
411 if ((current_time - xAxisProperties.min)
411 if ((current_time - xAxisProperties.min)
412 > (static_cast<double>(x_index + 1) * colormap_properties.h_resolutuon))
412 > (static_cast<double>(x_index + 1) * colormap_properties.h_resolutuon))
413 {
413 {
414 std::for_each(std::cbegin(y_access_pattern), std::cend(y_access_pattern),
414 std::for_each(std::cbegin(y_access_pattern), std::cend(y_access_pattern),
415 [&colormap, &line_values, x_index, avg_coef](const auto& acc) {
415 [&colormap, &line_values, x_index, avg_coef](const auto& acc) {
416 colormap->data()->setCell(
416 colormap->data()->setCell(
417 x_index, acc.first, line_values[acc.second] / avg_coef);
417 x_index, acc.first, line_values[acc.second] / avg_coef);
418 });
418 });
419 std::fill(std::begin(line_values), std::end(line_values), 0.);
419 std::fill(std::begin(line_values), std::end(line_values), 0.);
420 x_index++;
420 x_index++;
421 avg_coef = 0.;
421 avg_coef = 0.;
422 }
422 }
423 if (line->t() + x_min_resolution > current_time)
423 if (line->t() + x_min_resolution > current_time)
424 {
424 {
425 {
425 {
426 std::transform(std::begin(*line), std::end(*line), std::cbegin(line_values),
426 std::transform(std::begin(*line), std::end(*line), std::cbegin(line_values),
427 std::begin(line_values),
427 std::begin(line_values),
428 [](const auto& input, auto output) { return input.v() + output; });
428 [](const auto& input, auto output) { return input.v() + output; });
429 }
429 }
430 avg_coef += 1.;
430 avg_coef += 1.;
431 }
431 }
432 else
432 else
433 {
433 {
434 for (int y_index = 0; y_index < colormap_properties.v_size_px; y_index++)
434 for (int y_index = 0; y_index < colormap_properties.v_size_px; y_index++)
435 {
435 {
436 if (avg_coef > 0.)
436 if (avg_coef > 0.)
437 {
437 {
438 std::fill(std::begin(line_values), std::end(line_values), 0);
438 std::fill(std::begin(line_values), std::end(line_values), 0);
439 }
439 }
440 }
440 }
441 }
441 }
442 current_time += xAxisProperties.max_resolution * 0.9;
442 current_time += xAxisProperties.max_resolution * 0.9;
443 }
443 }
444 }
444 }
445
445
446 /*=============================================================*/
446 /*=============================================================*/
447
447
448 /**
448 /**
449 * Specialization of PlottablesUpdater for spectrograms
449 * Specialization of PlottablesUpdater for spectrograms
450 * @sa SpectrogramSeries
450 * @sa SpectrogramSeries
451 */
451 */
452 template <typename T>
452 template <typename T>
453 struct PlottablesUpdater<T,
453 struct PlottablesUpdater<T,
454 typename std::enable_if_t<std::is_base_of<SpectrogramTimeSerie, T>::value>>
454 typename std::enable_if_t<std::is_base_of<SpectrogramTimeSerie, T>::value>>
455 {
455 {
456 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
456 static void setPlotYAxisRange(T& dataSeries, const DateTimeRange& xAxisRange, QCustomPlot& plot)
457 {
457 {
458 auto [minValue, maxValue] = dataSeries.axis_range(1);
458 auto [minValue, maxValue] = dataSeries.axis_range(1);
459 plot.yAxis->setRange(QCPRange { minValue, maxValue });
459 plot.yAxis->setRange(QCPRange { minValue, maxValue });
460 }
460 }
461
461
462 static void updatePlottables(
462 static void updatePlottables(
463 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
463 T& dataSeries, PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes)
464 {
464 {
465 if (plottables.empty())
465 if (plottables.empty())
466 {
466 {
467 qCDebug(LOG_VisualizationGraphHelper())
467 qCDebug(LOG_VisualizationGraphHelper())
468 << QObject::tr("Can't update spectrogram: no colormap has been associated");
468 << QObject::tr("Can't update spectrogram: no colormap has been associated");
469 return;
469 return;
470 }
470 }
471
471
472 // Gets the colormap to update (normally there is only one colormap)
472 // Gets the colormap to update (normally there is only one colormap)
473 Q_ASSERT(plottables.size() == 1);
473 Q_ASSERT(plottables.size() == 1);
474 auto colormap = dynamic_cast<QCPColorMap*>(plottables.at(0));
474 auto colormap = dynamic_cast<QCPColorMap*>(plottables.at(0));
475 Q_ASSERT(colormap != nullptr);
475 Q_ASSERT(colormap != nullptr);
476 auto plot = colormap->parentPlot();
476 auto plot = colormap->parentPlot();
477 auto [minValue, maxValue] = dataSeries.axis_range(1);
477 auto [minValue, maxValue] = dataSeries.axis_range(1);
478 plot->yAxis->setRange(QCPRange { minValue, maxValue });
478 plot->yAxis->setRange(QCPRange { minValue, maxValue });
479 if (auto serie = dynamic_cast<SpectrogramTimeSerie*>(&dataSeries))
479 if (auto serie = dynamic_cast<SpectrogramTimeSerie*>(&dataSeries))
480 {
480 {
481 if (serie->size(0) > 2)
481 if (serie->size(0) > 2)
482 {
482 {
483 if (serie->y_is_log)
483 if (serie->y_is_log)
484 colormap->setDataScaleType(QCPAxis::stLogarithmic);
484 colormap->setDataScaleType(QCPAxis::stLogarithmic);
485 else
485 else
486 colormap->setDataScaleType(QCPAxis::stLinear);
486 colormap->setDataScaleType(QCPAxis::stLinear);
487 const auto& xAxis = serie->axis(0);
487 const auto& xAxis = serie->axis(0);
488 auto yAxis = serie->axis(1); // copy for in place reverse order
488 auto yAxis = serie->axis(1); // copy for in place reverse order
489 auto y_is_log = is_log(yAxis);
489 auto y_is_log = is_log(yAxis);
490 std::reverse(std::begin(yAxis), std::end(yAxis));
490 std::reverse(std::begin(yAxis), std::end(yAxis));
491 auto xAxisProperties = TimeSeriesUtils::axis_analysis<TimeSeriesUtils::IsLinear,
491 auto xAxisProperties = TimeSeriesUtils::axis_analysis<TimeSeriesUtils::IsLinear,
492 TimeSeriesUtils::CheckMedian>(xAxis, serie->min_sampling);
492 TimeSeriesUtils::CheckMedian>(xAxis, serie->min_sampling);
493 auto yAxisProperties = TimeSeriesUtils::axis_analysis<TimeSeriesUtils::IsLog,
493 auto yAxisProperties = TimeSeriesUtils::axis_analysis<TimeSeriesUtils::IsLog,
494 TimeSeriesUtils::DontCheckMedian>(yAxis);
494 TimeSeriesUtils::DontCheckMedian>(yAxis);
495 auto colormap_properties = CMAxisAnalysis(xAxisProperties, yAxisProperties);
495 auto colormap_properties = CMAxisAnalysis(xAxisProperties, yAxisProperties);
496
496
497 colormap->data()->setSize(
497 colormap->data()->setSize(
498 colormap_properties.h_size_px, colormap_properties.v_size_px);
498 colormap_properties.h_size_px, colormap_properties.v_size_px);
499 colormap->data()->setRange(
499 colormap->data()->setRange(
500 QCPRange { xAxisProperties.min, xAxisProperties.max }, { minValue, maxValue });
500 QCPRange { xAxisProperties.min, xAxisProperties.max }, { minValue, maxValue });
501
501
502 auto y_access_pattern = build_access_pattern<false, true>(
502 auto y_access_pattern = build_access_pattern<false, true>(
503 yAxis, yAxisProperties, colormap_properties);
503 yAxis, yAxisProperties, colormap_properties);
504 fill_data(serie, colormap, y_access_pattern, xAxisProperties, colormap_properties);
504 fill_data(serie, colormap, y_access_pattern, xAxisProperties, colormap_properties);
505 }
505 }
506 colormap->rescaleDataRange(true);
506 colormap->rescaleDataRange(true);
507 if (rescaleAxes)
507 if (rescaleAxes)
508 {
508 {
509 plot->rescaleAxes();
509 plot->rescaleAxes();
510 }
510 }
511 }
511 }
512 }
512 }
513 };
513 };
514
514
515 /**
515 /**
516 * Helper used to create/update plottables
516 * Helper used to create/update plottables
517 */
517 */
518 struct IPlottablesHelper
518 struct IPlottablesHelper
519 {
519 {
520 virtual ~IPlottablesHelper() noexcept = default;
520 virtual ~IPlottablesHelper() noexcept = default;
521 virtual PlottablesMap create(QCustomPlot& plot) const = 0;
521 virtual PlottablesMap create(QCustomPlot& plot) const = 0;
522 virtual void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const = 0;
522 virtual void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const = 0;
523 virtual void update(
523 virtual void update(
524 PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes = false) const = 0;
524 PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes = false) const = 0;
525 };
525 };
526
526
527 /**
527 /**
528 * Default implementation of IPlottablesHelper, which takes data series to create/update
528 * Default implementation of IPlottablesHelper, which takes data series to create/update
529 * plottables
529 * plottables
530 * @tparam T the data series' type
530 * @tparam T the data series' type
531 */
531 */
532 template <typename T>
532 template <typename T>
533 struct PlottablesHelper : public IPlottablesHelper
533 struct PlottablesHelper : public IPlottablesHelper
534 {
534 {
535 explicit PlottablesHelper(std::shared_ptr<T> dataSeries) : m_DataSeries { dataSeries } {}
535 explicit PlottablesHelper(std::shared_ptr<T> dataSeries) : m_DataSeries { dataSeries } {}
536
536
537 PlottablesMap create(QCustomPlot& plot) const override
537 PlottablesMap create(QCustomPlot& plot) const override
538 {
538 {
539 return PlottablesCreator<T>::createPlottables(plot, m_DataSeries);
539 return PlottablesCreator<T>::createPlottables(plot, m_DataSeries);
540 }
540 }
541
541
542 void update(
542 void update(
543 PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) const override
543 PlottablesMap& plottables, const DateTimeRange& range, bool rescaleAxes) const override
544 {
544 {
545 if (m_DataSeries)
545 if (m_DataSeries)
546 {
546 {
547 PlottablesUpdater<T>::updatePlottables(*m_DataSeries, plottables, range, rescaleAxes);
547 PlottablesUpdater<T>::updatePlottables(*m_DataSeries, plottables, range, rescaleAxes);
548 }
548 }
549 else
549 else
550 {
550 {
551 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
551 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
552 "between the type of data series and the "
552 "between the type of data series and the "
553 "type supposed";
553 "type supposed";
554 }
554 }
555 }
555 }
556
556
557 void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const override
557 void setYAxisRange(const DateTimeRange& xAxisRange, QCustomPlot& plot) const override
558 {
558 {
559 if (m_DataSeries)
559 if (m_DataSeries)
560 {
560 {
561 PlottablesUpdater<T>::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot);
561 PlottablesUpdater<T>::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot);
562 }
562 }
563 else
563 else
564 {
564 {
565 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
565 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
566 "between the type of data series and the "
566 "between the type of data series and the "
567 "type supposed";
567 "type supposed";
568 }
568 }
569 }
569 }
570
570
571 std::shared_ptr<T> m_DataSeries;
571 std::shared_ptr<T> m_DataSeries;
572 };
572 };
573
573
574 /// Creates IPlottablesHelper according to the type of data series a variable holds
574 /// Creates IPlottablesHelper according to the type of data series a variable holds
575 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<Variable2> variable) noexcept
575 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<Variable2> variable) noexcept
576 {
576 {
577 switch (variable->type())
577 switch (variable->type())
578 {
578 {
579 case DataSeriesType::SCALAR:
579 case DataSeriesType::SCALAR:
580 return std::make_unique<PlottablesHelper<ScalarTimeSerie>>(
580 return std::make_unique<PlottablesHelper<ScalarTimeSerie>>(
581 std::dynamic_pointer_cast<ScalarTimeSerie>(variable->data()));
581 std::dynamic_pointer_cast<ScalarTimeSerie>(variable->data()));
582 case DataSeriesType::SPECTROGRAM:
582 case DataSeriesType::SPECTROGRAM:
583 return std::make_unique<PlottablesHelper<SpectrogramTimeSerie>>(
583 return std::make_unique<PlottablesHelper<SpectrogramTimeSerie>>(
584 std::dynamic_pointer_cast<SpectrogramTimeSerie>(variable->data()));
584 std::dynamic_pointer_cast<SpectrogramTimeSerie>(variable->data()));
585 case DataSeriesType::VECTOR:
585 case DataSeriesType::VECTOR:
586 return std::make_unique<PlottablesHelper<VectorTimeSerie>>(
586 return std::make_unique<PlottablesHelper<VectorTimeSerie>>(
587 std::dynamic_pointer_cast<VectorTimeSerie>(variable->data()));
587 std::dynamic_pointer_cast<VectorTimeSerie>(variable->data()));
588 case DataSeriesType::MULTICOMPONENT:
588 case DataSeriesType::MULTICOMPONENT:
589 return std::make_unique<PlottablesHelper<MultiComponentTimeSerie>>(
589 return std::make_unique<PlottablesHelper<MultiComponentTimeSerie>>(
590 std::dynamic_pointer_cast<MultiComponentTimeSerie>(variable->data()));
590 std::dynamic_pointer_cast<MultiComponentTimeSerie>(variable->data()));
591 default:
591 default:
592 // Creates default helper
592 // Creates default helper
593 break;
593 break;
594 }
594 }
595
595
596 return std::make_unique<PlottablesHelper<TimeSeries::ITimeSerie>>(nullptr);
596 return std::make_unique<PlottablesHelper<TimeSeries::ITimeSerie>>(nullptr);
597 }
597 }
598
598
599 } // namespace
599 } // namespace
600
600
601 PlottablesMap VisualizationGraphHelper::create(
601 PlottablesMap VisualizationGraphHelper::create(
602 std::shared_ptr<Variable2> variable, QCustomPlot& plot) noexcept
602 std::shared_ptr<Variable2> variable, QCustomPlot& plot) noexcept
603 {
603 {
604 if (variable)
604 if (variable)
605 {
605 {
606 auto helper = createHelper(variable);
606 auto helper = createHelper(variable);
607 auto plottables = helper->create(plot);
607 auto plottables = helper->create(plot);
608 return plottables;
608 return plottables;
609 }
609 }
610 else
610 else
611 {
611 {
612 qCDebug(LOG_VisualizationGraphHelper())
612 qCDebug(LOG_VisualizationGraphHelper())
613 << QObject::tr("Can't create graph plottables : the variable is null");
613 << QObject::tr("Can't create graph plottables : the variable is null");
614 return PlottablesMap {};
614 return PlottablesMap {};
615 }
615 }
616 }
616 }
617
617
618 void VisualizationGraphHelper::setYAxisRange(
618 void VisualizationGraphHelper::setYAxisRange(
619 std::shared_ptr<Variable2> variable, QCustomPlot& plot) noexcept
619 std::shared_ptr<Variable2> variable, QCustomPlot& plot) noexcept
620 {
620 {
621 if (variable)
621 if (variable)
622 {
622 {
623 auto helper = createHelper(variable);
623 auto helper = createHelper(variable);
624 helper->setYAxisRange(variable->range(), plot);
624 helper->setYAxisRange(variable->range(), plot);
625 }
625 }
626 else
626 else
627 {
627 {
628 qCDebug(LOG_VisualizationGraphHelper())
628 qCDebug(LOG_VisualizationGraphHelper())
629 << QObject::tr("Can't set y-axis range of plot: the variable is null");
629 << QObject::tr("Can't set y-axis range of plot: the variable is null");
630 }
630 }
631 }
631 }
632
632
633 void VisualizationGraphHelper::updateData(
633 void VisualizationGraphHelper::updateData(
634 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& dateTime)
634 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& dateTime)
635 {
635 {
636 auto helper = createHelper(variable);
636 auto helper = createHelper(variable);
637 helper->update(plottables, dateTime);
637 helper->update(plottables, dateTime);
638 }
638 }
@@ -1,1577 +1,1578
1 #include "Visualization/VisualizationGraphWidget.h"
1 #include "Visualization/VisualizationGraphWidget.h"
2 #include "Visualization/IVisualizationWidgetVisitor.h"
2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 #include "Visualization/VisualizationCursorItem.h"
3 #include "Visualization/VisualizationCursorItem.h"
4 #include "Visualization/VisualizationDefs.h"
4 #include "Visualization/VisualizationDefs.h"
5 #include "Visualization/VisualizationGraphHelper.h"
5 #include "Visualization/VisualizationGraphHelper.h"
6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 #include "Visualization/VisualizationSelectionZoneItem.h"
8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 #include "Visualization/VisualizationSelectionZoneManager.h"
9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 #include "Visualization/VisualizationWidget.h"
10 #include "Visualization/VisualizationWidget.h"
11 #include "Visualization/VisualizationZoneWidget.h"
11 #include "Visualization/VisualizationZoneWidget.h"
12 #include "ui_VisualizationGraphWidget.h"
12 #include "ui_VisualizationGraphWidget.h"
13
13
14 #include <Actions/ActionsGuiController.h>
14 #include <Actions/ActionsGuiController.h>
15 #include <Actions/FilteringAction.h>
15 #include <Actions/FilteringAction.h>
16 #include <Common/MimeTypesDef.h>
16 #include <Common/MimeTypesDef.h>
17 #include <Common/containers.h>
17 #include <cpp_utils_qt/cpp_utils_qt.hpp>
18 #include <containers/algorithms.hpp>
18 #include <Data/DateTimeRangeHelper.h>
19 #include <Data/DateTimeRangeHelper.h>
19 #include <DragAndDrop/DragDropGuiController.h>
20 #include <DragAndDrop/DragDropGuiController.h>
20 #include <Settings/SqpSettingsDefs.h>
21 #include <Settings/SqpSettingsDefs.h>
21 #include <SqpApplication.h>
22 #include <SqpApplication.h>
22 #include <Time/TimeController.h>
23 #include <Time/TimeController.h>
23 #include <Variable/Variable2.h>
24 #include <Variable/Variable2.h>
24 #include <Variable/VariableController2.h>
25 #include <Variable/VariableController2.h>
25
26
26 #include <unordered_map>
27 #include <unordered_map>
27
28
28 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29
30
30 namespace
31 namespace
31 {
32 {
32
33
33 /// Key pressed to enable drag&drop in all modes
34 /// Key pressed to enable drag&drop in all modes
34 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
35 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
35
36
36 /// Key pressed to enable zoom on horizontal axis
37 /// Key pressed to enable zoom on horizontal axis
37 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
38 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
38
39
39 /// Key pressed to enable zoom on vertical axis
40 /// Key pressed to enable zoom on vertical axis
40 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
41 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
41
42
42 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
43 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
43 const auto PAN_SPEED = 5;
44 const auto PAN_SPEED = 5;
44
45
45 /// Key pressed to enable a calibration pan
46 /// Key pressed to enable a calibration pan
46 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
47 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
47
48
48 /// Key pressed to enable multi selection of selection zones
49 /// Key pressed to enable multi selection of selection zones
49 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
50 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
50
51
51 /// Minimum size for the zoom box, in percentage of the axis range
52 /// Minimum size for the zoom box, in percentage of the axis range
52 const auto ZOOM_BOX_MIN_SIZE = 0.8;
53 const auto ZOOM_BOX_MIN_SIZE = 0.8;
53
54
54 /// Format of the dates appearing in the label of a cursor
55 /// Format of the dates appearing in the label of a cursor
55 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
56 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
56
57
57 } // namespace
58 } // namespace
58
59
59 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate
60 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate
60 {
61 {
61
62
62 explicit VisualizationGraphWidgetPrivate(const QString& name)
63 explicit VisualizationGraphWidgetPrivate(const QString& name)
63 : m_Name { name }
64 : m_Name { name }
64 , m_Flags { GraphFlag::EnableAll }
65 , m_Flags { GraphFlag::EnableAll }
65 , m_IsCalibration { false }
66 , m_IsCalibration { false }
66 , m_RenderingDelegate { nullptr }
67 , m_RenderingDelegate { nullptr }
67 {
68 {
68 m_plot = new QCustomPlot();
69 m_plot = new QCustomPlot();
69 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
70 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
70 m_plot->setPlottingHint(QCP::phFastPolylines, true);
71 m_plot->setPlottingHint(QCP::phFastPolylines, true);
71 }
72 }
72
73
73 void updateData(
74 void updateData(
74 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& range)
75 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& range)
75 {
76 {
76 VisualizationGraphHelper::updateData(plottables, variable, range);
77 VisualizationGraphHelper::updateData(plottables, variable, range);
77
78
78 // Prevents that data has changed to update rendering
79 // Prevents that data has changed to update rendering
79 m_RenderingDelegate->onPlotUpdated();
80 m_RenderingDelegate->onPlotUpdated();
80 }
81 }
81
82
82 QString m_Name;
83 QString m_Name;
83 // 1 variable -> n qcpplot
84 // 1 variable -> n qcpplot
84 std::map<std::shared_ptr<Variable2>, PlottablesMap> m_VariableToPlotMultiMap;
85 std::map<std::shared_ptr<Variable2>, PlottablesMap> m_VariableToPlotMultiMap;
85 GraphFlags m_Flags;
86 GraphFlags m_Flags;
86 bool m_IsCalibration;
87 bool m_IsCalibration;
87 QCustomPlot* m_plot;
88 QCustomPlot* m_plot;
88 QPoint m_lastMousePos;
89 QPoint m_lastMousePos;
89 QCPRange m_lastXRange;
90 QCPRange m_lastXRange;
90 QCPRange m_lastYRange;
91 QCPRange m_lastYRange;
91 /// Delegate used to attach rendering features to the plot
92 /// Delegate used to attach rendering features to the plot
92 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
93 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
93
94
94 QCPItemRect* m_DrawingZoomRect = nullptr;
95 QCPItemRect* m_DrawingZoomRect = nullptr;
95 QStack<QPair<QCPRange, QCPRange>> m_ZoomStack;
96 QStack<QPair<QCPRange, QCPRange>> m_ZoomStack;
96
97
97 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
98 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
98 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
99 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
99
100
100 VisualizationSelectionZoneItem* m_DrawingZone = nullptr;
101 VisualizationSelectionZoneItem* m_DrawingZone = nullptr;
101 VisualizationSelectionZoneItem* m_HoveredZone = nullptr;
102 VisualizationSelectionZoneItem* m_HoveredZone = nullptr;
102 QVector<VisualizationSelectionZoneItem*> m_SelectionZones;
103 QVector<VisualizationSelectionZoneItem*> m_SelectionZones;
103
104
104 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
105 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
105
106
106 bool m_VariableAutoRangeOnInit = true;
107 bool m_VariableAutoRangeOnInit = true;
107
108
108 inline void enterPlotDrag(const QPoint& position)
109 inline void enterPlotDrag(const QPoint& position)
109 {
110 {
110 m_lastMousePos = m_plot->mapFromParent(position);
111 m_lastMousePos = m_plot->mapFromParent(position);
111 m_lastXRange = m_plot->xAxis->range();
112 m_lastXRange = m_plot->xAxis->range();
112 m_lastYRange = m_plot->yAxis->range();
113 m_lastYRange = m_plot->yAxis->range();
113 }
114 }
114
115
115 inline bool isDrawingZoomRect() { return m_DrawingZoomRect != nullptr; }
116 inline bool isDrawingZoomRect() { return m_DrawingZoomRect != nullptr; }
116 void updateZoomRect(const QPoint& newPos)
117 void updateZoomRect(const QPoint& newPos)
117 {
118 {
118 QPointF pos { m_plot->xAxis->pixelToCoord(newPos.x()),
119 QPointF pos { m_plot->xAxis->pixelToCoord(newPos.x()),
119 m_plot->yAxis->pixelToCoord(newPos.y()) };
120 m_plot->yAxis->pixelToCoord(newPos.y()) };
120 m_DrawingZoomRect->bottomRight->setCoords(pos);
121 m_DrawingZoomRect->bottomRight->setCoords(pos);
121 m_plot->replot(QCustomPlot::rpQueuedReplot);
122 m_plot->replot(QCustomPlot::rpQueuedReplot);
122 }
123 }
123
124
124 void applyZoomRect()
125 void applyZoomRect()
125 {
126 {
126 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
127 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
127 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
128 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
128
129
129 auto newAxisXRange = QCPRange { m_DrawingZoomRect->topLeft->coords().x(),
130 auto newAxisXRange = QCPRange { m_DrawingZoomRect->topLeft->coords().x(),
130 m_DrawingZoomRect->bottomRight->coords().x() };
131 m_DrawingZoomRect->bottomRight->coords().x() };
131
132
132 auto newAxisYRange = QCPRange { m_DrawingZoomRect->topLeft->coords().y(),
133 auto newAxisYRange = QCPRange { m_DrawingZoomRect->topLeft->coords().y(),
133 m_DrawingZoomRect->bottomRight->coords().y() };
134 m_DrawingZoomRect->bottomRight->coords().y() };
134
135
135 removeDrawingRect();
136 removeDrawingRect();
136
137
137 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
138 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
138 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
139 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
139 {
140 {
140 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
141 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
141 axisX->setRange(newAxisXRange);
142 axisX->setRange(newAxisXRange);
142 axisY->setRange(newAxisYRange);
143 axisY->setRange(newAxisYRange);
143
144
144 m_plot->replot(QCustomPlot::rpQueuedReplot);
145 m_plot->replot(QCustomPlot::rpQueuedReplot);
145 }
146 }
146 }
147 }
147
148
148 inline bool isDrawingZoneRect() { return m_DrawingZone != nullptr; }
149 inline bool isDrawingZoneRect() { return m_DrawingZone != nullptr; }
149 void updateZoneRect(const QPoint& newPos)
150 void updateZoneRect(const QPoint& newPos)
150 {
151 {
151 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
152 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
152 m_plot->replot(QCustomPlot::rpQueuedReplot);
153 m_plot->replot(QCustomPlot::rpQueuedReplot);
153 }
154 }
154
155
155 void startDrawingRect(const QPoint& pos)
156 void startDrawingRect(const QPoint& pos)
156 {
157 {
157 removeDrawingRect();
158 removeDrawingRect();
158
159
159 auto axisPos = posToAxisPos(pos);
160 auto axisPos = posToAxisPos(pos);
160
161
161 m_DrawingZoomRect = new QCPItemRect { m_plot };
162 m_DrawingZoomRect = new QCPItemRect { m_plot };
162 QPen p;
163 QPen p;
163 p.setWidth(2);
164 p.setWidth(2);
164 m_DrawingZoomRect->setPen(p);
165 m_DrawingZoomRect->setPen(p);
165
166
166 m_DrawingZoomRect->topLeft->setCoords(axisPos);
167 m_DrawingZoomRect->topLeft->setCoords(axisPos);
167 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
168 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
168 }
169 }
169
170
170 void removeDrawingRect()
171 void removeDrawingRect()
171 {
172 {
172 if (m_DrawingZoomRect)
173 if (m_DrawingZoomRect)
173 {
174 {
174 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
175 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
175 m_DrawingZoomRect = nullptr;
176 m_DrawingZoomRect = nullptr;
176 m_plot->replot(QCustomPlot::rpQueuedReplot);
177 m_plot->replot(QCustomPlot::rpQueuedReplot);
177 }
178 }
178 }
179 }
179
180
180 void selectZone(const QPoint& pos)
181 void selectZone(const QPoint& pos)
181 {
182 {
182 auto zoneAtPos = selectionZoneAt(pos);
183 auto zoneAtPos = selectionZoneAt(pos);
183 setSelectionZonesEditionEnabled(
184 setSelectionZonesEditionEnabled(
184 sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
185 sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
185 }
186 }
186
187
187 void startDrawingZone(const QPoint& pos)
188 void startDrawingZone(const QPoint& pos)
188 {
189 {
189 endDrawingZone();
190 endDrawingZone();
190
191
191 auto axisPos = posToAxisPos(pos);
192 auto axisPos = posToAxisPos(pos);
192
193
193 m_DrawingZone = new VisualizationSelectionZoneItem { m_plot };
194 m_DrawingZone = new VisualizationSelectionZoneItem { m_plot };
194 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
195 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
195 m_DrawingZone->setEditionEnabled(false);
196 m_DrawingZone->setEditionEnabled(false);
196 }
197 }
197
198
198 void endDrawingZone()
199 void endDrawingZone()
199 {
200 {
200 if (m_DrawingZone)
201 if (m_DrawingZone)
201 {
202 {
202 auto drawingZoneRange = m_DrawingZone->range();
203 auto drawingZoneRange = m_DrawingZone->range();
203 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0)
204 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0)
204 {
205 {
205 m_DrawingZone->setEditionEnabled(true);
206 m_DrawingZone->setEditionEnabled(true);
206 addSelectionZone(m_DrawingZone);
207 addSelectionZone(m_DrawingZone);
207 }
208 }
208 else
209 else
209 {
210 {
210 m_plot->removeItem(m_DrawingZone);
211 m_plot->removeItem(m_DrawingZone);
211 }
212 }
212
213
213 m_plot->replot(QCustomPlot::rpQueuedReplot);
214 m_plot->replot(QCustomPlot::rpQueuedReplot);
214 m_DrawingZone = nullptr;
215 m_DrawingZone = nullptr;
215 }
216 }
216 }
217 }
217
218
218 void moveSelectionZone(const QPoint& destination)
219 void moveSelectionZone(const QPoint& destination)
219 {
220 {
220 /*
221 /*
221 * I give up on this for now
222 * I give up on this for now
222 * TODO implement this, the difficulty is that selection zones have their own
223 * TODO implement this, the difficulty is that selection zones have their own
223 * event handling code which seems to rely on QCP GUI event handling propagation
224 * event handling code which seems to rely on QCP GUI event handling propagation
224 * which was a realy bad design choice.
225 * which was a realy bad design choice.
225 */
226 */
226 }
227 }
227
228
228 void setSelectionZonesEditionEnabled(bool value)
229 void setSelectionZonesEditionEnabled(bool value)
229 {
230 {
230 for (auto s : m_SelectionZones)
231 for (auto s : m_SelectionZones)
231 {
232 {
232 s->setEditionEnabled(value);
233 s->setEditionEnabled(value);
233 }
234 }
234 }
235 }
235
236
236 void addSelectionZone(VisualizationSelectionZoneItem* zone) { m_SelectionZones << zone; }
237 void addSelectionZone(VisualizationSelectionZoneItem* zone) { m_SelectionZones << zone; }
237
238
238 VisualizationSelectionZoneItem* selectionZoneAt(const QPoint& pos) const
239 VisualizationSelectionZoneItem* selectionZoneAt(const QPoint& pos) const
239 {
240 {
240 VisualizationSelectionZoneItem* selectionZoneItemUnderCursor = nullptr;
241 VisualizationSelectionZoneItem* selectionZoneItemUnderCursor = nullptr;
241 auto minDistanceToZone = -1;
242 auto minDistanceToZone = -1;
242 for (auto zone : m_SelectionZones)
243 for (auto zone : m_SelectionZones)
243 {
244 {
244 auto distanceToZone = zone->selectTest(pos, false);
245 auto distanceToZone = zone->selectTest(pos, false);
245 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
246 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
246 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance())
247 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance())
247 {
248 {
248 selectionZoneItemUnderCursor = zone;
249 selectionZoneItemUnderCursor = zone;
249 }
250 }
250 }
251 }
251
252
252 return selectionZoneItemUnderCursor;
253 return selectionZoneItemUnderCursor;
253 }
254 }
254
255
255 QVector<VisualizationSelectionZoneItem*> selectionZonesAt(
256 QVector<VisualizationSelectionZoneItem*> selectionZonesAt(
256 const QPoint& pos, const QCustomPlot& plot) const
257 const QPoint& pos, const QCustomPlot& plot) const
257 {
258 {
258 QVector<VisualizationSelectionZoneItem*> zones;
259 QVector<VisualizationSelectionZoneItem*> zones;
259 for (auto zone : m_SelectionZones)
260 for (auto zone : m_SelectionZones)
260 {
261 {
261 auto distanceToZone = zone->selectTest(pos, false);
262 auto distanceToZone = zone->selectTest(pos, false);
262 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance())
263 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance())
263 {
264 {
264 zones << zone;
265 zones << zone;
265 }
266 }
266 }
267 }
267
268
268 return zones;
269 return zones;
269 }
270 }
270
271
271 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem* zone, QCustomPlot& plot)
272 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem* zone, QCustomPlot& plot)
272 {
273 {
273 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone)
274 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone)
274 {
275 {
275 zone->moveToTop();
276 zone->moveToTop();
276 m_SelectionZones.removeAll(zone);
277 m_SelectionZones.removeAll(zone);
277 m_SelectionZones.append(zone);
278 m_SelectionZones.append(zone);
278 }
279 }
279 }
280 }
280
281
281 QPointF posToAxisPos(const QPoint& pos) const
282 QPointF posToAxisPos(const QPoint& pos) const
282 {
283 {
283 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
284 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
284 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
285 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
285 return QPointF { axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y()) };
286 return QPointF { axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y()) };
286 }
287 }
287
288
288 bool pointIsInAxisRect(const QPointF& axisPoint, QCustomPlot& plot) const
289 bool pointIsInAxisRect(const QPointF& axisPoint, QCustomPlot& plot) const
289 {
290 {
290 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
291 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
291 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
292 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
292 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
293 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
293 }
294 }
294
295
295 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis* axis)
296 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis* axis)
296 {
297 {
297 if (axis->scaleType() == QCPAxis::stLinear)
298 if (axis->scaleType() == QCPAxis::stLinear)
298 {
299 {
299 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
300 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
300 return QCPRange { axis->range().lower + diff, axis->range().upper + diff };
301 return QCPRange { axis->range().lower + diff, axis->range().upper + diff };
301 }
302 }
302 else
303 else
303 {
304 {
304 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
305 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
305 return QCPRange { axis->range().lower * diff, axis->range().upper * diff };
306 return QCPRange { axis->range().lower * diff, axis->range().upper * diff };
306 }
307 }
307 }
308 }
308
309
309 void setRange(const DateTimeRange& newRange, bool updateVar = true)
310 void setRange(const DateTimeRange& newRange, bool updateVar = true)
310 {
311 {
311 this->m_plot->xAxis->setRange(newRange.m_TStart, newRange.m_TEnd);
312 this->m_plot->xAxis->setRange(newRange.m_TStart, newRange.m_TEnd);
312 if (updateVar)
313 if (updateVar)
313 {
314 {
314 for (auto it = m_VariableToPlotMultiMap.begin(), end = m_VariableToPlotMultiMap.end();
315 for (auto it = m_VariableToPlotMultiMap.begin(), end = m_VariableToPlotMultiMap.end();
315 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
316 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
316 {
317 {
317 sqpApp->variableController().asyncChangeRange(it->first, newRange);
318 sqpApp->variableController().asyncChangeRange(it->first, newRange);
318 }
319 }
319 }
320 }
320 m_plot->replot(QCustomPlot::rpQueuedReplot);
321 m_plot->replot(QCustomPlot::rpQueuedReplot);
321 }
322 }
322
323
323 void setRange(const QCPRange& newRange)
324 void setRange(const QCPRange& newRange)
324 {
325 {
325 auto graphRange = DateTimeRange { newRange.lower, newRange.upper };
326 auto graphRange = DateTimeRange { newRange.lower, newRange.upper };
326 setRange(graphRange);
327 setRange(graphRange);
327 }
328 }
328
329
329 void rescaleY() { m_plot->yAxis->rescale(true); }
330 void rescaleY() { m_plot->yAxis->rescale(true); }
330
331
331 std::tuple<double, double> moveGraph(const QPoint& destination)
332 std::tuple<double, double> moveGraph(const QPoint& destination)
332 {
333 {
333 auto currentPos = m_plot->mapFromParent(destination);
334 auto currentPos = m_plot->mapFromParent(destination);
334 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
335 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
335 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
336 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
336 auto oldXRange = xAxis->range();
337 auto oldXRange = xAxis->range();
337 auto oldYRange = yAxis->range();
338 auto oldYRange = yAxis->range();
338 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
339 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
339 xAxis->setRange(m_lastXRange.lower + dx, m_lastXRange.upper + dx);
340 xAxis->setRange(m_lastXRange.lower + dx, m_lastXRange.upper + dx);
340 if (yAxis->scaleType() == QCPAxis::stLinear)
341 if (yAxis->scaleType() == QCPAxis::stLinear)
341 {
342 {
342 double dy
343 double dy
343 = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
344 = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
344 yAxis->setRange(m_lastYRange.lower + dy, m_lastYRange.upper + dy);
345 yAxis->setRange(m_lastYRange.lower + dy, m_lastYRange.upper + dy);
345 }
346 }
346 else
347 else
347 {
348 {
348 double dy
349 double dy
349 = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
350 = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
350 yAxis->setRange(m_lastYRange.lower * dy, m_lastYRange.upper * dy);
351 yAxis->setRange(m_lastYRange.lower * dy, m_lastYRange.upper * dy);
351 }
352 }
352 auto newXRange = xAxis->range();
353 auto newXRange = xAxis->range();
353 auto newYRange = yAxis->range();
354 auto newYRange = yAxis->range();
354 setRange(xAxis->range());
355 setRange(xAxis->range());
355 // m_lastMousePos = currentPos;
356 // m_lastMousePos = currentPos;
356 return { newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower };
357 return { newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower };
357 }
358 }
358
359
359 void zoom(double factor, int center, Qt::Orientation orientation)
360 void zoom(double factor, int center, Qt::Orientation orientation)
360 {
361 {
361 QCPAxis* axis = m_plot->axisRect()->rangeZoomAxis(orientation);
362 QCPAxis* axis = m_plot->axisRect()->rangeZoomAxis(orientation);
362 axis->scaleRange(factor, axis->pixelToCoord(center));
363 axis->scaleRange(factor, axis->pixelToCoord(center));
363 if (orientation == Qt::Horizontal)
364 if (orientation == Qt::Horizontal)
364 setRange(axis->range());
365 setRange(axis->range());
365 m_plot->replot(QCustomPlot::rpQueuedReplot);
366 m_plot->replot(QCustomPlot::rpQueuedReplot);
366 }
367 }
367
368
368 void transform(const DateTimeRangeTransformation& tranformation)
369 void transform(const DateTimeRangeTransformation& tranformation)
369 {
370 {
370 auto graphRange = m_plot->xAxis->range();
371 auto graphRange = m_plot->xAxis->range();
371 DateTimeRange range { graphRange.lower, graphRange.upper };
372 DateTimeRange range { graphRange.lower, graphRange.upper };
372 range = range.transform(tranformation);
373 range = range.transform(tranformation);
373 setRange(range);
374 setRange(range);
374 m_plot->replot(QCustomPlot::rpQueuedReplot);
375 m_plot->replot(QCustomPlot::rpQueuedReplot);
375 }
376 }
376
377
377 void move(double dx, double dy)
378 void move(double dx, double dy)
378 {
379 {
379 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
380 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
380 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
381 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
381 xAxis->setRange(QCPRange(xAxis->range().lower + dx, xAxis->range().upper + dx));
382 xAxis->setRange(QCPRange(xAxis->range().lower + dx, xAxis->range().upper + dx));
382 yAxis->setRange(QCPRange(yAxis->range().lower + dy, yAxis->range().upper + dy));
383 yAxis->setRange(QCPRange(yAxis->range().lower + dy, yAxis->range().upper + dy));
383 setRange(xAxis->range());
384 setRange(xAxis->range());
384 m_plot->replot(QCustomPlot::rpQueuedReplot);
385 m_plot->replot(QCustomPlot::rpQueuedReplot);
385 }
386 }
386
387
387 void move(double factor, Qt::Orientation orientation)
388 void move(double factor, Qt::Orientation orientation)
388 {
389 {
389 auto oldRange = m_plot->xAxis->range();
390 auto oldRange = m_plot->xAxis->range();
390 QCPAxis* axis = m_plot->axisRect()->rangeDragAxis(orientation);
391 QCPAxis* axis = m_plot->axisRect()->rangeDragAxis(orientation);
391 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear)
392 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear)
392 {
393 {
393 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
394 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
394 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
395 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
395 }
396 }
396 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic)
397 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic)
397 {
398 {
398 int start = 0, stop = 0;
399 int start = 0, stop = 0;
399 double diff = 0.;
400 double diff = 0.;
400 if (factor > 0.0)
401 if (factor > 0.0)
401 {
402 {
402 stop = m_plot->width() * factor / 10;
403 stop = m_plot->width() * factor / 10;
403 start = 2 * m_plot->width() * factor / 10;
404 start = 2 * m_plot->width() * factor / 10;
404 }
405 }
405 if (factor < 0.0)
406 if (factor < 0.0)
406 {
407 {
407 factor *= -1.0;
408 factor *= -1.0;
408 start = m_plot->width() * factor / 10;
409 start = m_plot->width() * factor / 10;
409 stop = 2 * m_plot->width() * factor / 10;
410 stop = 2 * m_plot->width() * factor / 10;
410 }
411 }
411 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
412 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
412 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
413 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
413 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
414 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
414 }
415 }
415 if (orientation == Qt::Horizontal)
416 if (orientation == Qt::Horizontal)
416 setRange(axis->range());
417 setRange(axis->range());
417 m_plot->replot(QCustomPlot::rpQueuedReplot);
418 m_plot->replot(QCustomPlot::rpQueuedReplot);
418 }
419 }
419 };
420 };
420
421
421 VisualizationGraphWidget::VisualizationGraphWidget(const QString& name, QWidget* parent)
422 VisualizationGraphWidget::VisualizationGraphWidget(const QString& name, QWidget* parent)
422 : VisualizationDragWidget { parent }
423 : VisualizationDragWidget { parent }
423 , ui { new Ui::VisualizationGraphWidget }
424 , ui { new Ui::VisualizationGraphWidget }
424 , impl { spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name) }
425 , impl { spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name) }
425 {
426 {
426 ui->setupUi(this);
427 ui->setupUi(this);
427 this->layout()->addWidget(impl->m_plot);
428 this->layout()->addWidget(impl->m_plot);
428 // 'Close' options : widget is deleted when closed
429 // 'Close' options : widget is deleted when closed
429 setAttribute(Qt::WA_DeleteOnClose);
430 setAttribute(Qt::WA_DeleteOnClose);
430
431
431 // The delegate must be initialized after the ui as it uses the plot
432 // The delegate must be initialized after the ui as it uses the plot
432 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
433 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
433
434
434 // Init the cursors
435 // Init the cursors
435 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
436 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
436 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
437 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
437 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
438 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
438 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
439 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
439
440
440 this->setFocusPolicy(Qt::WheelFocus);
441 this->setFocusPolicy(Qt::WheelFocus);
441 this->setMouseTracking(true);
442 this->setMouseTracking(true);
442 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
443 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
443 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
444 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
444 impl->m_plot->setParent(this);
445 impl->m_plot->setParent(this);
445
446
446 connect(&sqpApp->variableController(), &VariableController2::variableDeleted, this,
447 connect(&sqpApp->variableController(), &VariableController2::variableDeleted, this,
447 &VisualizationGraphWidget::variableDeleted);
448 &VisualizationGraphWidget::variableDeleted);
448 }
449 }
449
450
450
451
451 VisualizationGraphWidget::~VisualizationGraphWidget()
452 VisualizationGraphWidget::~VisualizationGraphWidget()
452 {
453 {
453 delete ui;
454 delete ui;
454 }
455 }
455
456
456 VisualizationZoneWidget* VisualizationGraphWidget::parentZoneWidget() const noexcept
457 VisualizationZoneWidget* VisualizationGraphWidget::parentZoneWidget() const noexcept
457 {
458 {
458 auto parent = parentWidget();
459 auto parent = parentWidget();
459 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget*>(parent))
460 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget*>(parent))
460 {
461 {
461 parent = parent->parentWidget();
462 parent = parent->parentWidget();
462 }
463 }
463
464
464 return qobject_cast<VisualizationZoneWidget*>(parent);
465 return qobject_cast<VisualizationZoneWidget*>(parent);
465 }
466 }
466
467
467 VisualizationWidget* VisualizationGraphWidget::parentVisualizationWidget() const
468 VisualizationWidget* VisualizationGraphWidget::parentVisualizationWidget() const
468 {
469 {
469 auto parent = parentWidget();
470 auto parent = parentWidget();
470 while (parent != nullptr && !qobject_cast<VisualizationWidget*>(parent))
471 while (parent != nullptr && !qobject_cast<VisualizationWidget*>(parent))
471 {
472 {
472 parent = parent->parentWidget();
473 parent = parent->parentWidget();
473 }
474 }
474
475
475 return qobject_cast<VisualizationWidget*>(parent);
476 return qobject_cast<VisualizationWidget*>(parent);
476 }
477 }
477
478
478 void VisualizationGraphWidget::setFlags(GraphFlags flags)
479 void VisualizationGraphWidget::setFlags(GraphFlags flags)
479 {
480 {
480 impl->m_Flags = std::move(flags);
481 impl->m_Flags = std::move(flags);
481 }
482 }
482
483
483 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable2> variable, DateTimeRange range)
484 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable2> variable, DateTimeRange range)
484 {
485 {
485 // Uses delegate to create the qcpplot components according to the variable
486 // Uses delegate to create the qcpplot components according to the variable
486 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
487 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
487
488
488 // Sets graph properties
489 // Sets graph properties
489 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
490 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
490
491
491 impl->m_VariableToPlotMultiMap.insert({ variable, std::move(createdPlottables) });
492 impl->m_VariableToPlotMultiMap.insert({ variable, std::move(createdPlottables) });
492
493
493 setGraphRange(range);
494 setGraphRange(range);
494 // If the variable already has its data loaded, load its units and its range in the graph
495 // If the variable already has its data loaded, load its units and its range in the graph
495 if (variable->data() != nullptr)
496 if (variable->data() != nullptr)
496 {
497 {
497 impl->m_RenderingDelegate->setAxesUnits(*variable);
498 impl->m_RenderingDelegate->setAxesUnits(*variable);
498 }
499 }
499 else
500 else
500 {
501 {
501 auto context = new QObject { this };
502 auto context = new QObject { this };
502 connect(
503 connect(
503 variable.get(), &Variable2::updated, context, [this, variable, context, range](QUuid) {
504 variable.get(), &Variable2::updated, context, [this, variable, context, range](QUuid) {
504 this->impl->m_RenderingDelegate->setAxesUnits(*variable);
505 this->impl->m_RenderingDelegate->setAxesUnits(*variable);
505 this->impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
506 this->impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
506 delete context;
507 delete context;
507 });
508 });
508 }
509 }
509 //TODO this is bad! when variable is moved to another graph it still fires
510 //TODO this is bad! when variable is moved to another graph it still fires
510 // even if this has been deleted
511 // even if this has been deleted
511 connect(variable.get(), &Variable2::updated, this, &VisualizationGraphWidget::variableUpdated);
512 connect(variable.get(), &Variable2::updated, this, &VisualizationGraphWidget::variableUpdated);
512 this->onUpdateVarDisplaying(variable, range); // My bullshit
513 this->onUpdateVarDisplaying(variable, range); // My bullshit
513 emit variableAdded(variable);
514 emit variableAdded(variable);
514 }
515 }
515
516
516 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable2> variable) noexcept
517 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable2> variable) noexcept
517 {
518 {
518 // Each component associated to the variable :
519 // Each component associated to the variable :
519 // - is removed from qcpplot (which deletes it)
520 // - is removed from qcpplot (which deletes it)
520 // - is no longer referenced in the map
521 // - is no longer referenced in the map
521 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
522 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
522 if (variableIt != impl->m_VariableToPlotMultiMap.cend())
523 if (variableIt != impl->m_VariableToPlotMultiMap.cend())
523 {
524 {
524 emit variableAboutToBeRemoved(variable);
525 emit variableAboutToBeRemoved(variable);
525
526
526 auto& plottablesMap = variableIt->second;
527 auto& plottablesMap = variableIt->second;
527
528
528 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
529 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
529 plottableIt != plottableEnd;)
530 plottableIt != plottableEnd;)
530 {
531 {
531 impl->m_plot->removePlottable(plottableIt->second);
532 impl->m_plot->removePlottable(plottableIt->second);
532 plottableIt = plottablesMap.erase(plottableIt);
533 plottableIt = plottablesMap.erase(plottableIt);
533 }
534 }
534
535
535 impl->m_VariableToPlotMultiMap.erase(variableIt);
536 impl->m_VariableToPlotMultiMap.erase(variableIt);
536 }
537 }
537
538
538 // Updates graph
539 // Updates graph
539 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
540 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
540 }
541 }
541
542
542 std::vector<std::shared_ptr<Variable2>> VisualizationGraphWidget::variables() const
543 std::vector<std::shared_ptr<Variable2>> VisualizationGraphWidget::variables() const
543 {
544 {
544 auto variables = std::vector<std::shared_ptr<Variable2>> {};
545 auto variables = std::vector<std::shared_ptr<Variable2>> {};
545 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
546 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
546 it != std::cend(impl->m_VariableToPlotMultiMap); ++it)
547 it != std::cend(impl->m_VariableToPlotMultiMap); ++it)
547 {
548 {
548 variables.push_back(it->first);
549 variables.push_back(it->first);
549 }
550 }
550
551
551 return variables;
552 return variables;
552 }
553 }
553
554
554 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable2> variable)
555 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable2> variable)
555 {
556 {
556 if (!variable)
557 if (!variable)
557 {
558 {
558 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
559 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
559 return;
560 return;
560 }
561 }
561
562
562 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
563 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
563 }
564 }
564
565
565 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
566 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
566 {
567 {
567 auto graphRange = impl->m_plot->xAxis->range();
568 auto graphRange = impl->m_plot->xAxis->range();
568 return DateTimeRange { graphRange.lower, graphRange.upper };
569 return DateTimeRange { graphRange.lower, graphRange.upper };
569 }
570 }
570
571
571 void VisualizationGraphWidget::setGraphRange(
572 void VisualizationGraphWidget::setGraphRange(
572 const DateTimeRange& range, bool updateVar, bool forward)
573 const DateTimeRange& range, bool updateVar, bool forward)
573 {
574 {
574 impl->setRange(range, updateVar);
575 impl->setRange(range, updateVar);
575 if (forward)
576 if (forward)
576 {
577 {
577 emit this->setrange_sig(this->graphRange(), true, false);
578 emit this->setrange_sig(this->graphRange(), true, false);
578 }
579 }
579 }
580 }
580
581
581 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
582 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
582 {
583 {
583 impl->m_VariableAutoRangeOnInit = value;
584 impl->m_VariableAutoRangeOnInit = value;
584 }
585 }
585
586
586 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
587 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
587 {
588 {
588 QVector<DateTimeRange> ranges;
589 QVector<DateTimeRange> ranges;
589 for (auto zone : impl->m_SelectionZones)
590 for (auto zone : impl->m_SelectionZones)
590 {
591 {
591 ranges << zone->range();
592 ranges << zone->range();
592 }
593 }
593
594
594 return ranges;
595 return ranges;
595 }
596 }
596
597
597 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange>& ranges)
598 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange>& ranges)
598 {
599 {
599 for (const auto& range : ranges)
600 for (const auto& range : ranges)
600 {
601 {
601 // note: ownership is transfered to QCustomPlot
602 // note: ownership is transfered to QCustomPlot
602 auto zone = new VisualizationSelectionZoneItem(&plot());
603 auto zone = new VisualizationSelectionZoneItem(&plot());
603 zone->setRange(range.m_TStart, range.m_TEnd);
604 zone->setRange(range.m_TStart, range.m_TEnd);
604 impl->addSelectionZone(zone);
605 impl->addSelectionZone(zone);
605 }
606 }
606
607
607 plot().replot(QCustomPlot::rpQueuedReplot);
608 plot().replot(QCustomPlot::rpQueuedReplot);
608 }
609 }
609
610
610 VisualizationSelectionZoneItem* VisualizationGraphWidget::addSelectionZone(
611 VisualizationSelectionZoneItem* VisualizationGraphWidget::addSelectionZone(
611 const QString& name, const DateTimeRange& range)
612 const QString& name, const DateTimeRange& range)
612 {
613 {
613 // note: ownership is transfered to QCustomPlot
614 // note: ownership is transfered to QCustomPlot
614 auto zone = new VisualizationSelectionZoneItem(&plot());
615 auto zone = new VisualizationSelectionZoneItem(&plot());
615 zone->setName(name);
616 zone->setName(name);
616 zone->setRange(range.m_TStart, range.m_TEnd);
617 zone->setRange(range.m_TStart, range.m_TEnd);
617 impl->addSelectionZone(zone);
618 impl->addSelectionZone(zone);
618
619
619 plot().replot(QCustomPlot::rpQueuedReplot);
620 plot().replot(QCustomPlot::rpQueuedReplot);
620
621
621 return zone;
622 return zone;
622 }
623 }
623
624
624 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem* selectionZone)
625 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem* selectionZone)
625 {
626 {
626 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
627 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
627
628
628 if (impl->m_HoveredZone == selectionZone)
629 if (impl->m_HoveredZone == selectionZone)
629 {
630 {
630 impl->m_HoveredZone = nullptr;
631 impl->m_HoveredZone = nullptr;
631 setCursor(Qt::ArrowCursor);
632 setCursor(Qt::ArrowCursor);
632 }
633 }
633
634
634 impl->m_SelectionZones.removeAll(selectionZone);
635 impl->m_SelectionZones.removeAll(selectionZone);
635 plot().removeItem(selectionZone);
636 plot().removeItem(selectionZone);
636 plot().replot(QCustomPlot::rpQueuedReplot);
637 plot().replot(QCustomPlot::rpQueuedReplot);
637 }
638 }
638
639
639 void VisualizationGraphWidget::undoZoom()
640 void VisualizationGraphWidget::undoZoom()
640 {
641 {
641 auto zoom = impl->m_ZoomStack.pop();
642 auto zoom = impl->m_ZoomStack.pop();
642 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
643 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
643 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
644 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
644
645
645 axisX->setRange(zoom.first);
646 axisX->setRange(zoom.first);
646 axisY->setRange(zoom.second);
647 axisY->setRange(zoom.second);
647
648
648 plot().replot(QCustomPlot::rpQueuedReplot);
649 plot().replot(QCustomPlot::rpQueuedReplot);
649 }
650 }
650
651
651 void VisualizationGraphWidget::zoom(
652 void VisualizationGraphWidget::zoom(
652 double factor, int center, Qt::Orientation orientation, bool forward)
653 double factor, int center, Qt::Orientation orientation, bool forward)
653 {
654 {
654 impl->zoom(factor, center, orientation);
655 impl->zoom(factor, center, orientation);
655 if (forward && orientation == Qt::Horizontal)
656 if (forward && orientation == Qt::Horizontal)
656 emit this->setrange_sig(this->graphRange(), true, false);
657 emit this->setrange_sig(this->graphRange(), true, false);
657 }
658 }
658
659
659 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
660 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
660 {
661 {
661 impl->move(factor, orientation);
662 impl->move(factor, orientation);
662 if (forward)
663 if (forward)
663 emit this->setrange_sig(this->graphRange(), true, false);
664 emit this->setrange_sig(this->graphRange(), true, false);
664 }
665 }
665
666
666 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
667 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
667 {
668 {
668 impl->move(dx, dy);
669 impl->move(dx, dy);
669 if (forward)
670 if (forward)
670 emit this->setrange_sig(this->graphRange(), true, false);
671 emit this->setrange_sig(this->graphRange(), true, false);
671 }
672 }
672
673
673 void VisualizationGraphWidget::transform(
674 void VisualizationGraphWidget::transform(
674 const DateTimeRangeTransformation& tranformation, bool forward)
675 const DateTimeRangeTransformation& tranformation, bool forward)
675 {
676 {
676 impl->transform(tranformation);
677 impl->transform(tranformation);
677 if (forward)
678 if (forward)
678 emit this->setrange_sig(this->graphRange(), true, false);
679 emit this->setrange_sig(this->graphRange(), true, false);
679 }
680 }
680
681
681 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor* visitor)
682 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor* visitor)
682 {
683 {
683 if (visitor)
684 if (visitor)
684 {
685 {
685 visitor->visit(this);
686 visitor->visit(this);
686 }
687 }
687 else
688 else
688 {
689 {
689 qCCritical(LOG_VisualizationGraphWidget())
690 qCCritical(LOG_VisualizationGraphWidget())
690 << tr("Can't visit widget : the visitor is null");
691 << tr("Can't visit widget : the visitor is null");
691 }
692 }
692 }
693 }
693
694
694 bool VisualizationGraphWidget::canDrop(Variable2& variable) const
695 bool VisualizationGraphWidget::canDrop(Variable2& variable) const
695 {
696 {
696 auto isSpectrogram
697 auto isSpectrogram
697 = [](auto& variable) { return variable.type() == DataSeriesType::SPECTROGRAM; };
698 = [](auto& variable) { return variable.type() == DataSeriesType::SPECTROGRAM; };
698
699
699 // - A spectrogram series can't be dropped on graph with existing plottables
700 // - A spectrogram series can't be dropped on graph with existing plottables
700 // - No data series can be dropped on graph with existing spectrogram series
701 // - No data series can be dropped on graph with existing spectrogram series
701 return isSpectrogram(variable)
702 return isSpectrogram(variable)
702 ? impl->m_VariableToPlotMultiMap.empty()
703 ? impl->m_VariableToPlotMultiMap.empty()
703 : std::none_of(impl->m_VariableToPlotMultiMap.cbegin(),
704 : std::none_of(impl->m_VariableToPlotMultiMap.cbegin(),
704 impl->m_VariableToPlotMultiMap.cend(),
705 impl->m_VariableToPlotMultiMap.cend(),
705 [isSpectrogram](const auto& entry) { return isSpectrogram(*entry.first); });
706 [isSpectrogram](const auto& entry) { return isSpectrogram(*entry.first); });
706 }
707 }
707
708
708 bool VisualizationGraphWidget::contains(Variable2& variable) const
709 bool VisualizationGraphWidget::contains(Variable2& variable) const
709 {
710 {
710 // Finds the variable among the keys of the map
711 // Finds the variable among the keys of the map
711 auto variablePtr = &variable;
712 auto variablePtr = &variable;
712 auto findVariable
713 auto findVariable
713 = [variablePtr](const auto& entry) { return variablePtr == entry.first.get(); };
714 = [variablePtr](const auto& entry) { return variablePtr == entry.first.get(); };
714
715
715 auto end = impl->m_VariableToPlotMultiMap.cend();
716 auto end = impl->m_VariableToPlotMultiMap.cend();
716 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
717 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
717 return it != end;
718 return it != end;
718 }
719 }
719
720
720 QString VisualizationGraphWidget::name() const
721 QString VisualizationGraphWidget::name() const
721 {
722 {
722 return impl->m_Name;
723 return impl->m_Name;
723 }
724 }
724
725
725 QMimeData* VisualizationGraphWidget::mimeData(const QPoint& position) const
726 QMimeData* VisualizationGraphWidget::mimeData(const QPoint& position) const
726 {
727 {
727 auto mimeData = new QMimeData;
728 auto mimeData = new QMimeData;
728
729
729 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
730 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
730 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
731 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
731 && selectionZoneItemUnderCursor)
732 && selectionZoneItemUnderCursor)
732 {
733 {
733 mimeData->setData(MIME_TYPE_TIME_RANGE,
734 mimeData->setData(MIME_TYPE_TIME_RANGE,
734 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
735 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
735 mimeData->setData(MIME_TYPE_SELECTION_ZONE,
736 mimeData->setData(MIME_TYPE_SELECTION_ZONE,
736 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
737 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
737 }
738 }
738 else
739 else
739 {
740 {
740 mimeData->setData(MIME_TYPE_GRAPH, QByteArray {});
741 mimeData->setData(MIME_TYPE_GRAPH, QByteArray {});
741
742
742 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
743 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
743 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
744 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
744 }
745 }
745
746
746 return mimeData;
747 return mimeData;
747 }
748 }
748
749
749 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint& dragPosition)
750 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint& dragPosition)
750 {
751 {
751 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
752 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
752 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
753 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
753 && selectionZoneItemUnderCursor)
754 && selectionZoneItemUnderCursor)
754 {
755 {
755
756
756 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
757 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
757 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
758 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
758
759
759 auto zoneSize = QSizeF { qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
760 auto zoneSize = QSizeF { qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
760 qAbs(zoneBottomRight.y() - zoneTopLeft.y()) }
761 qAbs(zoneBottomRight.y() - zoneTopLeft.y()) }
761 .toSize();
762 .toSize();
762
763
763 auto pixmap = QPixmap(zoneSize);
764 auto pixmap = QPixmap(zoneSize);
764 render(&pixmap, QPoint(), QRegion { QRect { zoneTopLeft.toPoint(), zoneSize } });
765 render(&pixmap, QPoint(), QRegion { QRect { zoneTopLeft.toPoint(), zoneSize } });
765
766
766 return pixmap;
767 return pixmap;
767 }
768 }
768
769
769 return QPixmap();
770 return QPixmap();
770 }
771 }
771
772
772 bool VisualizationGraphWidget::isDragAllowed() const
773 bool VisualizationGraphWidget::isDragAllowed() const
773 {
774 {
774 return true;
775 return true;
775 }
776 }
776
777
777 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
778 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
778 {
779 {
779 if (highlighted)
780 if (highlighted)
780 {
781 {
781 plot().setBackground(QBrush(QColor("#BBD5EE")));
782 plot().setBackground(QBrush(QColor("#BBD5EE")));
782 }
783 }
783 else
784 else
784 {
785 {
785 plot().setBackground(QBrush(Qt::white));
786 plot().setBackground(QBrush(Qt::white));
786 }
787 }
787
788
788 plot().update();
789 plot().update();
789 }
790 }
790
791
791 void VisualizationGraphWidget::addVerticalCursor(double time)
792 void VisualizationGraphWidget::addVerticalCursor(double time)
792 {
793 {
793 impl->m_VerticalCursor->setPosition(time);
794 impl->m_VerticalCursor->setPosition(time);
794 impl->m_VerticalCursor->setVisible(true);
795 impl->m_VerticalCursor->setVisible(true);
795
796
796 auto text
797 auto text
797 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
798 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
798 impl->m_VerticalCursor->setLabelText(text);
799 impl->m_VerticalCursor->setLabelText(text);
799 }
800 }
800
801
801 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
802 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
802 {
803 {
803 impl->m_VerticalCursor->setAbsolutePosition(position);
804 impl->m_VerticalCursor->setAbsolutePosition(position);
804 impl->m_VerticalCursor->setVisible(true);
805 impl->m_VerticalCursor->setVisible(true);
805
806
806 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
807 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
807 auto text
808 auto text
808 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
809 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
809 impl->m_VerticalCursor->setLabelText(text);
810 impl->m_VerticalCursor->setLabelText(text);
810 }
811 }
811
812
812 void VisualizationGraphWidget::removeVerticalCursor()
813 void VisualizationGraphWidget::removeVerticalCursor()
813 {
814 {
814 impl->m_VerticalCursor->setVisible(false);
815 impl->m_VerticalCursor->setVisible(false);
815 plot().replot(QCustomPlot::rpQueuedReplot);
816 plot().replot(QCustomPlot::rpQueuedReplot);
816 }
817 }
817
818
818 void VisualizationGraphWidget::addHorizontalCursor(double value)
819 void VisualizationGraphWidget::addHorizontalCursor(double value)
819 {
820 {
820 impl->m_HorizontalCursor->setPosition(value);
821 impl->m_HorizontalCursor->setPosition(value);
821 impl->m_HorizontalCursor->setVisible(true);
822 impl->m_HorizontalCursor->setVisible(true);
822 impl->m_HorizontalCursor->setLabelText(QString::number(value));
823 impl->m_HorizontalCursor->setLabelText(QString::number(value));
823 }
824 }
824
825
825 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
826 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
826 {
827 {
827 impl->m_HorizontalCursor->setAbsolutePosition(position);
828 impl->m_HorizontalCursor->setAbsolutePosition(position);
828 impl->m_HorizontalCursor->setVisible(true);
829 impl->m_HorizontalCursor->setVisible(true);
829
830
830 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
831 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
831 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
832 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
832 }
833 }
833
834
834 void VisualizationGraphWidget::removeHorizontalCursor()
835 void VisualizationGraphWidget::removeHorizontalCursor()
835 {
836 {
836 impl->m_HorizontalCursor->setVisible(false);
837 impl->m_HorizontalCursor->setVisible(false);
837 plot().replot(QCustomPlot::rpQueuedReplot);
838 plot().replot(QCustomPlot::rpQueuedReplot);
838 }
839 }
839
840
840 void VisualizationGraphWidget::closeEvent(QCloseEvent* event)
841 void VisualizationGraphWidget::closeEvent(QCloseEvent* event)
841 {
842 {
842 Q_UNUSED(event);
843 Q_UNUSED(event);
843
844
844 for (auto i : impl->m_SelectionZones)
845 for (auto i : impl->m_SelectionZones)
845 {
846 {
846 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
847 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
847 }
848 }
848
849
849 // Prevents that all variables will be removed from graph when it will be closed
850 // Prevents that all variables will be removed from graph when it will be closed
850 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
851 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
851 {
852 {
852 emit variableAboutToBeRemoved(variableEntry.first);
853 emit variableAboutToBeRemoved(variableEntry.first);
853 }
854 }
854 }
855 }
855
856
856 void VisualizationGraphWidget::enterEvent(QEvent* event)
857 void VisualizationGraphWidget::enterEvent(QEvent* event)
857 {
858 {
858 Q_UNUSED(event);
859 Q_UNUSED(event);
859 impl->m_RenderingDelegate->showGraphOverlay(true);
860 impl->m_RenderingDelegate->showGraphOverlay(true);
860 }
861 }
861
862
862 void VisualizationGraphWidget::leaveEvent(QEvent* event)
863 void VisualizationGraphWidget::leaveEvent(QEvent* event)
863 {
864 {
864 Q_UNUSED(event);
865 Q_UNUSED(event);
865 impl->m_RenderingDelegate->showGraphOverlay(false);
866 impl->m_RenderingDelegate->showGraphOverlay(false);
866
867
867 if (auto parentZone = parentZoneWidget())
868 if (auto parentZone = parentZoneWidget())
868 {
869 {
869 parentZone->notifyMouseLeaveGraph(this);
870 parentZone->notifyMouseLeaveGraph(this);
870 }
871 }
871 else
872 else
872 {
873 {
873 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
874 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
874 }
875 }
875
876
876 if (impl->m_HoveredZone)
877 if (impl->m_HoveredZone)
877 {
878 {
878 impl->m_HoveredZone->setHovered(false);
879 impl->m_HoveredZone->setHovered(false);
879 impl->m_HoveredZone = nullptr;
880 impl->m_HoveredZone = nullptr;
880 }
881 }
881 }
882 }
882
883
883 void VisualizationGraphWidget::wheelEvent(QWheelEvent* event)
884 void VisualizationGraphWidget::wheelEvent(QWheelEvent* event)
884 {
885 {
885 double factor;
886 double factor;
886 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
887 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
887 if (event->modifiers() == Qt::ControlModifier)
888 if (event->modifiers() == Qt::ControlModifier)
888 {
889 {
889 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
890 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
890 {
891 {
891 setCursor(Qt::SizeVerCursor);
892 setCursor(Qt::SizeVerCursor);
892 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
893 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
893 zoom(factor, event->pos().y(), Qt::Vertical);
894 zoom(factor, event->pos().y(), Qt::Vertical);
894 }
895 }
895 }
896 }
896 else if (event->modifiers() == Qt::ShiftModifier)
897 else if (event->modifiers() == Qt::ShiftModifier)
897 {
898 {
898 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
899 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
899 {
900 {
900 setCursor(Qt::SizeHorCursor);
901 setCursor(Qt::SizeHorCursor);
901 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
902 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
902 zoom(factor, event->pos().x(), Qt::Horizontal);
903 zoom(factor, event->pos().x(), Qt::Horizontal);
903 }
904 }
904 }
905 }
905 else
906 else
906 {
907 {
907 move(wheelSteps, Qt::Horizontal);
908 move(wheelSteps, Qt::Horizontal);
908 }
909 }
909 event->accept();
910 event->accept();
910 }
911 }
911
912
912
913
913 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent* event)
914 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent* event)
914 {
915 {
915 if (impl->isDrawingZoomRect())
916 if (impl->isDrawingZoomRect())
916 {
917 {
917 impl->updateZoomRect(event->pos());
918 impl->updateZoomRect(event->pos());
918 }
919 }
919 else if (impl->isDrawingZoneRect())
920 else if (impl->isDrawingZoneRect())
920 {
921 {
921 impl->updateZoneRect(event->pos());
922 impl->updateZoneRect(event->pos());
922 }
923 }
923 else if (event->buttons() == Qt::LeftButton)
924 else if (event->buttons() == Qt::LeftButton)
924 {
925 {
925 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
926 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
926 {
927 {
927 auto [dx, dy] = impl->moveGraph(event->pos());
928 auto [dx, dy] = impl->moveGraph(event->pos());
928 emit this->setrange_sig(this->graphRange(), true, false);
929 emit this->setrange_sig(this->graphRange(), true, false);
929 }
930 }
930 else if (sqpApp->plotsInteractionMode()
931 else if (sqpApp->plotsInteractionMode()
931 == SqpApplication::PlotsInteractionMode::SelectionZones)
932 == SqpApplication::PlotsInteractionMode::SelectionZones)
932 {
933 {
933 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
934 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
934 if (auto item = impl->m_plot->itemAt(posInPlot))
935 if (auto item = impl->m_plot->itemAt(posInPlot))
935 {
936 {
936 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
937 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
937 {
938 {
938 QMouseEvent e { QEvent::MouseMove, posInPlot, event->button(), event->buttons(),
939 QMouseEvent e { QEvent::MouseMove, posInPlot, event->button(), event->buttons(),
939 event->modifiers() };
940 event->modifiers() };
940 sqpApp->sendEvent(this->impl->m_plot, &e);
941 sqpApp->sendEvent(this->impl->m_plot, &e);
941 this->impl->m_plot->replot(QCustomPlot::rpImmediateRefresh);
942 this->impl->m_plot->replot(QCustomPlot::rpImmediateRefresh);
942 }
943 }
943 }
944 }
944 }
945 }
945 }
946 }
946 else
947 else
947 {
948 {
948 impl->m_RenderingDelegate->updateTooltip(event);
949 impl->m_RenderingDelegate->updateTooltip(event);
949 }
950 }
950 // event->accept();
951 // event->accept();
951 QWidget::mouseMoveEvent(event);
952 QWidget::mouseMoveEvent(event);
952 }
953 }
953
954
954 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent* event)
955 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent* event)
955 {
956 {
956 if (impl->isDrawingZoomRect())
957 if (impl->isDrawingZoomRect())
957 {
958 {
958 auto oldRange = this->graphRange();
959 auto oldRange = this->graphRange();
959 impl->applyZoomRect();
960 impl->applyZoomRect();
960 auto newRange = this->graphRange();
961 auto newRange = this->graphRange();
961 if (auto tf = DateTimeRangeHelper::computeTransformation(oldRange, newRange))
962 if (auto tf = DateTimeRangeHelper::computeTransformation(oldRange, newRange))
962 emit this->transform_sig(tf.value(), false);
963 emit this->transform_sig(tf.value(), false);
963 }
964 }
964 else if (impl->isDrawingZoneRect())
965 else if (impl->isDrawingZoneRect())
965 {
966 {
966 impl->endDrawingZone();
967 impl->endDrawingZone();
967 }
968 }
968 else
969 else
969 {
970 {
970 setCursor(Qt::ArrowCursor);
971 setCursor(Qt::ArrowCursor);
971 }
972 }
972 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
973 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
973 if (auto item = impl->m_plot->itemAt(posInPlot))
974 if (auto item = impl->m_plot->itemAt(posInPlot))
974 {
975 {
975 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
976 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
976 {
977 {
977 QMouseEvent e { QEvent::MouseButtonRelease, posInPlot, event->button(),
978 QMouseEvent e { QEvent::MouseButtonRelease, posInPlot, event->button(),
978 event->buttons(), event->modifiers() };
979 event->buttons(), event->modifiers() };
979 sqpApp->sendEvent(this->impl->m_plot, &e);
980 sqpApp->sendEvent(this->impl->m_plot, &e);
980 }
981 }
981 }
982 }
982 event->accept();
983 event->accept();
983 }
984 }
984
985
985 void VisualizationGraphWidget::mousePressEvent(QMouseEvent* event)
986 void VisualizationGraphWidget::mousePressEvent(QMouseEvent* event)
986 {
987 {
987 if (event->button() == Qt::RightButton)
988 if (event->button() == Qt::RightButton)
988 {
989 {
989 onGraphMenuRequested(event->pos());
990 onGraphMenuRequested(event->pos());
990 }
991 }
991 else
992 else
992 {
993 {
993 auto selectedZone = impl->selectionZoneAt(event->pos());
994 auto selectedZone = impl->selectionZoneAt(event->pos());
994 switch (sqpApp->plotsInteractionMode())
995 switch (sqpApp->plotsInteractionMode())
995 {
996 {
996 case SqpApplication::PlotsInteractionMode::DragAndDrop:
997 case SqpApplication::PlotsInteractionMode::DragAndDrop:
997 break;
998 break;
998 case SqpApplication::PlotsInteractionMode::SelectionZones:
999 case SqpApplication::PlotsInteractionMode::SelectionZones:
999 impl->setSelectionZonesEditionEnabled(true);
1000 impl->setSelectionZonesEditionEnabled(true);
1000 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
1001 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
1001 {
1002 {
1002 auto alreadySelectedZones
1003 auto alreadySelectedZones
1003 = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1004 = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1004 selectedZone->setAssociatedEditedZones(alreadySelectedZones);
1005 selectedZone->setAssociatedEditedZones(alreadySelectedZones);
1005 if (SciQLop::containers::contains(alreadySelectedZones, selectedZone))
1006 if (cpp_utils::containers::contains(alreadySelectedZones, selectedZone))
1006 {
1007 {
1007 alreadySelectedZones.removeOne(selectedZone);
1008 alreadySelectedZones.removeOne(selectedZone);
1008 }
1009 }
1009 else
1010 else
1010 {
1011 {
1011 alreadySelectedZones.append(selectedZone);
1012 alreadySelectedZones.append(selectedZone);
1012 }
1013 }
1013 parentVisualizationWidget()->selectionZoneManager().select(
1014 parentVisualizationWidget()->selectionZoneManager().select(
1014 alreadySelectedZones);
1015 alreadySelectedZones);
1015 }
1016 }
1016 else
1017 else
1017 {
1018 {
1018 if (!selectedZone)
1019 if (!selectedZone)
1019 {
1020 {
1020 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1021 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1021 impl->startDrawingZone(event->pos());
1022 impl->startDrawingZone(event->pos());
1022 }
1023 }
1023 else
1024 else
1024 {
1025 {
1025 parentVisualizationWidget()->selectionZoneManager().select(
1026 parentVisualizationWidget()->selectionZoneManager().select(
1026 { selectedZone });
1027 { selectedZone });
1027 }
1028 }
1028 }
1029 }
1029 {
1030 {
1030 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
1031 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
1031 if (auto item = impl->m_plot->itemAt(posInPlot))
1032 if (auto item = impl->m_plot->itemAt(posInPlot))
1032 {
1033 {
1033 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1034 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1034 {
1035 {
1035 QMouseEvent e { QEvent::MouseButtonPress, posInPlot, event->button(),
1036 QMouseEvent e { QEvent::MouseButtonPress, posInPlot, event->button(),
1036 event->buttons(), event->modifiers() };
1037 event->buttons(), event->modifiers() };
1037 sqpApp->sendEvent(this->impl->m_plot, &e);
1038 sqpApp->sendEvent(this->impl->m_plot, &e);
1038 }
1039 }
1039 }
1040 }
1040 }
1041 }
1041 break;
1042 break;
1042 case SqpApplication::PlotsInteractionMode::ZoomBox:
1043 case SqpApplication::PlotsInteractionMode::ZoomBox:
1043 impl->startDrawingRect(event->pos());
1044 impl->startDrawingRect(event->pos());
1044 break;
1045 break;
1045 default:
1046 default:
1046 if (auto item = impl->m_plot->itemAt(event->pos()))
1047 if (auto item = impl->m_plot->itemAt(event->pos()))
1047 {
1048 {
1048 emit impl->m_plot->itemClick(item, event);
1049 emit impl->m_plot->itemClick(item, event);
1049 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1050 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1050 {
1051 {
1051 setCursor(Qt::ClosedHandCursor);
1052 setCursor(Qt::ClosedHandCursor);
1052 impl->enterPlotDrag(event->pos());
1053 impl->enterPlotDrag(event->pos());
1053 }
1054 }
1054 }
1055 }
1055 else
1056 else
1056 {
1057 {
1057 setCursor(Qt::ClosedHandCursor);
1058 setCursor(Qt::ClosedHandCursor);
1058 impl->enterPlotDrag(event->pos());
1059 impl->enterPlotDrag(event->pos());
1059 }
1060 }
1060 }
1061 }
1061 }
1062 }
1062 // event->accept();
1063 // event->accept();
1063 QWidget::mousePressEvent(event);
1064 QWidget::mousePressEvent(event);
1064 }
1065 }
1065
1066
1066 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent* event)
1067 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent* event)
1067 {
1068 {
1068 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1069 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1069 }
1070 }
1070
1071
1071 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent* event)
1072 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent* event)
1072 {
1073 {
1073 switch (event->key())
1074 switch (event->key())
1074 {
1075 {
1075 case Qt::Key_Control:
1076 case Qt::Key_Control:
1076 event->accept();
1077 event->accept();
1077 break;
1078 break;
1078 case Qt::Key_Shift:
1079 case Qt::Key_Shift:
1079 event->accept();
1080 event->accept();
1080 break;
1081 break;
1081 default:
1082 default:
1082 QWidget::keyReleaseEvent(event);
1083 QWidget::keyReleaseEvent(event);
1083 break;
1084 break;
1084 }
1085 }
1085 setCursor(Qt::ArrowCursor);
1086 setCursor(Qt::ArrowCursor);
1086 // event->accept();
1087 // event->accept();
1087 }
1088 }
1088
1089
1089 void VisualizationGraphWidget::keyPressEvent(QKeyEvent* event)
1090 void VisualizationGraphWidget::keyPressEvent(QKeyEvent* event)
1090 {
1091 {
1091 switch (event->key())
1092 switch (event->key())
1092 {
1093 {
1093 case Qt::Key_Control:
1094 case Qt::Key_Control:
1094 setCursor(Qt::CrossCursor);
1095 setCursor(Qt::CrossCursor);
1095 break;
1096 break;
1096 case Qt::Key_Shift:
1097 case Qt::Key_Shift:
1097 break;
1098 break;
1098 case Qt::Key_M:
1099 case Qt::Key_M:
1099 impl->rescaleY();
1100 impl->rescaleY();
1100 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1101 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1101 break;
1102 break;
1102 case Qt::Key_Left:
1103 case Qt::Key_Left:
1103 if (event->modifiers() != Qt::ControlModifier)
1104 if (event->modifiers() != Qt::ControlModifier)
1104 {
1105 {
1105 move(-0.1, Qt::Horizontal);
1106 move(-0.1, Qt::Horizontal);
1106 }
1107 }
1107 else
1108 else
1108 {
1109 {
1109 zoom(2, this->width() / 2, Qt::Horizontal);
1110 zoom(2, this->width() / 2, Qt::Horizontal);
1110 }
1111 }
1111 break;
1112 break;
1112 case Qt::Key_Right:
1113 case Qt::Key_Right:
1113 if (event->modifiers() != Qt::ControlModifier)
1114 if (event->modifiers() != Qt::ControlModifier)
1114 {
1115 {
1115 move(0.1, Qt::Horizontal);
1116 move(0.1, Qt::Horizontal);
1116 }
1117 }
1117 else
1118 else
1118 {
1119 {
1119 zoom(0.5, this->width() / 2, Qt::Horizontal);
1120 zoom(0.5, this->width() / 2, Qt::Horizontal);
1120 }
1121 }
1121 break;
1122 break;
1122 case Qt::Key_Up:
1123 case Qt::Key_Up:
1123 if (event->modifiers() != Qt::ControlModifier)
1124 if (event->modifiers() != Qt::ControlModifier)
1124 {
1125 {
1125 move(0.1, Qt::Vertical);
1126 move(0.1, Qt::Vertical);
1126 }
1127 }
1127 else
1128 else
1128 {
1129 {
1129 zoom(0.5, this->height() / 2, Qt::Vertical);
1130 zoom(0.5, this->height() / 2, Qt::Vertical);
1130 }
1131 }
1131 break;
1132 break;
1132 case Qt::Key_Down:
1133 case Qt::Key_Down:
1133 if (event->modifiers() != Qt::ControlModifier)
1134 if (event->modifiers() != Qt::ControlModifier)
1134 {
1135 {
1135 move(-0.1, Qt::Vertical);
1136 move(-0.1, Qt::Vertical);
1136 }
1137 }
1137 else
1138 else
1138 {
1139 {
1139 zoom(2, this->height() / 2, Qt::Vertical);
1140 zoom(2, this->height() / 2, Qt::Vertical);
1140 }
1141 }
1141 break;
1142 break;
1142 default:
1143 default:
1143 QWidget::keyPressEvent(event);
1144 QWidget::keyPressEvent(event);
1144 break;
1145 break;
1145 }
1146 }
1146 }
1147 }
1147
1148
1148 QCustomPlot& VisualizationGraphWidget::plot() const noexcept
1149 QCustomPlot& VisualizationGraphWidget::plot() const noexcept
1149 {
1150 {
1150 return *impl->m_plot;
1151 return *impl->m_plot;
1151 }
1152 }
1152
1153
1153 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint& pos) noexcept
1154 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint& pos) noexcept
1154 {
1155 {
1155 QMenu graphMenu {};
1156 QMenu graphMenu {};
1156
1157
1157 // Iterates on variables (unique keys)
1158 // Iterates on variables (unique keys)
1158 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1159 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1159 end = impl->m_VariableToPlotMultiMap.cend();
1160 end = impl->m_VariableToPlotMultiMap.cend();
1160 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first))
1161 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first))
1161 {
1162 {
1162 // 'Remove variable' action
1163 // 'Remove variable' action
1163 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1164 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1164 [this, var = it->first]() { removeVariable(var); });
1165 [this, var = it->first]() { removeVariable(var); });
1165 }
1166 }
1166
1167
1167 if (!impl->m_ZoomStack.isEmpty())
1168 if (!impl->m_ZoomStack.isEmpty())
1168 {
1169 {
1169 if (!graphMenu.isEmpty())
1170 if (!graphMenu.isEmpty())
1170 {
1171 {
1171 graphMenu.addSeparator();
1172 graphMenu.addSeparator();
1172 }
1173 }
1173
1174
1174 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1175 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1175 }
1176 }
1176
1177
1177 // Selection Zone Actions
1178 // Selection Zone Actions
1178 auto selectionZoneItem = impl->selectionZoneAt(pos);
1179 auto selectionZoneItem = impl->selectionZoneAt(pos);
1179 if (selectionZoneItem)
1180 if (selectionZoneItem)
1180 {
1181 {
1181 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1182 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1182 selectedItems.removeAll(selectionZoneItem);
1183 selectedItems.removeAll(selectionZoneItem);
1183 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1184 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1184
1185
1185 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1186 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1186 if (!zoneActions.isEmpty() && !graphMenu.isEmpty())
1187 if (!zoneActions.isEmpty() && !graphMenu.isEmpty())
1187 {
1188 {
1188 graphMenu.addSeparator();
1189 graphMenu.addSeparator();
1189 }
1190 }
1190
1191
1191 QHash<QString, QMenu*> subMenus;
1192 QHash<QString, QMenu*> subMenus;
1192 QHash<QString, bool> subMenusEnabled;
1193 QHash<QString, bool> subMenusEnabled;
1193 QHash<QString, FilteringAction*> filteredMenu;
1194 QHash<QString, FilteringAction*> filteredMenu;
1194
1195
1195 for (auto zoneAction : zoneActions)
1196 for (auto zoneAction : zoneActions)
1196 {
1197 {
1197
1198
1198 auto isEnabled = zoneAction->isEnabled(selectedItems);
1199 auto isEnabled = zoneAction->isEnabled(selectedItems);
1199
1200
1200 auto menu = &graphMenu;
1201 auto menu = &graphMenu;
1201 QString menuPath;
1202 QString menuPath;
1202 for (auto subMenuName : zoneAction->subMenuList())
1203 for (auto subMenuName : zoneAction->subMenuList())
1203 {
1204 {
1204 menuPath += '/';
1205 menuPath += '/';
1205 menuPath += subMenuName;
1206 menuPath += subMenuName;
1206
1207
1207 if (!subMenus.contains(menuPath))
1208 if (!subMenus.contains(menuPath))
1208 {
1209 {
1209 menu = menu->addMenu(subMenuName);
1210 menu = menu->addMenu(subMenuName);
1210 subMenus[menuPath] = menu;
1211 subMenus[menuPath] = menu;
1211 subMenusEnabled[menuPath] = isEnabled;
1212 subMenusEnabled[menuPath] = isEnabled;
1212 }
1213 }
1213 else
1214 else
1214 {
1215 {
1215 menu = subMenus.value(menuPath);
1216 menu = subMenus.value(menuPath);
1216 if (isEnabled)
1217 if (isEnabled)
1217 {
1218 {
1218 // The sub menu is enabled if at least one of its actions is enabled
1219 // The sub menu is enabled if at least one of its actions is enabled
1219 subMenusEnabled[menuPath] = true;
1220 subMenusEnabled[menuPath] = true;
1220 }
1221 }
1221 }
1222 }
1222 }
1223 }
1223
1224
1224 FilteringAction* filterAction = nullptr;
1225 FilteringAction* filterAction = nullptr;
1225 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList()))
1226 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList()))
1226 {
1227 {
1227 filterAction = filteredMenu.value(menuPath);
1228 filterAction = filteredMenu.value(menuPath);
1228 if (!filterAction)
1229 if (!filterAction)
1229 {
1230 {
1230 filterAction = new FilteringAction { this };
1231 filterAction = new FilteringAction { this };
1231 filteredMenu[menuPath] = filterAction;
1232 filteredMenu[menuPath] = filterAction;
1232 menu->addAction(filterAction);
1233 menu->addAction(filterAction);
1233 }
1234 }
1234 }
1235 }
1235
1236
1236 auto action = menu->addAction(zoneAction->name());
1237 auto action = menu->addAction(zoneAction->name());
1237 action->setEnabled(isEnabled);
1238 action->setEnabled(isEnabled);
1238 action->setShortcut(zoneAction->displayedShortcut());
1239 action->setShortcut(zoneAction->displayedShortcut());
1239 QObject::connect(action, &QAction::triggered,
1240 QObject::connect(action, &QAction::triggered,
1240 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1241 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1241
1242
1242 if (filterAction && zoneAction->isFilteringAllowed())
1243 if (filterAction && zoneAction->isFilteringAllowed())
1243 {
1244 {
1244 filterAction->addActionToFilter(action);
1245 filterAction->addActionToFilter(action);
1245 }
1246 }
1246 }
1247 }
1247
1248
1248 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it)
1249 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it)
1249 {
1250 {
1250 it.value()->setEnabled(subMenusEnabled[it.key()]);
1251 it.value()->setEnabled(subMenusEnabled[it.key()]);
1251 }
1252 }
1252 }
1253 }
1253
1254
1254 if (!graphMenu.isEmpty())
1255 if (!graphMenu.isEmpty())
1255 {
1256 {
1256 graphMenu.exec(QCursor::pos());
1257 graphMenu.exec(QCursor::pos());
1257 }
1258 }
1258 }
1259 }
1259
1260
1260 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent* event) noexcept
1261 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent* event) noexcept
1261 {
1262 {
1262 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1263 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1263 }
1264 }
1264
1265
1265 void VisualizationGraphWidget::onMouseMove(QMouseEvent* event) noexcept
1266 void VisualizationGraphWidget::onMouseMove(QMouseEvent* event) noexcept
1266 {
1267 {
1267 // Handles plot rendering when mouse is moving
1268 // Handles plot rendering when mouse is moving
1268 impl->m_RenderingDelegate->updateTooltip(event);
1269 impl->m_RenderingDelegate->updateTooltip(event);
1269
1270
1270 auto axisPos = impl->posToAxisPos(event->pos());
1271 auto axisPos = impl->posToAxisPos(event->pos());
1271
1272
1272 // Zoom box and zone drawing
1273 // Zoom box and zone drawing
1273 if (impl->m_DrawingZoomRect)
1274 if (impl->m_DrawingZoomRect)
1274 {
1275 {
1275 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1276 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1276 }
1277 }
1277 else if (impl->m_DrawingZone)
1278 else if (impl->m_DrawingZone)
1278 {
1279 {
1279 impl->m_DrawingZone->setEnd(axisPos.x());
1280 impl->m_DrawingZone->setEnd(axisPos.x());
1280 }
1281 }
1281
1282
1282 // Cursor
1283 // Cursor
1283 if (auto parentZone = parentZoneWidget())
1284 if (auto parentZone = parentZoneWidget())
1284 {
1285 {
1285 if (impl->pointIsInAxisRect(axisPos, plot()))
1286 if (impl->pointIsInAxisRect(axisPos, plot()))
1286 {
1287 {
1287 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1288 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1288 }
1289 }
1289 else
1290 else
1290 {
1291 {
1291 parentZone->notifyMouseLeaveGraph(this);
1292 parentZone->notifyMouseLeaveGraph(this);
1292 }
1293 }
1293 }
1294 }
1294
1295
1295 // Search for the selection zone under the mouse
1296 // Search for the selection zone under the mouse
1296 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1297 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1297 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1298 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1298 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
1299 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
1299 {
1300 {
1300
1301
1301 // Sets the appropriate cursor shape
1302 // Sets the appropriate cursor shape
1302 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1303 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1303 setCursor(cursorShape);
1304 setCursor(cursorShape);
1304
1305
1305 // Manages the hovered zone
1306 // Manages the hovered zone
1306 if (selectionZoneItemUnderCursor != impl->m_HoveredZone)
1307 if (selectionZoneItemUnderCursor != impl->m_HoveredZone)
1307 {
1308 {
1308 if (impl->m_HoveredZone)
1309 if (impl->m_HoveredZone)
1309 {
1310 {
1310 impl->m_HoveredZone->setHovered(false);
1311 impl->m_HoveredZone->setHovered(false);
1311 }
1312 }
1312 selectionZoneItemUnderCursor->setHovered(true);
1313 selectionZoneItemUnderCursor->setHovered(true);
1313 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1314 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1314 plot().replot(QCustomPlot::rpQueuedReplot);
1315 plot().replot(QCustomPlot::rpQueuedReplot);
1315 }
1316 }
1316 }
1317 }
1317 else
1318 else
1318 {
1319 {
1319 // There is no zone under the mouse or the interaction mode is not "selection zones"
1320 // There is no zone under the mouse or the interaction mode is not "selection zones"
1320 if (impl->m_HoveredZone)
1321 if (impl->m_HoveredZone)
1321 {
1322 {
1322 impl->m_HoveredZone->setHovered(false);
1323 impl->m_HoveredZone->setHovered(false);
1323 impl->m_HoveredZone = nullptr;
1324 impl->m_HoveredZone = nullptr;
1324 }
1325 }
1325
1326
1326 setCursor(Qt::ArrowCursor);
1327 setCursor(Qt::ArrowCursor);
1327 }
1328 }
1328
1329
1329 impl->m_HasMovedMouse = true;
1330 impl->m_HasMovedMouse = true;
1330 VisualizationDragWidget::mouseMoveEvent(event);
1331 VisualizationDragWidget::mouseMoveEvent(event);
1331 }
1332 }
1332
1333
1333 void VisualizationGraphWidget::onMouseWheel(QWheelEvent* event) noexcept
1334 void VisualizationGraphWidget::onMouseWheel(QWheelEvent* event) noexcept
1334 {
1335 {
1335 // Processes event only if the wheel occurs on axis rect
1336 // Processes event only if the wheel occurs on axis rect
1336 if (!dynamic_cast<QCPAxisRect*>(impl->m_plot->layoutElementAt(event->posF())))
1337 if (!dynamic_cast<QCPAxisRect*>(impl->m_plot->layoutElementAt(event->posF())))
1337 {
1338 {
1338 return;
1339 return;
1339 }
1340 }
1340
1341
1341 auto value = event->angleDelta().x() + event->angleDelta().y();
1342 auto value = event->angleDelta().x() + event->angleDelta().y();
1342 if (value != 0)
1343 if (value != 0)
1343 {
1344 {
1344
1345
1345 auto direction = value > 0 ? 1.0 : -1.0;
1346 auto direction = value > 0 ? 1.0 : -1.0;
1346 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1347 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1347 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1348 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1348 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1349 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1349
1350
1350 auto zoomOrientations = QFlags<Qt::Orientation> {};
1351 auto zoomOrientations = QFlags<Qt::Orientation> {};
1351 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1352 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1352 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1353 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1353
1354
1354 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1355 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1355
1356
1356 if (!isZoomX && !isZoomY)
1357 if (!isZoomX && !isZoomY)
1357 {
1358 {
1358 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1359 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1359 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1360 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1360
1361
1361 axis->setRange(axis->range() + diff);
1362 axis->setRange(axis->range() + diff);
1362
1363
1363 if (plot().noAntialiasingOnDrag())
1364 if (plot().noAntialiasingOnDrag())
1364 {
1365 {
1365 plot().setNotAntialiasedElements(QCP::aeAll);
1366 plot().setNotAntialiasedElements(QCP::aeAll);
1366 }
1367 }
1367
1368
1368 // plot().replot(QCustomPlot::rpQueuedReplot);
1369 // plot().replot(QCustomPlot::rpQueuedReplot);
1369 }
1370 }
1370 }
1371 }
1371 }
1372 }
1372
1373
1373 void VisualizationGraphWidget::onMousePress(QMouseEvent* event) noexcept
1374 void VisualizationGraphWidget::onMousePress(QMouseEvent* event) noexcept
1374 {
1375 {
1375 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1376 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1376 auto isSelectionZoneMode
1377 auto isSelectionZoneMode
1377 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1378 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1378 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1379 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1379
1380
1380 if (!isDragDropClick && isLeftClick)
1381 if (!isDragDropClick && isLeftClick)
1381 {
1382 {
1382 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox)
1383 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox)
1383 {
1384 {
1384 // Starts a zoom box
1385 // Starts a zoom box
1385 impl->startDrawingRect(event->pos());
1386 impl->startDrawingRect(event->pos());
1386 }
1387 }
1387 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr)
1388 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr)
1388 {
1389 {
1389 // Starts a new selection zone
1390 // Starts a new selection zone
1390 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1391 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1391 if (!zoneAtPos)
1392 if (!zoneAtPos)
1392 {
1393 {
1393 impl->startDrawingZone(event->pos());
1394 impl->startDrawingZone(event->pos());
1394 }
1395 }
1395 }
1396 }
1396 }
1397 }
1397
1398
1398
1399
1399 // Allows zone edition only in selection zone mode without drag&drop
1400 // Allows zone edition only in selection zone mode without drag&drop
1400 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1401 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1401
1402
1402 // Selection / Deselection
1403 // Selection / Deselection
1403 if (isSelectionZoneMode)
1404 if (isSelectionZoneMode)
1404 {
1405 {
1405 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1406 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1406 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1407 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1407
1408
1408
1409
1409 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1410 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1410 && !isMultiSelectionClick)
1411 && !isMultiSelectionClick)
1411 {
1412 {
1412 parentVisualizationWidget()->selectionZoneManager().select(
1413 parentVisualizationWidget()->selectionZoneManager().select(
1413 { selectionZoneItemUnderCursor });
1414 { selectionZoneItemUnderCursor });
1414 }
1415 }
1415 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick)
1416 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick)
1416 {
1417 {
1417 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1418 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1418 }
1419 }
1419 else
1420 else
1420 {
1421 {
1421 // No selection change
1422 // No selection change
1422 }
1423 }
1423
1424
1424 if (selectionZoneItemUnderCursor && isLeftClick)
1425 if (selectionZoneItemUnderCursor && isLeftClick)
1425 {
1426 {
1426 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1427 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1427 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1428 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1428 }
1429 }
1429 }
1430 }
1430
1431
1431
1432
1432 impl->m_HasMovedMouse = false;
1433 impl->m_HasMovedMouse = false;
1433 VisualizationDragWidget::mousePressEvent(event);
1434 VisualizationDragWidget::mousePressEvent(event);
1434 }
1435 }
1435
1436
1436 void VisualizationGraphWidget::onMouseRelease(QMouseEvent* event) noexcept
1437 void VisualizationGraphWidget::onMouseRelease(QMouseEvent* event) noexcept
1437 {
1438 {
1438 if (impl->m_DrawingZoomRect)
1439 if (impl->m_DrawingZoomRect)
1439 {
1440 {
1440
1441
1441 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1442 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1442 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1443 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1443
1444
1444 auto newAxisXRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().x(),
1445 auto newAxisXRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().x(),
1445 impl->m_DrawingZoomRect->bottomRight->coords().x() };
1446 impl->m_DrawingZoomRect->bottomRight->coords().x() };
1446
1447
1447 auto newAxisYRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().y(),
1448 auto newAxisYRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().y(),
1448 impl->m_DrawingZoomRect->bottomRight->coords().y() };
1449 impl->m_DrawingZoomRect->bottomRight->coords().y() };
1449
1450
1450 impl->removeDrawingRect();
1451 impl->removeDrawingRect();
1451
1452
1452 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1453 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1453 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
1454 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
1454 {
1455 {
1455 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1456 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1456 axisX->setRange(newAxisXRange);
1457 axisX->setRange(newAxisXRange);
1457 axisY->setRange(newAxisYRange);
1458 axisY->setRange(newAxisYRange);
1458
1459
1459 plot().replot(QCustomPlot::rpQueuedReplot);
1460 plot().replot(QCustomPlot::rpQueuedReplot);
1460 }
1461 }
1461 }
1462 }
1462
1463
1463 impl->endDrawingZone();
1464 impl->endDrawingZone();
1464
1465
1465 // Selection / Deselection
1466 // Selection / Deselection
1466 auto isSelectionZoneMode
1467 auto isSelectionZoneMode
1467 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1468 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1468 if (isSelectionZoneMode)
1469 if (isSelectionZoneMode)
1469 {
1470 {
1470 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1471 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1471 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1472 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1472 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1473 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1473 && !impl->m_HasMovedMouse)
1474 && !impl->m_HasMovedMouse)
1474 {
1475 {
1475
1476
1476 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1477 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1477 if (zonesUnderCursor.count() > 1)
1478 if (zonesUnderCursor.count() > 1)
1478 {
1479 {
1479 // There are multiple zones under the mouse.
1480 // There are multiple zones under the mouse.
1480 // Performs the selection with a selection dialog.
1481 // Performs the selection with a selection dialog.
1481 VisualizationMultiZoneSelectionDialog dialog { this };
1482 VisualizationMultiZoneSelectionDialog dialog { this };
1482 dialog.setZones(zonesUnderCursor);
1483 dialog.setZones(zonesUnderCursor);
1483 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1484 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1484 dialog.activateWindow();
1485 dialog.activateWindow();
1485 dialog.raise();
1486 dialog.raise();
1486 if (dialog.exec() == QDialog::Accepted)
1487 if (dialog.exec() == QDialog::Accepted)
1487 {
1488 {
1488 auto selection = dialog.selectedZones();
1489 auto selection = dialog.selectedZones();
1489
1490
1490 if (!isMultiSelectionClick)
1491 if (!isMultiSelectionClick)
1491 {
1492 {
1492 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1493 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1493 }
1494 }
1494
1495
1495 for (auto it = selection.cbegin(); it != selection.cend(); ++it)
1496 for (auto it = selection.cbegin(); it != selection.cend(); ++it)
1496 {
1497 {
1497 auto zone = it.key();
1498 auto zone = it.key();
1498 auto isSelected = it.value();
1499 auto isSelected = it.value();
1499 parentVisualizationWidget()->selectionZoneManager().setSelected(
1500 parentVisualizationWidget()->selectionZoneManager().setSelected(
1500 zone, isSelected);
1501 zone, isSelected);
1501
1502
1502 if (isSelected)
1503 if (isSelected)
1503 {
1504 {
1504 // Puts the zone on top of the stack so it can be moved or resized
1505 // Puts the zone on top of the stack so it can be moved or resized
1505 impl->moveSelectionZoneOnTop(zone, plot());
1506 impl->moveSelectionZoneOnTop(zone, plot());
1506 }
1507 }
1507 }
1508 }
1508 }
1509 }
1509 }
1510 }
1510 else
1511 else
1511 {
1512 {
1512 if (!isMultiSelectionClick)
1513 if (!isMultiSelectionClick)
1513 {
1514 {
1514 parentVisualizationWidget()->selectionZoneManager().select(
1515 parentVisualizationWidget()->selectionZoneManager().select(
1515 { selectionZoneItemUnderCursor });
1516 { selectionZoneItemUnderCursor });
1516 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1517 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1517 }
1518 }
1518 else
1519 else
1519 {
1520 {
1520 parentVisualizationWidget()->selectionZoneManager().setSelected(
1521 parentVisualizationWidget()->selectionZoneManager().setSelected(
1521 selectionZoneItemUnderCursor,
1522 selectionZoneItemUnderCursor,
1522 !selectionZoneItemUnderCursor->selected()
1523 !selectionZoneItemUnderCursor->selected()
1523 || event->button() == Qt::RightButton);
1524 || event->button() == Qt::RightButton);
1524 }
1525 }
1525 }
1526 }
1526 }
1527 }
1527 else
1528 else
1528 {
1529 {
1529 // No selection change
1530 // No selection change
1530 }
1531 }
1531 }
1532 }
1532 }
1533 }
1533
1534
1534 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1535 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1535 {
1536 {
1536 auto graphRange = impl->m_plot->xAxis->range();
1537 auto graphRange = impl->m_plot->xAxis->range();
1537 auto dateTime = DateTimeRange { graphRange.lower, graphRange.upper };
1538 auto dateTime = DateTimeRange { graphRange.lower, graphRange.upper };
1538
1539
1539 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
1540 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
1540 {
1541 {
1541 auto variable = variableEntry.first;
1542 auto variable = variableEntry.first;
1542 qCDebug(LOG_VisualizationGraphWidget())
1543 qCDebug(LOG_VisualizationGraphWidget())
1543 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1544 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1544 qCDebug(LOG_VisualizationGraphWidget())
1545 qCDebug(LOG_VisualizationGraphWidget())
1545 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1546 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1546 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range()))
1547 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range()))
1547 {
1548 {
1548 impl->updateData(variableEntry.second, variable, variable->range());
1549 impl->updateData(variableEntry.second, variable, variable->range());
1549 }
1550 }
1550 }
1551 }
1551 }
1552 }
1552
1553
1553 void VisualizationGraphWidget::onUpdateVarDisplaying(
1554 void VisualizationGraphWidget::onUpdateVarDisplaying(
1554 std::shared_ptr<Variable2> variable, const DateTimeRange& range)
1555 std::shared_ptr<Variable2> variable, const DateTimeRange& range)
1555 {
1556 {
1556 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1557 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1557 if (it != impl->m_VariableToPlotMultiMap.end())
1558 if (it != impl->m_VariableToPlotMultiMap.end())
1558 {
1559 {
1559 impl->updateData(it->second, variable, range);
1560 impl->updateData(it->second, variable, range);
1560 }
1561 }
1561 }
1562 }
1562
1563
1563 void VisualizationGraphWidget::variableUpdated(QUuid id)
1564 void VisualizationGraphWidget::variableUpdated(QUuid id)
1564 {
1565 {
1565 for (auto& [var, plotables] : impl->m_VariableToPlotMultiMap)
1566 for (auto& [var, plotables] : impl->m_VariableToPlotMultiMap)
1566 {
1567 {
1567 if (var->ID() == id)
1568 if (var->ID() == id)
1568 {
1569 {
1569 impl->updateData(plotables, var, this->graphRange());
1570 impl->updateData(plotables, var, this->graphRange());
1570 }
1571 }
1571 }
1572 }
1572 }
1573 }
1573
1574
1574 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable2>& variable)
1575 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable2>& variable)
1575 {
1576 {
1576 this->removeVariable(variable);
1577 this->removeVariable(variable);
1577 }
1578 }
@@ -1,171 +1,171
1 #ifndef GUITESTUTILS_H
1 #ifndef GUITESTUTILS_H
2 #define GUITESTUTILS_H
2 #define GUITESTUTILS_H
3
3
4 #include <Common/cpp_utils.h>
4 #include <types/detectors.hpp>
5 #include <QCoreApplication>
5 #include <QCoreApplication>
6 #include <QCursor>
6 #include <QCursor>
7 #include <QDesktopWidget>
7 #include <QDesktopWidget>
8 #include <QMouseEvent>
8 #include <QMouseEvent>
9 #include <QPoint>
9 #include <QPoint>
10 #include <QtTest>
10 #include <QtTest>
11
11
12 #include <SqpApplication.h>
12 #include <SqpApplication.h>
13 #include <Variable/VariableController2.h>
13 #include <Variable/VariableController2.h>
14 #include <qcustomplot.h>
14 #include <qcustomplot.h>
15
15
16 template <typename T>
16 template <typename T>
17 QPoint center(T* widget)
17 QPoint center(T* widget)
18 {
18 {
19 return QPoint { widget->width() / 2, widget->height() / 2 };
19 return QPoint { widget->width() / 2, widget->height() / 2 };
20 }
20 }
21
21
22 HAS_METHOD(viewport)
22 HAS_METHOD(has_viewport, viewport)
23
23
24 template <typename T>
24 template <typename T>
25 static inline constexpr bool is_QWidgetOrDerived = std::is_base_of<QWidget, T>::value;
25 static inline constexpr bool is_QWidgetOrDerived = std::is_base_of<QWidget, T>::value;
26
26
27 template <typename T>
27 template <typename T>
28 using viewport_type = decltype(std::declval<T>().viewport());
28 using viewport_type = decltype(std::declval<T>().viewport());
29
29
30 HAS_METHOD(topLevelItem)
30 HAS_METHOD(has_topLevelItem, topLevelItem)
31
31
32 template <typename T>
32 template <typename T>
33 void mouseMove(T* widget, QPoint pos, Qt::MouseButton mouseModifier)
33 void mouseMove(T* widget, QPoint pos, Qt::MouseButton mouseModifier)
34 {
34 {
35 QCursor::setPos(widget->mapToGlobal(pos));
35 QCursor::setPos(widget->mapToGlobal(pos));
36 QMouseEvent event(QEvent::MouseMove, pos, Qt::NoButton, mouseModifier, Qt::NoModifier);
36 QMouseEvent event(QEvent::MouseMove, pos, Qt::NoButton, mouseModifier, Qt::NoModifier);
37 if constexpr (has_viewport<T>)
37 if constexpr (has_viewport_v<T>)
38 {
38 {
39 if constexpr (is_QWidgetOrDerived<viewport_type<T>>)
39 if constexpr (is_QWidgetOrDerived<viewport_type<T>>)
40 {
40 {
41 qApp->sendEvent(widget->viewport(), &event);
41 qApp->sendEvent(widget->viewport(), &event);
42 }
42 }
43 else
43 else
44 {
44 {
45 qApp->sendEvent(widget, &event);
45 qApp->sendEvent(widget, &event);
46 }
46 }
47 }
47 }
48 else
48 else
49 {
49 {
50 qApp->sendEvent(widget, &event);
50 qApp->sendEvent(widget, &event);
51 }
51 }
52 qApp->processEvents();
52 qApp->processEvents();
53 }
53 }
54
54
55
55
56 template <typename T>
56 template <typename T>
57 void setMouseTracking(T* widget)
57 void setMouseTracking(T* widget)
58 {
58 {
59 if constexpr (has_viewport<T>)
59 if constexpr (has_viewport_v<T>)
60 {
60 {
61 if constexpr (is_QWidgetOrDerived<viewport_type<T>>)
61 if constexpr (is_QWidgetOrDerived<viewport_type<T>>)
62 {
62 {
63 widget->viewport()->setMouseTracking(true);
63 widget->viewport()->setMouseTracking(true);
64 }
64 }
65 else
65 else
66 {
66 {
67 widget->setMouseTracking(true);
67 widget->setMouseTracking(true);
68 }
68 }
69 }
69 }
70 else
70 else
71 {
71 {
72 widget->setMouseTracking(true);
72 widget->setMouseTracking(true);
73 }
73 }
74 }
74 }
75
75
76 template <typename T, typename T2>
76 template <typename T, typename T2>
77 auto getItem(T* widget, T2 itemIndex)
77 auto getItem(T* widget, T2 itemIndex)
78 {
78 {
79 if constexpr (has_topLevelItem<T>)
79 if constexpr (has_topLevelItem_v<T>)
80 {
80 {
81 return widget->topLevelItem(itemIndex);
81 return widget->topLevelItem(itemIndex);
82 }
82 }
83 else
83 else
84 {
84 {
85 return widget->item(itemIndex);
85 return widget->item(itemIndex);
86 }
86 }
87 }
87 }
88
88
89 #define SELECT_ITEM(widget, itemIndex, item) \
89 #define SELECT_ITEM(widget, itemIndex, item) \
90 auto item = getItem(widget, itemIndex); \
90 auto item = getItem(widget, itemIndex); \
91 { \
91 { \
92 auto itemCenterPos = widget->visualItemRect(item).center(); \
92 auto itemCenterPos = widget->visualItemRect(item).center(); \
93 QTest::mouseClick(widget->viewport(), Qt::LeftButton, Qt::NoModifier, itemCenterPos); \
93 QTest::mouseClick(widget->viewport(), Qt::LeftButton, Qt::NoModifier, itemCenterPos); \
94 QVERIFY(widget->selectedItems().size() > 0); \
94 QVERIFY(widget->selectedItems().size() > 0); \
95 QVERIFY(widget->selectedItems().contains(item)); \
95 QVERIFY(widget->selectedItems().contains(item)); \
96 }
96 }
97
97
98
98
99 #define GET_CHILD_WIDGET_FOR_GUI_TESTS(parent, child, childType, childName) \
99 #define GET_CHILD_WIDGET_FOR_GUI_TESTS(parent, child, childType, childName) \
100 childType* child = parent.findChild<childType*>(childName); \
100 childType* child = parent.findChild<childType*>(childName); \
101 QVERIFY(child != Q_NULLPTR); \
101 QVERIFY(child != Q_NULLPTR); \
102 setMouseTracking(child);
102 setMouseTracking(child);
103
103
104 template <typename T1, typename T2, typename T3, typename T4 = void>
104 template <typename T1, typename T2, typename T3, typename T4 = void>
105 void dragnDropItem(T1* sourceWidget, T2* destWidget, T3* item, T4* destItem = Q_NULLPTR)
105 void dragnDropItem(T1* sourceWidget, T2* destWidget, T3* item, T4* destItem = Q_NULLPTR)
106 {
106 {
107 auto itemCenterPos = sourceWidget->visualItemRect(item).center();
107 auto itemCenterPos = sourceWidget->visualItemRect(item).center();
108 if constexpr (has_viewport<T1>)
108 if constexpr (has_viewport_v<T1>)
109 {
109 {
110 QTest::mousePress(sourceWidget->viewport(), Qt::LeftButton, Qt::NoModifier, itemCenterPos);
110 QTest::mousePress(sourceWidget->viewport(), Qt::LeftButton, Qt::NoModifier, itemCenterPos);
111 }
111 }
112 else
112 else
113 {
113 {
114 QTest::mousePress(sourceWidget, Qt::LeftButton, Qt::NoModifier, itemCenterPos);
114 QTest::mousePress(sourceWidget, Qt::LeftButton, Qt::NoModifier, itemCenterPos);
115 }
115 }
116 mouseMove(sourceWidget, itemCenterPos, Qt::LeftButton);
116 mouseMove(sourceWidget, itemCenterPos, Qt::LeftButton);
117 itemCenterPos += QPoint(0, -10);
117 itemCenterPos += QPoint(0, -10);
118 QTimer::singleShot(100, [destWidget, destItem]() {
118 QTimer::singleShot(100, [destWidget, destItem]() {
119 mouseMove(destWidget, destWidget->rect().center(), Qt::LeftButton);
119 mouseMove(destWidget, destWidget->rect().center(), Qt::LeftButton);
120 mouseMove(destWidget, destWidget->rect().center() + QPoint(0, -10), Qt::LeftButton);
120 mouseMove(destWidget, destWidget->rect().center() + QPoint(0, -10), Qt::LeftButton);
121 if constexpr (!std::is_same_v<void, T4>)
121 if constexpr (!std::is_same_v<void, T4>)
122 {
122 {
123 auto destItemCenterPos = destWidget->visualItemRect(destItem).center();
123 auto destItemCenterPos = destWidget->visualItemRect(destItem).center();
124 QTest::mouseRelease(destWidget, Qt::LeftButton, Qt::NoModifier, destItemCenterPos);
124 QTest::mouseRelease(destWidget, Qt::LeftButton, Qt::NoModifier, destItemCenterPos);
125 }
125 }
126 else if constexpr (has_viewport<T2>)
126 else if constexpr (has_viewport_v<T2>)
127 {
127 {
128 QTest::mouseRelease(destWidget->viewport(), Qt::LeftButton);
128 QTest::mouseRelease(destWidget->viewport(), Qt::LeftButton);
129 }
129 }
130 else
130 else
131 {
131 {
132 QTest::mouseRelease(destWidget, Qt::LeftButton);
132 QTest::mouseRelease(destWidget, Qt::LeftButton);
133 }
133 }
134 QTest::mouseRelease(destWidget->viewport(), Qt::LeftButton);
134 QTest::mouseRelease(destWidget->viewport(), Qt::LeftButton);
135 });
135 });
136 mouseMove(sourceWidget, itemCenterPos, Qt::LeftButton);
136 mouseMove(sourceWidget, itemCenterPos, Qt::LeftButton);
137 }
137 }
138
138
139 template <typename T>
139 template <typename T>
140 void scroll_graph(T* w, int dx)
140 void scroll_graph(T* w, int dx)
141 {
141 {
142 auto cent = center(w);
142 auto cent = center(w);
143 QTest::mousePress(w, Qt::LeftButton, Qt::NoModifier, cent, 1);
143 QTest::mousePress(w, Qt::LeftButton, Qt::NoModifier, cent, 1);
144 mouseMove(w, { cent.x() + dx, cent.y() }, Qt::LeftButton);
144 mouseMove(w, { cent.x() + dx, cent.y() }, Qt::LeftButton);
145 QTest::mouseRelease(w, Qt::LeftButton);
145 QTest::mouseRelease(w, Qt::LeftButton);
146 }
146 }
147
147
148 ALIAS_TEMPLATE_FUNCTION(isReady, static_cast<SqpApplication*>(qApp)->variableController().isReady)
148 ALIAS_TEMPLATE_FUNCTION(isReady, static_cast<SqpApplication*>(qApp)->variableController().isReady)
149
149
150 void waitForVar(std::shared_ptr<Variable2> var)
150 void waitForVar(std::shared_ptr<Variable2> var)
151 {
151 {
152 while (!isReady(var))
152 while (!isReady(var))
153 QCoreApplication::processEvents();
153 QCoreApplication::processEvents();
154 }
154 }
155
155
156 template <typename T>
156 template <typename T>
157 bool prepare_gui_test(T* w)
157 bool prepare_gui_test(T* w)
158 {
158 {
159 w->setGeometry(QRect(
159 w->setGeometry(QRect(
160 QPoint(QApplication::desktop()->geometry().center() - QPoint(250, 250)), QSize(500, 500)));
160 QPoint(QApplication::desktop()->geometry().center() - QPoint(250, 250)), QSize(500, 500)));
161 w->show();
161 w->show();
162 qApp->setActiveWindow(w);
162 qApp->setActiveWindow(w);
163 return QTest::qWaitForWindowActive(w);
163 return QTest::qWaitForWindowActive(w);
164 }
164 }
165
165
166 #define GET_CHILD_WIDGET_FOR_GUI_TESTS(parent, child, childType, childName) \
166 #define GET_CHILD_WIDGET_FOR_GUI_TESTS(parent, child, childType, childName) \
167 childType* child = parent.findChild<childType*>(childName); \
167 childType* child = parent.findChild<childType*>(childName); \
168 QVERIFY(child != Q_NULLPTR); \
168 QVERIFY(child != Q_NULLPTR); \
169 setMouseTracking(child);
169 setMouseTracking(child);
170
170
171 #endif
171 #endif
@@ -1,86 +1,86
1 #include <QMainWindow>
1 #include <QMainWindow>
2 #include <QObject>
2 #include <QObject>
3 #include <QScreen>
3 #include <QScreen>
4 #include <QString>
4 #include <QString>
5 #include <QWheelEvent>
5 #include <QWheelEvent>
6 #include <QtTest>
6 #include <QtTest>
7 #include <cstdlib>
7 #include <cstdlib>
8
8
9
9
10 #include <Common/cpp_utils.h>
10 #include <cpp_utils.hpp>
11 #include <SqpApplication.h>
11 #include <SqpApplication.h>
12
12
13 #include <GUITestUtils.h>
13 #include <GUITestUtils.h>
14
14
15 #include <Catalogue/CatalogueController.h>
15 #include <Catalogue/CatalogueController.h>
16 #include <Catalogue2/browser.h>
16 #include <Catalogue2/browser.h>
17
17
18 template <int EventsCount = 1000>
18 template <int EventsCount = 1000>
19 auto build_CatalogueBrowser_test()
19 auto build_CatalogueBrowser_test()
20 {
20 {
21 sqpApp->catalogueController().add("test");
21 sqpApp->catalogueController().add("test");
22 sqpApp->catalogueController().add("stuff");
22 sqpApp->catalogueController().add("stuff");
23 sqpApp->catalogueController().add("default");
23 sqpApp->catalogueController().add("default");
24 sqpApp->catalogueController().add("new catalogue", "default");
24 sqpApp->catalogueController().add("new catalogue", "default");
25 auto catalogue = sqpApp->catalogueController().add("new catalogue2", "default");
25 auto catalogue = sqpApp->catalogueController().add("new catalogue2", "default");
26 for (auto _ : std::array<char, EventsCount>())
26 for (auto _ : std::array<char, EventsCount>())
27 {
27 {
28 (void)_;
28 (void)_;
29 static int i = 0;
29 static int i = 0;
30 auto event = CatalogueController::make_event_ptr();
30 auto event = CatalogueController::make_event_ptr();
31 event->name = std::string("Event ") + std::to_string(i++);
31 event->name = std::string("Event ") + std::to_string(i++);
32 event->tags = { "tag1", "tag2" };
32 event->tags = { "tag1", "tag2" };
33 event->products = { CatalogueController::Event_t::Product_t {
33 event->products = { CatalogueController::Event_t::Product_t {
34 std::string("Product2") + std::to_string(rand() % 30),
34 std::string("Product2") + std::to_string(rand() % 30),
35 static_cast<double>(1532357932 + rand() % 100),
35 static_cast<double>(1532357932 + rand() % 100),
36 static_cast<double>(1532358932 + rand() % 100) },
36 static_cast<double>(1532358932 + rand() % 100) },
37 CatalogueController::Event_t::Product_t {
37 CatalogueController::Event_t::Product_t {
38 std::string("Product2") + std::to_string(rand() % 30),
38 std::string("Product2") + std::to_string(rand() % 30),
39 static_cast<double>(1532357932 + rand() % 200),
39 static_cast<double>(1532357932 + rand() % 200),
40 static_cast<double>(1532358932 + rand() % 200) },
40 static_cast<double>(1532358932 + rand() % 200) },
41 CatalogueController::Event_t::Product_t {
41 CatalogueController::Event_t::Product_t {
42 std::string("Product2") + std::to_string(rand() % 30),
42 std::string("Product2") + std::to_string(rand() % 30),
43 static_cast<double>(1532357932 + rand() % 70),
43 static_cast<double>(1532357932 + rand() % 70),
44 static_cast<double>(1532358932 + rand() % 70) } };
44 static_cast<double>(1532358932 + rand() % 70) } };
45 catalogue->add(event);
45 catalogue->add(event);
46 }
46 }
47 return std::make_unique<CataloguesBrowser>();
47 return std::make_unique<CataloguesBrowser>();
48 }
48 }
49
49
50 class A_CatalogueBrowser : public QObject
50 class A_CatalogueBrowser : public QObject
51 {
51 {
52 Q_OBJECT
52 Q_OBJECT
53 public:
53 public:
54 explicit A_CatalogueBrowser(QObject* parent = Q_NULLPTR) : QObject(parent) {}
54 explicit A_CatalogueBrowser(QObject* parent = Q_NULLPTR) : QObject(parent) {}
55
55
56 private slots:
56 private slots:
57 void can_sort_events()
57 void can_sort_events()
58 {
58 {
59 auto w = build_CatalogueBrowser_test();
59 auto w = build_CatalogueBrowser_test();
60 QVERIFY(prepare_gui_test(w.get()));
60 QVERIFY(prepare_gui_test(w.get()));
61 // GET_CHILD_WIDGET_FOR_GUI_TESTS((*w.get()),,,)
61 // GET_CHILD_WIDGET_FOR_GUI_TESTS((*w.get()),,,)
62 for (int i = 0; i < 1000000; i++)
62 for (int i = 0; i < 1000000; i++)
63 {
63 {
64 QThread::usleep(100);
64 QThread::usleep(100);
65 QCoreApplication::processEvents();
65 QCoreApplication::processEvents();
66 }
66 }
67 }
67 }
68 };
68 };
69
69
70 QT_BEGIN_NAMESPACE
70 QT_BEGIN_NAMESPACE
71 QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS
71 QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS
72 QT_END_NAMESPACE
72 QT_END_NAMESPACE
73 int main(int argc, char* argv[])
73 int main(int argc, char* argv[])
74 {
74 {
75 Q_INIT_RESOURCE(sqpguiresources);
75 Q_INIT_RESOURCE(sqpguiresources);
76
76
77 SqpApplication app { argc, argv };
77 SqpApplication app { argc, argv };
78 app.setAttribute(Qt::AA_Use96Dpi, true);
78 app.setAttribute(Qt::AA_Use96Dpi, true);
79 QTEST_DISABLE_KEYPAD_NAVIGATION;
79 QTEST_DISABLE_KEYPAD_NAVIGATION;
80 QTEST_ADD_GPU_BLACKLIST_SUPPORT;
80 QTEST_ADD_GPU_BLACKLIST_SUPPORT;
81 A_CatalogueBrowser tc;
81 A_CatalogueBrowser tc;
82 QTEST_SET_MAIN_SOURCE_PATH;
82 QTEST_SET_MAIN_SOURCE_PATH;
83 return QTest::qExec(&tc, argc, argv);
83 return QTest::qExec(&tc, argc, argv);
84 }
84 }
85
85
86 #include "main.moc"
86 #include "main.moc"
General Comments 0
You need to be logged in to leave comments. Login now