FOSSredder 0.5.0
Loading...
Searching...
No Matches
ActorViewModel.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 actorFormStateFromRow(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::actor::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, QObject* parent)
264 : QObject(parent), store_(store), commands_(commands),
265 selection_(selection), selectors_(selectors) {
266 bindSignals();
267 reloadFromSelection(true);
268}
269
270void ActorViewModel::setName(const QString& value) {
271 if (name_ == value) {
272 return;
273 }
274 name_ = value;
275 emit changed();
276}
277
278void ActorViewModel::setAliases(const QVariantList& value) {
279 if (aliases_ == value) {
280 return;
281 }
282 aliases_ = value;
283 if (aliasIndex_ >= aliases_.size()) {
284 aliasIndex_ = aliases_.isEmpty() ? -1 : aliases_.size() - 1;
285 }
286 emit changed();
287}
288
289void ActorViewModel::setAliasInputText(const QString& value) {
290 if (aliasInputText_ == value) {
291 return;
292 }
293 aliasInputText_ = value;
294 emit changed();
295}
296
298 if (aliasIndex_ == value) {
299 return;
300 }
301 aliasIndex_ = value;
302 emit changed();
303}
304
305void ActorViewModel::setSelectedContractIds(const QVariantList& value) {
306 if (selectedContractIds_ == value) {
307 return;
308 }
309 selectedContractIds_ = value;
310 emit changed();
311}
312
314 return !currentId().isEmpty();
315}
316
318 return !name_.trimmed().isEmpty();
319}
320
322 if (!isEdit()) {
323 return canSubmit();
324 }
325 return formStateChanged(savedName_, savedAliases_, savedSelectedContractIds_,
326 name_, aliases_, selectedContractIds_);
327}
328
329bool ActorViewModel::canAddAlias(const QString& value) const {
330 return canAddText(value);
331}
332
333bool ActorViewModel::hasValidAliasSelection() const {
334 return canRemoveAt(aliases_, aliasIndex_);
335}
336
337void ActorViewModel::addAlias(const QString& value) {
338 if (!appendAliasValue(aliases_, aliasInputText_, aliasIndex_, value)) {
339 return;
340 }
341 emit changed();
342}
343
345 if (!hasValidAliasSelection() ||
346 !removeAliasValue(aliases_, aliasIndex_, aliasIndex_)) {
347 return;
348 }
349 emit changed();
350}
351
353 setCurrentId({});
354 currentOwnerId_.clear();
355 clearFormState();
356 captureSavedState();
357}
358
360 const QVariantList rows = actorRows();
361 if (rows.isEmpty()) {
362 return;
363 }
364 setCurrentId(navigatedSelectionId(rows, isEdit() ? currentId() : QString(),
365 -1, rows.size() - 1));
366}
367
369 const QVariantList rows = actorRows();
370 if (rows.isEmpty()) {
371 return;
372 }
373 setCurrentId(
374 navigatedSelectionId(rows, isEdit() ? currentId() : QString(), 1, 0));
375}
376
378 if (!commands_) {
379 return {};
380 }
382 "ActorViewModel::submit",
383 isEdit() ? "Actor update submitted" : "Actor create submitted",
384 {{observability::context::kId, currentId().toStdString()},
385 {observability::context::kName, name_.toStdString()}});
386 const QString id = commands_->saveActor(
387 isEdit() ? currentId() : QString(), name_,
389 payload::mapper::toQStringList(selectedContractIds_));
390 if (!id.isEmpty()) {
391 setCurrentId(id);
392 }
393 captureSavedState();
394 return id;
395}
396
398 if (!commands_ || currentId().isEmpty()) {
399 return;
400 }
402 "ActorViewModel::deleteCurrent", "Actor delete submitted",
403 {{observability::context::kId, currentId().toStdString()}});
404 const QString removedId = currentId();
405 commands_->deleteActor(removedId);
406 setCurrentId(
407 deleteNextSelectionId(actorRows(), removedId, 0, QStringLiteral("id")));
408}
409
411 currentOwnerId_.clear();
412 clearFormState();
413 captureSavedState();
414}
415
416void ActorViewModel::setContractSelected(const QString& contractId,
417 bool selected) {
418 if (!setSelectedId(selectedContractIds_, contractId, selected)) {
419 return;
420 }
421 emit changed();
422}
423
425 return selection_ ? selection_->selectedActorId() : QString();
426}
427
428QVariantList ActorViewModel::actorRows() const {
429 return selectors_ ? selectors_->actorRows() : QVariantList{};
430}
431
432QVariantList ActorViewModel::contractRows() const {
433 return selectors_ ? selectors_->contractRows() : QVariantList{};
434}
435
436void ActorViewModel::setCurrentId(const QString& id) {
437 if (selection_) {
438 selection_->selectActor(id);
439 }
440}
441
442void ActorViewModel::selectActor(const QString& id) {
443 setCurrentId(id);
444}
445
446void ActorViewModel::bindSignals() {
447 if (!store_ || !selection_) {
448 return;
449 }
450
451 QObject::connect(store_, &WorkspaceStore::dataRevisionChanged, this,
452 [this]() {
453 reloadFromSelection(true);
454 });
455 QObject::connect(selection_, &WorkspaceSelection::selectedActorIdChanged,
456 this, [this]() {
457 reloadFromSelection(false);
458 });
459}
460
461void ActorViewModel::reloadFromSelection(bool forceReload) {
462 if (!selection_ || !selectors_) {
463 return;
464 }
465
466 const QString selectedId = currentId();
467 if (selectedId.isEmpty()) {
468 reloadFormState(forceReload, {});
469 return;
470 }
471
472 const QVariantMap row = rowById(actorRows(), selectedId);
473 const QVariantMap state =
474 row.isEmpty()
475 ? QVariantMap{}
476 : actorFormStateFromRow(
477 row.value(payload::keys::common::kName).toString(),
478 row.value(payload::keys::actor::kAliases).toList(),
479 row.value(payload::keys::state::kSelectedIds).toList());
480 reloadFormState(forceReload, state);
481}
482
483void ActorViewModel::clearFormState() {
484 name_.clear();
485 aliases_.clear();
486 aliasInputText_.clear();
487 aliasIndex_ = -1;
488 selectedContractIds_.clear();
489 emit changed();
490}
491
492void ActorViewModel::captureSavedState() {
493 savedName_ = name_;
494 savedAliases_ = aliases_;
495 savedSelectedContractIds_ = selectedContractIds_;
496 emit changed();
497}
498
499void ActorViewModel::applyFormState(const QVariantMap& state) {
500 name_ = state.value(QStringLiteral("name")).toString();
501 aliases_ = state.value(QStringLiteral("aliases")).toList();
502 aliasInputText_ = state.value(QStringLiteral("aliasInputText")).toString();
503 aliasIndex_ =
504 state.value(QStringLiteral("aliasIndex"), aliases_.isEmpty() ? -1 : 0)
505 .toInt();
506 selectedContractIds_ = state.value(QStringLiteral("selectedIds")).toList();
507 emit changed();
508}
509
510void ActorViewModel::reloadFormState(bool forceReload,
511 const QVariantMap& state) {
512 const QString nextOwnerId = currentId();
513 if (!forceReload && currentOwnerId_ == nextOwnerId) {
514 return;
515 }
516
517 currentOwnerId_ = nextOwnerId;
518 if (nextOwnerId.isEmpty() || state.isEmpty()) {
519 clearFormState();
520 captureSavedState();
521 return;
522 }
523
524 applyFormState(state);
525 captureSavedState();
526}
527
528} // namespace ui
Declares the QML API for the Actor 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.
QVariantList contractRows
Q_INVOKABLE void deleteCurrent()
Deletes the selected actor through workspace commands.
Q_INVOKABLE void next()
Selects the next actor row.
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 actor form selection.
Q_INVOKABLE void clear()
Clears the editable actor form.
Q_INVOKABLE void previous()
Selects the previous actor row.
void changed()
Emitted when actor view model state changed.
Q_INVOKABLE void selectActor(const QString &id)
Selects an actor for editing.
Q_INVOKABLE bool canAddAlias(const QString &value) const
Checks whether an alias value can be added to the form.
Q_INVOKABLE void addAlias(const QString &value)
Adds an alias to the actor form.
ActorViewModel(WorkspaceStore *store, WorkspaceCommands *commands, WorkspaceSelection *selection, WorkspaceSelectors *selectors, QObject *parent=nullptr)
Creates the actor view model bound to workspace roles.
void setAliasIndex(int value)
Updates the selected alias index.
void setName(const QString &value)
Updates the editable actor name.
Q_INVOKABLE void requestRemoveSelectedAlias()
Removes the currently selected alias when possible.
void setAliases(const QVariantList &value)
Replaces editable actor aliases.
QVariantList actorRows
Q_INVOKABLE QString submit()
Creates or updates the actor through workspace commands.
Q_INVOKABLE void enterCreateMode()
Clears selection and opens an empty create form.
void setSelectedContractIds(const QVariantList &value)
Replaces selected contract ids in the actor form.
Maps UI intent to core workspace writer commands.
Tracks the currently selected workspace entities.
void selectedActorIdChanged()
Emitted when the selected actor id changed.
void selectActor(const QString &id)
Selects an actor.
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.