Salome HOME
Merge branch 'V8_4_BR'
[modules/gui.git] / doc / salome / gui / input / howtos_and_best_practives.doc
1 /*!
2
3 \page howtos How-To's and Best Practices
4
5 These documents provide guidelines and best practices explaining different aspects
6 and usage examples of SALOME platform.
7
8 - \subpage use_case_builder
9 - \subpage drag_and_drop
10 - \subpage using_pluginsmanager
11
12 \page use_case_builder Customize data tree representation in the Object browser by means of use case builder
13
14 \tableofcontents
15
16 In SALOME, the representation of the data tree in the Object browser for the \a full
17 (CORBA-based) module is done basing on the study contents as it is supplied by SALOME
18 data server (SALOMEDS). In contrast to the \a light module which data tree is completely
19 defined and can be easily attuned by the module specific implementation, \a full module
20 must publish its data in the CORBA study by means of the corresponding API of SALOME
21 data server, namely \c SALOMEDS::StudyBuilder. As soon as data entities are published
22 in the study, they are shown in the Object browser, in the same order as they appear
23 in the study tree. Re-arrangement of the data entities with such approach is not a 
24 trivial task: for example, when copying/moving any data entity at the new position
25 within the tree, it is necessary to copy all its attributes as well
26 and to clear (in case of move operation) the data entity at the original position. Also, it is not possible to
27 make some data items in the tree "invisible" for the user (though it might be useful).
28
29 Use case builder provides an alternative and more flexible way for customizing the
30 data tree representation. It implements another approach to the data tree hierarchy,
31 based on the tree node attributes. With use case builder it is possible to arrange
32 and easily re-arrange the data items in the data tree in any appropriate way.
33
34 For example, with use case builder it is easy to implement such operations as
35 \ref drag_and_drop "Drag and Drop" and Copy/Cut/Paste. With use case builder approach
36 it is not important how data entities are arranged in the study tree, they even may 
37 lie on the same level - use case builder allows providing custom data tree
38 representation, completely indepedent on the study data tree itself. It is even possible
39 to hide some data entities in the tree representation while still keeping them in the
40 study (to store specific module data).
41
42 Object browser automatically checks it the module root data object
43 contains a tree node attribute and switches to the browsing of the
44 data tree for such module using the use case
45 builder. Otherwise, it browses data using an ordinary study tree iterator. Thus, it is
46 possible to have in the same study some modules based on use case builder approach and
47 others not using it.
48
49 \section use_case_builder_usage Use case builder usage
50
51 To obtain a reference to the use case builder, the function \c GetUseCaseBuilder() of the
52 \c SALOMEDS::Study interface can be used:
53
54 \code
55 interface Study
56 {
57   // Get reference to the use case builder
58   UseCaseBuilder GetUseCaseBuilder(); 
59 };
60 \endcode
61
62 \c SALOMEDS::UseCaseBuilder interface of the \c SALOMEDS CORBA module provides several
63 methods that can be used to build a custom data tree. Its API is
64 similar to the API of
65 \c SALOMEDS::StudyBuilder interface - it operates with terms \a "father object" and
66 \a "child object". In addition, use case builder uses term \a "current object" that is
67 used as a parent of the children objects added if the parent is not explicitly 
68 specified.
69
70 \code
71 interface UseCaseBuilder
72 {
73   // Set top-level root object of the use case tree as the current one.
74   // This method is usually used to add SComponent items to the top level of the tree
75   boolean SetRootCurrent();
76   
77   // Set the object theObject as the current object of the use case builder
78   boolean SetCurrentObject(in SObject theObject);
79   
80   // Append object SObject to the end of children list of the current object
81   boolean Append(in SObject theObject);
82   
83   // Append object SObject to the end of children list of the parent object theFather
84   boolean AppendTo(in SObject theFather, in SObject theObject);
85   
86   // Insert object theFirst before the object theNext (under the same parent object)
87   boolean InsertBefore(in SObject theFirst, in SObject theNext);
88   
89   // Remove object from the use case tree (without removing it from the study)
90   boolean Remove(in SObject theObject);
91   
92   // Check if the object theObject has children (in the use case tree)
93   boolean HasChildren(in SObject theObject);
94   
95   // Get father object of the given object theObject in the use cases tree
96   SObject GetFather(in SObject theObject);
97   
98   // Check if given object theObject is added to the use case tree
99   boolean IsUseCaseNode(in SObject theObject);
100   
101   // Get the current object of the use case builder
102   SObject GetCurrentObject();
103 };
104 \endcode
105
106 \section browse_use_case_tree Browsing use case data tree
107
108 Browsing the use case tree can be done by means of the use case iterator, that is
109 provided by the \c SALOMEDS::UseCaseIterator interface of the \c SALOMEDS CORBA
110 module. Access to the use case iterator can be done via \c SALOMEDS::UseCaseBuilder
111 interface:
112
113 \code
114 interface UseCaseBuilder
115 {
116   // Get a reference to the use case iterator and initialize it
117   // by the given object theObject
118   UseCaseIterator GetUseCaseIterator(in SObject theObject);
119 };
120 \endcode
121
122 The API of the \c SALOMEDS::UseCaseIterator interface is similar to the 
123 \c SALOMEDS::ChildIterator:
124
125 \code
126 interface UseCaseIterator
127 {
128   // Activate or reset use case iterator; boolean parameter allLevels
129   // specifies if the iterator should browse recursively on all sub-levels or
130   // on the first sub-level only.
131   void Init(in boolean allLevels);
132   // Check if the iterator can browse to the next item
133   boolean More();
134   // Browse the iterator to the next object
135   void Next();
136   // Get the object currently pointed by the iterator
137   SObject Value();
138 };
139 \endcode
140
141 Typical usage of the \c UseCaseIterator is as follows:
142
143 \code
144 // get use case builder
145 SALOMEDS::UseCaseBuilder_var useCaseBuilder = study->GetUseCaseBuilder();
146
147 // get the use case iterator
148 SALOMEDS::UseCaseIterator_var iter = useCaseIter->GetUseCaseIterator( sobject.in() );
149 // iterate through the sub-items recursively
150 for ( useCaseIter->Init( true ); useCaseIter->More(); useCaseIter->Next() ) {
151   SALOMEDS::SObject_var child = useCaseIter->Value();
152   // do something with the child
153   // ...
154   // clean-up
155   child->UnRegister();
156 }
157 // clean-up
158 useCaseIter->UnRegister();
159 useCaseBuilder->UnRegister();
160 \endcode
161
162 \section use_case_compatibility Remark about compatibility with existing studies
163
164 If you decide to switch your module to the use case builder approach to provide
165 customization for the data tree representation, you must take care of compatibility
166 with existing SALOME studies. Basically it means that you have to add
167 a simple code to  \c Load() (and \c LoadASCII() if necessary) method
168 of your module, which adds tree node attributes to all data entities
169 in the data tree of your module. The simplest way to do
170 this is to iterate through all data items and recursively add them to
171 the use case builder:
172
173 \code
174 // find component
175 SALOMEDS::SComponent_var comp = study->FindComponent( "MYMODULE" );
176 // add tree node attributes only if component data is present in the study
177 if ( !CORBA::is_nil( comp ) ) {
178   // get the use case builder
179   SALOMEDS::UseCaseBuilder_var useCaseBuilder = study->GetUseCaseBuilder();
180   // check if tree nodes are already set
181   if ( !useCaseBuilder->IsUseCaseNode( comp.in() ) ) {
182     // set the current node of the use case builder to the root
183     useCaseBuilder->SetRootCurrent();
184     // add component item to the top level of the use case tree
185     useCaseBuilder->Append( comp.in() );
186     // iterate through all child items recursively
187     SALOMEDS::ChildIterator_var iter = study->NewChildIterator( comp.in() );
188     for ( iter->InitEx( true ); iter->More(); iter->Next() ) {
189       SALOMEDS::SObject_var sobj   = iter->Value();
190       SALOMEDS::SObject_var father = sobj->GetFather();
191       // add an object to the corresponding level in the use case tree
192       useCaseBuilder->AppendTo( father.in(), sobj.in() );
193       // clean up (avoid memory leaks)
194       sobj->UnRegister();
195       father->UnRegister();
196     }
197   }
198   useCaseBuilder->UnRegister(); // clean up
199 }
200 \endcode
201
202 \page drag_and_drop Implementing Drag and Drop functionality in SALOME module
203
204 \tableofcontents
205
206 <b>Drag and Drop</b> provides a simple visual mechanism to transfer
207 information between and within applications. 
208
209 In some aspects Drag and drop operates similarly to the clipboard copy/cut/paste
210 mechanism.
211
212 Since SALOME GUI is implemented on Qt, the drag and drop functionality support
213 is provided by means of the corresponding Qt mechanisms.
214
215 Currently dragging and dropping of the items can be done within Object browser only,
216 however this functionality can be extended to other GUI elements as well.
217
218 \section enable_drag_and_drop Enabling drag and drop in SALOME module
219
220 The Drag and drop functionality is enabled by default in the Object browser. However,
221 to allow dragging of a data object or dropping data on it, it is necessary to redefine
222 \c isDraggable() and \c isDropAccepted() methods of the corresponding class, a successor
223 of the \c SUIT_DataObject. These methods are defined in the base class \c SUIT_DataObject
224 and default implementation of both functions returns \c false, which prevents dragging and
225 dropping:
226
227 \code
228 bool SUIT_DataObject::isDraggable() const
229 {
230   return false;
231 }
232
233 bool SUIT_DataObject::isDropAccepted() const
234 {
235   return false;
236 }
237 \endcode
238
239 If your data model is based on the \c SUIT_DataObject and \c SUIT_TreeModel, just
240 re-implement these functions in your successor data object class and return \c true
241 when it is needed (for example, depending on the data object type, state, etc).
242
243 Another alternative is available if your module is directly inherited from
244 \c LightApp_Module or \c SalomeApp_Module class (as the majority of existing SALOME modules).
245 The class \c LightApp_Module (and thus \c SalomeApp_Module also) already provides
246 high-level API that can be used for enabling drag and drop functionality.
247
248 To enable dragging, redefine \c isDraggable() method of your module class. In this method
249 you can analyze the data object subject to the drag operation and decide if
250 it is necessary to enable or prevent its dragging:
251
252 \code
253 bool MyModuleGUI::isDraggable( const SUIT_DataObject* what ) const
254 {
255   bool result = false;
256   const MyDataObject* obj = dynamic_cast<const MyDataObject*>( what );
257   if ( obj ) {
258     // check if object can be dragged
259     result = <some condition>;
260   }
261   return result;
262 }
263 \endcode
264
265 Note, that you should not invoke here method \c isDragEnabled() of your data object class
266 (in case if it inherits \c LightApp_DataObject or \c SalomeApp_DataObject), unless you
267 redefine methods \c isDraggable() and \c isDropAccepted() in your data object class. 
268 The reason is that the implementation of these methods in \c LightApp_DataObject class
269 redirects calls to the \c LightApp_Module - be careful to avoid entering endless
270 recursion loop.
271
272 To allow data dropping to an object (the object under the mouse cursor in the
273 Object browser during the drag operation) redefine \c isDropAccepted() method of your
274 module class:
275
276 \code
277 bool MyModuleGUI::isDropAccepted( const SUIT_DataObject* where ) const
278 {
279   bool result = false;
280   const MyDataObject* obj = dynamic_cast<const MyDataObject*>( where );
281   if ( obj ) {
282     // check if object supports dropping
283     result = <some condition>;
284   }
285   return result;
286 }
287 \endcode
288
289 The caution about avoiding recursive loop mentioned above is also valid for
290 \c isDropAccepted() method.
291
292 \section handle_data_dropping Handling data dropping
293
294 When dragging operation is completed (the data is dropped to an object) the module owning
295 the item on which the data is dropped is notified by invoking its \c dropObjects() method:
296
297 \code
298 void LightApp_Module::dropObjects( const DataObjectList& what,
299                                    SUIT_DataObject* where,
300                                    const int row,
301                                    Qt::DropAction action )
302 {
303 }
304 \endcode
305
306 The default implementation does nothing. However, this method can be redifined in the
307 successor class and handle the operation properly. The list of dropped
308 data objects is passed via \c what parameter. The data object on which
309 the data is dropped is passed via \c where parameter. The parameter \c row specifies in the children list
310 the position of object where data is dropped; if this parameter is equal to -1, the
311 data is dropped to the end of the children list. Performed drop action is passed
312 via \c action parameter; possible values are \c Qt::CopyAction and \c Qt::MoveAction
313 (other actions are currently unsupported).
314
315 The method \c dropObjects() should analyze the parameters and apply
316 the corresponding actions for rearrangement of the data tree, copying or moving the data items depending on the
317 operation performed. For example:
318
319 \code
320 void MyModuleGUI::dropObjects( const DataObjectList& what, SUIT_DataObject* where,
321                                const int row, Qt::DropAction action )
322 {
323   if ( action != Qt::CopyAction && action != Qt::MoveAction )
324     return; // unsupported action
325
326   // get parent object
327   MyDataObject* whereObj = dynamic_cast<MyDataObject*>( where );
328   if ( !dataObj ) return; // wrong parent
329
330   // iterate through all objects being dropped
331   for ( int i = 0; i < what.count(); i++ ) {
332     MyDataObject* whatObj = dynamic_cast<MyDataObject*>( what[i] );
333     if ( !whatObj ) continue;                // skip wrong objects
334     // perform copying or moving
335     copyOrMove( whatObj,                     // data object being copied/moved
336                 whereObj,                    // target data object
337                 row,                         // index in the target data object
338                 action == Qt::CopyAction );  // true if copying is done
339   }
340   // update Object browser
341   getApp()->updateObjectBrowser( false );
342 }
343 \endcode
344
345 In the above code the function \c copyOrMove() performs actual data tree rearrangement.
346
347 \section drag_drop_light_modules Drag and Drop in "light" modules
348
349 The data model of the \a light (not having CORBA engine) SALOME module is usually
350 based on the custom tree of data objects. The general approach is to
351 inherit a custom data
352 object class from the \c LightApp_DataObject and a custom data model from the
353 \c LightApp_DataModel class. The data model class is responsible for building the
354 appropriate presentation of the data tree in the Object browser.
355
356 Thus, the implementation of the drag and drop functionality in a \a light module (more
357 precisely, the method \a dropObjects() as described above), consists in copying data
358 entities (by creating new instances of the corresponding data object class) or moving
359 existing data objects to the new position in a tree. The Object browser will update the
360 tree representation automatically, as soon as \c updateObjectBrowser() function is called.
361
362 \section drag_drop_full_modules Using UseCaseBuilder for Drag and Drop handling in "full" modules
363
364 Drag and drop operation requires underlying data model to allow flexible re-arrangement of
365 the data entities inside the data tree. In a \a full (CORBA engine based) SALOME
366 module, which data model is usually based on the hierarchy of \c SALOMEDS::SObject entities
367 provided by the data server functionality, re-arrangement of the data
368 tree is not a trivial task.
369
370 However, SALOME data server (\c SALOMEDS) CORBA module proposes a mechanism that can be used
371 to customize data tree representation in a simple and flexible way -
372 \ref use_case_builder "use case builder".
373
374 With use case builder, the \c dropObjects() function can be easily implemented. For example:
375
376 \code
377 // GUI part: process objects dropping
378 void MyModuleGUI::dropObjects( const DataObjectList& what, SUIT_DataObject* where,
379                                const int row, Qt::DropAction action )
380 {
381   if ( action != Qt::CopyAction && action != Qt::MoveAction )
382     return; // unsupported action
383
384   // get parent object
385   SalomeApp_DataObject* dataObj = dynamic_cast<SalomeApp_DataObject*>( where );
386   if ( !dataObj ) return; // wrong parent
387   _PTR(SObject) parentObj = dataObj->object();
388
389   // collect objects being dropped
390   MYMODULE_ORB::object_list_var objects = new MYMODULE_ORB::object_list();
391   objects->length( what.count() );
392   int count = 0;
393   for ( int i = 0; i < what.count(); i++ ) {
394     dataObj = dynamic_cast<SalomeApp_DataObject*>( what[i] );
395     if ( !dataObj ) continue;  // skip wrong objects
396     _PTR(SObject) sobj = dataObj->object();
397     objects[i] = _CAST(SObject, sobj)->GetSObject();
398     count++;
399   }
400   objects->length( count );
401
402   // call engine function
403   engine()->copyOrMove( objects.in(),                              // what
404                         _CAST(SObject, parentObj)->GetSObject(),   // where
405                         row,                                       // row
406                         action == Qt::CopyAction );                // isCopy
407
408   // update Object browser
409   getApp()->updateObjectBrowser( false );
410 }
411
412 // Engine part: perform actual data copying / moving
413 void MyModule::copyOrMove( const MYMODULE_ORB::object_list& what,
414                            SALOMEDS::SObject_ptr where,
415                            CORBA::Long row, CORBA::Boolean isCopy )
416 {
417   if ( CORBA::is_nil( where ) ) return; // bad parent
418
419   SALOMEDS::Study_var study = where->GetStudy();                               // study
420   SALOMEDS::StudyBuilder_var studyBuilder = study->NewBuilder();               // study builder
421   SALOMEDS::UseCaseBuilder_var useCaseBuilder = study->GetUseCaseBuilder();    // use case builder
422   SALOMEDS::SComponent_var father = where->GetFatherComponent();               // father component
423   std::string dataType = father->ComponentDataType();
424   if ( dataType != "MYMODULE" ) return; // not a MYMODULE component
425   
426   SALOMEDS::SObject_var objAfter;
427   if ( row >= 0 && useCaseBuilder->HasChildren( where ) ) {
428     // insert at a given row -> find insertion position
429     SALOMEDS::UseCaseIterator_var useCaseIt = useCaseBuilder->GetUseCaseIterator( where );
430     int i;
431     for ( i = 0; i < row && useCaseIt->More(); i++, useCaseIt->Next() );
432     if ( i == row && useCaseIt->More() ) {
433       objAfter = useCaseIt->Value();
434     }
435   }
436   // process all objects in a given list
437   for ( int i = 0; i < what.length(); i++ ) {
438     SALOMEDS::SObject_var sobj = what[i];
439     if ( CORBA::is_nil( sobj ) ) continue; // skip bad object
440     if ( isCopy ) {
441       // copying is performed
442       // get the name of the object
443       CORBA::String_var name = sobj->GetName();
444       // create a new object, as a child of the component object
445       SALOMEDS::SObject_var new_sobj = studyBuilder->NewObject( father );
446       new_sobj->SetAttrString( "AttributeName", name.in() );
447       sobj = new_sobj;
448       // ... perform other necessary data copying like
449       // adding the corresponding attributes or creation
450       // of servant data entities...
451     }
452     // insert the object or its copy to the use case tree
453     if ( !CORBA::is_nil( objAfter ) )
454       useCaseBuilder->InsertBefore( sobj, objAfter ); // insert at a given row
455     else
456       useCaseBuilder->AppendTo( where, sobj );        // append to the
457   end of the list
458   }
459 }
460
461 \endcode
462
463 */