FOSSredder 0.5.0
Loading...
Searching...
No Matches
ContractViewModel.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 indexOfId(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 = indexOfId(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 = indexOfId(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 if (rows.isEmpty()) {
111 return {};
112 }
113 const int removedIndex = indexOfId(rows, removedId, idKey);
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
119QVariantList displayRowsWithEmpty(const QVariantList& rows,
120 const QString& emptyDisplay,
121 const QString& displayKey) {
122 QVariantList out;
123 QVariantMap emptyRow;
124 emptyRow.insert(payload::keys::common::kId, QString());
125 emptyRow.insert(payload::keys::common::kDisplay, emptyDisplay);
126 out.push_back(emptyRow);
127
128 for (const auto& rowValue : rows) {
129 const QVariantMap row = rowValue.toMap();
130 QString display = row.value(displayKey).toString();
131 if (display.isEmpty()) {
132 display = row.value(payload::keys::common::kDisplay).toString();
133 }
134 if (display.isEmpty()) {
135 display = row.value(payload::keys::common::kName).toString();
136 }
137 QVariantMap displayRow;
138 displayRow.insert(payload::keys::common::kId,
139 row.value(payload::keys::common::kId).toString());
140 displayRow.insert(payload::keys::common::kDisplay, display);
141 out.push_back(displayRow);
142 }
143 return out;
144}
145
146bool canAddText(const QString& value) {
147 return !value.trimmed().isEmpty();
148}
149
150bool canRemoveAt(const QVariantList& values, int index) {
151 return index >= 0 && index < values.size();
152}
153
154bool containsTrimmed(const QVariantList& values, const QString& id) {
155 const QString nextId = id.trimmed();
156 if (nextId.isEmpty()) {
157 return false;
158 }
159 for (const auto& value : values) {
160 if (stringValue(value).trimmed() == nextId) {
161 return true;
162 }
163 }
164 return false;
165}
166
167QVariantList addUniqueTrimmed(const QVariantList& values,
168 const QString& value) {
169 const QString next = value.trimmed();
170 QVariantList out = normalizeStringValues(values);
171 if (next.isEmpty() || containsTrimmed(out, next)) {
172 return out;
173 }
174 out.push_back(next);
175 return out;
176}
177
178QVariantList removeTrimmed(const QVariantList& values, const QString& value) {
179 const QString target = value.trimmed();
180 if (target.isEmpty()) {
181 return values;
182 }
183 QVariantList out;
184 out.reserve(values.size());
185 for (const auto& entry : values) {
186 const QString current = stringValue(entry).trimmed();
187 if (current != target) {
188 out.push_back(current);
189 }
190 }
191 return out;
192}
193
194QVariantList removeAt(const QVariantList& values, int index) {
195 QVariantList out = normalizeStringValues(values);
196 if (!canRemoveAt(out, index)) {
197 return out;
198 }
199 out.removeAt(index);
200 return out;
201}
202
203int aliasIndexAfterRemoval(const QVariantList& aliases, int removedIndex) {
204 if (aliases.isEmpty()) {
205 return -1;
206 }
207 return removedIndex < aliases.size() ? removedIndex : aliases.size() - 1;
208}
209
210bool appendAliasValue(QVariantList& aliases, QString& aliasInputText,
211 int& aliasIndex, const QString& value) {
212 const QVariantList next = addUniqueTrimmed(aliases, value);
213 if (next == aliases) {
214 return false;
215 }
216 aliases = next;
217 aliasIndex = aliases.isEmpty() ? -1 : aliases.size() - 1;
218 aliasInputText.clear();
219 return true;
220}
221
222bool removeAliasValue(QVariantList& aliases, int& aliasIndex, int index) {
223 const QVariantList next = removeAt(aliases, index);
224 if (next == aliases) {
225 return false;
226 }
227 aliases = next;
228 aliasIndex = aliasIndexAfterRemoval(aliases, index);
229 return true;
230}
231
232bool setSelectedId(QVariantList& selectedIds, const QString& id,
233 bool selected) {
234 const QString normalizedId = id.trimmed();
235 if (normalizedId.isEmpty()) {
236 return false;
237 }
238 const QVariantList next = selected
239 ? addUniqueTrimmed(selectedIds, normalizedId)
240 : removeTrimmed(selectedIds, normalizedId);
241 if (next == selectedIds) {
242 return false;
243 }
244 selectedIds = next;
245 return true;
246}
247
248QStringList normalizedStringList(const QVariantList& values) {
249 const QVariantList normalized = normalizeStringValues(values);
250 QStringList out;
251 out.reserve(normalized.size());
252 for (const auto& value : normalized) {
253 out.push_back(value.toString());
254 }
255 out.sort();
256 return out;
257}
258
259QString normalizedAllocatableMode(const QString& value) {
260 const QString next = value.trimmed();
261 return next.isEmpty() ? QStringLiteral("mixed") : next;
262}
263
264QVariantMap contractBaseFormStateFromRow(const QString& name,
265 const QVariantList& aliases) {
266 QVariantMap out;
267 const QVariantList normalizedAliases = normalizeStringValues(aliases);
268 out.insert(payload::keys::common::kName, name);
269 out.insert(payload::keys::contract::kAliases, normalizedAliases);
270 out.insert(payload::keys::state::kAliasInputText, QString());
272 normalizedAliases.isEmpty() ? -1 : 0);
273 return out;
274}
275
276QVariantMap contractFormState(const QString& name, const QString& type,
277 const QString& allocatableMode,
278 const QVariantList& actorIds,
279 const QVariantList& propertyIds,
280 const QVariantList& aliases) {
281 QVariantMap out = contractBaseFormStateFromRow(name, aliases);
282 const QVariantList normalizedActorIds = normalizeStringValues(actorIds);
283 QVariantList singleActorId;
284 if (!normalizedActorIds.isEmpty()) {
285 singleActorId.push_back(normalizedActorIds.first());
286 }
287
288 out.insert(payload::keys::common::kType, type);
290 normalizedAllocatableMode(allocatableMode));
291 out.insert(payload::keys::state::kSelectedActorIds, singleActorId);
293 normalizeStringValues(propertyIds));
294 return out;
295}
296
297} // namespace
298
300 WorkspaceCommands* commands,
301 WorkspaceSelection* selection,
302 WorkspaceSelectors* selectors,
303 QObject* parent)
304 : QObject(parent), store_(store), commands_(commands),
305 selection_(selection), selectors_(selectors) {
306 bindSignals();
307 reloadFromSelection(true);
308}
309
310void ContractViewModel::setName(const QString& value) {
311 if (name_ == value) {
312 return;
313 }
314 name_ = value;
315 emit changed();
316}
317
318void ContractViewModel::setAliases(const QVariantList& value) {
319 if (aliases_ == value) {
320 return;
321 }
322 aliases_ = value;
323 if (aliasIndex_ >= aliases_.size()) {
324 aliasIndex_ = aliases_.isEmpty() ? -1 : aliases_.size() - 1;
325 }
326 emit changed();
327}
328
329void ContractViewModel::setAliasInputText(const QString& value) {
330 if (aliasInputText_ == value) {
331 return;
332 }
333 aliasInputText_ = value;
334 emit changed();
335}
336
338 if (aliasIndex_ == value) {
339 return;
340 }
341 aliasIndex_ = value;
342 emit changed();
343}
344
345bool ContractViewModel::canAddAlias(const QString& value) const {
346 return canAddText(value);
347}
348
349bool ContractViewModel::hasValidAliasSelection() const {
350 return canRemoveAt(aliases_, aliasIndex_);
351}
352
353void ContractViewModel::addAlias(const QString& value) {
354 if (!appendAliasValue(aliases_, aliasInputText_, aliasIndex_, value)) {
355 return;
356 }
357 emit changed();
358}
359
361 if (!hasValidAliasSelection() ||
362 !removeAliasValue(aliases_, aliasIndex_, aliasIndex_)) {
363 return;
364 }
365 emit changed();
366}
367
369 return selection_ ? selection_->selectedContractId() : QString();
370}
371
372QString ContractViewModel::type() const {
373 return type_;
374}
375
376void ContractViewModel::setType(const QString& value) {
377 if (type_ == value) {
378 return;
379 }
380 type_ = value;
381 emit changed();
382}
383
385 return allocatableMode_;
386}
387
388void ContractViewModel::setAllocatableMode(const QString& value) {
389 const QString next = normalizedAllocatableMode(value);
390 if (allocatableMode_ == next) {
391 return;
392 }
393 allocatableMode_ = next;
394 emit changed();
395}
396
398 return selectedActorIds_;
399}
400
401void ContractViewModel::setSelectedActorIds(const QVariantList& value) {
402 if (selectedActorIds_ == value) {
403 return;
404 }
405 selectedActorIds_ = value;
406 emit changed();
407}
408
410 return selectedPropertyIds_;
411}
412
413void ContractViewModel::setSelectedPropertyIds(const QVariantList& value) {
414 if (selectedPropertyIds_ == value) {
415 return;
416 }
417 selectedPropertyIds_ = value;
418 emit changed();
419}
420
421QVariantList ContractViewModel::actorRows() const {
422 return selectors_ ? selectors_->actorRows() : QVariantList{};
423}
424
426 return selectors_ ? displayRowsWithEmpty(actorRows(), tr("No actor"),
427 QStringLiteral("name"))
428 : QVariantList{};
429}
430
432 return selectors_ ? selectors_->contractRows() : QVariantList{};
433}
434
436 return selectors_ ? selectors_->propertyRows() : QVariantList{};
437}
438
440 if (!selectors_) {
441 return 0;
442 }
443 const QString selectedActorId = selectedActorIds_.isEmpty()
444 ? QString()
445 : selectedActorIds_.front().toString();
446 const int index = indexOfId(actorDisplayRows(), selectedActorId);
447 return index >= 0 ? index : 0;
448}
449
451 return !currentId().isEmpty();
452}
453
455 return !name_.trimmed().isEmpty() && !type_.trimmed().isEmpty() &&
456 (!selectedActorIds_.isEmpty() || !selectedPropertyIds_.isEmpty());
457}
458
460 currentOwnerId_.clear();
461 clearFormState();
462 captureSavedState();
463}
464
466 if (selection_) {
467 selection_->selectContract(QString());
468 }
469 currentOwnerId_.clear();
470 clearFormState();
471 captureSavedState();
472}
473
474void ContractViewModel::selectContract(const QString& id) {
475 if (selection_) {
476 selection_->selectContract(id);
477 }
478}
479
480void ContractViewModel::selectPrimaryActor(const QString& actorId) {
481 const QString nextId = actorId.trimmed();
482 QVariantList next;
483 if (!nextId.isEmpty()) {
484 next.push_back(nextId);
485 }
487}
488
489void ContractViewModel::setPropertySelected(const QString& propertyId,
490 bool selected) {
491 if (!selection_) {
492 return;
493 }
494 if (!setSelectedId(selectedPropertyIds_, propertyId, selected)) {
495 return;
496 }
497 emit changed();
498}
499
501 if (!selection_) {
502 return;
503 }
504 const QVariantList rows = contractRows();
505 if (rows.isEmpty()) {
506 return;
507 }
508 const QString nextId = navigatedSelectionId(
509 rows, isEdit() ? currentId() : QString(), -1, rows.size() - 1);
510 selection_->selectContract(nextId);
511}
512
514 if (!selection_) {
515 return;
516 }
517 const QVariantList rows = contractRows();
518 if (rows.isEmpty()) {
519 return;
520 }
521 const QString nextId =
522 navigatedSelectionId(rows, isEdit() ? currentId() : QString(), 1, 0);
523 selection_->selectContract(nextId);
524}
525
527 if (!commands_ || !selection_) {
528 return {};
529 }
531 "ContractViewModel::submit",
532 isEdit() ? "Contract update submitted" : "Contract create submitted",
533 {{observability::context::kId, currentId().toStdString()},
534 {observability::context::kName, name_.toStdString()},
535 {"type", type_.toStdString()}});
536 const QStringList aliasValues = payload::mapper::toQStringList(aliases_);
537 const QStringList actorIds =
538 payload::mapper::toQStringList(selectedActorIds_);
539 const QStringList propertyIds =
540 payload::mapper::toQStringList(selectedPropertyIds_);
541 const QString id = commands_->saveContract(
542 isEdit() ? currentId() : QString(), name_, type_, actorIds, propertyIds,
543 aliasValues, allocatableMode_);
544 if (!id.isEmpty()) {
545 selection_->selectContract(id);
546 }
547 captureSavedState();
548 return id;
549}
550
552 if (!commands_ || currentId().isEmpty()) {
553 return;
554 }
556 "ContractViewModel::deleteCurrent", "Contract delete submitted",
557 {{observability::context::kId, currentId().toStdString()}});
558 const QString removedId = currentId();
559 commands_->deleteContract(removedId);
560 const QString nextId =
561 deleteNextSelectionId(contractRows(), removedId, 0, QStringLiteral("id"));
562 if (selection_)
563 selection_->selectContract(nextId);
564}
565
567 if (!isEdit()) {
568 return canSubmit();
569 }
570 if (!selectors_) {
571 return false;
572 }
573 return savedName_ != name_ || savedType_ != type_ ||
574 savedAllocatableMode_ != allocatableMode_ ||
575 normalizedStringList(savedAliases_) !=
576 normalizedStringList(aliases_) ||
577 normalizedStringList(savedSelectedActorIds_) !=
578 normalizedStringList(selectedActorIds_) ||
579 normalizedStringList(savedSelectedPropertyIds_) !=
580 normalizedStringList(selectedPropertyIds_);
581}
582
583void ContractViewModel::clearFormState() {
584 name_.clear();
585 aliases_.clear();
586 aliasInputText_.clear();
587 aliasIndex_ = -1;
588 type_.clear();
589 allocatableMode_ = QStringLiteral("mixed");
590 selectedActorIds_.clear();
591 selectedPropertyIds_.clear();
592 emit changed();
593}
594
595void ContractViewModel::bindSignals() {
596 if (!store_ || !selection_) {
597 return;
598 }
599
600 QObject::connect(store_, &WorkspaceStore::dataRevisionChanged, this,
601 [this]() {
602 reloadFromSelection(true);
603 });
604 QObject::connect(selection_, &WorkspaceSelection::selectedContractIdChanged,
605 this, [this]() {
606 reloadFromSelection(false);
607 });
608}
609
610void ContractViewModel::applyFormState(const QVariantMap& state) {
611 const QVariantMap next = state;
612 name_ = next.value(QStringLiteral("name")).toString();
613 aliases_ = next.value(QStringLiteral("aliases")).toList();
614 aliasInputText_ = next.value(QStringLiteral("aliasInputText")).toString();
615 aliasIndex_ =
616 next.value(QStringLiteral("aliasIndex"), aliases_.isEmpty() ? -1 : 0)
617 .toInt();
618 type_ = next.value(QStringLiteral("type")).toString();
619 allocatableMode_ = normalizedAllocatableMode(
621 QStringLiteral("mixed"))
622 .toString());
623 selectedActorIds_ = next.value(QStringLiteral("selectedActorIds")).toList();
624 selectedPropertyIds_ =
625 next.value(QStringLiteral("selectedPropertyIds")).toList();
626 emit changed();
627}
628
629void ContractViewModel::captureSavedState() {
630 savedName_ = name_;
631 savedAliases_ = aliases_;
632 savedType_ = type_;
633 savedAllocatableMode_ = allocatableMode_;
634 savedSelectedActorIds_ = selectedActorIds_;
635 savedSelectedPropertyIds_ = selectedPropertyIds_;
636 emit changed();
637}
638
639void ContractViewModel::reloadFromSelection(bool forceReload) {
640 if (!selection_ || !selectors_) {
641 return;
642 }
643
644 const QString nextOwnerId = currentId();
645 if (!forceReload && currentOwnerId_ == nextOwnerId) {
646 return;
647 }
648
649 currentOwnerId_ = nextOwnerId;
650 if (nextOwnerId.isEmpty()) {
651 clearFormState();
652 captureSavedState();
653 return;
654 }
655
656 const QVariantMap row = rowById(contractRows(), nextOwnerId);
657 if (row.isEmpty()) {
658 clearFormState();
659 captureSavedState();
660 return;
661 }
662
663 QVariantMap state = contractFormState(
664 row.value(payload::keys::common::kName).toString(),
665 row.value(payload::keys::common::kType).toString(),
667 QStringLiteral("mixed"))
668 .toString(),
669 row.value(payload::keys::contract::kActorIds).toList(),
670 row.value(payload::keys::contract::kPropertyIds).toList(),
671 row.value(payload::keys::contract::kAliases).toList());
672 applyFormState(state);
673 captureSavedState();
674}
675
676} // namespace ui
Declares the QML API for the Contract view.
Declarations for the UI PayloadKeys component.
Declarations for the UI payload mapper helpers.
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.
void setSelectedPropertyIds(const QVariantList &value)
Replaces selected property ids for this contract.
void setName(const QString &value)
Updates the editable contract name.
void setAliasInputText(const QString &value)
Updates the current alias input text.
Q_INVOKABLE void addAlias(const QString &value)
Adds an alias to the contract form.
Q_INVOKABLE void clear()
Clears the current contract form without changing workspace data.
Q_INVOKABLE void requestRemoveSelectedAlias()
Removes the currently selected alias when possible.
void setType(const QString &value)
Updates the editable contract type.
Q_INVOKABLE void setPropertySelected(const QString &propertyId, bool selected)
Toggles a property link in the current form.
void setSelectedActorIds(const QVariantList &value)
Replaces selected actor ids for this contract.
ContractViewModel(WorkspaceStore *store, WorkspaceCommands *commands, WorkspaceSelection *selection, WorkspaceSelectors *selectors, QObject *parent=nullptr)
Creates the contract view model bound to workspace roles.
Q_INVOKABLE void selectPrimaryActor(const QString &actorId)
Selects the primary actor for the contract.
Q_INVOKABLE QString submit()
Creates or updates the current contract through workspace commands.
Q_INVOKABLE void deleteCurrent()
Deletes the selected contract through workspace commands.
Q_INVOKABLE void next()
Selects the next contract row.
Q_INVOKABLE void selectContract(const QString &id)
Selects a contract for editing.
void setAliasIndex(int value)
Updates the selected alias index.
void setAliases(const QVariantList &value)
Replaces editable contract aliases.
Q_INVOKABLE bool canAddAlias(const QString &value) const
Checks whether an alias value can be added to the form.
Q_INVOKABLE void previous()
Selects the previous contract row.
Q_INVOKABLE void enterCreateMode()
Enters create mode and clears the contract form.
QVariantList selectedPropertyIds
void setAllocatableMode(const QString &value)
Updates the editable allocatable mode.
Maps UI intent to core workspace writer commands.
Tracks the currently selected workspace entities.
void selectedContractIdChanged()
Emitted when the selected contract id changed.
Projects workspace store data into QML-facing read models.
QVariantList actorRows() const
Returns actor rows for QML tables and dropdowns.
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
const auto kSelectedPropertyIds
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.