.. _drag_and_drop: ********************************************************* Implementing Drag and Drop functionality in SALOME module ********************************************************* .. contents:: Table of Contents **Drag and Drop** provides a simple visual mechanism to transfer information between and within applications. In some aspects Drag and drop operates similarly to the clipboard copy/cut/paste mechanism. Since SALOME GUI is implemented on Qt, the drag and drop functionality support is provided by means of the corresponding Qt mechanisms. Currently dragging and dropping of the items can be done within Object browser only, however this functionality can be extended to other GUI elements as well. .. _enable_drag_and_drop: Enabling drag and drop in SALOME module ======================================= The Drag and drop functionality is enabled by default in the Object browser. However, to allow dragging of a data object or dropping data on it, it is necessary to redefine **isDraggable()** and **isDropAccepted()** methods of the corresponding class, a successor of the **SUIT_DataObject**. These methods are defined in the base class **SUIT_DataObject** and default implementation of both functions returns **false**, which prevents dragging and dropping: :: bool SUIT_DataObject::isDraggable() const { return false; } bool SUIT_DataObject::isDropAccepted() const { return false; } If your data model is based on the **SUIT_DataObject** and **SUIT_TreeModel**, just re-implement these functions in your successor data object class and return **true** when it is needed (for example, depending on the data object type, state, etc). Another alternative is available if your module is directly inherited from **LightApp_Module** or **SalomeApp_Module** class (as the majority of existing SALOME modules). The class **LightApp_Module** (and thus **SalomeApp_Module** also) already provides high-level API that can be used for enabling drag and drop functionality. To enable dragging, redefine **isDraggable()** method of your module class. In this method you can analyze the data object subject to the drag operation and decide if it is necessary to enable or prevent its dragging: :: bool MyModuleGUI::isDraggable( const SUIT_DataObject* what ) const { bool result = false; const MyDataObject* obj = dynamic_cast( what ); if ( obj ) { // check if object can be dragged result = ; } return result; } Note, that you should not invoke here method **isDragEnabled()** of your data object class (in case if it inherits **LightApp_DataObject** or **SalomeApp_DataObject**), unless you redefine methods **isDraggable()** and **isDropAccepted()** in your data object class. The reason is that the implementation of these methods in **LightApp_DataObject** class redirects calls to the **LightApp_Module** - be careful to avoid entering endless recursion loop. To allow data dropping to an object (the object under the mouse cursor in the Object browser during the drag operation) redefine **isDropAccepted()** method of your module class: :: bool MyModuleGUI::isDropAccepted( const SUIT_DataObject* where ) const { bool result = false; const MyDataObject* obj = dynamic_cast( where ); if ( obj ) { // check if object supports dropping result = ; } return result; } The caution about avoiding recursive loop mentioned above is also valid for **isDropAccepted()** method. .. _handle_data_dropping: Handling data dropping ====================== When dragging operation is completed (the data is dropped to an object) the module owning the item on which the data is dropped is notified by invoking its **dropObjects()** method: :: void LightApp_Module::dropObjects( const DataObjectList& what, SUIT_DataObject* where, const int row, Qt::DropAction action ) { } The default implementation does nothing. However, this method can be redifined in the successor class and handle the operation properly. The list of dropped data objects is passed via **what** parameter. The data object on which the data is dropped is passed via **where** parameter. The parameter **row** specifies in the children list the position of object where data is dropped; if this parameter is equal to -1, the data is dropped to the end of the children list. Performed drop action is passed via **action** parameter; possible values are **Qt::CopyAction** and **Qt::MoveAction** (other actions are currently unsupported). The method **dropObjects()** should analyze the parameters and apply the corresponding actions for rearrangement of the data tree, copying or moving the data items depending on the operation performed. For example: :: void MyModuleGUI::dropObjects( const DataObjectList& what, SUIT_DataObject* where, const int row, Qt::DropAction action ) { if ( action != Qt::CopyAction && action != Qt::MoveAction ) return; // unsupported action // get parent object MyDataObject* whereObj = dynamic_cast( where ); if ( !dataObj ) return; // wrong parent // iterate through all objects being dropped for ( int i = 0; i < what.count(); i++ ) { MyDataObject* whatObj = dynamic_cast( what[i] ); if ( !whatObj ) continue; // skip wrong objects // perform copying or moving copyOrMove( whatObj, // data object being copied/moved whereObj, // target data object row, // index in the target data object action == Qt::CopyAction ); // true if copying is done } // update Object browser getApp()->updateObjectBrowser( false ); } In the above code the function **copyOrMove()** performs actual data tree rearrangement. .. _drag_drop_light_modules: Drag and Drop in "light" modules ================================ The data model of the **light** (not having CORBA engine) SALOME module is usually based on the custom tree of data objects. The general approach is to inherit a custom data object class from the **LightApp_DataObject** and a custom data model from the **LightApp_DataModel** class. The data model class is responsible for building the appropriate presentation of the data tree in the Object browser. Thus, the implementation of the drag and drop functionality in a **light** module (more precisely, the method **dropObjects()** as described above), consists in copying data entities (by creating new instances of the corresponding data object class) or moving existing data objects to the new position in a tree. The Object browser will update the tree representation automatically, as soon as **updateObjectBrowser()** function is called. .. _drag_drop_full_modules: Using UseCaseBuilder for Drag and Drop handling in "full" modules ================================================================= Drag and drop operation requires underlying data model to allow flexible re-arrangement of the data entities inside the data tree. In a **full** (CORBA engine based) SALOME module, which data model is usually based on the hierarchy of **SALOMEDS::SObject** entities provided by the data server functionality, re-arrangement of the data tree is not a trivial task. However, SALOME data server (**SALOMEDS**) CORBA module proposes a mechanism that can be used to customize data tree representation in a simple and flexible way - :ref:`use_case_builder`. With use case builder, the **dropObjects()** function can be easily implemented. For example: :: // GUI part: process objects dropping void MyModuleGUI::dropObjects( const DataObjectList& what, SUIT_DataObject* where, const int row, Qt::DropAction action ) { if ( action != Qt::CopyAction && action != Qt::MoveAction ) return; // unsupported action // get parent object SalomeApp_DataObject* dataObj = dynamic_cast( where ); if ( !dataObj ) return; // wrong parent _PTR(SObject) parentObj = dataObj->object(); // collect objects being dropped MYMODULE_ORB::object_list_var objects = new MYMODULE_ORB::object_list(); objects->length( what.count() ); int count = 0; for ( int i = 0; i < what.count(); i++ ) { dataObj = dynamic_cast( what[i] ); if ( !dataObj ) continue; // skip wrong objects _PTR(SObject) sobj = dataObj->object(); objects[i] = _CAST(SObject, sobj)->GetSObject(); count++; } objects->length( count ); // call engine function engine()->copyOrMove( objects.in(), // what _CAST(SObject, parentObj)->GetSObject(), // where row, // row action == Qt::CopyAction ); // isCopy // update Object browser getApp()->updateObjectBrowser( false ); } // Engine part: perform actual data copying / moving void MyModule::copyOrMove( const MYMODULE_ORB::object_list& what, SALOMEDS::SObject_ptr where, CORBA::Long row, CORBA::Boolean isCopy ) { if ( CORBA::is_nil( where ) ) return; // bad parent SALOMEDS::Study_var study = where->GetStudy(); // study SALOMEDS::StudyBuilder_var studyBuilder = study->NewBuilder(); // study builder SALOMEDS::UseCaseBuilder_var useCaseBuilder = study->GetUseCaseBuilder(); // use case builder SALOMEDS::SComponent_var father = where->GetFatherComponent(); // father component std::string dataType = father->ComponentDataType(); if ( dataType != "MYMODULE" ) return; // not a MYMODULE component SALOMEDS::SObject_var objAfter; if ( row >= 0 && useCaseBuilder->HasChildren( where ) ) { // insert at a given row -> find insertion position SALOMEDS::UseCaseIterator_var useCaseIt = useCaseBuilder->GetUseCaseIterator( where ); int i; for ( i = 0; i < row && useCaseIt->More(); i++, useCaseIt->Next() ); if ( i == row && useCaseIt->More() ) { objAfter = useCaseIt->Value(); } } // process all objects in a given list for ( int i = 0; i < what.length(); i++ ) { SALOMEDS::SObject_var sobj = what[i]; if ( CORBA::is_nil( sobj ) ) continue; // skip bad object if ( isCopy ) { // copying is performed // get the name of the object CORBA::String_var name = sobj->GetName(); // create a new object, as a child of the component object SALOMEDS::SObject_var new_sobj = studyBuilder->NewObject( father ); new_sobj->SetAttrString( "AttributeName", name.in() ); sobj = new_sobj; // ... perform other necessary data copying like // adding the corresponding attributes or creation // of servant data entities... } // insert the object or its copy to the use case tree if ( !CORBA::is_nil( objAfter ) ) useCaseBuilder->InsertBefore( sobj, objAfter ); // insert at a given row else useCaseBuilder->AppendTo( where, sobj ); // append to the end of the list } }