Flip Programming Guide

 

Writing a DocumentObserver

This chapter will describe the use of flip::DocumentObserver with the Flip framework.

Overview

The Flip framework offers a flip::DocumentObserver template class which is the basis to write a document observer. A document observer allows to receive model changes, and parse the model tree effectively.

In the following, it will be assumed that the model has the format listed in the listings below. In particular, Root will be the root class of the model. The listings skip all functions just to show the needed members for clarity.

The example is an hypothetic taxi company, where the taxis can change of color. The taxi company has a boss, which can be happy or upset. The taxi company also maintains the total number of courses since the beginning of time.

class Boss
:  public ohm::flip::Object
{
public:
   bool                 did_happyness_change () const;
private:
   ohm::flip::Bool      _happy_flag;
};
class Taxi
:  public ohm::flip::Object
{
public:
   bool                 did_color_change () const;
private:
   ohm::flip::Int64     _color;
};
typedef ohm::flip::Collection <Taxi>   TaxiColl;
class Root
:  public ohm::flip::Object
{
public:
   bool                 did_nbr_taxi_course_change () const;
private:
   ohm::flip::Int64     _nbr_taxi_course;
   Boss                 _boss;
   TaxiColl             _taxi_coll;
};

Setting Up

To set up the model, we have to make a DocumentObserver for the Root class. For that we make a class that inherits from ohm::flip::DocumentObserver which template parameter is Root .

Its constructor takes a flip::DocumentClient on which we will bind the observer.

ListingDocumentObserver declaration

class DocumentObserver
:  public ohm::flip::DocumentObserver <Root>
{
public:
                  DocumentObserver (ohm::flip::DocumentClient & document);
   virtual        ~DocumentObserver () {}
   // inherited from ohm::flip::DocumentObserver <model::Root>
   virtual void   document_changed (model::Root & root);
   virtual void   signal (flip::Object * obj_ptr, archi::UInt32 type, const flip::DataMaster & signal_data);
private:
   
};

The following code bind our observer to the Flip document. At this point any model change to the document will be notified through the document_changed virtual method, and any model signal will be notified through the signal virtual method.

ListingDocumentObserver constructor definition

DocumentObserver::DocumentObserver (flip::DocumentClient & document)
: _document (document)
{
   _document.bind (*this);
}

Parsing the Model Tree

Before we put write the implementation of document_changed we are going to add some private methods to keep the code clean

ListingDocumentObserver declaration

class DocumentObserver
:  public ohm::flip::DocumentObserver <Root>
{
public:
                  DocumentObserver (ohm::flip::DocumentClient & document);
   virtual        ~DocumentObserver () {}
   // inherited from ohm::flip::DocumentObserver <model::Root>
   virtual void   document_changed (model::Root & root);
   virtual void   signal (flip::Object * obj_ptr, archi::UInt32 type, const flip::DataMaster & signal_data);
private:
   void           document_changed (Boss & boss);
   void           document_changed (Taxi & taxi);
}

The code to handle each class is always to check changes for its direct basic typed members. To keep the code clean, each compound object should have its own document_changed method as illutrated here.

Listingdocument_changed (Root &) definition

void  DocumentObserver::document_changed (Root & root)
{
   if (root.did_nbr_taxi_course_change ())
   {
      // display the number of taxi course
   }
   if (_boss.is_invalid ())
   {
      // _boss contains a modification
      document_changed (_boss);
   }
   if (_taxi_coll.is_invalid ())
   {
      // the taxi collection contains a modification, check every taxi
      TaxiColl::iterator it = _taxi_coll.begin ();
      const TaxiColl::iterator it_end = _taxi_coll.end ();
      for (; it != it_end ; ++it)
      {
         Taxi & taxi = *it;
         if (taxi.is_invalid ())
         {
            document_changed (taxi);
         }
      }
   }
}

Note:In theory, every call to is_invalid can be omited. But this is done for speed.

Listingdocument_changed (Boss &) definition

void  DocumentObserver::document_changed (Boss & boss)
{
   if (boss.did_happyness_change ())
   {
      // display the new boss face
   }
}

Listingdocument_changed (Taxi &) definition

void  DocumentObserver::document_changed (Taxi & taxi)
{
   if (taxi.did_color_change ())
   {
      // display the new taxi color
   }
}

Referencing Objects

Many times you will want to associate some kind of object to a model object, for example a graphic object. Objects will need to be created when model objects are created, released when model objects are released and be accessed.

In the following listings, we will consider that we want to associate a graphical object WidgetTaxi to the model class Taxi . The WidgetTaxi will need to be embeded in the root graphical object denoted WidgetRoot and associated to the model class Root .

Listingdocument_changed (Root &) definition

void  DocumentObserver::document_changed (Root & root)
{
   WidgetRoot * widget_ptr = 0;
   if (root.was_inited ())
   {
      widget_ptr = new WidgetRoot;
      
      root.reset_data (widget_ptr);                // 1.
   }
   else
   {
      widget_ptr = root.get_data <WidgetRoot> ();  // 2.
   }
   if (root.did_nbr_taxi_course_change ())
   {
      // display the number of taxi course
   }
   if (_boss.is_invalid ())
   {
      // _boss contains a modification
      document_changed (_boss);
   }
   if (_taxi_coll.is_invalid ())
   {
      // the taxi collection contains a modification, check every taxi
      TaxiColl::iterator it = _taxi_coll.begin ();
      const TaxiColl::iterator it_end = _taxi_coll.end ();
      for (; it != it_end ; ++it)
      {
         Taxi & taxi = *it;
         if (taxi.is_invalid ())
         {
            document_changed (taxi);
         }
      }
   }
   if (root.was_restored ())
   {
      delete widget_ptr;
      widget_ptr = 0;
      
      root.reset_data <WidgetRoot> ();             // 3.
   }
}
  1. Set the referenced object associated to type WidgetRoot to the root object
  2. Retrieve the referenced object associated to type WidgetRoot of the root object
  3. Reset the referenced object associated to type WidgetRoot of the root object

And finally for the taxi

Listingdocument_changed (Taxi &) definition

void  DocumentObserver::document_changed (Taxi & taxi)
{
   Root & root = taxi.get_ancestor <Root> ();
   WidgetRoot * widget_root_ptr = root.get_data <WidgetRoot> ();
   WidgetTaxi * widget_ptr = 0;
   if (taxi.was_inited ())
   {
      widget_ptr = new WidgetTaxi (*widget_root_ptr);
      
      taxi.reset_data (widget_ptr);
   }
   else
   {
      widget_ptr = taxi.get_data <WidgetTaxi> ();
   }
   if (taxi.did_color_change ())
   {
      widget_ptr->set_color (taxi.get_color ());
   }
   if (taxi.was_restored ())
   {
      delete widget_ptr;
      widget_ptr = 0;
      
      taxi.reset_data <WidgetTaxi> ();
   }
}

Handling Signals

To handle a signal, we need to send one. We are going to say that if the number of taxi courses change, as it is always increasing in this example, this is going to make the boss happy.

We cannot change the model while parsing it, but we can send signals, and it is possible to modify the model within a signal.

We are going to complete the Boss class so that it can produce a signal and every thing needed to make him happy in the model.

class Boss
:  public ohm::flip::Object
{
public:
   enum Signal
   {
      Signal_MAKE_HAPPY = 0,
   };
   void                 signal_make_happy ();
   void                 tx_make_happy ();
   bool                 did_happyness_change () const;
private:
   ohm::flip::Bool      _happy_flag;
   ohm::flip::TxSessionGuard
                        _tx_session_guard;
};
void  DocumentObserver::signal_make_happy ()
{
   send_signal (Signal_MAKE_HAPPY);
}
void  DocumentObserver::tx_make_happy ()
{
   if (!_tx_session_guard.start ()) return;
   _happy_flag = true;
   _tx_session_guard.commit ();
}

Now we are going to send the signal when the number of taxi courses changes, by modifying the code above.

Listingdocument_changed (Root &) definition

void  DocumentObserver::document_changed (Root & root)
{
   if (root.did_nbr_taxi_course ())
   {
      _boss.signal_make_happy ();
   }
   [... skipped content ...]
}

And now we can implement the signal method which will handle the signal. Before we put write the implementation of signal we are going to add some private methods to keep the code clean

ListingDocumentObserver declaration

class DocumentObserver
:  public ohm::flip::DocumentObserver <Root>
{
public:
                  DocumentObserver (ohm::flip::DocumentClient & document);
   virtual        ~DocumentObserver () {}
   // inherited from ohm::flip::DocumentObserver <model::Root>
   virtual void   document_changed (model::Root & root);
   virtual void   signal (flip::Object * obj_ptr, archi::UInt32 type, const flip::DataMaster & signal_data);
private:
   void           document_changed (Boss & boss);
   void           document_changed (Taxi & taxi);
   void           signal (model::Boss & boss, archi::UInt32 type, const flip::DataMaster & signal_data);
   void           signal_make_happy (model::Boss & boss, const flip::DataMaster & signal_data);
}

The following listing is the main dispatcher, acting on types.

Listingsignal definition

void  DocumentObserver::signal (flip::Object * obj_ptr, archi::UInt32 type, const flip::DataMaster & signal_data)
{
   flip::DocumentClient::AutoUserInputs auto_ui (_document);   // 1.
   bool continue_flag = true;
   if (continue_flag)                                          // 2.
   {
      Boss * cast_ptr = dynamic_cast <Boss *> (obj_ptr);
      if (cast_ptr != 0)
      {
         signal (*cast_ptr, type, signal_data);
         continue_flag = false;
      }
   }
}
  1. Automatic variable that enables the possibility to create transactions within the signal.
  2. The same pattern is to be repeated over and over again for every types supported by the signal

The following listing is the Boss dispatcher, acting on signal types.

Listingsignal definition

void  DocumentObserver::signal (Boss & boss, archi::UInt32 type, const flip::DataMaster & signal_data)
{
   switch (type)
   {
   case Boss::Signal_MAKE_HAPPY:
      signal_make_happy (boos, signal_data);
      break;
   }
}

And finally the signal handling change the model.

Listingsignal definition

void  DocumentObserver::signal_make_happy (Boss & boss, const flip::DataMaster & signal_data)
{
   boss.tx_make_happy ();
}