Salome HOME
Updated copyright comment
[modules/shaper.git] / src / XGUI / XGUI_DataModel.cpp
index a574c035332be147695d3b3f949daf78e229125a..16b4997ff20e4d244250620147419016f4d0ee97 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2017  CEA/DEN, EDF R&D
+// Copyright (C) 2014-2024  CEA, EDF
 //
 // This library is free software; you can redistribute it and/or
 // modify it under the terms of the GNU Lesser General Public
 //
 // You should have received a copy of the GNU Lesser General Public
 // License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 //
-// See http://www.salome-platform.org/ or
-// email : webmaster.salome@opencascade.com<mailto:webmaster.salome@opencascade.com>
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
 //
 
 #include "XGUI_DataModel.h"
 #include "XGUI_ObjectsBrowser.h"
+#include "XGUI_Workshop.h"
 
 #include <ModuleBase_IconFactory.h>
 #include <ModuleBase_ITreeNode.h>
 
 #include <ModelAPI_Session.h>
 #include <ModelAPI_ResultField.h>
+#include <ModelAPI_Tools.h>
+#include <ModelAPI_CompositeFeature.h>
 
 #include <Config_FeatureMessage.h>
 
 #include <Events_Loop.h>
 
+#include <QMimeData>
+#include <QMessageBox>
+
 #include <cassert>
 
+#ifdef _MSC_VER
+#pragma warning(disable: 4100)
+#endif
 
+static bool isValidNode(const ModuleBase_ITreeNode* theNode)
+{
+  ModuleBase_ITreeNode* aParent = 0;
+  try {
+    aParent = theNode->parent();
+  }
+  catch (...) {
+    return false;
+  }
+  if (aParent)
+    return isValidNode(aParent);
+  return true;
+}
 
 // Constructor *************************************************
 XGUI_DataModel::XGUI_DataModel(QObject* theParent) : QAbstractItemModel(theParent)//,
@@ -68,18 +89,31 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
     for (aIt = aObjects.cbegin(); aIt != aObjects.cend(); aIt++) {
       if ((*aIt)->isInHistory())
         aCreated.append(*aIt);
+      if ((*aIt)->groupName() == ModelAPI_ResultPart::group()) {
+        emit beforeTreeRebuild();
+        myRoot->update();
+        rebuildDataTree();
+        emit treeRebuilt();
+        return;
+      }
     }
+    if (aCreated.length() == 0)
+      return;
+
+    emit beforeTreeRebuild();
     QTreeNodesList aNodes = myRoot->objectCreated(aCreated);
     ModuleBase_ITreeNode* aParent;
     int aRow = 0;
     QModelIndex aParentIndex1, aParentIndex2;
     ObjectPtr aObj;
+    bool aRebuildAll = false;
+
     foreach(ModuleBase_ITreeNode* aNode, aNodes) {
       aObj = aNode->object();
       aParent = aNode->parent();
       if (aObj.get() && (aObj->groupName() == ModelAPI_Folder::group())) {
         aParent->update();
-        rebuildDataTree();
+        aRebuildAll = true;
       }
       else {
         aRow = aParent->nodeRow(aNode);
@@ -89,6 +123,10 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
         dataChanged(aParentIndex1, aParentIndex2);
       }
     }
+    if (aRebuildAll)
+      rebuildDataTree();
+
+    emit treeRebuilt();
   }
   else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_DELETED)) {
     std::shared_ptr<ModelAPI_ObjectDeletedMessage> aUpdMsg =
@@ -97,6 +135,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
       aUpdMsg->groups();
     QTreeNodesList aList;
     std::list<std::pair<std::shared_ptr<ModelAPI_Document>, std::string>>::const_iterator aIt;
+    emit beforeTreeRebuild();
     for (aIt = aMsgGroups.cbegin(); aIt != aMsgGroups.cend(); aIt++) {
       aList.append(myRoot->objectsDeleted(aIt->first, aIt->second.c_str()));
     }
@@ -112,6 +151,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
         aNode->parent()->update();
     }
     rebuildDataTree();
+    emit treeRebuilt();
   }
   else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_UPDATED)) {
     std::shared_ptr<ModelAPI_ObjectUpdatedMessage> aUpdMsg =
@@ -121,6 +161,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
     QObjectPtrList aCreated;
     std::set<ObjectPtr>::const_iterator aIt;
     bool aRebuildAll = false;
+    emit beforeTreeRebuild();
     for (aIt = aObjects.cbegin(); aIt != aObjects.cend(); aIt++) {
       ObjectPtr aObj = (*aIt);
       if (!aObj->isInHistory())
@@ -136,7 +177,6 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
     }
     if (aRebuildAll) {
       myRoot->update();
-      rebuildDataTree();
     }
     else {
       QSet<ModuleBase_ITreeNode*> aParents;
@@ -159,8 +199,9 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
       foreach(ModuleBase_ITreeNode* aNode, aParents) {
         aNode->update();
       }
-      rebuildDataTree();
     }
+    rebuildDataTree();
+    emit treeRebuilt();
   }
   else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_ORDER_UPDATED)) {
     std::shared_ptr<ModelAPI_OrderUpdatedMessage> aUpdMsg =
@@ -170,16 +211,20 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
       std::string aGroup = aUpdMsg->reordered()->group();
       ModuleBase_ITreeNode* aNode = myRoot->findParent(aDoc, aGroup.c_str());
       if (aNode) {
+        emit beforeTreeRebuild();
         aNode->update();
         rebuildDataTree();
+        emit treeRebuilt();
       }
     }
   }
   else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_DOCUMENT_CHANGED)) {
-    DocumentPtr aDoc = ModelAPI_Session::get()->activeDocument();
-    ModuleBase_ITreeNode* aRoot = myRoot->findRoot(aDoc);
-    if (aRoot) {
-      updateSubTree(aRoot);
+    if (ModelAPI_Session::get()->hasModuleDocument()) {
+      DocumentPtr aDoc = ModelAPI_Session::get()->activeDocument();
+      ModuleBase_ITreeNode* aRoot = myRoot->findRoot(aDoc);
+      if (aRoot) {
+        updateSubTree(aRoot);
+      }
     }
   }
   else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_TO_REDISPLAY)) {
@@ -189,13 +234,15 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
 
     QObjectPtrList aCreated;
     std::set<ObjectPtr>::const_iterator aIt;
-    bool aRebuildAll = false;
     for (aIt = aObjects.cbegin(); aIt != aObjects.cend(); aIt++) {
       ObjectPtr aObj = (*aIt);
       if (aObj->groupName() == ModelAPI_ResultField::group()) {
         aCreated.append(aObj);
       }
     }
+    if (aCreated.length() == 0)
+      return;
+    emit beforeTreeRebuild();
     foreach(ObjectPtr aObj, aCreated) {
       ModuleBase_ITreeNode* aNode = myRoot->subNode(aObj);
       if (aNode) {
@@ -218,6 +265,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
         dataChanged(aFirstIdx, aLastIdx);
       }
     }
+    emit treeRebuilt();
   }
 }
 
@@ -233,7 +281,6 @@ void XGUI_DataModel::rebuildDataTree()
 {
   beginResetModel();
   endResetModel();
-  emit treeRebuilt();
 }
 
 //******************************************************
@@ -334,12 +381,165 @@ bool XGUI_DataModel::removeRows(int theRow, int theCount, const QModelIndex& the
 Qt::ItemFlags XGUI_DataModel::flags(const QModelIndex& theIndex) const
 {
   if (theIndex.isValid()) {
-    ModuleBase_ITreeNode* aNode = (ModuleBase_ITreeNode*)theIndex.internalPointer();
-    return aNode->flags(theIndex.column());
+    ModuleBase_ITreeNode* aNode = static_cast<ModuleBase_ITreeNode*>(theIndex.internalPointer());
+    // Check that the pointer is Valid
+    if (!isValidNode(aNode))
+      return Qt::NoItemFlags;
+    Qt::ItemFlags aResultFlags = aNode->flags(theIndex.column());
+    // Drag and drop of Part features only if:
+    // - PartSet is active
+    // - active Part feature of PartSet is dragged
+    // - finally if it does not break dependencies between features (but here only drag possibility is checked)
+    SessionPtr aSession = ModelAPI_Session::get();
+    if (aSession->hasModuleDocument() && aSession->moduleDocument() == aSession->activeDocument()) {
+
+      ObjectPtr aNodeObj = aNode->object();
+      if (aNodeObj.get() && aNodeObj->groupName() == ModelAPI_Feature::group())
+      {
+        FeaturePtr aNodeFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aNodeObj);
+        if (aNodeFeature.get() && aNodeFeature->getKind() == "Part" && !aNodeFeature->isDisabled())
+          aResultFlags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
+      }
+    }
+    return aResultFlags;
+  }
+  return Qt::ItemIsDropEnabled | Qt::ItemFlags();
+}
+
+bool XGUI_DataModel::canDropMimeData(const QMimeData *theData, Qt::DropAction theAction,
+  int theRow, int theColumn, const QModelIndex &theParent) const
+{
+  if (theParent.isValid())
+    return false;
+  ModuleBase_ITreeNode* aSubNode = myRoot->subNode(theRow);
+  if ((aSubNode && aSubNode->object() && aSubNode->object()->groupName() == ModelAPI_Feature::group())
+      || theRow == myRoot->childrenCount()) // into the end of a list of features
+  {
+    return true;
+  }
+
+  return false; // in other cases drop is forbidden
+}
+
+QMimeData* XGUI_DataModel::mimeData(const QModelIndexList& theIndexes) const
+{
+  std::set<int> aRows; // to avoid duplication of rows and for sorting the indices
+  foreach (QModelIndex anIndex, theIndexes) {
+    if (anIndex.isValid() && anIndex.internalPointer())
+      aRows.insert(anIndex.row());
   }
-  return Qt::ItemFlags();
+  QByteArray anEncodedData;
+  QDataStream aStream(&anEncodedData, QIODevice::WriteOnly);
+  for(std::set<int>::iterator aRIter = aRows.begin(); aRIter != aRows.end(); aRIter++)
+    aStream << *aRIter;
+
+  QMimeData* aMimeData = new QMimeData();
+  aMimeData->setData("xgui/moved.rows", anEncodedData);
+  return aMimeData;
 }
 
+bool XGUI_DataModel::dropMimeData(const QMimeData *theData, Qt::DropAction theAction,
+  int theRow, int theColumn, const QModelIndex &theParent)
+{
+  FeaturePtr aDropAfter; // after this feature it is dropped, NULL if drop the the first place
+  if (theRow > 0)
+  {
+    ModuleBase_ITreeNode* aNode = myRoot->subNode(theRow - 1);
+    if (aNode && aNode->object() && aNode->object()->groupName() == ModelAPI_Feature::group())
+      aDropAfter = std::dynamic_pointer_cast<ModelAPI_Feature>(aNode->object());
+  }
+  SessionPtr aSession = ModelAPI_Session::get();
+  if (aDropAfter.get()) // move to the upper enabled feature
+  {
+    while (aDropAfter.get() && (aDropAfter->isDisabled() || !aDropAfter->isInHistory()))
+      aDropAfter = aDropAfter->document()->nextFeature(aDropAfter, true);
+  }
+  else { // move after invisible items, not the first (which is coordinate system by default)
+    std::list<FeaturePtr> allFeatures = aSession->get()->moduleDocument()->allFeatures();
+    std::list<FeaturePtr>::iterator aFeature = allFeatures.begin();
+    for(; aFeature != allFeatures.end(); aFeature++)
+    {
+      if ((*aFeature)->isInHistory())
+        break;
+      aDropAfter = *aFeature;
+    }
+  }
+  // move after the composite feature memebers, if they are invisible (sub elements of sketch)
+  CompositeFeaturePtr aComposite = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aDropAfter);
+  if (aComposite.get())
+  {
+    FeaturePtr aNext = aDropAfter->document()->nextFeature(aDropAfter);
+    while (aNext.get() && !aNext->isInHistory() && aComposite->isSub(aNext)) {
+      aDropAfter = aNext;
+      aNext = aDropAfter->document()->nextFeature(aNext);
+    }
+  }
+
+  QByteArray anEncodedData = theData->data("xgui/moved.rows");
+  if (anEncodedData.isEmpty())
+    return false; // dropped something alien, decline
+
+  QDataStream stream(&anEncodedData, QIODevice::ReadOnly);
+  std::list<FeaturePtr> aDropped;
+  while (!stream.atEnd()) {
+    int aRow;
+    stream >> aRow;
+    ModuleBase_ITreeNode* aNode = myRoot->subNode(aRow);
+    if (aNode)
+    {
+      FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aNode->object());
+      // feature moved after itself is not moved, add only Part feature, other skip
+      if (aFeature.get() && aFeature != aDropAfter && aFeature->getKind() == "Part")
+        aDropped.push_back(aFeature);
+    }
+  }
+  if (aDropped.empty()) // nothing to move
+    return false;
+
+  // check for the movement is valid due to existing dependencies
+  std::wstring anErrorStr = ModelAPI_Tools::validateMovement(aDropAfter, aDropped);
+  if (!anErrorStr.empty())
+  {
+    QMessageBox aMessageBox;
+    aMessageBox.setWindowTitle(QObject::tr("Move part"));
+    aMessageBox.setIcon(QMessageBox::Warning);
+    aMessageBox.setStandardButtons(QMessageBox::Ok);
+    aMessageBox.setDefaultButton(QMessageBox::Ok);
+    QString aMessageText(QObject::tr("Part(s) cannot be moved because of breaking dependencies."));
+    aMessageBox.setText(aMessageText);
+    aMessageBox.setDetailedText(QString::fromStdWString(anErrorStr));
+    aMessageBox.exec();
+    return false;
+  }
+
+  if (aSession->isOperation())
+  {
+    QMessageBox aMessageBox;
+    aMessageBox.setWindowTitle(QObject::tr("Move part"));
+    aMessageBox.setIcon(QMessageBox::Warning);
+    aMessageBox.setStandardButtons(QMessageBox::Ok);
+    aMessageBox.setDefaultButton(QMessageBox::Ok);
+    QString aMessageText(QObject::tr("Cannot move part(s) during another operation."));
+    aMessageBox.setText(aMessageText);
+    aMessageBox.exec();
+    return false;
+  }
+
+  aSession->startOperation("Move Part");
+  DocumentPtr aPartSet = aSession->moduleDocument();
+  for (std::list<FeaturePtr>::iterator aDrop = aDropped.begin(); aDrop != aDropped.end(); aDrop++)
+  {
+    aPartSet->moveFeature(*aDrop, aDropAfter);
+    aDropAfter = *aDrop;
+  }
+  aSession->finishOperation();
+
+  updateSubTree(myRoot);
+  myWorkshop->updateHistory();
+
+  // returns false in any case to avoid calling removeRows after it,
+  return false; // because number of rows stays the same
+}
 
 //******************************************************
 QModelIndex XGUI_DataModel::documentRootIndex(DocumentPtr theDoc, int theColumn) const
@@ -419,3 +619,10 @@ DocumentPtr XGUI_DataModel::document(const QModelIndex& theIndex) const
   ModuleBase_ITreeNode* aNode = (ModuleBase_ITreeNode*)theIndex.internalPointer();
   return aNode->document();
 }
+
+
+//******************************************************
+bool XGUI_DataModel::hasNode(ModuleBase_ITreeNode* theNode) const
+{
+  return myRoot->hasSubNode(theNode);
+}