/*************************************************************************** * Copyright (c) 2014 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #endif #include #include #include #include #include #include #include #include "DlgPropertyLink.h" #include "ui_DlgPropertyLink.h" #include "Application.h" #include "Document.h" #include "BitmapFactory.h" #include "PropertyView.h" #include "Selection.h" #include "Tree.h" #include "TreeParams.h" #include "View3DInventor.h" #include "ViewProviderDocumentObject.h" using namespace Gui::Dialog; class ItemDelegate: public QStyledItemDelegate { public: explicit ItemDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override { if (index.column() != 1) { return nullptr; } return QStyledItemDelegate::createEditor(parent, option, index); } }; /* TRANSLATOR Gui::Dialog::DlgPropertyLink */ DlgPropertyLink::DlgPropertyLink(QWidget* parent) : QDialog(parent) , SelectionObserver(false, ResolveMode::NoResolve) , ui(new Ui_DlgPropertyLink) { // clang-format off ui->setupUi(this); connect(ui->checkObjectType, &QCheckBox::toggled, this, &DlgPropertyLink::onObjectTypeToggled); connect(ui->typeTree, &QTreeWidget::itemSelectionChanged, this, &DlgPropertyLink::onTypeTreeItemSelectionChanged); connect(ui->searchBox, &ExpressionLineEdit::textChanged, this, &DlgPropertyLink::onSearchBoxTextChanged); ui->typeTree->hide(); ui->searchBox->installEventFilter(this); ui->searchBox->setNoProperty(true); ui->searchBox->setExactMatch(Gui::ExpressionParameter::instance()->isExactMatch()); timer = new QTimer(this); timer->setSingleShot(true); connect(timer, &QTimer::timeout, this, &DlgPropertyLink::onTimer); ui->treeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); ui->treeWidget->setItemDelegate(new ItemDelegate(this)); ui->treeWidget->setMouseTracking(true); connect(ui->treeWidget, &QTreeWidget::itemEntered, this, &DlgPropertyLink::onItemEntered); connect(ui->treeWidget, &QTreeWidget::itemExpanded, this, &DlgPropertyLink::onItemExpanded); connect(ui->treeWidget, &QTreeWidget::itemSelectionChanged, this, &DlgPropertyLink::onItemSelectionChanged); connect(ui->searchBox, &QLineEdit::returnPressed, this, &DlgPropertyLink::onItemSearch); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &DlgPropertyLink::onClicked); refreshButton = ui->buttonBox->addButton(tr("Reset"), QDialogButtonBox::ActionRole); resetButton = ui->buttonBox->addButton(tr("Clear"), QDialogButtonBox::ResetRole); // clang-format on } /** * Destroys the object and frees any allocated resources */ DlgPropertyLink::~DlgPropertyLink() { detachObserver(); // no need to delete child widgets, Qt does it all for us delete ui; } QList DlgPropertyLink::getLinksFromProperty(const App::PropertyLinkBase* prop) { QList res; if (!prop) { return res; } std::vector objs; std::vector subs; prop->getLinks(objs, true, &subs, false); if (subs.empty()) { for (auto obj : objs) { res.push_back(App::SubObjectT(obj, nullptr)); } } else if (objs.size() == 1) { for (auto& sub : subs) { res.push_back(App::SubObjectT(objs.front(), sub.c_str())); } } else { int i = 0; for (auto obj : objs) { res.push_back(App::SubObjectT(obj, subs[i++].c_str())); } } return res; } QString DlgPropertyLink::formatObject(App::Document* ownerDoc, App::DocumentObject* obj, const char* sub) { if (!obj || !obj->isAttachedToDocument()) { return QLatin1String("?"); } const char* objName = obj->getNameInDocument(); std::string _objName; if (ownerDoc && ownerDoc != obj->getDocument()) { _objName = obj->getFullName(); objName = _objName.c_str(); } if (!sub || !sub[0]) { if (obj->Label.getStrValue() == obj->getNameInDocument()) { return QLatin1String(objName); } return QString::fromLatin1("%1 (%2)").arg(QLatin1String(objName), QString::fromUtf8(obj->Label.getValue())); } auto sobj = obj->getSubObject(sub); if (!sobj || sobj->Label.getStrValue() == sobj->getNameInDocument()) { return QString::fromLatin1("%1.%2").arg(QLatin1String(objName), QString::fromUtf8(sub)); } return QString::fromLatin1("%1.%2 (%3)") .arg(QLatin1String(objName), QString::fromUtf8(sub), QString::fromUtf8(sobj->Label.getValue())); } static inline bool isLinkSub(const QList& links) { for (const auto& link : links) { if (&link == &links.front()) { continue; } if (link.getDocumentName() != links.front().getDocumentName() || link.getObjectName() != links.front().getObjectName()) { return false; } } return true; } QString DlgPropertyLink::formatLinks(App::Document* ownerDoc, QList links) { if (!ownerDoc || links.empty()) { return {}; } auto obj = links.front().getObject(); if (!obj) { return QLatin1String("?"); } if (links.size() == 1 && links.front().getSubName().empty()) { return formatObject(ownerDoc, links.front()); } QStringList list; if (isLinkSub(links)) { int i = 0; for (auto& link : links) { list << QString::fromUtf8(link.getSubName().c_str()); if (++i >= 3) { break; } } return QString::fromLatin1("%1 [%2%3]") .arg(formatObject(ownerDoc, obj, nullptr), list.join(QLatin1String(", ")), QLatin1String(links.size() > 3 ? " ..." : "")); } int i = 0; for (auto& link : links) { list << formatObject(ownerDoc, link); if (++i >= 3) { break; } } return QString::fromLatin1("[%1%2]").arg(list.join(QLatin1String(", ")), QLatin1String(links.size() > 3 ? " ..." : "")); } void DlgPropertyLink::init(const App::DocumentObjectT& prop, bool tryFilter) { ui->treeWidget->blockSignals(true); ui->treeWidget->clear(); ui->treeWidget->blockSignals(false); ui->typeTree->blockSignals(true); ui->typeTree->clear(); ui->typeTree->blockSignals(false); oldLinks.clear(); docItems.clear(); typeItems.clear(); itemMap.clear(); inList.clear(); selectedTypes.clear(); currentObj = nullptr; searchItem = nullptr; subSelections.clear(); selections.clear(); objProp = prop; auto owner = objProp.getObject(); if (!owner || !owner->isAttachedToDocument()) { return; } ui->searchBox->setDocumentObject(owner); auto propLink = Base::freecad_dynamic_cast(objProp.getProperty()); if (!propLink) { return; } oldLinks = getLinksFromProperty(propLink); if (propLink->getScope() != App::LinkScope::Hidden) { // populate inList to filter out any objects that contains the owner object // of the editing link property inList = owner->getInListEx(true); inList.insert(owner); } std::vector docs; singleSelect = false; if (propLink->isDerivedFrom(App::PropertyXLinkSub::getClassTypeId()) || propLink->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) { allowSubObject = true; singleParent = true; } else if (propLink->isDerivedFrom(App::PropertyLink::getClassTypeId())) { singleSelect = true; } else if (propLink->isDerivedFrom(App::PropertyLinkSubList::getClassTypeId())) { allowSubObject = true; } if (App::PropertyXLink::supportXLink(propLink)) { allowSubObject = true; docs = App::GetApplication().getDocuments(); } else { docs.push_back(owner->getDocument()); } bool isLinkList = false; if (propLink->isDerivedFrom(App::PropertyXLinkList::getClassTypeId()) || propLink->isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { isLinkList = true; allowSubObject = false; } if (singleSelect) { singleParent = true; ui->treeWidget->setSelectionMode(QAbstractItemView::SingleSelection); } else { ui->treeWidget->setSelectionMode(QAbstractItemView::MultiSelection); } ui->checkSubObject->setVisible(allowSubObject); if (!allowSubObject) { ui->treeWidget->setColumnCount(1); } else { ui->treeWidget->setColumnCount(2); // make sure to show a horizontal scrollbar if needed ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); } std::set expandDocs; if (oldLinks.empty()) { expandDocs.insert(owner->getDocument()); } else { for (auto& link : oldLinks) { auto doc = link.getDocument(); if (doc) { expandDocs.insert(doc); } } } QPixmap docIcon(Gui::BitmapFactory().pixmap("Document")); for (auto d : docs) { auto item = new QTreeWidgetItem(ui->treeWidget); item->setIcon(0, docIcon); item->setText(0, QString::fromUtf8(d->Label.getValue())); item->setData(0, Qt::UserRole, QByteArray("")); item->setData(0, Qt::UserRole + 1, QByteArray(d->getName())); item->setFlags(Qt::ItemIsEnabled); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); if (expandDocs.count(d)) { item->setExpanded(true); } docItems[d] = item; } if (allowSubObject) { if (propLink->testFlag(App::PropertyLinkBase::LinkSyncSubObject)) { ui->checkSubObject->setChecked(true); } else { for (auto& link : oldLinks) { auto sobj = link.getSubObject(); if (sobj && sobj != link.getObject()) { ui->checkSubObject->setChecked(true); break; } } } } if (oldLinks.isEmpty()) { return; } // Try to select items corresponding to the current links inside the // property ui->treeWidget->blockSignals(true); for (auto& link : oldLinks) { onSelectionChanged(Gui::SelectionChanges(SelectionChanges::AddSelection, link.getDocumentName(), link.getObjectName(), link.getSubName())); } ui->treeWidget->blockSignals(false); // For link list type property, try to auto filter type if (tryFilter && isLinkList) { Base::Type objType; for (const auto& link : std::as_const(oldLinks)) { auto obj = link.getSubObject(); if (!obj) { continue; } if (objType.isBad()) { objType = obj->getTypeId(); continue; } for (; objType != App::DocumentObject::getClassTypeId(); objType = objType.getParent()) { if (obj->isDerivedFrom(objType)) { break; } } } Base::Type baseType; // get only geometric types if (objType.isDerivedFrom(App::GeoFeature::getClassTypeId())) { baseType = App::GeoFeature::getClassTypeId(); } else { baseType = App::DocumentObject::getClassTypeId(); } // get the direct base class of App::DocumentObject which 'obj' is derived from while (!objType.isBad()) { Base::Type parType = objType.getParent(); if (parType == baseType) { baseType = objType; break; } objType = parType; } if (!baseType.isBad()) { const char* name = baseType.getName(); auto it = typeItems.find(QByteArray::fromRawData(name, strlen(name) + 1)); if (it != typeItems.end()) { it->second->setSelected(true); } ui->checkObjectType->setChecked(true); } } } void DlgPropertyLink::onClicked(QAbstractButton* button) { if (button == resetButton) { ui->treeWidget->blockSignals(true); ui->treeWidget->selectionModel()->clearSelection(); for (auto item : subSelections) { item->setText(1, QString()); } ui->treeWidget->blockSignals(false); subSelections.clear(); Gui::Selection().clearSelection(); } else if (button == refreshButton) { init(objProp); } } void DlgPropertyLink::hideEvent(QHideEvent* ev) { detachObserver(); QDialog::hideEvent(ev); } void DlgPropertyLink::closeEvent(QCloseEvent* ev) { detachObserver(); QDialog::closeEvent(ev); } void DlgPropertyLink::attachObserver() { if (isSelectionAttached()) { return; } Gui::Selection().selStackPush(); attachSelection(); if (!parentView) { for (auto p = parent(); p; p = p->parent()) { auto view = qobject_cast(p); if (view) { parentView = view; for (auto& sel : Gui::Selection().getCompleteSelection(ResolveMode::NoResolve)) { savedSelections.emplace_back(sel.DocName, sel.FeatName, sel.SubName); } break; } } } auto view = qobject_cast(parentView.data()); if (view) { view->blockSelection(true); } } void DlgPropertyLink::showEvent(QShowEvent* ev) { attachObserver(); QDialog::showEvent(ev); } void DlgPropertyLink::onItemEntered(QTreeWidgetItem*) { int timeout = Gui::TreeParams::getPreSelectionDelay() / 2; if (timeout < 0) { timeout = 1; } timer->start(timeout); Gui::Selection().rmvPreselect(); } void DlgPropertyLink::leaveEvent(QEvent* ev) { Gui::Selection().rmvPreselect(); QDialog::leaveEvent(ev); } void DlgPropertyLink::detachObserver() { if (isSelectionAttached()) { detachSelection(); } auto view = qobject_cast(parentView.data()); if (view && !savedSelections.empty()) { try { Gui::Selection().clearSelection(); } catch (Py::Exception& e) { e.clear(); } for (auto& sel : savedSelections) { if (sel.getSubObject()) { Gui::Selection().addSelection(sel.getDocumentName().c_str(), sel.getObjectName().c_str(), sel.getSubName().c_str()); } } savedSelections.clear(); } if (view) { view->blockSelection(false); } parentView = nullptr; } void DlgPropertyLink::onItemSelectionChanged() { auto newSelections = ui->treeWidget->selectedItems(); if (newSelections.isEmpty() || selections.contains(newSelections.back())) { selections = newSelections; if (newSelections.isEmpty()) { currentObj = nullptr; } return; } selections = newSelections; auto sobjs = getLinkFromItem(newSelections.back()); App::DocumentObject* obj = !sobjs.empty() ? sobjs.front().getObject() : nullptr; if (!obj) { Gui::Selection().clearSelection(); return; } bool focus = false; // Do auto view switch if tree view does not do it if (!TreeParams::getSyncView()) { focus = ui->treeWidget->hasFocus(); auto doc = Gui::Application::Instance->getDocument(sobjs.front().getDocumentName().c_str()); if (doc) { auto vp = Base::freecad_dynamic_cast( doc->getViewProvider(obj)); if (vp) { // If the view provider uses a special window for rendering, switch to it MDIView* view = vp->getMDIView(); if (view) { doc->setActiveWindow(view); } else { doc->setActiveView(vp, Gui::View3DInventor::getClassTypeId()); } } } } // Sync 3d view selection. To give a better visual feedback, we // only keep the latest selection. bool blocked = blockSelection(true); Gui::Selection().clearSelection(); for (auto& sobj : sobjs) { Gui::Selection().addSelection(sobj.getDocumentName().c_str(), sobj.getObjectName().c_str(), sobj.getSubName().c_str()); } blockSelection(blocked); // Enforce single parent if (singleParent && currentObj && currentObj != obj) { ui->treeWidget->blockSignals(true); const auto items = ui->treeWidget->selectedItems(); for (auto item : items) { if (item != selections.back()) { item->setSelected(false); } } auto last = selections.back(); selections.clear(); selections.append(last); ui->treeWidget->blockSignals(false); } currentObj = obj; if (focus) { // FIXME: does not work, why? ui->treeWidget->setFocus(); } } QTreeWidgetItem* DlgPropertyLink::findItem(App::DocumentObject* obj, const char* subname, bool* pfound) { if (pfound) { *pfound = false; } if (!obj || !obj->isAttachedToDocument()) { return nullptr; } std::vector sobjs; if (subname && subname[0]) { if (!allowSubObject) { obj = obj->getSubObject(subname); if (!obj) { return nullptr; } } else { sobjs = obj->getSubObjectList(subname); } } auto itDoc = docItems.find(obj->getDocument()); if (itDoc == docItems.end()) { return nullptr; } onItemExpanded(itDoc->second); auto it = itemMap.find(obj); if (it == itemMap.end() || it->second->isHidden()) { return nullptr; } if (!allowSubObject) { if (pfound) { *pfound = true; } return it->second; } QTreeWidgetItem* item = it->second; bool first = true; for (auto o : sobjs) { if (first) { first = false; continue; } onItemExpanded(item); bool found = false; for (int i = 0, count = item->childCount(); i < count; ++i) { auto child = item->child(i); if (strcmp(o->getNameInDocument(), child->data(0, Qt::UserRole).toByteArray().constData()) == 0) { item = child; found = true; break; } } if (!found) { return item; } } if (pfound) { *pfound = true; } return item; } void DlgPropertyLink::onSelectionChanged(const Gui::SelectionChanges& msg) { if (msg.Type != SelectionChanges::AddSelection) { return; } bool found = false; auto selObj = msg.Object.getObject(); App::ElementNamePair elementName; const char* subname = msg.pSubName; if (!ui->checkSubObject->isChecked()) { selObj = App::GeoFeature::resolveElement(selObj, subname, elementName); if (!selObj) { return; } subname = elementName.oldName.c_str(); } auto item = findItem(selObj, msg.pSubName, &found); if (!item) { return; } if (!item->isSelected()) { ui->treeWidget->blockSignals(true); if (singleSelect || (singleParent && currentObj && currentObj != selObj)) { ui->treeWidget->selectionModel()->clearSelection(); } currentObj = selObj; item->setSelected(true); selections.append(item); ui->treeWidget->blockSignals(false); } ui->treeWidget->scrollToItem(item); if (allowSubObject) { QString element = QString::fromLatin1(msg.Object.getOldElementName().c_str()); if (element.size()) { QStringList list; QString text = item->text(1); if (text.size()) { list = text.split(QLatin1Char(',')); } if (list.indexOf(element) < 0) { list << element; item->setText(1, list.join(QLatin1String(","))); subSelections.insert(item); } } else if (subSelections.erase(item)) { item->setText(1, QString()); } } } void DlgPropertyLink::accept() { QDialog::accept(); } static QTreeWidgetItem* _getLinkFromItem(std::ostringstream& ss, QTreeWidgetItem* item, const char* objName) { auto parent = item->parent(); assert(parent); QByteArray nextName = parent->data(0, Qt::UserRole).toByteArray(); if (nextName.isEmpty()) { return item; } item = _getLinkFromItem(ss, parent, nextName); ss << objName << '.'; return item; } QList DlgPropertyLink::getLinkFromItem(QTreeWidgetItem* item, bool needSubName) const { QList res; auto parent = item->parent(); if (!parent) { return res; } std::ostringstream ss; auto parentItem = _getLinkFromItem(ss, item, item->data(0, Qt::UserRole).toByteArray().constData()); App::SubObjectT sobj(parentItem->data(0, Qt::UserRole + 1).toByteArray().constData(), parentItem->data(0, Qt::UserRole).toByteArray().constData(), ss.str().c_str()); QString elements; if (needSubName && allowSubObject) { elements = item->text(1); } if (elements.isEmpty()) { res.append(App::SubObjectT()); res.last() = std::move(sobj); return res; } const auto split = elements.split(QLatin1Char(',')); for (const QString& element : split) { res.append(App::SubObjectT()); res.last() = App::SubObjectT(sobj.getDocumentName().c_str(), sobj.getObjectName().c_str(), (sobj.getSubName() + element.toLatin1().constData()).c_str()); } return res; } void DlgPropertyLink::onTimer() { auto item = ui->treeWidget->itemAt(ui->treeWidget->viewport()->mapFromGlobal(QCursor::pos())); if (!item) { return; } auto sobjs = getLinkFromItem(item); if (sobjs.isEmpty()) { return; } const auto& sobj = sobjs.front(); Gui::Selection().setPreselect(sobj.getDocumentName().c_str(), sobj.getObjectName().c_str(), sobj.getSubName().c_str(), 0, 0, 0, Gui::SelectionChanges::MsgSource::TreeView); } QList DlgPropertyLink::currentLinks() const { auto items = ui->treeWidget->selectedItems(); QList res; for (auto item : items) { res.append(getLinkFromItem(item)); } return res; } QList DlgPropertyLink::originalLinks() const { return oldLinks; } QString DlgPropertyLink::linksToPython(const QList& links) { if (links.isEmpty()) { return QLatin1String("None"); } if (links.size() == 1) { return QString::fromLatin1(links.front().getSubObjectPython(false).c_str()); } std::ostringstream ss; if (isLinkSub(links)) { ss << '(' << links.front().getObjectPython() << ", ["; for (const auto& link : links) { const auto& sub = link.getSubName(); if (!sub.empty()) { ss << "u'" << Base::Tools::escapedUnicodeFromUtf8(sub.c_str()) << "',"; } } ss << "])"; } else { ss << '['; for (const auto& link : links) { ss << link.getSubObjectPython(false) << ','; } ss << ']'; } return QString::fromLatin1(ss.str().c_str()); } void DlgPropertyLink::filterObjects() { for (int i = 0, count = ui->treeWidget->topLevelItemCount(); i < count; ++i) { auto item = ui->treeWidget->topLevelItem(i); for (int j = 0, c = item->childCount(); j < c; ++j) { filterItem(item->child(j)); } } } void DlgPropertyLink::filterItem(QTreeWidgetItem* item) { if (filterType(item)) { item->setHidden(true); return; } item->setHidden(false); for (int i = 0, count = item->childCount(); i < count; ++i) { filterItem(item->child(i)); } } bool DlgPropertyLink::eventFilter(QObject* obj, QEvent* e) { if (obj == ui->searchBox && e->type() == QEvent::KeyPress && static_cast(e)->key() == Qt::Key_Escape) { ui->searchBox->setText(QString()); return true; } return QDialog::eventFilter(obj, e); } void DlgPropertyLink::onItemSearch() { itemSearch(ui->searchBox->text(), true); } void DlgPropertyLink::keyPressEvent(QKeyEvent* ev) { if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { if (ui->searchBox->hasFocus()) { return; } } QDialog::keyPressEvent(ev); } void DlgPropertyLink::itemSearch(const QString& text, bool select) { if (searchItem) { searchItem->setBackground(0, bgBrush); } auto owner = objProp.getObject(); if (!owner) { return; } std::string txt(text.toUtf8().constData()); try { if (txt.empty()) { return; } if (txt.find("<<") == std::string::npos) { auto pos = txt.find('.'); if (pos == std::string::npos) { txt += '.'; } else if (pos != txt.size() - 1) { txt.insert(pos + 1, "<<"); if (txt.back() != '.') { txt += '.'; } txt += ">>."; } } else if (txt.back() != '.') { txt += '.'; } txt += "_self"; auto path = App::ObjectIdentifier::parse(owner, txt); if (path.getPropertyName() != "_self") { return; } App::DocumentObject* obj = path.getDocumentObject(); if (!obj) { return; } bool found; const char* subname = path.getSubObjectName().c_str(); QTreeWidgetItem* item = findItem(obj, subname, &found); if (!item) { return; } if (select) { if (!found) { return; } Gui::Selection().addSelection(obj->getDocument()->getName(), obj->getNameInDocument(), subname); } else { Selection().setPreselect(obj->getDocument()->getName(), obj->getNameInDocument(), subname, 0, 0, 0, Gui::SelectionChanges::MsgSource::TreeView); searchItem = item; ui->treeWidget->scrollToItem(searchItem); bgBrush = searchItem->background(0); searchItem->setBackground(0, QColor(255, 255, 0, 100)); } } catch (...) { } } QTreeWidgetItem* DlgPropertyLink::createItem(App::DocumentObject* obj, QTreeWidgetItem* parent) { if (!obj || !obj->isAttachedToDocument()) { return nullptr; } if (inList.find(obj) != inList.end()) { return nullptr; } auto vp = Base::freecad_dynamic_cast( Application::Instance->getViewProvider(obj)); if (!vp) { return nullptr; } QTreeWidgetItem* item; if (parent) { item = new QTreeWidgetItem(parent); } else { item = new QTreeWidgetItem(ui->treeWidget); } item->setIcon(0, vp->getIcon()); item->setText(0, QString::fromUtf8((obj)->Label.getValue())); item->setData(0, Qt::UserRole, QByteArray(obj->getNameInDocument())); item->setData(0, Qt::UserRole + 1, QByteArray(obj->getDocument()->getName())); if (allowSubObject) { item->setChildIndicatorPolicy(!obj->getLinkedObject(true)->getOutList().empty() ? QTreeWidgetItem::ShowIndicator : QTreeWidgetItem::DontShowIndicator); item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); } const char* typeName = obj->getTypeId().getName(); QByteArray typeData = QByteArray::fromRawData(typeName, strlen(typeName) + 1); item->setData(0, Qt::UserRole + 2, typeData); QByteArray proxyType; auto prop = Base::freecad_dynamic_cast(obj->getPropertyByName("Proxy")); if (prop) { Base::PyGILStateLocker lock; Py::Object proxy = prop->getValue(); if (!proxy.isNone() && !proxy.isString()) { const char* name = nullptr; if (proxy.hasAttr("__class__")) { proxyType = QByteArray(proxy.getAttr("__class__").as_string().c_str()); } else { name = proxy.ptr()->ob_type->tp_name; proxyType = QByteArray::fromRawData(name, strlen(name) + 1); } auto it = typeItems.find(proxyType); if (it != typeItems.end()) { proxyType = it->first; } else if (name) { proxyType = QByteArray(name, proxyType.size()); } } } item->setData(0, Qt::UserRole + 3, proxyType); filterItem(item); return item; } QTreeWidgetItem* DlgPropertyLink::createTypeItem(Base::Type type) { if (type.isBad()) { return nullptr; } QTreeWidgetItem* item = nullptr; if (!type.isBad() && type != App::DocumentObject::getClassTypeId()) { Base::Type parentType = type.getParent(); if (!parentType.isBad()) { const char* name = parentType.getName(); auto typeData = QByteArray::fromRawData(name, strlen(name) + 1); auto& typeItem = typeItems[typeData]; if (!typeItem) { typeItem = createTypeItem(parentType); typeItem->setData(0, Qt::UserRole, typeData); } item = typeItem; } } if (!item) { item = new QTreeWidgetItem(ui->typeTree); } else { item = new QTreeWidgetItem(item); } item->setExpanded(true); item->setText(0, QString::fromLatin1(type.getName())); if (type == App::DocumentObject::getClassTypeId()) { item->setFlags(Qt::ItemIsEnabled); } return item; } bool DlgPropertyLink::filterType(QTreeWidgetItem* item) { auto proxyType = item->data(0, Qt::UserRole + 3).toByteArray(); QTreeWidgetItem* proxyItem = nullptr; if (proxyType.size()) { auto& pitem = typeItems[proxyType]; if (!pitem) { pitem = new QTreeWidgetItem(ui->typeTree); pitem->setText(0, QString::fromLatin1(proxyType)); pitem->setIcon(0, item->icon(0)); pitem->setData(0, Qt::UserRole, proxyType); } proxyItem = pitem; } auto typeData = item->data(0, Qt::UserRole + 2).toByteArray(); Base::Type type = Base::Type::fromName(typeData.constData()); if (type.isBad()) { return false; } QTreeWidgetItem*& typeItem = typeItems[typeData]; if (!typeItem) { typeItem = createTypeItem(type); typeItem->setData(0, Qt::UserRole, typeData); } if (!proxyType.size()) { QIcon icon = typeItem->icon(0); if (icon.isNull()) { typeItem->setIcon(0, item->icon(0)); } } if (!ui->checkObjectType->isChecked() || selectedTypes.empty()) { return false; } if (proxyItem && selectedTypes.count(proxyType)) { return false; } for (auto t = type; !t.isBad() && t != App::DocumentObject::getClassTypeId(); t = t.getParent()) { const char* name = t.getName(); if (selectedTypes.count(QByteArray::fromRawData(name, strlen(name) + 1))) { return false; } } return true; } void DlgPropertyLink::onItemExpanded(QTreeWidgetItem* item) { if (item->childCount()) { return; } QByteArray docName = item->data(0, Qt::UserRole + 1).toByteArray(); auto doc = App::GetApplication().getDocument(docName); if (!doc) { return; } QByteArray objName = item->data(0, Qt::UserRole).toByteArray(); if (objName.isEmpty()) { for (auto obj : doc->getObjects()) { auto newItem = createItem(obj, item); if (newItem) { itemMap[obj] = newItem; } } } else if (allowSubObject) { auto obj = doc->getObject(objName); if (!obj) { return; } std::set childSet; std::string sub; for (auto child : obj->getLinkedObject(true)->getOutList()) { if (!childSet.insert(child).second) { continue; } sub = child->getNameInDocument(); sub += "."; if (obj->getSubObject(sub.c_str())) { createItem(child, item); } } } } void DlgPropertyLink::onObjectTypeToggled(bool on) { ui->typeTree->setVisible(on); filterObjects(); } void DlgPropertyLink::onTypeTreeItemSelectionChanged() { selectedTypes.clear(); const auto items = ui->typeTree->selectedItems(); for (auto item : items) { selectedTypes.insert(item->data(0, Qt::UserRole).toByteArray()); } if (ui->checkObjectType->isChecked()) { filterObjects(); } } void DlgPropertyLink::onSearchBoxTextChanged(const QString& text) { itemSearch(text, false); } #include "moc_DlgPropertyLink.cpp"