Flip Basics Programming Guide

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)
}
  1. Make an observer by inheriting from DocumentObserver
  2. Override the pure virtual function declared in DocumentObserver
  3. Attach the observer to the document
  4. When commit is called, it will fire the document_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)
   }
}
  1. Take the branch if the value was changed
  2. Extract the new tempo value
  3. 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.

  1. Track life cycle is also detected through the use of changed
  2. The track was just added to the container
  3. 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
  4. 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.