23 const QVariantMap map = value.toMap();
25 const QString aliasValue = map.value(QStringLiteral(
"value")).toString();
26 if (!aliasValue.isEmpty()) {
29 const QString aliasSource = map.value(QStringLiteral(
"source")).toString();
30 if (!aliasSource.isEmpty()) {
33 const QString
id = map.value(QStringLiteral(
"id")).toString();
38 return value.toString();
41QVariantList normalizeStringValues(
const QVariantList& values) {
43 out.reserve(values.size());
44 for (
const auto& value : values) {
50QString rowIdAt(
const QVariantList& rows,
int index,
52 if (index < 0 || index >= rows.size()) {
55 return rows.at(index).toMap().value(idKey).toString();
58int indexOfId(
const QVariantList& rows,
const QString&
id,
63 for (
int i = 0; i < rows.size(); ++i) {
64 if (rows.at(i).toMap().value(idKey).toString() ==
id) {
71QVariantMap rowById(
const QVariantList& rows,
const QString&
id,
73 const int index = indexOfId(rows,
id.trimmed(), idKey);
74 return index >= 0 ? rows.at(index).toMap() : QVariantMap{};
78navigatedSelectionId(
const QVariantList& rows,
const QString& currentId,
79 int delta,
int defaultIndex = 0,
81 const int currentIndex = indexOfId(rows, currentId, idKey);
82 if (currentIndex < 0) {
87 return rowIdAt(rows, 0, idKey);
90 return rowIdAt(rows, rows.size() - 1, idKey);
92 return rowIdAt(rows, defaultIndex, idKey);
95 return currentIndex >= rows.size() - 1
97 : rowIdAt(rows, currentIndex + 1, idKey);
100 return currentIndex <= 0 ? QString()
101 : rowIdAt(rows, currentIndex - 1, idKey);
103 return rowIdAt(rows, currentIndex, idKey);
107deleteNextSelectionId(
const QVariantList& rows,
const QString& removedId,
108 int defaultIndex = 0,
110 if (rows.isEmpty()) {
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);
119QVariantList displayRowsWithEmpty(
const QVariantList& rows,
120 const QString& emptyDisplay,
121 const QString& displayKey) {
123 QVariantMap emptyRow;
126 out.push_back(emptyRow);
128 for (
const auto& rowValue : rows) {
129 const QVariantMap row = rowValue.toMap();
130 QString display = row.value(displayKey).toString();
131 if (display.isEmpty()) {
134 if (display.isEmpty()) {
137 QVariantMap displayRow;
141 out.push_back(displayRow);
146bool canAddText(
const QString& value) {
147 return !value.trimmed().isEmpty();
150bool canRemoveAt(
const QVariantList& values,
int index) {
151 return index >= 0 && index < values.size();
154bool containsTrimmed(
const QVariantList& values,
const QString&
id) {
155 const QString nextId =
id.trimmed();
156 if (nextId.isEmpty()) {
159 for (
const auto& value : values) {
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)) {
178QVariantList removeTrimmed(
const QVariantList& values,
const QString& value) {
179 const QString target = value.trimmed();
180 if (target.isEmpty()) {
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);
194QVariantList removeAt(
const QVariantList& values,
int index) {
195 QVariantList out = normalizeStringValues(values);
196 if (!canRemoveAt(out, index)) {
203int aliasIndexAfterRemoval(
const QVariantList& aliases,
int removedIndex) {
204 if (aliases.isEmpty()) {
207 return removedIndex < aliases.size() ? removedIndex : aliases.size() - 1;
210bool appendAliasValue(QVariantList& aliases, QString& aliasInputText,
211 int& aliasIndex,
const QString& value) {
212 const QVariantList next = addUniqueTrimmed(aliases, value);
213 if (next == aliases) {
217 aliasIndex = aliases.isEmpty() ? -1 : aliases.size() - 1;
218 aliasInputText.clear();
223 const QVariantList next = removeAt(aliases, index);
224 if (next == aliases) {
228 aliasIndex = aliasIndexAfterRemoval(aliases, index);
232bool setSelectedId(QVariantList& selectedIds,
const QString&
id,
234 const QString normalizedId =
id.trimmed();
235 if (normalizedId.isEmpty()) {
238 const QVariantList next = selected
239 ? addUniqueTrimmed(selectedIds, normalizedId)
240 : removeTrimmed(selectedIds, normalizedId);
241 if (next == selectedIds) {
248QStringList normalizedStringList(
const QVariantList& values) {
249 const QVariantList normalized = normalizeStringValues(values);
251 out.reserve(normalized.size());
252 for (
const auto& value : normalized) {
253 out.push_back(value.toString());
259QString normalizedAllocatableMode(
const QString& value) {
260 const QString next = value.trimmed();
261 return next.isEmpty() ? QStringLiteral(
"mixed") : next;
264QVariantMap contractBaseFormStateFromRow(
const QString& name,
265 const QVariantList& aliases) {
267 const QVariantList normalizedAliases = normalizeStringValues(aliases);
272 normalizedAliases.isEmpty() ? -1 : 0);
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());
290 normalizedAllocatableMode(allocatableMode));
293 normalizeStringValues(propertyIds));
304 : QObject(parent), store_(store), commands_(commands),
305 selection_(selection), selectors_(selectors) {
307 reloadFromSelection(
true);
311 if (name_ == value) {
319 if (aliases_ == value) {
323 if (aliasIndex_ >= aliases_.size()) {
324 aliasIndex_ = aliases_.isEmpty() ? -1 : aliases_.size() - 1;
330 if (aliasInputText_ == value) {
333 aliasInputText_ = value;
338 if (aliasIndex_ == value) {
346 return canAddText(value);
349bool ContractViewModel::hasValidAliasSelection()
const {
350 return canRemoveAt(aliases_, aliasIndex_);
354 if (!appendAliasValue(aliases_, aliasInputText_, aliasIndex_, value)) {
361 if (!hasValidAliasSelection() ||
362 !removeAliasValue(aliases_, aliasIndex_, aliasIndex_)) {
369 return selection_ ? selection_->selectedContractId() : QString();
377 if (type_ == value) {
385 return allocatableMode_;
389 const QString
next = normalizedAllocatableMode(value);
390 if (allocatableMode_ ==
next) {
393 allocatableMode_ =
next;
398 return selectedActorIds_;
402 if (selectedActorIds_ == value) {
405 selectedActorIds_ = value;
410 return selectedPropertyIds_;
414 if (selectedPropertyIds_ == value) {
417 selectedPropertyIds_ = value;
421QVariantList ContractViewModel::actorRows()
const {
422 return selectors_ ? selectors_->
actorRows() : QVariantList{};
426 return selectors_ ? displayRowsWithEmpty(actorRows(), tr(
"No actor"),
427 QStringLiteral(
"name"))
432 return selectors_ ? selectors_->contractRows() : QVariantList{};
436 return selectors_ ? selectors_->propertyRows() : QVariantList{};
443 const QString selectedActorId = selectedActorIds_.isEmpty()
445 : selectedActorIds_.front().toString();
447 return index >= 0 ? index : 0;
455 return !name_.trimmed().isEmpty() && !type_.trimmed().isEmpty() &&
456 (!selectedActorIds_.isEmpty() || !selectedPropertyIds_.isEmpty());
460 currentOwnerId_.clear();
467 selection_->selectContract(QString());
469 currentOwnerId_.clear();
476 selection_->selectContract(
id);
481 const QString nextId = actorId.trimmed();
483 if (!nextId.isEmpty()) {
484 next.push_back(nextId);
494 if (!setSelectedId(selectedPropertyIds_, propertyId, selected)) {
505 if (rows.isEmpty()) {
508 const QString nextId = navigatedSelectionId(
510 selection_->selectContract(nextId);
518 if (rows.isEmpty()) {
521 const QString nextId =
523 selection_->selectContract(nextId);
527 if (!commands_ || !selection_) {
531 "ContractViewModel::submit",
532 isEdit() ?
"Contract update submitted" :
"Contract create submitted",
535 {
"type", type_.toStdString()}});
537 const QStringList actorIds =
539 const QStringList propertyIds =
541 const QString
id = commands_->saveContract(
542 isEdit() ?
currentId() : QString(), name_, type_, actorIds, propertyIds,
543 aliasValues, allocatableMode_);
545 selection_->selectContract(
id);
552 if (!commands_ ||
currentId().isEmpty()) {
556 "ContractViewModel::deleteCurrent",
"Contract delete submitted",
559 commands_->deleteContract(removedId);
560 const QString nextId =
561 deleteNextSelectionId(
contractRows(), removedId, 0, QStringLiteral(
"id"));
563 selection_->selectContract(nextId);
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_);
583void ContractViewModel::clearFormState() {
586 aliasInputText_.clear();
589 allocatableMode_ = QStringLiteral(
"mixed");
590 selectedActorIds_.clear();
591 selectedPropertyIds_.clear();
595void ContractViewModel::bindSignals() {
596 if (!store_ || !selection_) {
602 reloadFromSelection(
true);
606 reloadFromSelection(
false);
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();
616 next.value(QStringLiteral(
"aliasIndex"), aliases_.isEmpty() ? -1 : 0)
618 type_ =
next.value(QStringLiteral(
"type")).toString();
619 allocatableMode_ = normalizedAllocatableMode(
621 QStringLiteral(
"mixed"))
623 selectedActorIds_ =
next.value(QStringLiteral(
"selectedActorIds")).toList();
624 selectedPropertyIds_ =
625 next.value(QStringLiteral(
"selectedPropertyIds")).toList();
629void ContractViewModel::captureSavedState() {
631 savedAliases_ = aliases_;
633 savedAllocatableMode_ = allocatableMode_;
634 savedSelectedActorIds_ = selectedActorIds_;
635 savedSelectedPropertyIds_ = selectedPropertyIds_;
639void ContractViewModel::reloadFromSelection(
bool forceReload) {
640 if (!selection_ || !selectors_) {
645 if (!forceReload && currentOwnerId_ == nextOwnerId) {
649 currentOwnerId_ = nextOwnerId;
650 if (nextOwnerId.isEmpty()) {
656 const QVariantMap row = rowById(
contractRows(), nextOwnerId);
663 QVariantMap state = contractFormState(
667 QStringLiteral(
"mixed"))
672 applyFormState(state);
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.
QVariantList propertyRows
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.
QVariantList actorDisplayRows
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.
QVariantList contractRows
Q_INVOKABLE void enterCreateMode()
Enters create mode and clears the contract form.
QVariantList selectedPropertyIds
void setAllocatableMode(const QString &value)
Updates the editable allocatable mode.
QVariantList selectedActorIds
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.
void traceViewModel(const char *origin, std::string message, core::errors::ErrorContext context={})
Emits a view model trace event.
const auto kAllocatableMode
const auto kSelectedPropertyIds
const auto kAliasInputText
const auto kSelectedActorIds
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.