Controlling the Model
Now that we can declare a model and we know how to organize it, this chapter describes how to manipulate it.
This chapter will use the model declared in the chapter Declaring the Model.
The Document
To manipulate the model, one must have an instance of the model which is called a document.
A Document
is therefore described by the model it uses, a unique user 64-bit identifier, a manufacturer/product identifier as well as a component identifier. The latters only allows to know who is the originator of a modification and allows for concurrent modification of a document.
#include "flip/DataModel.h" |
#include "flip/Document.h" |
class Model : public flip::DataModel <Model> {}; |
...code skipped... |
void run () |
{ |
declare (); (1) |
Document document (Model::use (), 123456789ULL, 'acme', 'gui '); (2) |
} |
- You must first declare your model before you use it
- This line will create a
Document
which usesModel
,123456789ULL
user unique identifier,'acme'
manufacturer/product for the'gui '
component
How it Works
When you change the document, Flip will know that it was modified. Transactions represent the difference between two document states. When the document is modified, Flip internally keeps the old and new state of the document.
When you have done the necessary changes that represent a logical modification of the document, you can then commit
the document to apply those changes.
In that sense, a transaction (or modification of the document) is always made so that the new document is valid from the model point of view.
Modifying a Property
For this example, let's suppose that we want to change the tempo.
#include "flip/DataModel.h" |
#include "flip/Document.h" |
class Model : public flip::DataModel <Model> {}; |
...code skipped... |
void run () |
{ |
declare (); |
Document document (Model::use (), 123456789ULL, 'acme', 'gui '); |
Song & song = document.root <Song> (); (1) |
song.tempo = 120.0; (2) |
document.commit (); (3) |
} |
- This extract the unique
Song
, the root of the document. - This sets the tempo of the song to 120 beats per minutes, now the document is marked as modified.
- This will apply the change to the document.
Commiting a document marks the boundary of the document change, which is computed into a transaction.
Working with Array
s
For this example, let's suppose that we want to insert a new track into the song. We suppose here that the document is already set up.
class Song : public flip::Object |
{ |
public: |
static void declare (); |
Track & add_track (); |
flip::Float tempo; |
flip::Array <Track> |
tracks; |
}; |
Track & Song::add_track () |
{ |
auto it = tracks.emplace (tracks.end ()); (1) |
return *it; (2) |
} |
- Emplace a new track constructed in-place before the
end ()
iterator
. This is similar to push a new element at the back of theArray
- Dereference the returned
iterator
Note: One should note that in the above example, the modification is not commited to the document.
Working with Collection
s
For this example, let's suppose that we want to insert a new clip into the track.
class Clip : public flip::Object |
{ |
public: |
static void declare (); |
Clip () = default; |
Clip (double position, double duration); |
flip::Float position; |
flip::Float duration; |
}; |
class Track : public flip::Object |
{ |
public: |
static void declare (); |
Clip & add_clip (double position, double duration); |
flip::Collection <Clip> |
clips; |
}; |
Clip & Track::add_clip (double position, double duration) |
{ |
auto it = clips.emplace (clips.end (), position, duration); (1) |
return *it; (2) |
} |
- Emplace a new clip constructed in-place with the second constructor of
Clip
- Dereference the returned
iterator
Note: One should note that in the above example, the modification is not commited to the document.
Working with Map
s
For this example, let's suppose that we want to insert a new parameter into the track.
class Parameter : public flip::Object |
{ |
public: |
static void declare (); |
Parameter () = default; |
Parameter (double value); |
flip::Float value; |
}; |
class Track : public flip::Object |
{ |
public: |
static void declare (); |
Parameter & add_parameter (std::string name, double value); |
flip::Map <Parameter> |
parameters; |
}; |
Parameter & Track::add_parameter (std::string name, double value) |
{ |
auto it = parameters.emplace (name, value); (1) |
return *it; (2) |
} |
- Emplace a new parameter constructed in-place with the second constructor of
Parameter
- Dereference the returned
iterator
Note: One should note that in the above example, the modification is not commited to the document.
Working with Variant
s
For this example, let's suppose that the clip has some content which can be either audio or midi content. Let also suppose that no content is not permitted.
To do that, you would typically use a Variant
.
class Content : public flip::Object |
{ |
public: |
static void declare (); |
}; |
class ContentAudio : public Content |
{ |
public: |
static void declare (); |
flip::String url; |
}; |
class ContentMidi : public Content |
{ |
public: |
static void declare (); |
flip::Blob midi_events; |
}; |
class Clip : public flip::Object |
{ |
public: |
static void declare (); |
Clip () = default; |
Clip (double position, double duration); |
flip::Float position; |
flip::Float duration; |
Variant <Content> |
content; |
}; |
class Track : public flip::Object |
{ |
public: |
static void declare (); |
Clip & add_clip_audio (double position, double duration); |
flip::Collection <Clip> |
clips; |
}; |
Clip & Track::add_clip_audio (double position, double duration, std::string url) |
{ |
auto it = clips.emplace (clips.end (), position, duration); |
auto & clip = *it; |
clip.content.reset <ContentAudio> ().url = url; (1) |
return clip; |
} |
- Set the content of the clip to be an audio content with its url
Note: One should note that in the above example, failure to provide an initial value to the variant at commit time will trigger a flip validation failure.
Working with Optional
s
For this example, let's suppose that the clip has some content which can be either audio or midi content. Let also suppose that no content is allowed this time.
To do that, you would typically use a Optional
.
class Content : public flip::Object |
{ |
public: |
static void declare (); |
}; |
class ContentAudio : public Content |
{ |
public: |
static void declare (); |
flip::String url; |
}; |
class ContentMidi : public Content |
{ |
public: |
static void declare (); |
flip::Blob midi_events; |
}; |
class Clip : public flip::Object |
{ |
public: |
static void declare (); |
Clip () = default; |
Clip (double position, double duration); |
flip::Float position; |
flip::Float duration; |
Optional <Content> |
content; |
}; |
class Track : public flip::Object |
{ |
public: |
static void declare (); |
Clip & add_clip_audio (double position, double duration); |
flip::Collection <Clip> |
clips; |
}; |
Clip & Track::add_clip_audio (double position, double duration, std::string url) |
{ |
auto it = clips.emplace (clips.end (), position, duration); |
auto & clip = *it; |
clip.content.reset <ContentAudio> ().url = url; (1) |
return clip; |
} |
Clip & Track::add_clip_no_content (double position, double duration) (2) |
{ |
auto it = clips.emplace (clips.end (), position, duration); |
return *it; |
} |
- Set the content of the clip to be an audio content with its url
- Set the content of the clip to none
Now that we know how to modify the model in different situations, the next part will be about observing those changes with an observer, and is covered in the next chapter Observing the Model.