FOSSredder 0.5.0
Loading...
Searching...
No Matches
PropertyViewModel.cpp
Go to the documentation of this file.
1
5
7
8#include <QStringList>
9
17
18namespace ui {
19
20namespace {
21
22QString stringValue(const QVariant& value) {
23 const QVariantMap map = value.toMap();
24 if (!map.isEmpty()) {
25 const QString aliasValue = map.value(QStringLiteral("value")).toString();
26 if (!aliasValue.isEmpty()) {
27 return aliasValue;
28 }
29 const QString aliasSource = map.value(QStringLiteral("source")).toString();
30 if (!aliasSource.isEmpty()) {
31 return aliasSource;
32 }
33 const QString id = map.value(QStringLiteral("id")).toString();
34 if (!id.isEmpty()) {
35 return id;
36 }
37 }
38 return value.toString();
39}
40
41QVariantList normalizeStringValues(const QVariantList& values) {
42 QVariantList out;
43 out.reserve(values.size());
44 for (const auto& value : values) {
45 out.push_back(stringValue(value));
46 }
47 return out;
48}
49
50QString rowIdAt(const QVariantList& rows, int index,
51 const QString& idKey = payload::keys::common::kId) {
52 if (index < 0 || index >= rows.size()) {
53 return {};
54 }
55 return rows.at(index).toMap().value(idKey).toString();
56}
57
58int rowIndexOfId(const QVariantList& rows, const QString& id,
59 const QString& idKey = payload::keys::common::kId) {
60 if (id.isEmpty()) {
61 return -1;
62 }
63 for (int i = 0; i < rows.size(); ++i) {
64 if (rows.at(i).toMap().value(idKey).toString() == id) {
65 return i;
66 }
67 }
68 return -1;
69}
70
71QVariantMap rowById(const QVariantList& rows, const QString& id,
72 const QString& idKey = payload::keys::common::kId) {
73 const int index = rowIndexOfId(rows, id.trimmed(), idKey);
74 return index >= 0 ? rows.at(index).toMap() : QVariantMap{};
75}
76
77QString
78navigatedSelectionId(const QVariantList& rows, const QString& currentId,
79 int delta, int defaultIndex = 0,
80 const QString& idKey = payload::keys::common::kId) {
81 const int currentIndex = rowIndexOfId(rows, currentId, idKey);
82 if (currentIndex < 0) {
83 if (rows.isEmpty()) {
84 return {};
85 }
86 if (delta > 0) {
87 return rowIdAt(rows, 0, idKey);
88 }
89 if (delta < 0) {
90 return rowIdAt(rows, rows.size() - 1, idKey);
91 }
92 return rowIdAt(rows, defaultIndex, idKey);
93 }
94 if (delta > 0) {
95 return currentIndex >= rows.size() - 1
96 ? QString()
97 : rowIdAt(rows, currentIndex + 1, idKey);
98 }
99 if (delta < 0) {
100 return currentIndex <= 0 ? QString()
101 : rowIdAt(rows, currentIndex - 1, idKey);
102 }
103 return rowIdAt(rows, currentIndex, idKey);
104}
105
106QString
107deleteNextSelectionId(const QVariantList& rows, const QString& removedId,
108 int defaultIndex = 0,
109 const QString& idKey = payload::keys::common::kId) {
110 const int removedIndex = rowIndexOfId(rows, removedId, idKey);
111 if (rows.isEmpty()) {
112 return {};
113 }
114 const int nextIndex = removedIndex >= 0 ? removedIndex + 1 : defaultIndex;
115 const int wrapped = nextIndex % rows.size();
116 return rowIdAt(rows, wrapped < 0 ? wrapped + rows.size() : wrapped, idKey);
117}
118
119bool canAddText(const QString& value) {
120 return !value.trimmed().isEmpty();
121}
122
123bool canRemoveAt(const QVariantList& values, int index) {
124 return index >= 0 && index < values.size();
125}
126
127bool containsTrimmed(const QVariantList& values, const QString& id) {
128 const QString nextId = id.trimmed();
129 if (nextId.isEmpty()) {
130 return false;
131 }
132 for (const auto& value : values) {
133 if (stringValue(value).trimmed() == nextId) {
134 return true;
135 }
136 }
137 return false;
138}
139
140QVariantList addUniqueTrimmed(const QVariantList& values,
141 const QString& value) {
142 const QString next = value.trimmed();
143 QVariantList out = normalizeStringValues(values);
144 if (next.isEmpty() || containsTrimmed(out, next)) {
145 return out;
146 }
147 out.push_back(next);
148 return out;
149}
150
151QVariantList removeTrimmed(const QVariantList& values, const QString& value) {
152 const QString target = value.trimmed();
153 if (target.isEmpty()) {
154 return values;
155 }
156 QVariantList out;
157 out.reserve(values.size());
158 for (const auto& entry : values) {
159 const QString current = stringValue(entry).trimmed();
160 if (current != target) {
161 out.push_back(current);
162 }
163 }
164 return out;
165}
166
167QVariantList removeAt(const QVariantList& values, int index) {
168 QVariantList out = normalizeStringValues(values);
169 if (!canRemoveAt(out, index)) {
170 return out;
171 }
172 out.removeAt(index);
173 return out;
174}
175
176int aliasIndexAfterRemoval(const QVariantList& aliases, int removedIndex) {
177 if (aliases.isEmpty()) {
178 return -1;
179 }
180 return removedIndex < aliases.size() ? removedIndex : aliases.size() - 1;
181}
182
183bool appendAliasValue(QVariantList& aliases, QString& aliasInputText,
184 int& aliasIndex, const QString& value) {
185 const QVariantList next = addUniqueTrimmed(aliases, value);
186 if (next == aliases) {
187 return false;
188 }
189 aliases = next;
190 aliasIndex = aliases.isEmpty() ? -1 : aliases.size() - 1;
191 aliasInputText.clear();
192 return true;
193}
194
195bool removeAliasValue(QVariantList& aliases, int& aliasIndex, int index) {
196 const QVariantList next = removeAt(aliases, index);
197 if (next == aliases) {
198 return false;
199 }
200 aliases = next;
201 aliasIndex = aliasIndexAfterRemoval(aliases, index);
202 return true;
203}
204
205bool setSelectedId(QVariantList& selectedIds, const QString& id,
206 bool selected) {
207 const QString normalizedId = id.trimmed();
208 if (normalizedId.isEmpty()) {
209 return false;
210 }
211 const QVariantList next = selected
212 ? addUniqueTrimmed(selectedIds, normalizedId)
213 : removeTrimmed(selectedIds, normalizedId);
214 if (next == selectedIds) {
215 return false;
216 }
217 selectedIds = next;
218 return true;
219}
220
221QStringList normalizedStringList(const QVariantList& values) {
222 const QVariantList normalized = normalizeStringValues(values);
223 QStringList out;
224 out.reserve(normalized.size());
225 for (const auto& value : normalized) {
226 out.push_back(value.toString());
227 }
228 out.sort();
229 return out;
230}
231
232bool formStateChanged(const QString& savedName,
233 const QVariantList& savedAliases,
234 const QVariantList& savedContractIds,
235 const QString& currentName, const QVariantList& aliases,
236 const QVariantList& contractIds) {
237 return savedName != currentName ||
238 normalizedStringList(savedAliases) != normalizedStringList(aliases) ||
239 normalizedStringList(savedContractIds) !=
240 normalizedStringList(contractIds);
241}
242
243QVariantMap propertyFormStateFromRow(const QString& name,
244 const QVariantList& aliases,
245 const QVariantList& selectedIds = {}) {
246 QVariantMap out;
247 const QVariantList normalizedAliases = normalizeStringValues(aliases);
248 out.insert(payload::keys::common::kName, name);
249 out.insert(payload::keys::property::kAliases, normalizedAliases);
250 out.insert(payload::keys::state::kAliasInputText, QString());
252 normalizedAliases.isEmpty() ? -1 : 0);
254 normalizeStringValues(selectedIds));
255 return out;
256}
257
258} // namespace
259
261 WorkspaceCommands* commands,
262 WorkspaceSelection* selection,
263 WorkspaceSelectors* selectors,
264 QObject* parent)
265 : QObject(parent), store_(store), commands_(commands),
266 selection_(selection), selectors_(selectors) {
267 bindSignals();
268 reloadFromSelection(true);
269}
270
271void PropertyViewModel::setName(const QString& value) {
272 if (name_ == value) {
273 return;
274 }
275 name_ = value;
276 emit changed();
277}
278
279void PropertyViewModel::setAliases(const QVariantList& value) {
280 if (aliases_ == value) {
281 return;
282 }
283 aliases_ = value;
284 if (aliasIndex_ >= aliases_.size()) {
285 aliasIndex_ = aliases_.isEmpty() ? -1 : aliases_.size() - 1;
286 }
287 emit changed();
288}
289
290void PropertyViewModel::setAliasInputText(const QString& value) {
291 if (aliasInputText_ == value) {
292 return;
293 }
294 aliasInputText_ = value;
295 emit changed();
296}
297
299 if (aliasIndex_ == value) {
300 return;
301 }
302 aliasIndex_ = value;
303 emit changed();
304}
305
306void PropertyViewModel::setSelectedContractIds(const QVariantList& value) {
307 if (selectedContractIds_ == value) {
308 return;
309 }
310 selectedContractIds_ = value;
311 emit changed();
312}
313
315 return !currentId().isEmpty();
316}
317
319 return !name_.trimmed().isEmpty();
320}
321
323 if (!isEdit()) {
324 return canSubmit();
325 }
326 return formStateChanged(savedName_, savedAliases_, savedSelectedContractIds_,
327 name_, aliases_, selectedContractIds_);
328}
329
330bool PropertyViewModel::canAddAlias(const QString& value) const {
331 return canAddText(value);
332}
333
334bool PropertyViewModel::hasValidAliasSelection() const {
335 return canRemoveAt(aliases_, aliasIndex_);
336}
337
338void PropertyViewModel::addAlias(const QString& value) {
339 if (!appendAliasValue(aliases_, aliasInputText_, aliasIndex_, value)) {
340 return;
341 }
342 emit changed();
343}
344
346 if (!hasValidAliasSelection() ||
347 !removeAliasValue(aliases_, aliasIndex_, aliasIndex_)) {
348 return;
349 }
350 emit changed();
351}
352
354 setCurrentId({});
355 currentOwnerId_.clear();
356 clearFormState();
357 captureSavedState();
358}
359
361 const QVariantList rows = propertyRows();
362 if (rows.isEmpty()) {
363 return;
364 }
365 setCurrentId(navigatedSelectionId(rows, isEdit() ? currentId() : QString(),
366 -1, rows.size() - 1));
367}
368
370 const QVariantList rows = propertyRows();
371 if (rows.isEmpty()) {
372 return;
373 }
374 setCurrentId(
375 navigatedSelectionId(rows, isEdit() ? currentId() : QString(), 1, 0));
376}
377
379 if (!commands_) {
380 return {};
381 }
383 "PropertyViewModel::submit",
384 isEdit() ? "Property update submitted" : "Property create submitted",
385 {{observability::context::kId, currentId().toStdString()},
386 {observability::context::kName, name_.toStdString()}});
387 const QString id = commands_->saveProperty(
388 isEdit() ? currentId() : QString(), name_,
390 payload::mapper::toQStringList(selectedContractIds_));
391 if (!id.isEmpty()) {
392 setCurrentId(id);
393 }
394 captureSavedState();
395 return id;
396}
397
399 if (!commands_ || currentId().isEmpty()) {
400 return;
401 }
403 "PropertyViewModel::deleteCurrent", "Property delete submitted",
404 {{observability::context::kId, currentId().toStdString()}});
405 const QString removedId = currentId();
406 commands_->deleteProperty(removedId);
407 setCurrentId(deleteNextSelectionId(propertyRows(), removedId, 0,
408 QStringLiteral("id")));
409}
410
412 currentOwnerId_.clear();
413 clearFormState();
414 captureSavedState();
415}
416
417void PropertyViewModel::setContractSelected(const QString& contractId,
418 bool selected) {
419 if (!setSelectedId(selectedContractIds_, contractId, selected)) {
420 return;
421 }
422 emit changed();
423}
424
426 return selection_ ? selection_->selectedPropertyId() : QString();
427}
428
430 return selectors_ ? selectors_->propertyRows() : QVariantList{};
431}
432
434 return selectors_ ? selectors_->contractRows() : QVariantList{};
435}
436
437void PropertyViewModel::setCurrentId(const QString& id) {
438 if (selection_) {
439 selection_->selectProperty(id);
440 }
441}
442
443void PropertyViewModel::selectProperty(const QString& id) {
444 setCurrentId(id);
445}
446
447void PropertyViewModel::bindSignals() {
448 if (!store_ || !selection_) {
449 return;
450 }
451
452 QObject::connect(store_, &WorkspaceStore::dataRevisionChanged, this,
453 [this]() {
454 reloadFromSelection(true);
455 });
456 QObject::connect(selection_, &WorkspaceSelection::selectedPropertyIdChanged,
457 this, [this]() {
458 reloadFromSelection(false);
459 });
460}
461
462void PropertyViewModel::reloadFromSelection(bool forceReload) {
463 if (!selection_ || !selectors_) {
464 return;
465 }
466
467 const QString selectedId = currentId();
468 if (selectedId.isEmpty()) {
469 reloadFormState(forceReload, {});
470 return;
471 }
472
473 const QVariantMap row = rowById(propertyRows(), selectedId);
474 const QVariantMap state =
475 row.isEmpty()
476 ? QVariantMap{}
477 : propertyFormStateFromRow(
478 row.value(payload::keys::common::kName).toString(),
479 row.value(payload::keys::property::kAliases).toList(),
480 row.value(payload::keys::state::kSelectedIds).toList());
481 reloadFormState(forceReload, state);
482}
483
484void PropertyViewModel::clearFormState() {
485 name_.clear();
486 aliases_.clear();
487 aliasInputText_.clear();
488 aliasIndex_ = -1;
489 selectedContractIds_.clear();
490 emit changed();
491}
492
493void PropertyViewModel::captureSavedState() {
494 savedName_ = name_;
495 savedAliases_ = aliases_;
496 savedSelectedContractIds_ = selectedContractIds_;
497 emit changed();
498}
499
500void PropertyViewModel::applyFormState(const QVariantMap& state) {
501 name_ = state.value(QStringLiteral("name")).toString();
502 aliases_ = state.value(QStringLiteral("aliases")).toList();
503 aliasInputText_ = state.value(QStringLiteral("aliasInputText")).toString();
504 aliasIndex_ =
505 state.value(QStringLiteral("aliasIndex"), aliases_.isEmpty() ? -1 : 0)
506 .toInt();
507 selectedContractIds_ = state.value(QStringLiteral("selectedIds")).toList();
508 emit changed();
509}
510
511void PropertyViewModel::reloadFormState(bool forceReload,
512 const QVariantMap& state) {
513 const QString nextOwnerId = currentId();
514 if (!forceReload && currentOwnerId_ == nextOwnerId) {
515 return;
516 }
517
518 currentOwnerId_ = nextOwnerId;
519 if (nextOwnerId.isEmpty() || state.isEmpty()) {
520 clearFormState();
521 captureSavedState();
522 return;
523 }
524
525 applyFormState(state);
526 captureSavedState();
527}
528
529} // namespace ui
Declarations for the UI PayloadKeys component.
Declarations for the UI payload mapper helpers.
Declares the QML API for the Property view.
Declarations for the UI Trace component.
Declares the workspace selection state synchronized with the store.
Declares read-only workspace selectors for QML payloads.
Declares the UI workspace snapshot store.
Q_INVOKABLE void addAlias(const QString &value)
Adds an alias to the property form.
Q_INVOKABLE void previous()
Selects the previous property row.
Q_INVOKABLE void selectProperty(const QString &id)
Selects a property for editing.
Q_INVOKABLE bool canAddAlias(const QString &value) const
Checks whether an alias value can be added to the form.
Q_INVOKABLE void clear()
Clears the editable property form.
Q_INVOKABLE void next()
Selects the next property row.
void changed()
Emitted when property view model state changed.
Q_INVOKABLE QString submit()
Creates or updates the property through workspace commands.
void setAliasInputText(const QString &value)
Updates the current alias input text.
Q_INVOKABLE void setContractSelected(const QString &contractId, bool selected)
Adds or removes a contract id from the property form selection.
PropertyViewModel(WorkspaceStore *store, WorkspaceCommands *commands, WorkspaceSelection *selection, WorkspaceSelectors *selectors, QObject *parent=nullptr)
Creates the property view model bound to workspace roles.
Q_INVOKABLE void requestRemoveSelectedAlias()
Removes the currently selected alias when possible.
void setName(const QString &value)
Updates the editable property name.
void setAliases(const QVariantList &value)
Replaces editable property aliases.
Q_INVOKABLE void enterCreateMode()
Clears selection and opens an empty create form.
Q_INVOKABLE void deleteCurrent()
Deletes the selected property through workspace commands.
void setAliasIndex(int value)
Updates the selected alias index.
void setSelectedContractIds(const QVariantList &value)
Replaces selected contract ids in the property form.
Maps UI intent to core workspace writer commands.
Tracks the currently selected workspace entities.
void selectedPropertyIdChanged()
Emitted when the selected property id changed.
void selectProperty(const QString &id)
Selects a property.
Projects workspace store data into QML-facing read models.
Owns the current UI-side workspace snapshot and revision lifecycle.
void dataRevisionChanged()
void removeAliasValue(std::vector< Alias > &aliases, const std::string &value)
Removes aliases with a given value.
constexpr auto kName
Definition Trace.h:33
constexpr auto kId
Definition Trace.h:30
void traceViewModel(const char *origin, std::string message, core::errors::ErrorContext context={})
Emits a view model trace event.
Definition Trace.h:97
QString stringValue(const QVariantMap &map, const QString &key)
Reads a string value from a map.
QStringList toQStringList(const std::vector< std::string > &values)
Converts standard strings to a Qt string list.
Declares UI-side workspace command submission.