Observing the Model
Now that we can declare a model and we know how to organize it and manipulate it, this chapter describes how to listen to its changes.
This chapter will use the model declared in the chapter Declaring the Model.
Declaring and Binding the Observer
To listen to document modification, one must declare an observer.
All observers must inherit from the template class DocumentObserver
, which takes the root class of your model as a template parameter.
To receive document changes, one must register a document observer to the document.
Note: Only one observer can be setup per document, but an observer might dispatch itself to other observers if it wants to.
class Observer : public flip::DocumentObserver <Song> (1) |
{ |
public: |
virtual void document_changed (Song & song) override; (2) |
} |
void run () |
{ |
Observer observer; |
Document document (Model::use (), observer, 123456789ULL, 'OmSt', 'gui '); (3) |
Song & song = document.root <Song> (); |
song.tempo = 120.0; |
document.commit (); (4) |
} |
- Make an observer by inheriting from
DocumentObserver
- Override the pure virtual function declared in
DocumentObserver
- Attach the observer to the document
- When
commit
is called, it will fire thedocument_changed
method
Listening to Property Changes
When commit
is called, it will fire the document_changed
method. This method contains the song as a parameter. This song has then the ability to represent the document in its old state as well as new state.
Because of this property, Flip types can know when they have changed. Therefore, listening to tempo change in our example would look like this :
void Observer::document_changed (Song & song) |
{ |
if (song.tempo.changed ()) (1) |
{ |
double new_tempo = song.tempo; (2) |
double old_tempo = song.tempo.before (); (3) |
} |
} |
- Take the branch if the value was changed
- Extract the new tempo value
- Extract the old tempo value
Listening to Container Changes
Listening to container changes works in the same way. The container can know when it has changed. But the container also has the ability to represent both its old and new states.
void Observer::document_changed (Song & song) |
{ |
if (song.tracks.changed ()) |
{ |
auto it = song.tracks.begin (); |
auto it_end = song.tracks.end (); |
for (; it != it_end ; ++it) |
{ |
auto & track = *it; |
if (track.changed ()) (1) |
{ |
if (track.added ()) (2) |
{ |
... |
} |
else if (track.resident ()) (3) |
{ |
... |
} |
else if (track.removed ()) (4) |
{ |
... |
} |
} |
} |
} |
} |
The code above will iterate other the list of tracks if it did change.
- Track life cycle is also detected through the use of
changed
- The track was just added to the container
- The track is resident, which means that it was neither added or removed from the container. Since the track itself changed (1), this means that it has inner modification
- The track was removed from the container. Its data is still available to the observer, but the model object will be destroyed when
document_changed
exits.
Note: Traversing a Collection
for changes is exactly similar to traversing an Array
.
In the code above, a Track
will be reported as changed when the Track
changed itself, or one of its children, for example one Clip
position
Important: When an object is changed
this means that subtree it represents have changed, maybe somewhere deep in the tree.
Listening to Container Elements Move
Containers like Array
or Collection
have a splice
operation which allows to move elements from one container or in the same container for Array
.
If splice
is used, then the document needs to be observed in a slightly different way, to be able to detect moves in the hierarchy.
An element moved is neither added
or removed
so it is resident. However the iterator that wrap the object can be also added
or removed
. When no splice operation is called, the iterator state is always the same as the element it wraps.
Then when an element is moving, the difference between the iterator state and the element state allows to observe moves, as shown in the example below.
void Observer::document_changed (Song & song) |
{ |
if (song.tracks.changed ()) |
{ |
auto it = song.tracks.begin (); |
auto it_end = song.tracks.end (); |
for (; it != it_end ; ++it) |
{ |
auto & track = *it; |
if (it.removed () && track.resident ()) |
{ |
// The track is moving from this position... |
} |
if (it.added () && track.resident ()) |
{ |
// ... to this position |
} |
} |
} |
} |
The next chapter, Signalling the Model will show how to send non-persistent messages to objects of the model.