diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index dac3577be5..6aebb3d574 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -756,6 +756,11 @@ void TreeWidget::onItemExpanded(QTreeWidgetItem * item) if (item && item->type() == TreeWidget::ObjectType) { DocumentObjectItem* obj = static_cast(item); obj->setExpandedStatus(true); + auto it = DocumentMap.find(obj->object()->getDocument()); + if(it==DocumentMap.end()) + Base::Console().Warning("DocumentItem::onItemExpanded: cannot find object document\n"); + else + it->second->populateItem(obj); } } @@ -930,7 +935,7 @@ DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent) : QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(doc) { // Setup connections - connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, _1)); + connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, nullptr,_1)); connectDelObject = doc->signalDeletedObject.connect(boost::bind(&DocumentItem::slotDeleteObject, this, _1)); connectChgObject = doc->signalChangedObject.connect(boost::bind(&DocumentItem::slotChangeObject, this, _1)); connectRenObject = doc->signalRelabelObject.connect(boost::bind(&DocumentItem::slotRenameObject, this, _1)); @@ -956,173 +961,185 @@ DocumentItem::~DocumentItem() connectExpObject.disconnect(); } +#define FOREACH_ITEM(_item, _obj) \ + auto _it = ObjectMap.find(std::string(_obj.getObject()->getNameInDocument()));\ + if(_it == ObjectMap.end() || _it->second->empty()) return;\ + for(auto _item : *_it->second){{ + +#define FOREACH_ITEM_ALL(_item) \ + for(auto _v : ObjectMap) {\ + for(auto _item : *_v.second) { + +#define FOREACH_ITEM_NAME(_item,_name) \ + auto _it = ObjectMap.find(_name);\ + if(_it != ObjectMap.end()) {\ + for(auto _item : *_it->second) { + +#define END_FOREACH_ITEM }} + + void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v) { - std::string name (v.getObject()->getNameInDocument()); - std::map::iterator it = ObjectMap.find(name); - if (it != ObjectMap.end()) - it->second->setBackgroundColor(0,Qt::yellow); + FOREACH_ITEM(item,v) + item->setBackgroundColor(0,Qt::yellow); + END_FOREACH_ITEM } void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v) { - std::string name (v.getObject()->getNameInDocument()); - std::map::iterator it = ObjectMap.find(name); - if (it != ObjectMap.end()) { - it->second->setData(0, Qt::BackgroundColorRole,QVariant()); - } + FOREACH_ITEM(item,v) + item->setData(0, Qt::BackgroundColorRole,QVariant()); + END_FOREACH_ITEM } -void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) +void DocumentItem::slotNewObject(DocumentObjectItem *parent, + const Gui::ViewProviderDocumentObject& obj) { - if (obj.showInTree()){ - std::string displayName = obj.getObject()->Label.getValue(); - std::string objectName = obj.getObject()->getNameInDocument(); - std::map::iterator it = ObjectMap.find(objectName); - if (it == ObjectMap.end()) { - // cast to non-const object - DocumentObjectItem* item = new DocumentObjectItem( - const_cast(&obj), this); - item->setIcon(0, obj.getIcon()); - item->setText(0, QString::fromUtf8(displayName.c_str())); - ObjectMap[objectName] = item; + if (!obj.showInTree()) return; - // it may be possible that the new object claims already existing objects. If this is the - // case we need to make sure this is shown by the tree - if(!obj.claimChildren().empty()) - slotChangeObject(obj); - }else { - Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n"); - } + std::string name = obj.getObject()->getNameInDocument(); + auto &ptrs = ObjectMap[name]; + if(!ptrs) { + assert(parent==NULL); + ptrs.reset(new DocumentObjectItems); + }else if(ptrs->size() && parent==NULL) { + Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n"); + return; } + std::string displayName = obj.getObject()->Label.getValue(); + std::string objectName = obj.getObject()->getNameInDocument(); + DocumentObjectItem* item = new DocumentObjectItem( + const_cast(&obj), + parent?static_cast(parent):this, ptrs); + item->setIcon(0, obj.getIcon()); + item->setText(0, QString::fromUtf8(displayName.c_str())); + populateItem(item); } void DocumentItem::slotDeleteObject(const Gui::ViewProviderDocumentObject& view) { - App::DocumentObject* obj = view.getObject(); - std::string objectName = obj->getNameInDocument(); - std::map::iterator it = ObjectMap.find(objectName); - if (it != ObjectMap.end()) { - QTreeWidgetItem* parent = it->second->parent(); - if (it->second->childCount() > 0) { - // When removing an object check if there are multiple parents of its children - // - // this removes the children from their parent - QList children = it->second->takeChildren(); - for (QList::iterator jt = children.begin(); jt != children.end(); ++jt) { - std::vector parents = getAllParents(static_cast(*jt)); - for (std::vector::iterator kt = parents.begin(); kt != parents.end(); ++kt) { - if (*kt != it->second) { - // there is another parent object of this child - (*kt)->addChild(*jt); - break; - } - } - } + auto it = ObjectMap.find(std::string(view.getObject()->getNameInDocument())); + if(it == ObjectMap.end() || it->second->empty()) return; + auto &items = *(it->second); + for(auto cit=items.begin(),citNext=cit;cit!=items.end();cit=citNext) { + ++citNext; + delete *cit; + } + if(items.empty()) + ObjectMap.erase(it); - // if there are still children, move them to the document item (#0001905) - QList freeChildren; - for (QList::iterator jt = children.begin(); jt != children.end(); ++jt) { - if (!(*jt)->parent()) - freeChildren << *jt; - } + // Check for any child of the deleted object is not in the tree, and put it + // under document item. + const auto &children = view.claimChildren(); + for(auto child : children) { + if(!child || !pDocument->getDocument()->isIn(child)) + continue; + auto it = ObjectMap.find(std::string(child->getNameInDocument())); + if(it==ObjectMap.end() || it->second->empty()) { + ViewProvider* vp = pDocument->getViewProvider(child); + if(!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) + continue; + slotNewObject(0,static_cast(*vp)); + } + } +} - if (!freeChildren.isEmpty()) - this->addChildren(freeChildren); +void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh) { + if(item->populated && !refresh) return; + + // Lazy loading policy: We will create an item for each children object if + // a) the item is expanded, or b) there is at least one free child, i.e. + // child originally located at root. + + const auto &children = item->object()->claimChildren(); + + item->setChildIndicatorPolicy(children.empty()? + QTreeWidgetItem::DontShowIndicator:QTreeWidgetItem::ShowIndicator); + + if(!item->populated && !item->isExpanded()) { + bool doPopulate = false; + for(auto child : children) { + if(!child || !pDocument->getDocument()->isIn(child)){ + // Note: It is possible that we receive an invalid pointer from + // claimChildren(), e.g. if multiple properties were changed in + // a transaction and slotChangedObject() is triggered by one + // property being reset before the invalid pointer has been + // removed from another. Currently this happens for + // PartDesign::Body when cancelling a new feature in the dialog. + // First the new feature is deleted, then the Tip property is + // reset, but claimChildren() accesses the Model property which + // still contains the pointer to the deleted feature + continue; + } + auto it = ObjectMap.find(std::string(child->getNameInDocument())); + if(it == ObjectMap.end() || it->second->empty()) { + ViewProvider* vp = pDocument->getViewProvider(child); + if(!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) + continue; + doPopulate = true; + break; + } + if((*it->second->begin())->parent() == this) { + doPopulate = true; + break; + } + } + if(!doPopulate) return; + } + item->populated = true; + + auto oldItems = item->takeChildren(); + for(auto child : children) { + if(!child || !pDocument->getDocument()->isIn(child)) + continue; + + bool found = false; + for(auto it=oldItems.begin(),itNext=it;it!=oldItems.end();it=itNext) { + ++itNext; + DocumentObjectItem *childItem = static_cast(*it); + if(childItem->object()->getObject() != child) continue; + found = true; + oldItems.erase(it); + item->addChild(childItem); + break; + } + if(found) continue; + + const char* name = child->getNameInDocument(); + if (!name) { + Base::Console().Warning("Gui::DocumentItem::populate(): Cannot reparent unknown object.\n"); + continue; } - parent->takeChild(parent->indexOfChild(it->second)); - delete it->second; - ObjectMap.erase(it); + // This algo will be recursively applied to newly created child items + // through slotNewObject -> populateItem + + auto it = ObjectMap.find(name); + if(it==ObjectMap.end() || it->second->empty()) { + ViewProvider* vp = pDocument->getViewProvider(child); + if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) + slotNewObject(item,static_cast(*vp)); + continue; + } + DocumentObjectItem *childItem = *it->second->begin(); + if(childItem->parent() != this) + slotNewObject(item,*childItem->object()); + else { + this->removeChild(childItem); + item->addChild(childItem); + } } + for(auto childItem : oldItems) + delete childItem; } void DocumentItem::slotChangeObject(const Gui::ViewProviderDocumentObject& view) { - // As we immediately add a newly created object to the tree we check here which - // item (this or a DocumentObjectItem) is the parent of the associated item of 'view' - App::DocumentObject* obj = view.getObject(); - std::string objectName = obj->getNameInDocument(); - std::map::iterator it = ObjectMap.find(objectName); - if (it != ObjectMap.end()) { - // use new grouping style - DocumentObjectItem* parent_of_group = it->second; - std::set children; - std::vector group = view.claimChildren(); - int group_index = 0; // counter of children inserted to the tree - for (std::vector::iterator jt = group.begin(); jt != group.end(); ++jt) { - if (*jt) { - if (view.getObject()->getDocument()->isIn(*jt)){ - // Note: It is possible that we receive an invalid pointer from claimChildren(), e.g. if multiple properties - // were changed in a transaction and slotChangedObject() is triggered by one property being reset - // before the invalid pointer has been removed from another. Currently this happens for PartDesign::Body - // when cancelling a new feature in the dialog. First the new feature is deleted, then the Tip property is - // reset, but claimChildren() accesses the Model property which still contains the pointer to the deleted feature - const char* internalName = (*jt)->getNameInDocument(); - if (internalName) { - std::map::iterator kt = ObjectMap.find(internalName); - if (kt != ObjectMap.end()) { - DocumentObjectItem* child_of_group = kt->second; - children.insert(child_of_group); - QTreeWidgetItem* parent_of_child = child_of_group->parent(); - - if (parent_of_child) { - if (parent_of_child != parent_of_group) { - if (parent_of_group != child_of_group) { - // This child's parent must be adjusted - parent_of_child->removeChild(child_of_group); - // Insert the child at the correct position according to the order of the children returned - // by claimChildren - if (group_index <= parent_of_group->childCount()) - parent_of_group->insertChild(group_index, child_of_group); - else - parent_of_group->addChild(child_of_group); - group_index++; - } else { - Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Object references to itself.\n"); - } - } else { - // The child already in the right group, but we may need to ajust it's index to follow the order of claimChildren - int index=parent_of_group->indexOfChild (child_of_group); - if (index>group_index) { - parent_of_group->takeChild (index); - parent_of_group->insertChild (group_index, child_of_group); - } - group_index++; - } - } else { - Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): " - "'%s' claimed a top level object '%s' to be it's child.\n", objectName.c_str(), internalName); - } - } - } - else { - Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Cannot reparent unknown object.\n"); - } - } - else { - Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Group references unknown object.\n"); - } - } // empty PropertyLink - } - - // move all children which are not part of the group anymore to this item - int count = parent_of_group->childCount(); - for (int i=0; i < count; i++) { - QTreeWidgetItem* child = parent_of_group->child(i); - if (children.find(child) == children.end()) { - parent_of_group->takeChild(i); - this->addChild(child); - } - } - - // set the text label - std::string displayName = obj->Label.getValue(); - parent_of_group->setText(0, QString::fromUtf8(displayName.c_str())); - } - else { - Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Cannot change unknown object.\n"); - } + QString displayName = QString::fromUtf8(view.getObject()->Label.getValue()); + FOREACH_ITEM(item,view) + item->setText(0, displayName); + populateItem(item,true); + END_FOREACH_ITEM } void DocumentItem::slotRenameObject(const Gui::ViewProviderDocumentObject& obj) @@ -1134,75 +1151,70 @@ void DocumentItem::slotRenameObject(const Gui::ViewProviderDocumentObject& obj) void DocumentItem::slotActiveObject(const Gui::ViewProviderDocumentObject& obj) { std::string objectName = obj.getObject()->getNameInDocument(); - std::map::iterator jt = ObjectMap.find(objectName); - if (jt == ObjectMap.end()) + if(ObjectMap.find(objectName) == ObjectMap.end()) return; // signal is emitted before the item gets created - for (std::map::iterator it = ObjectMap.begin(); - it != ObjectMap.end(); ++it) - { - QFont f = it->second->font(0); - f.setBold(it == jt); - it->second->setFont(0,f); + for(auto v : ObjectMap) { + for(auto item : *v.second) { + QFont f = item->font(0); + f.setBold(item->object() == &obj); + item->setFont(0,f); + } } } void DocumentItem::slotHighlightObject (const Gui::ViewProviderDocumentObject& obj,const Gui::HighlightMode& high,bool set) { - std::string objectName = obj.getObject()->getNameInDocument(); - std::map::iterator jt = ObjectMap.find(objectName); - if (jt == ObjectMap.end()) - return; // signal is emitted before the item gets created + FOREACH_ITEM(item,obj) + QFont f = item->font(0); + switch (high) { + case Gui::Bold: f.setBold(set); break; + case Gui::Italic: f.setItalic(set); break; + case Gui::Underlined: f.setUnderline(set); break; + case Gui::Overlined: f.setOverline(set); break; + case Gui::Blue: + if(set) + item->setBackgroundColor(0,QColor(200,200,255)); + else + item->setData(0, Qt::BackgroundColorRole,QVariant()); + break; + case Gui::LightBlue: + if(set) + item->setBackgroundColor(0,QColor(230,230,255)); + else + item->setData(0, Qt::BackgroundColorRole,QVariant()); + break; + default: + break; + } - QFont f = jt->second->font(0); - switch (high) { - case Gui::Bold: f.setBold(set); break; - case Gui::Italic: f.setItalic(set); break; - case Gui::Underlined: f.setUnderline(set); break; - case Gui::Overlined: f.setOverline(set); break; - case Gui::Blue: - if(set) - jt->second->setBackgroundColor(0,QColor(200,200,255)); - else - jt->second->setData(0, Qt::BackgroundColorRole,QVariant()); - break; - case Gui::LightBlue: - if(set) - jt->second->setBackgroundColor(0,QColor(230,230,255)); - else - jt->second->setData(0, Qt::BackgroundColorRole,QVariant()); - break; - default: - break; - } - - jt->second->setFont(0,f); + item->setFont(0,f); + END_FOREACH_ITEM } void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj,const Gui::TreeItemMode& mode) { - std::string objectName = obj.getObject()->getNameInDocument(); - std::map::iterator jt = ObjectMap.find(objectName); - if (jt == ObjectMap.end()) - return; // signal is emitted before the item gets created + FOREACH_ITEM(item,obj) + if(!item->parent()->isExpanded()) continue; + switch (mode) { + case Gui::Expand: + item->setExpanded(true); + break; + case Gui::Collapse: + item->setExpanded(false); + break; + case Gui::Toggle: + if (item->isExpanded()) + item->setExpanded(false); + else + item->setExpanded(true); + break; - switch (mode) { - case Gui::Expand: - jt->second->setExpanded(true); - break; - case Gui::Collapse: - jt->second->setExpanded(false); - break; - case Gui::Toggle: - if (jt->second->isExpanded()) - jt->second->setExpanded(false); - else - jt->second->setExpanded(true); - break; - - default: - // not defined enum - assert(0); - } + default: + // not defined enum + assert(0); + } + populateItem(item); + END_FOREACH_ITEM } const Gui::Document* DocumentItem::document() const @@ -1242,9 +1254,9 @@ const Gui::Document* DocumentItem::document() const void DocumentItem::testStatus(void) { - for (std::map::iterator pos = ObjectMap.begin();pos!=ObjectMap.end();++pos) { - pos->second->testStatus(); - } + FOREACH_ITEM_ALL(item); + item->testStatus(); + END_FOREACH_ITEM; } void DocumentItem::setData (int column, int role, const QVariant & value) @@ -1260,41 +1272,37 @@ void DocumentItem::setData (int column, int role, const QVariant & value) void DocumentItem::setObjectHighlighted(const char* name, bool select) { Q_UNUSED(select); - std::map::iterator pos; - pos = ObjectMap.find(name); - if (pos != ObjectMap.end()) { + Q_UNUSED(name); + // FOREACH_ITEM_NAME(item,name); //pos->second->setData(0, Qt::TextColorRole, QVariant(Qt::red)); //treeWidget()->setItemSelected(pos->second, select); - } + // END_FOREACH_ITEM; } void DocumentItem::setObjectSelected(const char* name, bool select) { - std::map::iterator pos; - pos = ObjectMap.find(name); - if (pos != ObjectMap.end()) { - treeWidget()->setItemSelected(pos->second, select); - } + FOREACH_ITEM_NAME(item,name); + treeWidget()->setItemSelected(item, select); + END_FOREACH_ITEM; } void DocumentItem::clearSelection(void) { // Block signals here otherwise we get a recursion and quadratic runtime bool ok = treeWidget()->blockSignals(true); - for (std::map::iterator pos = ObjectMap.begin();pos!=ObjectMap.end();++pos) { - pos->second->setSelected(false); - } + FOREACH_ITEM_ALL(item); + item->setSelected(false); + END_FOREACH_ITEM; treeWidget()->blockSignals(ok); } void DocumentItem::updateSelection(void) { std::vector sel; - for (std::map::iterator pos = ObjectMap.begin();pos!=ObjectMap.end();++pos) { - if (treeWidget()->isItemSelected(pos->second)) { - sel.push_back(pos->second->object()->getObject()); - } - } + FOREACH_ITEM_ALL(item); + if (treeWidget()->isItemSelected(item)) + sel.push_back(item->object()->getObject()); + END_FOREACH_ITEM; Gui::Selection().setSelection(pDocument->getDocument()->getName(), sel); } @@ -1324,9 +1332,9 @@ void DocumentItem::selectItems(void) // get an array of all tree items of the document and sort it in ascending order // with regard to their document object std::vector items; - for (std::map::iterator it = ObjectMap.begin(); it != ObjectMap.end(); ++it) { - items.push_back(it->second); - } + FOREACH_ITEM_ALL(item); + items.push_back(item); + END_FOREACH_ITEM; std::sort(items.begin(), items.end(), ObjectItem_Less()); // get and sort all selected document objects of the given document @@ -1368,47 +1376,28 @@ void DocumentItem::selectItems(void) static_cast(treeWidget())->setItemsSelected(deselitems, false); } -std::vector DocumentItem::getAllParents(DocumentObjectItem* item) const -{ - std::vector parents; - App::DocumentObject* obj = item->object()->getObject(); - std::vector inlist = obj->getInList(); - - for (std::vector::iterator it = inlist.begin(); it != inlist.end(); ++it) { - Gui::ViewProvider* vp = pDocument->getViewProvider(*it); - if (!vp) - continue; - std::vector child = vp->claimChildren(); - for (std::vector::iterator jt = child.begin(); jt != child.end(); ++jt) { - if (*jt == obj) { - std::map::const_iterator kt; - kt = ObjectMap.find((*it)->getNameInDocument()); - if (kt != ObjectMap.end()) { - parents.push_back(kt->second); - } - break; - } - } - } - - return parents; -} - // ---------------------------------------------------------------------------- DocumentObjectItem::DocumentObjectItem(Gui::ViewProviderDocumentObject* pcViewProvider, - QTreeWidgetItem* parent) + QTreeWidgetItem* parent, DocumentObjectItemsPtr selves) : QTreeWidgetItem(parent, TreeWidget::ObjectType), previousStatus(-1), viewObject(pcViewProvider) + , myselves(selves), populated(false) { setFlags(flags()|Qt::ItemIsEditable); // Setup connections connectIcon = pcViewProvider->signalChangeIcon.connect(boost::bind(&DocumentObjectItem::slotChangeIcon, this)); connectTool = pcViewProvider->signalChangeToolTip.connect(boost::bind(&DocumentObjectItem::slotChangeToolTip, this, _1)); connectStat = pcViewProvider->signalChangeStatusTip.connect(boost::bind(&DocumentObjectItem::slotChangeStatusTip, this, _1)); + myselves->insert(this); } DocumentObjectItem::~DocumentObjectItem() { + auto it = myselves->find(this); + if(it == myselves->end()) + assert(0); + else + myselves->erase(it); connectIcon.disconnect(); connectTool.disconnect(); connectStat.disconnect(); diff --git a/src/Gui/Tree.h b/src/Gui/Tree.h index 9bd17d97d5..d761a5f63b 100644 --- a/src/Gui/Tree.h +++ b/src/Gui/Tree.h @@ -37,6 +37,8 @@ namespace Gui { class ViewProviderDocumentObject; class DocumentObjectItem; +typedef std::set DocumentObjectItems; +typedef std::shared_ptr DocumentObjectItemsPtr; class DocumentItem; /// highlight modes for the tree items @@ -154,12 +156,13 @@ public: void selectItems(void); void testStatus(void); void setData(int column, int role, const QVariant & value); + void populateItem(DocumentObjectItem *item, bool refresh = false); protected: /** Adds a view provider to the document item. * If this view provider is already added nothing happens. */ - void slotNewObject(const Gui::ViewProviderDocumentObject&); + void slotNewObject(DocumentObjectItem *parent, const Gui::ViewProviderDocumentObject&); /** Removes a view provider from the document item. * If this view provider is not added nothing happens. */ @@ -171,11 +174,10 @@ protected: void slotResetEdit (const Gui::ViewProviderDocumentObject&); void slotHighlightObject (const Gui::ViewProviderDocumentObject&,const Gui::HighlightMode&,bool); void slotExpandObject (const Gui::ViewProviderDocumentObject&,const Gui::TreeItemMode&); - std::vector getAllParents(DocumentObjectItem*) const; - + private: const Gui::Document* pDocument; - std::map ObjectMap; + std::map ObjectMap; typedef boost::BOOST_SIGNALS_NAMESPACE::connection Connection; Connection connectNewObject; @@ -197,7 +199,8 @@ private: class DocumentObjectItem : public QTreeWidgetItem { public: - DocumentObjectItem(Gui::ViewProviderDocumentObject* pcViewProvider, QTreeWidgetItem * parent); + DocumentObjectItem(Gui::ViewProviderDocumentObject* pcViewProvider, + QTreeWidgetItem * parent, DocumentObjectItemsPtr selves); ~DocumentObjectItem(); Gui::ViewProviderDocumentObject* object() const; @@ -220,7 +223,11 @@ private: Connection connectTool; Connection connectStat; + DocumentObjectItemsPtr myselves; + bool populated; + friend class TreeWidget; + friend class DocumentItem; }; /**