##// END OF EJS Templates
Generates unique name for variable duplicate
Alexandre Leroux -
r709:8d80a1dd4059
parent child
Show More
@@ -0,0 +1,35
1 #ifndef SCIQLOP_STRINGUTILS_H
2 #define SCIQLOP_STRINGUTILS_H
3
4 #include "CoreGlobal.h"
5
6 #include <vector>
7
8 class QString;
9
10 /**
11 * Utility class with methods for strings
12 */
13 struct SCIQLOP_CORE_EXPORT StringUtils {
14 /**
15 * Generates a unique name from a default name and a set of forbidden names.
16 *
17 * Generating the unique name is done by adding an index to the default name and stopping at the
18 * first index for which the generated name is not in the forbidden names.
19 *
20 * Examples (defaultName, forbiddenNames -> result):
21 * - "FGM", {"FGM"} -> "FGM1"
22 * - "FGM", {"ABC"} -> "FGM"
23 * - "FGM", {"FGM", "FGM1"} -> "FGM2"
24 * - "FGM", {"FGM", "FGM2"} -> "FGM1"
25 * - "", {"ABC"} -> "1"
26 *
27 * @param defaultName the default name
28 * @param forbiddenNames the set of forbidden names
29 * @return the unique name generated
30 */
31 static QString uniqueName(const QString &defaultName,
32 const std::vector<QString> &forbiddenNames) noexcept;
33 };
34
35 #endif // SCIQLOP_STRINGUTILS_H
@@ -0,0 +1,30
1 #include "Common/StringUtils.h"
2
3 #include <QRegExp>
4 #include <QString>
5
6 #include <set>
7
8 QString StringUtils::uniqueName(const QString &defaultName,
9 const std::vector<QString> &forbiddenNames) noexcept
10 {
11 // Gets the base of the unique name to generate, by removing trailing number (for example, base
12 // name of "FGM12" is "FGM")
13 auto baseName = defaultName;
14 baseName.remove(QRegExp{QStringLiteral("\\d*$")});
15
16 // Finds the unique name by adding an index to the base name and stops when the generated name
17 // isn't forbidden
18 QString newName{};
19 auto forbidden = true;
20 for (auto i = 0; forbidden; ++i) {
21 newName = (i == 0) ? baseName : baseName + QString::number(i);
22 forbidden = newName.isEmpty()
23 || std::any_of(forbiddenNames.cbegin(), forbiddenNames.cend(),
24 [&newName](const auto &name) {
25 return name.compare(newName, Qt::CaseInsensitive) == 0;
26 });
27 }
28
29 return newName;
30 }
@@ -0,0 +1,50
1 #include <Common/StringUtils.h>
2
3 #include <QObject>
4 #include <QtTest>
5
6 class TestStringUtils : public QObject {
7 Q_OBJECT
8
9 private slots:
10 void testUniqueName_data();
11 void testUniqueName();
12 };
13
14 void TestStringUtils::testUniqueName_data()
15 {
16 // ////////////// //
17 // Test structure //
18 // ////////////// //
19
20 QTest::addColumn<QString>("defaultName");
21 QTest::addColumn<std::vector<QString> >("forbiddenNames");
22 QTest::addColumn<QString>("expectedName");
23
24 // ////////// //
25 // Test cases //
26 // ////////// //
27
28 QTest::newRow("uniqueName") << "FGM" << std::vector<QString>{"FGM2"} << "FGM";
29 QTest::newRow("uniqueName2") << "FGM2" << std::vector<QString>{"FGM", "FGM1", "FGM2"} << "FGM3";
30 QTest::newRow("uniqueName3") << "FGM1" << std::vector<QString>{"FGM1"} << "FGM";
31 QTest::newRow("uniqueName4") << "FGM" << std::vector<QString>{"FGM"} << "FGM1";
32 QTest::newRow("uniqueName5") << "FGM" << std::vector<QString>{"FGM", "FGM1", "FGM3"} << "FGM2";
33 QTest::newRow("uniqueName6") << "FGM" << std::vector<QString>{"A", "B", "C"} << "FGM";
34 QTest::newRow("uniqueName7") << "FGM" << std::vector<QString>{"fGm", "FGm1", "Fgm2"} << "FGM3";
35 QTest::newRow("uniqueName8") << "" << std::vector<QString>{"A", "B", "C"} << "1";
36 QTest::newRow("uniqueName9") << "24" << std::vector<QString>{"A", "B", "C"} << "1";
37 }
38
39 void TestStringUtils::testUniqueName()
40 {
41 QFETCH(QString, defaultName);
42 QFETCH(std::vector<QString>, forbiddenNames);
43 QFETCH(QString, expectedName);
44
45 auto result = StringUtils::uniqueName(defaultName, forbiddenNames);
46 QCOMPARE(result, expectedName);
47 }
48
49 QTEST_MAIN(TestStringUtils)
50 #include "TestStringUtils.moc"
@@ -1,62 +1,63
1 1
2 2 core_moc_headers = [
3 3 'include/Data/IDataProvider.h',
4 4 'include/DataSource/DataSourceController.h',
5 5 'include/DataSource/DataSourceItemAction.h',
6 6 'include/Network/NetworkController.h',
7 7 'include/Time/TimeController.h',
8 8 'include/Variable/Variable.h',
9 9 'include/Variable/VariableCacheController.h',
10 10 'include/Variable/VariableController.h',
11 11 'include/Variable/VariableAcquisitionWorker.h',
12 12 'include/Variable/VariableCacheStrategy.h',
13 13 'include/Variable/VariableSynchronizationGroup.h',
14 14 'include/Variable/VariableModel.h',
15 15 'include/Visualization/VisualizationController.h'
16 16 ]
17 17
18 18
19 19 core_moc_files = qt5.preprocess(moc_headers : core_moc_headers)
20 20
21 21 core_sources = [
22 22 'src/Common/DateUtils.cpp',
23 'src/Common/StringUtils.cpp',
23 24 'src/Data/ScalarSeries.cpp',
24 25 'src/Data/DataSeriesIterator.cpp',
25 26 'src/Data/ArrayDataIterator.cpp',
26 27 'src/Data/VectorSeries.cpp',
27 28 'src/DataSource/DataSourceController.cpp',
28 29 'src/DataSource/DataSourceItem.cpp',
29 30 'src/DataSource/DataSourceItemAction.cpp',
30 31 'src/Network/NetworkController.cpp',
31 32 'src/Plugin/PluginManager.cpp',
32 33 'src/Settings/SqpSettingsDefs.cpp',
33 34 'src/Time/TimeController.cpp',
34 35 'src/Variable/Variable.cpp',
35 36 'src/Variable/VariableCacheController.cpp',
36 37 'src/Variable/VariableController.cpp',
37 38 'src/Variable/VariableAcquisitionWorker.cpp',
38 39 'src/Variable/VariableCacheStrategy.cpp',
39 40 'src/Variable/VariableSynchronizationGroup.cpp',
40 41 'src/Variable/VariableModel.cpp',
41 42 'src/Visualization/VisualizationController.cpp'
42 43 ]
43 44
44 45 core_inc = include_directories(['include', '../plugin/include'])
45 46
46 47 sciqlop_core_lib = library('sciqlopcore',
47 48 core_sources,
48 49 core_moc_files,
49 50 cpp_args : '-DCORE_LIB',
50 51 include_directories : core_inc,
51 52 dependencies : [qt5core, qt5network],
52 53 install : true
53 54 )
54 55
55 56
56 57 sciqlop_core = declare_dependency(link_with : sciqlop_core_lib,
57 58 include_directories : core_inc,
58 59 dependencies : [qt5core, qt5network])
59 60
60 61
61 62 subdir('tests')
62 63
@@ -1,269 +1,284
1 1 #include <Variable/Variable.h>
2 2 #include <Variable/VariableModel.h>
3 3
4 4 #include <Common/DateUtils.h>
5 #include <Common/StringUtils.h>
5 6
6 7 #include <Data/IDataSeries.h>
7 8
8 9 #include <QSize>
9 10 #include <unordered_map>
10 11
11 12 Q_LOGGING_CATEGORY(LOG_VariableModel, "VariableModel")
12 13
13 14 namespace {
14 15
15 16 // Column indexes
16 17 const auto NAME_COLUMN = 0;
17 18 const auto TSTART_COLUMN = 1;
18 19 const auto TEND_COLUMN = 2;
19 20 const auto UNIT_COLUMN = 3;
20 21 const auto MISSION_COLUMN = 4;
21 22 const auto PLUGIN_COLUMN = 5;
22 23 const auto NB_COLUMNS = 6;
23 24
24 25 // Column properties
25 26 const auto DEFAULT_HEIGHT = 25;
26 27 const auto DEFAULT_WIDTH = 100;
27 28
28 29 struct ColumnProperties {
29 30 ColumnProperties(const QString &name = {}, int width = DEFAULT_WIDTH,
30 31 int height = DEFAULT_HEIGHT)
31 32 : m_Name{name}, m_Width{width}, m_Height{height}
32 33 {
33 34 }
34 35
35 36 QString m_Name;
36 37 int m_Width;
37 38 int m_Height;
38 39 };
39 40
40 41 const auto COLUMN_PROPERTIES = QHash<int, ColumnProperties>{
41 42 {NAME_COLUMN, {QObject::tr("Name")}}, {TSTART_COLUMN, {QObject::tr("tStart"), 180}},
42 43 {TEND_COLUMN, {QObject::tr("tEnd"), 180}}, {UNIT_COLUMN, {QObject::tr("Unit")}},
43 44 {MISSION_COLUMN, {QObject::tr("Mission")}}, {PLUGIN_COLUMN, {QObject::tr("Plugin")}}};
44 45
45 46 /// Format for datetimes
46 47 const auto DATETIME_FORMAT = QStringLiteral("dd/MM/yyyy \nhh:mm:ss:zzz");
47 48
49 QString uniqueName(const QString &defaultName,
50 const std::vector<std::shared_ptr<Variable> > &variables)
51 {
52 auto forbiddenNames = std::vector<QString>(variables.size());
53 std::transform(variables.cbegin(), variables.cend(), forbiddenNames.begin(),
54 [](const auto &variable) { return variable->name(); });
55 auto uniqueName = StringUtils::uniqueName(defaultName, forbiddenNames);
56 Q_ASSERT(!uniqueName.isEmpty());
57
58 return uniqueName;
59 }
48 60
49 61 } // namespace
50 62
51 63 struct VariableModel::VariableModelPrivate {
52 64 /// Variables created in SciQlop
53 65 std::vector<std::shared_ptr<Variable> > m_Variables;
54 66 std::unordered_map<std::shared_ptr<Variable>, double> m_VariableToProgress;
55 67
56 68 /// Return the row index of the variable. -1 if it's not found
57 69 int indexOfVariable(Variable *variable) const noexcept;
58 70 };
59 71
60 72 VariableModel::VariableModel(QObject *parent)
61 73 : QAbstractTableModel{parent}, impl{spimpl::make_unique_impl<VariableModelPrivate>()}
62 74 {
63 75 }
64 76
65 77 void VariableModel::addVariable(std::shared_ptr<Variable> variable) noexcept
66 78 {
67 79 auto insertIndex = rowCount();
68 80 beginInsertRows({}, insertIndex, insertIndex);
69 81
82 // Generates unique name for the variable
83 variable->setName(uniqueName(variable->name(), impl->m_Variables));
84
70 85 impl->m_Variables.push_back(variable);
71 86 connect(variable.get(), &Variable::updated, this, &VariableModel::onVariableUpdated);
72 87
73 88 endInsertRows();
74 89 }
75 90
76 91 std::shared_ptr<Variable> VariableModel::createVariable(const QString &name,
77 92 const SqpRange &dateTime,
78 93 const QVariantHash &metadata) noexcept
79 94 {
80 95 auto variable = std::make_shared<Variable>(name, dateTime, metadata);
81 96 addVariable(variable);
82 97
83 98 return variable;
84 99 }
85 100
86 101 void VariableModel::deleteVariable(std::shared_ptr<Variable> variable) noexcept
87 102 {
88 103 if (!variable) {
89 104 qCCritical(LOG_Variable()) << "Can't delete a null variable from the model";
90 105 return;
91 106 }
92 107
93 108 // Finds variable in the model
94 109 auto begin = impl->m_Variables.cbegin();
95 110 auto end = impl->m_Variables.cend();
96 111 auto it = std::find(begin, end, variable);
97 112 if (it != end) {
98 113 auto removeIndex = std::distance(begin, it);
99 114
100 115 // Deletes variable
101 116 beginRemoveRows({}, removeIndex, removeIndex);
102 117 impl->m_Variables.erase(it);
103 118 endRemoveRows();
104 119 }
105 120 else {
106 121 qCritical(LOG_VariableModel())
107 122 << tr("Can't delete variable %1 from the model: the variable is not in the model")
108 123 .arg(variable->name());
109 124 }
110 125
111 126 // Removes variable from progress map
112 127 impl->m_VariableToProgress.erase(variable);
113 128 }
114 129
115 130
116 131 std::shared_ptr<Variable> VariableModel::variable(int index) const
117 132 {
118 133 return (index >= 0 && index < impl->m_Variables.size()) ? impl->m_Variables[index] : nullptr;
119 134 }
120 135
121 136 std::vector<std::shared_ptr<Variable> > VariableModel::variables() const
122 137 {
123 138 return impl->m_Variables;
124 139 }
125 140
126 141 void VariableModel::setDataProgress(std::shared_ptr<Variable> variable, double progress)
127 142 {
128 143 if (progress > 0.0) {
129 144 impl->m_VariableToProgress[variable] = progress;
130 145 }
131 146 else {
132 147 impl->m_VariableToProgress.erase(variable);
133 148 }
134 149 auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN);
135 150
136 151 emit dataChanged(modelIndex, modelIndex);
137 152 }
138 153
139 154 int VariableModel::columnCount(const QModelIndex &parent) const
140 155 {
141 156 Q_UNUSED(parent);
142 157
143 158 return NB_COLUMNS;
144 159 }
145 160
146 161 int VariableModel::rowCount(const QModelIndex &parent) const
147 162 {
148 163 Q_UNUSED(parent);
149 164
150 165 return impl->m_Variables.size();
151 166 }
152 167
153 168 QVariant VariableModel::data(const QModelIndex &index, int role) const
154 169 {
155 170 if (!index.isValid()) {
156 171 return QVariant{};
157 172 }
158 173
159 174 if (index.row() < 0 || index.row() >= rowCount()) {
160 175 return QVariant{};
161 176 }
162 177
163 178 if (role == Qt::DisplayRole) {
164 179 if (auto variable = impl->m_Variables.at(index.row()).get()) {
165 180 switch (index.column()) {
166 181 case NAME_COLUMN:
167 182 return variable->name();
168 183 case TSTART_COLUMN: {
169 184 auto range = variable->realRange();
170 185 return range != INVALID_RANGE
171 186 ? DateUtils::dateTime(range.m_TStart).toString(DATETIME_FORMAT)
172 187 : QVariant{};
173 188 }
174 189 case TEND_COLUMN: {
175 190 auto range = variable->realRange();
176 191 return range != INVALID_RANGE
177 192 ? DateUtils::dateTime(range.m_TEnd).toString(DATETIME_FORMAT)
178 193 : QVariant{};
179 194 }
180 195 case UNIT_COLUMN:
181 196 return variable->metadata().value(QStringLiteral("units"));
182 197 case MISSION_COLUMN:
183 198 return variable->metadata().value(QStringLiteral("mission"));
184 199 case PLUGIN_COLUMN:
185 200 return variable->metadata().value(QStringLiteral("plugin"));
186 201 default:
187 202 // No action
188 203 break;
189 204 }
190 205
191 206 qWarning(LOG_VariableModel())
192 207 << tr("Can't get data (unknown column %1)").arg(index.column());
193 208 }
194 209 else {
195 210 qWarning(LOG_VariableModel()) << tr("Can't get data (no variable)");
196 211 }
197 212 }
198 213 else if (role == VariableRoles::ProgressRole) {
199 214 if (auto variable = impl->m_Variables.at(index.row())) {
200 215
201 216 auto it = impl->m_VariableToProgress.find(variable);
202 217 if (it != impl->m_VariableToProgress.cend()) {
203 218 return it->second;
204 219 }
205 220 }
206 221 }
207 222
208 223 return QVariant{};
209 224 }
210 225
211 226 QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int role) const
212 227 {
213 228 if (role != Qt::DisplayRole && role != Qt::SizeHintRole) {
214 229 return QVariant{};
215 230 }
216 231
217 232 if (orientation == Qt::Horizontal) {
218 233 auto propertiesIt = COLUMN_PROPERTIES.find(section);
219 234 if (propertiesIt != COLUMN_PROPERTIES.cend()) {
220 235 // Role is either DisplayRole or SizeHintRole
221 236 return (role == Qt::DisplayRole)
222 237 ? QVariant{propertiesIt->m_Name}
223 238 : QVariant{QSize{propertiesIt->m_Width, propertiesIt->m_Height}};
224 239 }
225 240 else {
226 241 qWarning(LOG_VariableModel())
227 242 << tr("Can't get header data (unknown column %1)").arg(section);
228 243 }
229 244 }
230 245
231 246 return QVariant{};
232 247 }
233 248
234 249 void VariableModel::abortProgress(const QModelIndex &index)
235 250 {
236 251 if (auto variable = impl->m_Variables.at(index.row())) {
237 252 emit abortProgessRequested(variable);
238 253 }
239 254 }
240 255
241 256 void VariableModel::onVariableUpdated() noexcept
242 257 {
243 258 // Finds variable that has been updated in the model
244 259 if (auto updatedVariable = dynamic_cast<Variable *>(sender())) {
245 260 auto updatedVariableIndex = impl->indexOfVariable(updatedVariable);
246 261
247 262 if (updatedVariableIndex > -1) {
248 263 emit dataChanged(createIndex(updatedVariableIndex, 0),
249 264 createIndex(updatedVariableIndex, columnCount() - 1));
250 265 }
251 266 }
252 267 }
253 268
254 269 int VariableModel::VariableModelPrivate::indexOfVariable(Variable *variable) const noexcept
255 270 {
256 271 auto begin = std::cbegin(m_Variables);
257 272 auto end = std::cend(m_Variables);
258 273 auto it
259 274 = std::find_if(begin, end, [variable](const auto &var) { return var.get() == variable; });
260 275
261 276 if (it != end) {
262 277 // Gets the index of the variable in the model: we assume here that views have the same
263 278 // order as the model
264 279 return std::distance(begin, it);
265 280 }
266 281 else {
267 282 return -1;
268 283 }
269 284 }
@@ -1,18 +1,19
1 1
2 2
3 3 tests = [
4 [['Common/TestStringUtils.cpp'],'test_string_utils','StringUtils test'],
4 5 [['Data/TestDataSeries.cpp'],'test_data','DataSeries test'],
5 6 [['Data/TestOneDimArrayData.cpp'],'test_1d','One Dim Array test'],
6 7 [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'],
7 8 [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'],
8 9 [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'],
9 10 [['Variable/TestVariable.cpp'],'test_variable','Variable test']
10 11 ]
11 12
12 13 foreach unit_test : tests
13 14 test_moc_files = qt5.preprocess(moc_sources : unit_test[0])
14 15 test_exe = executable(unit_test[1],unit_test[0] , test_moc_files,
15 16 dependencies : [sciqlop_core, qt5test])
16 17 test(unit_test[2], test_exe, args: ['-teamcity', '-o', '@0@.teamcity.txt'.format(unit_test[1])])
17 18 endforeach
18 19
General Comments 0
You need to be logged in to leave comments. Login now