Skip to content

Commit 2e87df6

Browse files
committed
Add track list
1 parent 80e84a5 commit 2e87df6

28 files changed

+1535
-18
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# View-Model ↔ Document Binding Prompt
2+
3+
Use this prompt to guide new view-model bindings for future document elements. Keep property specifics out; focus on state design, controller-driven transitions, transactions, and synchronization patterns. Derive patterns from existing implementations (e.g., `TrackViewModelContextData`, `LabelViewModelContextData`, `TempoViewModelContextData`).
4+
5+
## Core Goals
6+
- Mirror document collections to view-model collections with clear ownership and mapping tables in both directions.
7+
- Drive UI interactions through controllers; never mutate document state directly from QML.
8+
- Guard against infinite loops by conditioning view→doc updates on active states.
9+
- Wrap mutating operations in transactions with start/commit/abort semantics tied to state entry/exit.
10+
11+
- Instrument every state with entry/exit logging (use `qCInfo` with a per-context logging category) to trace flows during debugging, even for states without handlers.
12+
13+
## State Machine Design
14+
- Build a `QStateMachine` with explicit states for idle, rubber-band selection, move/drag, edit/adjust flows, and per-property operations as needed.
15+
- Keep transitions driven by signals emitted from interaction controllers and internal guards (e.g., started/not-started, commit/abort, finish).
16+
- Example (move flow, list rotation):
17+
- `idle``movePending` on `moveTransactionWillStart` (from controller).
18+
- `movePending``moveProcessing` on `moveTransactionStarted`; → `idle` on `moveTransactionNotStarted`.
19+
- `moveProcessing``moveCommitting` on `moveTransactionWillCommit`; → `moveAborting` on `moveTransactionWillAbort`.
20+
- `moveCommitting`/`moveAborting``idle` (reset guards).
21+
- Example (edit flow, text-like):
22+
- `idle``namePending` on `nameTransactionWillStart`.
23+
- `namePending``nameProgressing` on `nameTransactionStarted`; → `idle` if not started.
24+
- `nameProgressing``nameCommitting` on commit; → `nameAborting` on abort.
25+
- `nameCommitting`/`nameAborting``idle`.
26+
- Rubber-band selection: `idle``rubberBandDragging` on start, back to `idle` on finish.
27+
28+
## Controller-Driven Transitions
29+
- Wire controller signals to emit local signals that the state machine consumes (two patterns):
30+
1) **Started/Committed/Aborted**: drag/move and text edits use started/commit/abort triplets.
31+
2) **Started/Finished**: toggle/slider/height edits use started/finished pairs.
32+
- Set the current target item/index when the controller signals a start; clear it on commit/finish/abort handlers.
33+
- For rotations or list moves: only propagate view→doc when in the move-processing state; otherwise apply doc→view rotations.
34+
35+
## Transactions
36+
- Begin a transaction when entering the corresponding "pending" state. Abort immediately if the controller could not start.
37+
- On commit states: if the new value differs, write to the document and `commitTransaction` with a descriptive label; otherwise `abortTransaction`.
38+
- On abort states: always `abortTransaction` and reset local guards (`target`, flags like `moveChanged`).
39+
- Track whether any change occurred during move/drag (`moveChanged`) to avoid committing no-op transactions.
40+
41+
## Synchronization Patterns
42+
- Maintain bidirectional maps: `doc -> view` and `view -> doc`. Insert/remove bindings on collection signals (`itemInserted`/`itemRemoved`), not "aboutTo" when you need the item fully constructed.
43+
- When binding a new item:
44+
- Create the view-model item, insert into both maps and the view-model collection at the correct index.
45+
- Connect doc→view signals to update view items, guarded by equality checks.
46+
- Connect view→doc signals but gate them with state checks (only honor during the relevant progressing/doing states; otherwise revert the view to the doc value).
47+
- Initialize view properties from the doc model after wiring connections.
48+
- Selection sync: listen to document selection model `itemSelected` and mark the view item selected; initialize selection for pre-selected items after binding.
49+
- Rotation sync: doc→view rotations apply when *not* moving; view→doc rotations apply only while the move state is active, and should mark a change flag.
50+
51+
## Example Snippets
52+
- **Doc→View guarded update** (avoid loops):
53+
```cpp
54+
connect(control, &ControlType::propertyChanged, viewItem, [=](auto value) {
55+
if (viewItem->property() == value) return;
56+
viewItem->setProperty(value);
57+
});
58+
```
59+
- **View→Doc gated by state**:
60+
```cpp
61+
connect(viewItem, &ViewType::propertyChanged, docItem, [=] {
62+
if (!stateMachine->configuration().contains(propertyProgressingState)) {
63+
viewItem->setProperty(docItem->property());
64+
return;
65+
}
66+
// defer actual write to commit handler
67+
});
68+
```
69+
- **Transaction commit handler**:
70+
```cpp
71+
void ContextData::onNameCommittingStateEntered() {
72+
if (!target || nameTxId == Invalid) { target = {}; return; }
73+
auto viewItem = viewMap.value(target);
74+
if (viewItem->name() == target->name()) {
75+
tx->abortTransaction(nameTxId);
76+
} else {
77+
target->setName(viewItem->name());
78+
tx->commitTransaction(nameTxId, tr("Renaming item"));
79+
}
80+
nameTxId = {}; target = {};
81+
}
82+
```
83+
- **Rotate handling**:
84+
```cpp
85+
connect(docList, &List::rotated, this, [=](int l, int m, int r) {
86+
if (stateMachine->configuration().contains(moveProcessingState)) return;
87+
viewList->rotate(l, m, r);
88+
});
89+
connect(viewList, &ViewList::rotated, this, [=](int l, int m, int r) {
90+
if (!stateMachine->configuration().contains(moveProcessingState)) return;
91+
moveChanged = true;
92+
docList->rotate(l, m, r);
93+
});
94+
```
95+
96+
## Implementation Checklist
97+
- Define states and transitions before binding to controllers; start the state machine immediately.
98+
- Create controllers via context helper methods; hook all relevant signals to emit local transition signals and set the current target.
99+
- Bind document collections first, then replay existing selection to the view.
100+
- For each commit/finish handler: compare values, write document, commit transaction; otherwise abort. Always reset `target` and flags.
101+
- Keep all strings ASCII; add concise comments only where non-obvious.
102+
103+
Use this prompt verbatim when extending bindings to new document elements to maintain consistent interaction, transaction, and synchronization behavior across the codebase.

src/libs/application/dspxmodel/src/Control.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace dspx {
1414

1515
class ControlPrivate;
1616

17-
class Control : public QObject {
17+
class DSPX_MODEL_EXPORT Control : public QObject {
1818
Q_OBJECT
1919
QML_ELEMENT
2020
QML_UNCREATABLE("")

src/libs/application/dspxmodel/src/ModelStrategy.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ namespace dspx {
6161
P_ControlGain,
6262
P_ControlMute,
6363
P_ControlPan,
64+
P_ControlRecord,
6465
P_ControlSolo,
6566
P_Denominator,
6667
P_EditorId,
6768
P_EditorName,
69+
P_Height,
6870
P_JsonObject,
6971
P_KeyNumber,
7072
P_Language,
@@ -358,7 +360,9 @@ namespace dspx {
358360
{P_ControlGain, QMetaType::Double},
359361
{P_ControlPan, QMetaType::Double, validatePan},
360362
{P_ControlMute, QMetaType::Bool},
361-
{P_ControlSolo, QMetaType::Bool}
363+
{P_ControlRecord, QMetaType::Bool},
364+
{P_ControlSolo, QMetaType::Bool},
365+
{P_Height, QMetaType::Double, validateDoubleGreaterOrEqualZero}
362366
};
363367
case EI_WorkspaceInfo: return {
364368
{P_JsonObject, QMetaType::QJsonObject}

src/libs/application/dspxmodel/src/Track.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace dspx {
3131
d->pModel = ModelPrivate::get(model);
3232
d->name = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Name).toString();
3333
d->colorId = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ColorId).toInt();
34+
d->height = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Height).toDouble();
3435
d->control = d->pModel->createObject<TrackControl>(handle);
3536
d->workspace = d->pModel->createObject<Workspace>(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Workspace));
3637
d->clips = d->pModel->createObject<ClipSequence>(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children));
@@ -53,6 +54,16 @@ namespace dspx {
5354
d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_ColorId, colorId);
5455
}
5556

57+
double Track::height() const {
58+
Q_D(const Track);
59+
return d->height;
60+
}
61+
62+
void Track::setHeight(double height) {
63+
Q_D(Track);
64+
d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_Height, height);
65+
}
66+
5667
TrackControl *Track::control() const {
5768
Q_D(const Track);
5869
return d->control;
@@ -82,6 +93,8 @@ namespace dspx {
8293
};
8394
track.workspace["diffscope"] = QJsonObject{
8495
{"colorId", colorId()},
96+
{"height", height()},
97+
{"record", control()->record()},
8598
};
8699
return track;
87100
}
@@ -92,6 +105,8 @@ namespace dspx {
92105
clips()->fromQDspx(track.clips);
93106
workspace()->fromQDspx(track.workspace);
94107
setColorId(track.workspace["diffscope"]["colorId"].toInt());
108+
setHeight(track.workspace["diffscope"]["height"].toDouble(80));
109+
control()->setRecord(track.workspace["diffscope"]["record"].toBool());
95110
}
96111

97112
TrackList *Track::trackList() const {
@@ -112,9 +127,15 @@ namespace dspx {
112127
Q_EMIT colorIdChanged(d->colorId);
113128
break;
114129
}
130+
case ModelStrategy::P_Height: {
131+
d->height = value.toDouble();
132+
Q_EMIT heightChanged(d->height);
133+
break;
134+
}
115135
case ModelStrategy::P_ControlGain:
116136
case ModelStrategy::P_ControlPan:
117137
case ModelStrategy::P_ControlMute:
138+
case ModelStrategy::P_ControlRecord:
118139
case ModelStrategy::P_ControlSolo: {
119140
ModelPrivate::proxySetEntityPropertyNotify(d->control, property, value);
120141
break;

src/libs/application/dspxmodel/src/Track.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace dspx {
2626
Q_DECLARE_PRIVATE(Track)
2727
Q_PROPERTY(ClipSequence *clips READ clips CONSTANT)
2828
Q_PROPERTY(int colorId READ colorId WRITE setColorId NOTIFY colorIdChanged)
29+
Q_PROPERTY(double height READ height WRITE setHeight NOTIFY heightChanged)
2930
Q_PROPERTY(TrackControl *control READ control CONSTANT)
3031
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
3132
Q_PROPERTY(Workspace *workspace READ workspace CONSTANT)
@@ -39,6 +40,9 @@ namespace dspx {
3940
int colorId() const;
4041
void setColorId(int colorId);
4142

43+
double height() const;
44+
void setHeight(double height);
45+
4246
TrackControl *control() const;
4347

4448
QString name() const;
@@ -54,6 +58,7 @@ namespace dspx {
5458
Q_SIGNALS:
5559
void nameChanged(const QString &name);
5660
void colorIdChanged(int colorId);
61+
void heightChanged(double height);
5762
void trackListChanged();
5863

5964
protected:

src/libs/application/dspxmodel/src/TrackControl.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ namespace dspx {
1313
TrackControl *q_ptr;
1414
ModelPrivate *pModel;
1515
bool solo;
16+
bool record;
1617
};
1718

1819
TrackControl::TrackControl(Handle handle, Model *model) : Control(handle, model), d_ptr(new TrackControlPrivate) {
1920
Q_D(TrackControl);
2021
d->q_ptr = this;
2122
d->pModel = ModelPrivate::get(model);
2223
d->solo = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlSolo).toBool();
24+
d->record = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlRecord).toBool();
2325
}
2426

2527
TrackControl::~TrackControl() = default;
@@ -34,6 +36,16 @@ namespace dspx {
3436
d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_ControlSolo, solo);
3537
}
3638

39+
bool TrackControl::record() const {
40+
Q_D(const TrackControl);
41+
return d->record;
42+
}
43+
44+
void TrackControl::setRecord(bool record) {
45+
Q_D(TrackControl);
46+
d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_ControlRecord, record);
47+
}
48+
3749
QDspx::TrackControl TrackControl::toQDspx() const {
3850
return {
3951
.gain = gain(),
@@ -57,6 +69,11 @@ namespace dspx {
5769
Q_EMIT soloChanged(d->solo);
5870
break;
5971
}
72+
case ModelStrategy::P_ControlRecord: {
73+
d->record = value.toBool();
74+
Q_EMIT recordChanged(d->record);
75+
break;
76+
}
6077
default: {
6178
Control::handleProxySetEntityProperty(property, value);
6279
}

src/libs/application/dspxmodel/src/TrackControl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,29 @@ namespace dspx {
1111

1212
class TrackControlPrivate;
1313

14-
class TrackControl : public Control {
14+
class DSPX_MODEL_EXPORT TrackControl : public Control {
1515
Q_OBJECT
1616
QML_ELEMENT
1717
QML_UNCREATABLE("")
1818
Q_DECLARE_PRIVATE(TrackControl)
1919
Q_PROPERTY(bool solo READ solo WRITE setSolo NOTIFY soloChanged)
20+
Q_PROPERTY(bool record READ record WRITE setRecord NOTIFY recordChanged)
2021

2122
public:
2223
~TrackControl() override;
2324

2425
bool solo() const;
2526
void setSolo(bool solo);
2627

28+
bool record() const;
29+
void setRecord(bool record);
30+
2731
QDspx::TrackControl toQDspx() const;
2832
void fromQDspx(const QDspx::TrackControl &trackControl);
2933

3034
Q_SIGNALS:
3135
void soloChanged(bool solo);
36+
void recordChanged(bool record);
3237

3338
private:
3439
friend class ModelPrivate;

src/libs/application/dspxmodel/src/Track_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace dspx {
1313
ClipSequence *clips;
1414
QString name;
1515
int colorId;
16+
double height;
1617
TrackControl *control;
1718
Workspace *workspace;
1819
TrackList *trackList;

0 commit comments

Comments
 (0)