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. |
} |
} |
- Set the referenced object associated to type WidgetRoot to the root object
- Retrieve the referenced object associated to type WidgetRoot of the root object
- 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; |
} |
} |
} |
- Automatic variable that enables the possibility to create transactions within the signal.
- 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 (); |
} |