SVN_SILENT made messages (.desktop file)
[kdepim.git] / akonadi_next / Mainpage.dox
blob0db1b6d2d1dbbe31f984f5aba77a6b18345003cd
1 /** @mainpage akonadi_next
3 Some classes to be merged into akonadi when finished. This doc will also need to be merged.
5 <h1>How to use Akonadi in your application</h1>
7 This library provides classes for KDE applications to communicate with the akonadi server. The most high-level interface to Akonadi is the Models and Views provided in this library. Ready to use models are provided for use with views to interact with a tree of collections, a list of items in a collection, or a combined tree of Collections and items.
9 <h2>Collections and Items</h2>
11 In the Akonadi concept, Items are individual objects of PIM data, eg emails, contacts, events, notes etc. The data in an item is stored in it a typed payload. For example, if an Akonadi Item holds a contact, the contact is available as a KABC::Addressee:
13 @code
14   if (item.hasPayload<KABC::Addressee>())
15   {
16     KABC::Addressee addr = item.payload<KABC::Addressee>();
17     // use addr in some way...
18   }
19 @endcode
21 Additionally, an Item must have a mimetype which corresponds to the type of payload it holds.
23 Collections are simply containers of Items. A Collection has a name and a list of mimetypes that it may contain. If a collection may contain events if it can contain the mimetype 'text/calendar'. All Collections have the same mimetype. A Collection which can itself contain Collections must be able to contain the Collection mimetype.
25 @code
26   Collection col;
27   // This collection can contain events and nested collections.
28   col.setContentMimetypes( QStringList() << Collection::mimeType() << "text/calendar" );
29 @endcode
31 This systems makes it simple to create PIM applications. For example, to create an application for viewing and editing events, you simply need to tell akonadi to retrieve all items matching the mimetype 'text/calendar'.
33 <h2> Models and Views</h2>
34 Akonadi models and views are a high level way to interact with the akonadi server. Most applications will use these classes.
36 Models provide an interface for viewing, deleting and moving Items and Collections. New Items can also be created by dropping data of the appropriate type on a model. Additionally, the models are updated automatically if another application changes the data or inserts of deletes items etc.
38 Akonadi provides several models for particular uses, eg the MailModel is used for emails and the ContactsModel is used for showing contacts. Additional specific models can be implemented using EntityTreeModel as a base class.
40 A typical use of these would be to create a model and use proxy models to make the view show different parts of the model. For example, show a collection tree in on one side and show items in a selected collection in another view.
42 @code
43   mailModel = new MailModel( session, monitor, this);
45   collectionTree = new MimeTypeFilterProxyModel(this);
46   collectionTree->setSourceModel(mailModel);
47   // Filter out everything that is not a collection.
48   collectionTree->addMimeTypeInclusionFilter( Collection::mimeType() );
49   collectionTree->setHeaderSet(EntityTreeModel::CollectionTreeHeaders);
51   collectionView = new EntityTreeView(this);
52   collectionView->setModel(collectionTree);
54   itemList = new MimeTypeFilterProxyModel(this);
55   itemList->setSourceModel(mailModel);
56   // Filter out collections
57   itemList->addMimeTypeExclusionFilter( Collection::mimeType() );
58   itemList->setHeaderSet(EntityTreeModel::ItemListHeaders);
60   itemView = new EntityTreeView(this);
61   itemView->setModel(itemList);
62 @endcode
64 \image html mailmodelapp.png "An email application using MailModel"
66 @warning Some of the features in the examples below are not fully implemented yet. See entitytreemodel.h for a list of remaining issues with the api and implementation of these classes.
68 The content of the model is determined by the configuration of the Monitor passed into it. The examples below show a use of the EntityTreeModel and some proxy models for a simple heirarchical note collection. As the model is generic, the configuration and proxy models will also work with any other mimetype.
70 @code
72 // Configure what should be shown in the model:
73 Monitor *monitor = new Monitor( this );
74 monitor->fetchCollection( true );
75 monitor->setItemFetchScope( scope );
76 monitor->setCollectionMonitored( Collection::root() );
77 monitor->setMimeTypeMonitored( MyEntity::mimeType() );
79 Session *session = new Session( QByteArray( "MyEmailApp-" ) + QByteArray::number( qrand() ), this );
81 EntityTreeModel *entityTree = new Akonadi::EntityTreeModel(session, monitor, this);
82 @endcode
84 \image html entitytreemodel.png "A plain EntityTreeModel in a view"
86 The EntityTreeModel can be further configured for certain behaviours such as fetching of collections and items.
88 To create a model of only a collection tree and no items, set that in the model. This is just like CollectionModel:
90 @code
91 entityTree->setItemPopulationStrategy(EntityTreeModel::NoItemPopulation);
92 @endcode
95 \image html entitytreemodel-collections.png "A plain EntityTreeModel which does not fetch items."
97 Or, create a model of only items and not child collections. This is just like ItemModel:
99 @code
100 entityTree->setRootCollection(myCollection);
101 entityTree->setCollectionFetchStrategy(EntityTreeModel::FetchNoCollections);
102 @endcode
104 The CollectionModel-like and ItemModel-like configurations will probably not be used much. Usually, a full tree of collections and items will be created and proxy models will be used to filter collections or items as needed.
106 Or, to create a model which includes items and first level collections:
108 @code
109 entityTree->setCollectionFetchStrategy(EntityTreeModel::FetchFirstLevelCollections);
110 @endcode
112 The items in the model can also be inserted lazily for performance reasons. The Collection tree is always built immediately.
114 Additionally, a DescendantEntitiesProxyModel may be used to alter how the items in the tree are presented.
116 @code
117 // ... Create an entityTreeModel
118 DescendantEntitiesProxyModel *descProxy = new DescendantEntitiesProxyModel(this);
119 descProxy->setSourceModel(entityTree);
120 view->setModel(descProxy);
121 @endcode
123 \image html descendantentitiesproxymodel.png "A DescendantEntitiesProxyModel wrapping an EntityTreeModel"
125 DescendantEntitiesProxyModel can also display the same data as FlatCollectionProxyModel, but optionally for the entire entity tree, not just collections.
127 @code
128 // ... Create an entityTreeModel
129 DescendantEntitiesProxyModel *descProxy = new DescendantEntitiesProxyModel(this);
130 descProxy->setSourceModel(entityTree);
132 // #### This is new
133 descProxy->setDisplayAncestorData(true, QString(" / "));
135 view->setModel(descProxy);
137 @endcode
139 \image html descendantentitiesproxymodel-withansecnames.png "A DescendantEntitiesProxyModel with ancestor names."
142 This proxy can be combined with a filter to for example remove collections.
144 @code
145 // ... Create an entityTreeModel
146 DescendantEntitiesProxyModel *descProxy = new DescendantEntitiesProxyModel(this);
147 descProxy->setSourceModel(entityTree);
149 // #### This is new.
150 MimeTypeFilterProxyModel *filterModel = new MimeTypeFilterProxyModel(this);
151 filterModel->setSourceModel(descProxy);
152 filterModel->setExclusionFilter(QStringList() << Collection::mimeType());
154 view->setModel(filterModel);
155 @endcode
157 \image html descendantentitiesproxymodel-colfilter.png "An MimeTypeFilterProxyModel wrapping a DescendantEntitiesProxyModel wrapping an EntityTreeModel"
159 It is also possible to show the root item as part of the selectable model:
161 @code
163 entityTree->setIncludeRootCollection(true);
165 @endcode
167 \image html entitytreemodel-showroot.png "An EntityTreeModel showing Collection::root"
169 By default the displayed name of the root collection is '[*]', because it doesn't require i18n, and is generic. It can be changed too.
171 @code
172 entityTree->setIncludeRootCollection(true);
173 entityTree->setRootCollectionDisplayName(i18nc("Name of top level for all collections in the application", "[All]"))
174 @endcode
176 \image html entitytreemodel-showrootwithname.png "An EntityTreeModel showing Collection::root with an application specific name."
178 These can of course be combined to create an application which uses one EntityTreeModel along with several proxies and views.
180 @code
181 // ... create an EntityTreeModel.
182 MimeTypeFilterProxyModel *collectionTree = new MimeTypeFilterProxyModel(this);
183 collectionTree->setSourceModel(entityTree);
184 // Filter to include collections only:
185 collectionTree->setInclusionFilter(QStringList() << Collection::mimeType());
186 EntityTreeView *treeView = new EntityTreeView(this);
187 treeView->setModel(collectionTree);
189 MimeTypeFilterProxyModel *itemList = new MimeTypeFilterProxyModel(this);
190 itemList->setSourceModel(entityTree);
191 // Filter *out* collections
192 itemList->setExclusionFilter(QStringList() << Collection::mimeType());
193 EntityTreeView *listView = new EntityTreeView(this);
194 listView->setModel(itemList);
195 @endcode
198 \image html treeandlistapp.png "A single EntityTreeModel with several views and proxies."
200 Or to also show items of child collections in the list:
202 @code
203   collectionTree = new MimeTypeFilterProxyModel(this);
204   collectionTree->setSourceModel(entityTree);
206   // Include only collections in this proxy model.
207   collectionTree->addMimeTypeInclusionFilter( Collection::mimeType() );
209   treeview->setModel(collectionTree);
211   descendedList = new DescendantEntitiesProxyModel(this);
212   descendedList->setSourceModel(entityTree);
214   itemList = new MimeTypeFilterProxyModel(this);
215   itemList->setSourceModel(descendedList);
217   // Exclude collections from the list view.
218   itemList->addMimeTypeExclusionFilter( Collection::mimeType() );
220   listView = new EntityTreeView(this);
221   listView->setModel(itemList);
222 @endcode
224 \image html treeandlistappwithdesclist.png "Showing descendants of all Collections in the list"
226 Note that it is important in this case to use the DescendantEntitesProxyModel before the MimeTypeFilterProxyModel. Otherwise, by filtering out the collections first, you would also be filtering out their child items.
228 A SelectionProxyModel can be used to simplify managing selection in one view through multiple proxy models to a representation in another view. The selectionModel of the initial view is used to create a proxied model which includes only the selected indexes and their children.
230 @code
231   collectionTree = new MimeTypeFilterProxyModel(this);
232   collectionTree->setSourceModel(entityTree);
234   // Include only collections in this proxy model.
235   collectionTree->addMimeTypeInclusionFilter( Collection::mimeType() );
237   treeview->setModel(collectionTree);
239   // SelectionProxyModel can handle complex selections:
240   treeview->setSelectionMode(QAbstractItemView::ExtendedSelection);
242   SelectionProxyModel *selProxy = new SelectionProxyModel(treeview->selectionModel(), this);
243   selProxy->setSourceModel(etm);
245   EntityTreeView *selView = new EntityTreeView(splitter);
246   selView->setModel(selProxy);
247 @endcode
249 \image html selectionproxymodelsimpleselection.png "A Selection in one view creating a model for use with another view."
251 The SelectionProxyModel can handle complex selections.
253 \image html selectionproxymodelmultipleselection.png "Non-contiguous selection creating a new simple model in a second view."
255 If an index and one or more of its descendants are selected, only the top-most selected index (including all of its descendants) are included in the proxy model. (Though this is configurable. See below)
257 \image html selectionproxymodelmultipleselection-withdescendant.png "Selecting an item and its descendant."
259 SelectionProxyModel allows configuration using the methods setStartWithChildTrees, setOmitDescendants, setIncludeAllSelected. See testapp/proxymodeltestapp to try out the 5 valid configurations.
261 Obviously, the SelectionProxyModel may be used in a view, or further processed with other proxy models. See the example_contacts application for example which uses a further DescendantEntitiesProxyModel and MimeTypeFilterProxyModel on top of a SelectionProxyModel.
263 The SelectionProxyModel orders its items in the same top-to-bottom order as they appear in the source model. Note that this order may be different to the order in the selection model if there is a QSortFilterProxyModel between the selection and the source model.
265 \image html selectionproxymodel-ordered.png "Ordered items in the SelectionProxyModel"
267 A different view can also be used with the model, such as a QColumnView. However this does not work because QColumnView does not listen to signals from the model (http://www.qtsoftware.com/developer/task-tracker/index_html?id=246999&method=entry).
269 @code
270 // ... Create an EntityTreeModel
271 view = new QColumnView(this);
272 view->setModel(entityTree);
273 @endcode
275 Work is also started on time based proxy models so that Items containing calendar data can be represented in a table.
277 <h2>Jobs and Monitors</h2>
279 The lower level way to interact with Akonadi is to use Jobs and Monitors (This is what models use internally). Jobs are used to make changes to akonadi, and in some cases (eg, a fetch job) emit a signal with data resulting from the job. A Monitor reports changes made to the data stored in Akonadi (eg, creating, updating, deleting or moving an item or collection ) via signals.
281 Typically, an application will configure a monitor to report changes to a particular Collection, mimetype or resource, and then connect to the signals it emits.
283 Most applications will use some of the low level api for actions unrelated to a model-tree view, such as creating new items and collections.
285 <h2>Tricky details</h2>
287 <h3>Change Conflicts</h3>
288 It is possible that while an application is editing an item, that item gets updated in akonadi. Akonadi will notify the application that that item has changed via a Monitor signal. It is the responsibility of the application to handle the conflict by for example offering the user a dialog to resolve it. Alternatively, the application could ignore the dataChanged signal for that item, and will get another chance to resolve the conflict when trying to save the result back to akonadi. In that case, the ItemModifyJob will fail and report that the revision number of the item on the server differs from its revision number as reported by the job. Again, it is up to the application to handle this case.
290 This is something that every application using akonadi will have to handle.
292 <h3>Using Entity::Id as an identifier</h3>
294 Items and Collections have a id() member which is a unique identifier used by akonadi. It can be useful to use the id() as an identifier when storing Collections or Items.
296 However, as an item and a collection can have the same id(), if you need to store both Collections and Items together by a simple identifier, conflicts can occur.
298 @code
299   QString getRemoteIdById( Entity::Id id )
300   {
301     // Note:
302     // m_items is QHash<Entity::Id, Item>
303     // m_collections is QHash<Entity::Id, Collection>
304     if ( m_items.contains( id ) )
305     {
306       // Oops, we could accidentally match a collection here.
307       return m_items.value( id ).remoteId();
308     } else if ( m_collections.contains( id ) )
309     {
310       return m_collections.value( id ).remoteId();
311     }
312     return QString();
313   }
314 @endcode
316 In this case, it makes more sense to use a normal qint64 as the internal identifier, and use the sign bit to determine if the identifier refers to a Collection or an Item. This is done in the implementation of EntityTreeModel to tell Collections and Items apart.
318 @code
319   QString getRemoteIdByInternalIdentifier( qint64 internalIdentifier )
320   {
321     // Note:
322     // m_items is QHash<Entity::Id, Item>
323     // m_collections is QHash<Entity::Id, Collection>
325     // If the id is negative, it refers to an Item
326     // Otherwise it refers to a Collection.
328     if ( internalIdentifier < 0 )
329     {
330       // Reverse the sign of the id before using it.
331       return m_items.value( internalIdentifier * -1 ).remoteId();
332     } else
333     {
334       return m_collections.value( internalIdentifier ).remoteId();
335     }
336   }
337 @endcode
339 <h3>Unordered Lists</h3>
340 Collection and Item both provide a ::List to represent groups of objects. However the objects in the list are usually not ordered in any particular way, even though the API provides methods to work with an ordered list. It makes more sense to think of it as a Set instead of a list in most cases.
342 For example, when using an ItemFetchJob to fetch the items in a collection, the items could be in any order when returned from the job. The order that a Monitor emits notices of changes is also indeterminate. By using a Transaction however, it is sometimes possible to retrieve objects in order. Additionally, using s constructor overload in the CollectionFetchJob it is possible to retrieve collections in a particular order.
344 @code
345   Collection::List getCollections(QList<Collection::Id> idsToGet)
346   {
347     Collection::List getList;
348     foreach(Collection::Id id, idsToGet)
349     {
350       getList << Collection(id);
351     }
352     CollectionFetchJob *job = CollectionFetchJob(getList);
353     if (job->exec())
354     {
355        // job->collections() is in the same order as the ids in idsToGet.
356     }
357   }
359 @endcode
362 <h3>Be careful when fetching collections recursively</h3>
363 Collections could be returned in multiple signal calls and out of order.
365 @TODO: more info.
368 <h2>Resources</h2>
369 The KDEPIM module includes resources for handling many types of PIM data, such as imap email, vcard files and vcard directories, ical event files etc. These cover many of the sources for your PIM data, but in the case that you need to use data from another source (for example a website providing a contacts storage service and an api), you simply have to write a new resource.
371 http://techbase.kde.org/Development/Tutorials/Akonadi/Resources
373 <h2>Serializers</h2>
374 Serializers provide the functionality of converting raw data, for example from a file, to a strongly typed object of PIM data. For example, the addressee serializer reads data from a file and creates a KABC::Addressee object.
376 New serializers can also easily be written if the data you are dealing with is not one of the standard PIM data types.
378 <h1>Implementation details</h1>
380 <h3>Updating Akonadi Models</h3>
382 @note The details here are only relevant if you are writing a new view using EntityTreeModel, or writing a new model.
384 Because communication with akonadi happens asynchronously, and the models only hold a cached copy of the data on the akonadi server, some typical behaviours of models are not followed by Akonadi models.
386 For example, when setting data on a model via a view, most models syncronously update their internal store and notify akonadi to update its view of the data by returning <tt>true</tt>.
388 @dot
389 digraph utmg {
390     rankdir = LR;
391     { node [label="",style=invis, height=0, width=0 ];
392       V_Set_Data; V_Result; V_Data_Changed;
393       M_Set_Data; M_Result;
394     }
395     { node [shape=box, fillcolor=lightyellow, style=filled,fontsize=26];
396       View [label=":View"]; Model [label=":Model"];
397     }
398     { node [style=invis];
399       View_EOL; Model_EOL;
400     }
401     {
402       V_Set_Data -> M_Set_Data [label="Set Data"];
403       M_Result -> V_Result [label="Success",arrowhead="vee", style="dashed"];
404       V_Result -> V_Data_Changed [label="Update View \n[ Success = True ]"];
405     }
407     // Dashed Vertical lines for object lifetimes.
408     edge [style=dashed, arrowhead=none];
409     { rank = same; View -> V_Set_Data -> V_Result -> V_Data_Changed -> View_EOL; }
410     { rank = same; Model -> M_Set_Data -> M_Result -> Model_EOL; }
412     // Make sure top nodes are in a straight line.
413     { View -> Model [style=invis]; }
414     // And the bottom nodes.
415     { View_EOL -> Model_EOL [style=invis]; }
417 @enddot
419 Akonadi models only cache data from the akonadi server. To update data on an Akonadi::Entity stored in a model, the model makes a request to the akonadi server to update the model data. At that point the data cached internally in the model is not updated, so <tt>false</tt> is always returned from setData. If the request to update data on the akonadi server is successful, an Akonadi::Monitor notifies the model that the data on that item has changed. The model then updates its internal data store and notifies the view that the data has changed. The details of how the Monitor communicates with akonadi are omitted for clarity.
421 @dot
422 digraph utmg {
423     rankdir = LR;
424     { node [label="",style=invis, height=0, width=0 ];
425       ETV_Set_Data; ETV_Result; ETV_Data_Changed;
426       ETM_Set_Data; ETM_Result; ETM_Changed;
427       M_Dummy_1; M_Changed;
428       AS_Modify;
429     }
430     { node [shape=box, fillcolor=lightyellow, style=filled,fontsize=26];
431       EntityTreeView [label=":View"]; EntityTreeModel [label=":Model"]; Monitor [label=":Monitor"]; Akonadi_Server [label=":Akonadi"];
432     }
433     { node [style=invis];
434       EntityTreeView_EOL; EntityTreeModel_EOL; Monitor_EOL; Akonadi_Server_EOL;
435     }
436     {
437       { rank = same; ETV_Set_Data -> ETM_Set_Data [label="Set Data"]; }
438       { rank = same; ETM_Result -> ETV_Result [label="False",arrowhead="vee", style="dashed"]; }
439       { rank = same; ETM_Result -> M_Dummy_1 [style=invis]; }
440       { rank = same; ETM_Set_Data -> AS_Modify [arrowhead="vee",taillabel="Modify Item", labeldistance=14.0, labelangle=10]; }
441       { rank = same; M_Changed -> ETM_Changed [arrowhead="vee",label="Item Changed"]; }
442       { rank = same; ETM_Changed -> ETV_Data_Changed [arrowhead="vee",label="Update View"]; }
443     }
445     // Dashed Vertical lines for object lifetimes.
446     edge [style=dashed, arrowhead=none];
447     { rank = same; EntityTreeView -> ETV_Set_Data -> ETV_Result -> ETV_Data_Changed -> EntityTreeView_EOL; }
448     { rank = same; EntityTreeModel -> ETM_Set_Data -> ETM_Result -> ETM_Changed -> EntityTreeModel_EOL; }
449     { rank = same; Monitor -> M_Dummy_1 -> M_Changed -> Monitor_EOL; }
450     { rank = same; Akonadi_Server -> AS_Modify -> Akonadi_Server_EOL; }
452     // Make sure top nodes are in a straight line.
453     { EntityTreeView -> EntityTreeModel -> Monitor -> Akonadi_Server [style=invis]; }
454     // And the bottom nodes.
455     { EntityTreeView_EOL -> EntityTreeModel_EOL -> Monitor_EOL -> Akonadi_Server_EOL [style=invis]; }
457 @enddot
459 Similarly, in drag and drop operations, most models would update an internal data store and return <tt>true</tt> from dropMimeData if the drop is successful.
461 @dot
462 digraph utmg {
463     rankdir = LR;
464     { node [label="",style=invis, height=0, width=0 ];
465       Left_Phantom; Left_Phantom_DropEvent;
466       V_DropEvent; V_Result; V_Data_Changed; V_Dummy_1;
467       M_DropMimeData; M_Result;
468     }
469     { node [shape=box, fillcolor=lightyellow, style=filled,fontsize=26];
470       View [label=":View"]; Model [label=":Model"];
471     }
472     { node [style=invis];
473        Left_Phantom_EOL;
474        View_EOL; Model_EOL;
475     }
476     {
477       Left_Phantom_DropEvent -> V_DropEvent [label="DropEvent"];
478       V_DropEvent -> M_DropMimeData [label="DropMimeData"];
479       M_Result -> V_Result [label="Success",arrowhead="vee", style="dashed"];
480       V_Result -> V_Data_Changed [label="Update View \n[Success = True]"];
481     }
483     // Dashed Vertical lines for object lifetimes.
484     edge [style=dashed, arrowhead=none];
485     { rank = same; View -> V_DropEvent -> V_Result -> V_Dummy_1 -> V_Data_Changed -> View_EOL; }
486     { rank = same; Model -> M_DropMimeData -> M_Result -> Model_EOL; }
488     //Phantom line
489     { rank= same; Left_Phantom -> Left_Phantom_DropEvent -> Left_Phantom_EOL [style=invis]; }
491     // Make sure top nodes are in a straight line.
492     {  Left_Phantom -> View -> Model [style=invis]; }
493     // And the bottom nodes.
494     {  Left_Phantom_EOL ->  View_EOL -> Model_EOL [style=invis]; }
497 @enddot
499 Akonadi models, for the same reason as above, always return false from dropMimeData. At the same time a suitable request is sent to the akonadi server to make the changes resulting from the drop (for example, moving or copying an entity, or adding a new entity to a collection etc). If that request is successful, the Akonadi::Monitor notifies the model that the data is changed and the model updates its internal store and notifies the view that the model data is changed.
501 @dot
503 digraph utmg {
504     rankdir = LR;
505     { node [label="",style=invis, height=0, width=0 ];
506       Left_Phantom; Left_Phantom_DropEvent;
507       ETV_DropEvent; ETV_Result; ETV_Data_Changed;
508       ETM_DropMimeData; ETM_Result; ETM_Changed;
509       M_Dummy_1; M_Changed;
510       AS_Modify;
511     }
512     { node [shape=box, fillcolor=lightyellow, style=filled,fontsize=26];
513       EntityTreeView [label=":View"];
514       EntityTreeModel [label=":Model"];
515       Monitor [label=":Monitor"];
516       Akonadi_Server [label=":Akonadi"];
517     }
518     { node [style=invis];
519       Left_Phantom_EOL;
520       EntityTreeView_EOL; EntityTreeModel_EOL; Monitor_EOL; Akonadi_Server_EOL;
521     }
523     {
524       { rank = same; Left_Phantom_DropEvent -> ETV_DropEvent [label="DropEvent"]; }
525       { rank = same; ETV_DropEvent -> ETM_DropMimeData [label="Drop MimeData"]; }
526       { rank = same; ETM_Result -> ETV_Result [label="False",arrowhead="vee", style="dashed"]; }
527       { rank = same; ETM_Result -> M_Dummy_1 [style=invis]; }
528       { rank = same; ETM_DropMimeData -> AS_Modify [arrowhead="vee",taillabel="Action", labeldistance=14.0, labelangle=10]; }
529       { rank = same; M_Changed -> ETM_Changed [arrowhead="vee",label="Item Changed"]; }
530       { rank = same; ETM_Changed -> ETV_Data_Changed [arrowhead="vee",label="Update View"]; }
531     }
533     // Dashed Vertical lines for object lifetimes.
534     edge [style=dashed, arrowhead=none];
535     { rank = same; EntityTreeView -> ETV_DropEvent -> ETV_Result -> ETV_Data_Changed -> EntityTreeView_EOL; }
536     { rank = same; EntityTreeModel -> ETM_DropMimeData -> ETM_Result -> ETM_Changed -> EntityTreeModel_EOL; }
537     { rank = same; Monitor -> M_Dummy_1 -> M_Changed -> Monitor_EOL; }
538     { rank = same; Akonadi_Server -> AS_Modify -> Akonadi_Server_EOL; }
540     //Phantom line
541     { rank= same; Left_Phantom -> Left_Phantom_DropEvent -> Left_Phantom_EOL [style=invis]; }
543     // Make sure top nodes are in a straight line.
544     { Left_Phantom -> EntityTreeView -> EntityTreeModel -> Monitor -> Akonadi_Server [style=invis]; }
545     // And the bottom nodes.
546     { Left_Phantom_EOL -> EntityTreeView_EOL -> EntityTreeModel_EOL -> Monitor_EOL -> Akonadi_Server_EOL [style=invis]; }
550 @enddot