From 0633abb70d0c73c9df656721e23a6f7f6ddb1df7 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 22 Jan 2020 17:13:47 +0800 Subject: [PATCH] Gui: refactor link property editor PropertyLinkItem now support all major types of link property. PropertyLinkListItem is no longer necessary, but kept for backward compatible, which is now identical to PropertyLinkItem. DlgPropertyLink, the link selection dialog, is now made modeless, so that that user can select geometry sub-element directory from 3D view. --- src/App/PropertyLinks.h | 9 + src/Gui/DlgPropertyLink.cpp | 1155 +++++++++++++++++------ src/Gui/DlgPropertyLink.h | 78 +- src/Gui/DlgPropertyLink.ui | 97 +- src/Gui/ExpressionCompleter.cpp | 17 + src/Gui/ExpressionCompleter.h | 3 + src/Gui/MetaTypes.h | 3 + src/Gui/View3DInventorViewer.cpp | 2 +- src/Gui/propertyeditor/PropertyItem.cpp | 458 ++------- src/Gui/propertyeditor/PropertyItem.h | 67 +- 10 files changed, 1170 insertions(+), 719 deletions(-) diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index c37aca223c..5ac9517dcc 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -832,6 +832,9 @@ public: virtual Property *Copy(void) const override; virtual void Paste(const Property &from) override; + virtual const char* getEditorName(void) const override + { return "Gui::PropertyEditor::PropertyLinkItem"; } + /// Return a copy of the property if any changes caused by importing external object virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; @@ -974,6 +977,9 @@ public: virtual Property *Copy(void) const override; virtual void Paste(const Property &from) override; + virtual const char* getEditorName(void) const override + { return "Gui::PropertyEditor::PropertyLinkListItem"; } + /// Return a copy of the property if any changes caused by importing external object virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; @@ -1248,6 +1254,9 @@ public: virtual Property *Copy(void) const override; virtual void Paste(const Property &from) override; + virtual const char* getEditorName(void) const override + { return "Gui::PropertyEditor::PropertyLinkListItem"; } + virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; virtual Property *CopyOnLabelChange(App::DocumentObject *obj, diff --git a/src/Gui/DlgPropertyLink.cpp b/src/Gui/DlgPropertyLink.cpp index d415149bf9..a049f6e2da 100644 --- a/src/Gui/DlgPropertyLink.cpp +++ b/src/Gui/DlgPropertyLink.cpp @@ -27,92 +27,60 @@ # include # include # include +# include #endif +#include #include #include #include #include +#include +#include "Document.h" +#include "View3DInventor.h" +#include "Tree.h" +#include "Selection.h" +#include "PropertyView.h" #include "BitmapFactory.h" #include "DlgPropertyLink.h" #include "Application.h" #include "ViewProviderDocumentObject.h" +#include "MetaTypes.h" #include "ui_DlgPropertyLink.h" using namespace Gui::Dialog; /* TRANSLATOR Gui::Dialog::DlgPropertyLink */ -DlgPropertyLink::DlgPropertyLink(const QStringList& list, QWidget* parent, Qt::WindowFlags fl, bool xlink) - : QDialog(parent, fl), link(list), ui(new Ui_DlgPropertyLink) +DlgPropertyLink::DlgPropertyLink(QWidget* parent) + : QDialog(parent), SelectionObserver(false,0) + , ui(new Ui_DlgPropertyLink) { -#ifdef FC_DEBUG - assert(list.size() >= 4); -#endif - - // populate inList to filter out any objects that contains the owner object - // of the editing link property - auto doc = App::GetApplication().getDocument(qPrintable(link[0])); - if(doc) { - auto obj = doc->getObject(qPrintable(link[3])); - if(obj && obj->getNameInDocument()) { - inList = obj->getInListEx(true); - inList.insert(obj); - } - } - ui->setupUi(this); ui->typeTree->hide(); + ui->searchBox->installEventFilter(this); + ui->searchBox->setNoProperty(true); - if(!xlink) - ui->comboBox->hide(); - else { - std::string linkDoc = qPrintable(link[0]); - for(auto doc : App::GetApplication().getDocuments()) { - QString name(QString::fromUtf8(doc->Label.getValue())); - ui->comboBox->addItem(name,QVariant(QString::fromLatin1(doc->getName()))); - if(linkDoc == doc->getName()) - ui->comboBox->setCurrentIndex(ui->comboBox->count()-1); - } - } + timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), this, SLOT(onTimer())); - Base::Type baseType; - - App::Document *linkedDoc = doc; - if (link.size()>FC_XLINK_VALUE_INDEX) - linkedDoc = App::GetApplication().getDocument(qPrintable(link[FC_XLINK_VALUE_INDEX])); - if(linkedDoc) { - QString objName = link[1]; // linked object name - auto obj = linkedDoc->getObject((const char*)objName.toLatin1()); - if (obj && inList.find(obj)==inList.end()) { - Base::Type objType = obj->getTypeId(); - // 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()) { - std::string name = objType.getName(); - Base::Type parType = objType.getParent(); - if (parType == baseType) { - baseType = objType; - break; - } - objType = parType; - } - } - } - if(!baseType.isBad()) { - types.insert(baseType.getName()); - ui->checkObjectType->setChecked(true); - }else - findObjects(); + ui->treeWidget->setMouseTracking(true); + connect(ui->treeWidget, SIGNAL(itemEntered(QTreeWidgetItem*, int)), + this, SLOT(onItemEntered(QTreeWidgetItem*))); connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(onItemExpanded(QTreeWidgetItem*))); + + connect(ui->treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged())); + + connect(ui->searchBox, SIGNAL(returnPressed()), this, SLOT(onItemSearch())); + + connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(onClicked(QAbstractButton*))); + + refreshButton = ui->buttonBox->addButton(tr("Refresh"), QDialogButtonBox::ActionRole); + resetButton = ui->buttonBox->addButton(tr("Clear"), QDialogButtonBox::ResetRole); } /** @@ -120,220 +88,759 @@ DlgPropertyLink::DlgPropertyLink(const QStringList& list, QWidget* parent, Qt::W */ DlgPropertyLink::~DlgPropertyLink() { + detachObserver(); + // no need to delete child widgets, Qt does it all for us delete ui; } -void DlgPropertyLink::setSelectionMode(QAbstractItemView::SelectionMode mode) +QList DlgPropertyLink::getLinksFromProperty(const App::PropertyLinkBase *prop) { - ui->treeWidget->setSelectionMode(mode); - findObjects(); + 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,0)); + } 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->getNameInDocument()) + 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(QList links) +{ + for(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 QString(); + + 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,0), + 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->getNameInDocument()) + 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; + } + + 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); + } + + if(!allowSubObject) { + ui->treeWidget->setColumnCount(1); + } else { + ui->treeWidget->setColumnCount(2); + + // make sure to show a horizontal scrollbar if needed +#if QT_VERSION >= 0x050000 + ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); +#else + ui->treeWidget->header()->setResizeMode(0, QHeaderView::ResizeToContents); +#endif + } + + 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(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(auto link : 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(isConnectionAttached()) + 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(0)) + savedSelections.emplace_back(sel.DocName, sel.FeatName, sel.SubName); + break; + } + } + } + auto view = qobject_cast(parentView.data()); + if(view) + view->blockConnection(true); +} + +void DlgPropertyLink::showEvent(QShowEvent *ev) { + attachObserver(); + QDialog::showEvent(ev); +} + +void DlgPropertyLink::onItemEntered(QTreeWidgetItem *) { + int timeout = Gui::TreeParams::Instance()->PreSelectionDelay()/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(isConnectionAttached()) + detachSelection(); + + auto view = qobject_cast(parentView.data()); + if(view && savedSelections.size()) { + Gui::Selection().clearSelection(); + 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->blockConnection(false); + + parentView = nullptr; +} + +void DlgPropertyLink::onItemSelectionChanged() +{ + auto newSelections = ui->treeWidget->selectedItems(); + + if(newSelections.isEmpty() || selections.contains(newSelections.back())) { + selections = newSelections; + if(newSelections.isEmpty()) + currentObj = 0; + return; + } + + selections = newSelections; + + auto sobjs = getLinkFromItem(newSelections.back()); + App::DocumentObject *obj = sobjs.size()?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::Instance()->SyncView()) { + 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) { + doc->setActiveView(vp, Gui::View3DInventor::getClassTypeId()); + } + } + } + + // Sync 3d view selection. To give a better visual feedback, we + // only keep the latest selection. + bool blocked = blockConnection(true); + Gui::Selection().clearSelection(); + for(auto &sobj : sobjs) + Gui::Selection().addSelection(sobj.getDocumentName().c_str(), + sobj.getObjectName().c_str(), + sobj.getSubName().c_str()); + blockConnection(blocked); + + // Enforce single parent + if(singleParent && currentObj && currentObj!=obj) { + ui->treeWidget->blockSignals(true); + for(auto item : ui->treeWidget->selectedItems()) { + 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->getNameInDocument()) + return 0; + + std::vector sobjs; + if(subname && subname[0]) { + if(!allowSubObject) { + obj = obj->getSubObject(subname); + if(!obj) + return 0; + } else + sobjs = obj->getSubObjectList(subname); + } + + auto itDoc = docItems.find(obj->getDocument()); + if(itDoc == docItems.end()) + return 0; + onItemExpanded(itDoc->second); + + auto it = itemMap.find(obj); + if(it == itemMap.end()) + return 0; + + 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();ichild(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 SelectionChanges& msg) +{ + if (msg.Type != SelectionChanges::AddSelection) + return; + + bool found = false; + auto selObj = msg.Object.getObject(); + auto item = findItem(selObj, msg.pSubName, &found); + if(!item || !found) + 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() { - QList items = ui->treeWidget->selectedItems(); - if (items.isEmpty()) { - QMessageBox::warning(this, tr("No selection"), tr("Please select an object from the list")); - return; - } - QDialog::accept(); } -static QStringList getLinkFromItem(const QStringList &link, QTreeWidgetItem *selItem) { - QStringList list = link; - if(link.size()>FC_XLINK_VALUE_INDEX) { - QString subname; - auto parent = selItem; - for(auto item=parent;;item=parent) { - parent = item->parent(); - if(!parent) { - list[1] = item->data(0,Qt::UserRole).toString(); - break; - } - subname = QString::fromLatin1("%1.%2"). - arg(item->data(0,Qt::UserRole).toString()).arg(subname); - } - list[FC_XLINK_VALUE_INDEX] = subname; - if(subname.size()) - list[2] = QString::fromLatin1("%1 (%2.%3)"). - arg(selItem->text(0)).arg(list[1]).arg(subname); - else - list[2] = selItem->text(0); - QString docName(selItem->data(0, Qt::UserRole+1).toString()); - if(list.size()>FC_XLINK_VALUE_INDEX+1) - list[FC_XLINK_VALUE_INDEX+1] = docName; - else - list << docName; - }else{ - list[1] = selItem->data(0,Qt::UserRole).toString(); - list[2] = selItem->text(0); - if (list[1].isEmpty()) - list[2] = QString::fromUtf8(""); - } - return list; +static QTreeWidgetItem *_getLinkFromItem(std::ostringstream &ss, QTreeWidgetItem *item, const char *objName) { + auto parent = item->parent(); + assert(parent); + const char *nextName = parent->data(0, Qt::UserRole).toByteArray().constData(); + if(!nextName[0]) + return item; + + item = _getLinkFromItem(ss, parent, nextName); + ss << objName << '.'; + return item; } -QStringList DlgPropertyLink::propertyLink() const +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; + } + + for(const QString &element : elements.split(QLatin1Char(','))) { + 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,2); +} + +QList DlgPropertyLink::currentLinks() const { auto items = ui->treeWidget->selectedItems(); - if (items.isEmpty()) { - return link; - } - return getLinkFromItem(link,items[0]); + QList res; + for(auto item : items) + res.append(getLinkFromItem(item)); + return res; } -QVariantList DlgPropertyLink::propertyLinkList() const +QList DlgPropertyLink::originalLinks() const { - QVariantList varList; - QList items = ui->treeWidget->selectedItems(); - if (items.isEmpty()) { - QStringList l = link; - l[1] = QString(); - l[2] = QString(); - varList << l; + return oldLinks; +} + +QString DlgPropertyLink::linksToPython(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(auto link : links) { + const auto &sub = link.getSubName(); + if(sub.size()) + ss << "u'" << Base::Tools::escapedUnicodeFromUtf8(sub.c_str()) << "',"; + } + ss << "])"; } else { - for (QList::iterator it = items.begin(); it != items.end(); ++it) - varList << getLinkFromItem(link,*it); + ss << '['; + for(auto link : links) + ss << link.getSubObjectPython(false) << ','; + ss << ']'; } - return varList; + + return QString::fromLatin1(ss.str().c_str()); } -void DlgPropertyLink::findObjects() +void DlgPropertyLink::filterObjects() { - bool filterType = ui->checkObjectType->isChecked(); - ui->treeWidget->clear(); - - QString docName = link[0]; // document name of the owner object of this editing property - - bool isSingleSelection = (ui->treeWidget->selectionMode() == QAbstractItemView::SingleSelection); - App::Document* doc = App::GetApplication().getDocument((const char*)docName.toLatin1()); - if (doc) { - - // build list of objects names already in property so we can mark them as selected later on - std::set selectedNames; - - // Add a "None" entry on top - auto* item = new QTreeWidgetItem(ui->treeWidget); - item->setText(0,tr("None (Remove link)")); - QByteArray ba(""); - item->setData(0,Qt::UserRole, ba); - - if (!isSingleSelection) { - - QString ownerName = link[3]; - QString proName = link[4]; - auto owner = doc->getObject(ownerName.toLatin1()); - if(owner) { - App::Property* prop = owner->getPropertyByName((const char*)proName.toLatin1()); - // gather names of objects currently in property - if (prop && prop->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { - const App::PropertyLinkList* propll = static_cast(prop); - std::vector links = propll->getValues(); - for (std::vector::iterator it = links.begin(); it != links.end(); ++it) { - selectedNames.insert((*it)->getNameInDocument()); - } - } - } - } - - Base::PyGILStateLocker lock; - std::map typeInfos; - - QTreeWidgetItem *selected = 0; - for(auto obj : doc->getObjects()) { - auto it = types.end(); - auto prop = Base::freecad_dynamic_cast( - obj->getPropertyByName("Proxy")); - bool pass = false; - if(prop && !prop->getValue().isNone()) { - std::string typeName = prop->getValue().type().as_string(); - if(refreshTypes) { - QIcon &icon = typeInfos[typeName]; - if(icon.isNull()) { - auto vp = Application::Instance->getViewProvider(obj); - if(vp) - icon = vp->getIcon(); - } - } - if(filterType) - pass = types.count(typeName); - } - if(refreshTypes) { - auto type = obj->getTypeId(); - QIcon &icon = typeInfos[type.getName()]; - if(!prop && icon.isNull()) { - auto vp = Application::Instance->getViewProvider(obj); - if(vp) - icon = vp->getIcon(); - } - for(type = type.getParent();!type.isBad();type=type.getParent()) - typeInfos.emplace(type.getName(),QIcon()); - } - if(filterType && !pass && types.size()) { - for(auto type = obj->getTypeId();!type.isBad();type=type.getParent()) { - it = types.find(type.getName()); - if(it!=types.end()) - break; - } - if(it == types.end()) - continue; - } - - auto item = createItem(obj,0); - if(item && selectedNames.count(obj->getNameInDocument())) { - if(!selected) - selected = item; - item->setSelected(true); - } - } - if(selected) - ui->treeWidget->scrollToItem(selected); - - if(refreshTypes) { - refreshTypes = false; - ui->typeTree->blockSignals(true); - ui->typeTree->clear(); - QTreeWidgetItem *selected = 0; - QIcon icon = BitmapFactory().pixmap("px"); - for(auto &v : typeInfos) { - auto item = new QTreeWidgetItem(ui->typeTree); - item->setText(0,QString::fromLatin1(v.first.c_str())); - item->setIcon(0,v.second.isNull()?icon:v.second); - if(types.count(v.first)) { - item->setSelected(true); - if(!selected) - selected = item; - } - } - if(selected) - ui->typeTree->scrollToItem(selected); - ui->typeTree->blockSignals(false); - if(types.size() && !selected) { - types.clear(); - findObjects(); - } - } + for(int i=0, count=ui->treeWidget->topLevelItemCount(); itreeWidget->topLevelItem(i); + for(int j=0, c=item->childCount(); jchild(j)); } } -QTreeWidgetItem *DlgPropertyLink::createItem(App::DocumentObject *obj, QTreeWidgetItem *parent) { +void DlgPropertyLink::filterItem(QTreeWidgetItem *item) { + if(filterType(item)) { + item->setHidden(true); + return; + } + item->setHidden(false); + for(int i=0, count=item->childCount(); ichild(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,2); + 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->getNameInDocument()) return 0; if(inList.find(obj)!=inList.end()) return 0; - auto vp = Gui::Application::Instance->getViewProvider(obj); + auto vp = Base::freecad_dynamic_cast( + Application::Instance->getViewProvider(obj)); if(!vp) return 0; - QString searchText = ui->searchBox->text(); - if (!searchText.isEmpty()) { - QString label = QString::fromUtf8((obj)->Label.getValue()); - if (!label.contains(searchText,Qt::CaseInsensitive)) - return 0; - } + QTreeWidgetItem* item; if(parent) item = new QTreeWidgetItem(parent); @@ -343,54 +850,170 @@ QTreeWidgetItem *DlgPropertyLink::createItem(App::DocumentObject *obj, QTreeWidg 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(link.size()>=5) { - item->setChildIndicatorPolicy(obj->hasChildElement()||vp->getChildRoot()? + + if(allowSubObject) { + item->setChildIndicatorPolicy(obj->getLinkedObject(true)->getOutList().size()? QTreeWidgetItem::ShowIndicator:QTreeWidgetItem::DontShowIndicator); } + + 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 = 0; + 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 0; + + QTreeWidgetItem *item = 0; + 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 = 0; + 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(link.size()<5 || item->childCount()) + if(item->childCount()) return; - std::string name(qPrintable(item->data(0, Qt::UserRole).toString())); - std::string docName(qPrintable(item->data(0, Qt::UserRole+1).toString())); - auto doc = App::GetApplication().getDocument(docName.c_str()); - if(doc) { - auto obj = doc->getObject(name.c_str()); + const char *docName = item->data(0, Qt::UserRole+1).toByteArray().constData(); + auto doc = App::GetApplication().getDocument(docName); + if(!doc) + return; + + const char *objName = item->data(0, Qt::UserRole).toByteArray().constData(); + if(!objName[0]) { + 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; - auto vp = Application::Instance->getViewProvider(obj); - if(!vp) return; - for(auto obj : vp->claimChildren()) - createItem(obj,item); + 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::on_checkObjectType_toggled(bool on) { ui->typeTree->setVisible(on); - findObjects(); + filterObjects(); } void DlgPropertyLink::on_typeTree_itemSelectionChanged() { - types.clear(); + + selectedTypes.clear(); for(auto item : ui->typeTree->selectedItems()) - types.insert(item->text(0).toLatin1().constData()); - findObjects(); + selectedTypes.insert(item->data(0, Qt::UserRole).toByteArray()); + + if(ui->checkObjectType->isChecked()) + filterObjects(); } -void DlgPropertyLink::on_searchBox_textChanged(const QString& /*search*/) +void DlgPropertyLink::on_searchBox_textChanged(const QString& text) { - findObjects(); + itemSearch(text,false); } -void DlgPropertyLink::on_comboBox_currentIndexChanged(int index) -{ - link[0] = ui->comboBox->itemData(index).toString(); - refreshTypes = true; - findObjects(); -} - - #include "moc_DlgPropertyLink.cpp" diff --git a/src/Gui/DlgPropertyLink.h b/src/Gui/DlgPropertyLink.h index e156a68dfd..29e1156c9c 100644 --- a/src/Gui/DlgPropertyLink.h +++ b/src/Gui/DlgPropertyLink.h @@ -26,42 +26,100 @@ #include #include +#include +#include +#include #define FC_XLINK_VALUE_INDEX 5 namespace Gui { namespace Dialog { class Ui_DlgPropertyLink; -class DlgPropertyLink : public QDialog +class DlgPropertyLink : public QDialog, public Gui::SelectionObserver { Q_OBJECT public: - DlgPropertyLink(const QStringList& list, QWidget* parent = 0, Qt::WindowFlags fl = 0, bool xlink=false); + DlgPropertyLink(QWidget* parent = 0); ~DlgPropertyLink(); - void setSelectionMode(QAbstractItemView::SelectionMode mode); void accept(); - QStringList propertyLink() const; - QVariantList propertyLinkList() const; + + QList currentLinks() const; + QList originalLinks() const; + + void init(const App::DocumentObjectT &prop, bool tryFilter=true); + + static QString linksToPython(QList links); + + static QList getLinksFromProperty(const App::PropertyLinkBase *prop); + + static QString formatObject(App::Document *ownerDoc, App::DocumentObject *obj, const char *sub); + + static inline QString formatObject(App::Document *ownerDoc, const App::SubObjectT &sobj) { + return formatObject(ownerDoc, sobj.getObject(), sobj.getSubName().c_str()); + } + + static QString formatLinks(App::Document *ownerDoc, QList links); + +protected: + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + void closeEvent (QCloseEvent * e); + void leaveEvent(QEvent *); + bool eventFilter(QObject *obj, QEvent *ev); + void keyPressEvent(QKeyEvent *ev); + + void detachObserver(); + void attachObserver(); + + void onSelectionChanged(const Gui::SelectionChanges& msg); private Q_SLOTS: void on_checkObjectType_toggled(bool); void on_typeTree_itemSelectionChanged(); void on_searchBox_textChanged(const QString&); - void on_comboBox_currentIndexChanged(int); void onItemExpanded(QTreeWidgetItem * item); + void onItemSelectionChanged(); + void onItemEntered(QTreeWidgetItem *item); + void onItemSearch(); + void onTimer(); + void onClicked(QAbstractButton *); private: QTreeWidgetItem *createItem(App::DocumentObject *obj, QTreeWidgetItem *parent); - void findObjects(); + QTreeWidgetItem *createTypeItem(Base::Type type); + void filterObjects(); + void filterItem(QTreeWidgetItem *item); + bool filterType(QTreeWidgetItem *item); + QTreeWidgetItem *findItem(App::DocumentObject *obj, const char *subname=0, bool *found=nullptr); + void itemSearch(const QString &text, bool select); + QList getLinkFromItem(QTreeWidgetItem *, bool needSubName=true) const; private: - QStringList link; Ui_DlgPropertyLink* ui; + QTimer *timer; + QPushButton *resetButton; + QPushButton *refreshButton; + + QPointer parentView; + std::vector savedSelections; + + App::DocumentObjectT objProp; std::set inList; - std::set types; - bool refreshTypes = true; + std::map docItems; + std::map itemMap; + std::map typeItems; + std::set subSelections; + QList selections; + std::set selectedTypes; + QList oldLinks; + bool allowSubObject = false; + bool singleSelect = false; + bool singleParent = false; + App::DocumentObject *currentObj = nullptr; + QTreeWidgetItem *searchItem = nullptr; + QBrush bgBrush; }; } // namespace Dialog diff --git a/src/Gui/DlgPropertyLink.ui b/src/Gui/DlgPropertyLink.ui index 133e3f47ed..f4698d262b 100644 --- a/src/Gui/DlgPropertyLink.ui +++ b/src/Gui/DlgPropertyLink.ui @@ -14,28 +14,6 @@ Link - - - - Filter by type - - - - - - - true - - - false - - - - 1 - - - - @@ -46,7 +24,7 @@ - + A search pattern to filter the results above @@ -54,22 +32,18 @@ - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - QAbstractItemView::ExtendedSelection + + + QAbstractItemView::NoEditTriggers - + + false + + + true + + false @@ -82,8 +56,57 @@ + + + + QAbstractItemView::ExtendedSelection + + + false + + + true + + + false + + + + 1 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Filter by type + + + + + + Gui::ExpressionLineEdit + QLineEdit +
Gui/ExpressionCompleter.h
+
+
+ + treeWidget + checkObjectType + typeTree + searchBox + buttonBox + diff --git a/src/Gui/ExpressionCompleter.cpp b/src/Gui/ExpressionCompleter.cpp index 8de8c6cae2..0c377627b6 100644 --- a/src/Gui/ExpressionCompleter.cpp +++ b/src/Gui/ExpressionCompleter.cpp @@ -36,6 +36,10 @@ public: setDocumentObject(obj); } + void setNoProperty(bool enabled) { + noProperty = enabled; + } + void setDocumentObject(const App::DocumentObject *obj) { beginResetModel(); if(obj) { @@ -337,6 +341,13 @@ void ExpressionCompleter::setDocumentObject(const App::DocumentObject *obj) { static_cast(m)->setDocumentObject(obj); } +void ExpressionCompleter::setNoProperty(bool enabled) { + noProperty = enabled; + auto m = model(); + if(m) + static_cast(m)->setNoProperty(enabled); +} + QString ExpressionCompleter::pathFromIndex ( const QModelIndex & index ) const { auto m = model(); @@ -545,6 +556,12 @@ void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDo } } +void ExpressionLineEdit::setNoProperty(bool enabled) { + noProperty = enabled; + if(completer) + completer->setNoProperty(enabled); +} + bool ExpressionLineEdit::completerActive() const { return completer && completer->popup() && completer->popup()->isVisible(); diff --git a/src/Gui/ExpressionCompleter.h b/src/Gui/ExpressionCompleter.h index 7370f16d7b..df75a4ccdf 100644 --- a/src/Gui/ExpressionCompleter.h +++ b/src/Gui/ExpressionCompleter.h @@ -42,6 +42,8 @@ public: void setDocumentObject(const App::DocumentObject*); + void setNoProperty(bool enabled=true); + public Q_SLOTS: void slotUpdate(const QString &prefix, int pos); @@ -65,6 +67,7 @@ public: void setDocumentObject(const App::DocumentObject *currentDocObj); bool completerActive() const; void hideCompleter(); + void setNoProperty(bool enabled=true); Q_SIGNALS: void textChanged2(QString text, int pos); public Q_SLOTS: diff --git a/src/Gui/MetaTypes.h b/src/Gui/MetaTypes.h index 1ff64406b1..8825c2b9c0 100644 --- a/src/Gui/MetaTypes.h +++ b/src/Gui/MetaTypes.h @@ -27,6 +27,7 @@ #include #include #include +#include Q_DECLARE_METATYPE(Base::Vector3f) Q_DECLARE_METATYPE(Base::Vector3d) @@ -34,5 +35,7 @@ Q_DECLARE_METATYPE(Base::Matrix4D) Q_DECLARE_METATYPE(Base::Placement) Q_DECLARE_METATYPE(Base::Quantity) Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(App::SubObjectT) +Q_DECLARE_METATYPE(QList) #endif // GUI_METATYPES_H diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index eda68903ff..83196b85b1 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -861,7 +861,7 @@ void View3DInventorViewer::checkGroupOnTop(const SelectionChanges &Reason) { return; } if(childRoot->findChild(childVp->getRoot())<0) { - FC_WARN("cannot find '" << childVp->getObject()->getFullName() + FC_LOG("cannot find '" << childVp->getObject()->getFullName() << "' in geo group '" << grp->getNameInDocument() << "'"); break; } diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 0af57b952d..c9757b38e0 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,7 @@ #include using namespace Gui::PropertyEditor; +using namespace Gui::Dialog; Gui::PropertyEditor::PropertyItemFactory* Gui::PropertyEditor::PropertyItemFactory::_singleton = NULL; @@ -3572,7 +3574,7 @@ QVariant PropertyTransientFileItem::editorData(QWidget *editor) const // --------------------------------------------------------------- -LinkSelection::LinkSelection(const QStringList& list) : link(list) +LinkSelection::LinkSelection(const App::SubObjectT &link) : link(link) { } @@ -3582,15 +3584,23 @@ LinkSelection::~LinkSelection() void LinkSelection::select() { + auto sobj = link.getSubObject(); + if(!sobj) { + QMessageBox::critical(getMainWindow(), tr("Error"), tr("Object not found")); + return; + } + Gui::Selection().selStackPush(); Gui::Selection().clearSelection(); - Gui::Selection().addSelection((const char*)link[0].toLatin1(), - (const char*)link[1].toLatin1()); + Gui::Selection().addSelection(link.getDocumentName().c_str(), + link.getObjectName().c_str(), + link.getSubName().c_str()); this->deleteLater(); } // --------------------------------------------------------------- -LinkLabel::LinkLabel (QWidget * parent, bool xlink) : QWidget(parent), isXLink(xlink) +LinkLabel::LinkLabel (QWidget * parent, const App::Property *prop) + : QWidget(parent), objProp(prop), dlg(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); @@ -3599,6 +3609,8 @@ LinkLabel::LinkLabel (QWidget * parent, bool xlink) : QWidget(parent), isXLink(x label = new QLabel(this); label->setAutoFillBackground(true); label->setTextFormat(Qt::RichText); + // Below is necessary for the hytperlink to be clickable without losing focus + label->setTextInteractionFlags(Qt::TextBrowserInteraction); layout->addWidget(label); editButton = new QPushButton(QLatin1String("..."), this); @@ -3620,23 +3632,42 @@ LinkLabel::~LinkLabel() { } -void LinkLabel::setPropertyLink(const QStringList& o) +void LinkLabel::updatePropertyLink() { - link = o; - QString linkcolor = QApplication::palette().color(QPalette::Link).name(); - QString text = QString::fromLatin1( - "" - "

" - "%4" - "

" - ) - .arg(link[0], link[1], linkcolor, link[2]); + QString text; + auto owner = objProp.getObject(); + auto prop = Base::freecad_dynamic_cast(objProp.getProperty()); + + link = QVariant(); + + if(owner && prop) { + auto links = DlgPropertyLink::getLinksFromProperty(prop); + if(links.size() == 1) { + auto &sobj = links.front(); + link = QVariant::fromValue(sobj); + QString linkcolor = QApplication::palette().color(QPalette::Link).name(); + text = QString::fromLatin1( + "" + "

" + "%5" + "

" + ) + .arg(QLatin1String(sobj.getDocumentName().c_str()), + QLatin1String(sobj.getObjectName().c_str()), + QString::fromUtf8(sobj.getSubName().c_str()), + linkcolor, + DlgPropertyLink::formatObject( + owner->getDocument(), sobj.getObject(), sobj.getSubName().c_str())); + } else if (links.size()) { + text = DlgPropertyLink::formatLinks(owner->getDocument(), links); + } + } label->setText(text); } -QStringList LinkLabel::propertyLink() const +QVariant LinkLabel::propertyLink() const { return link; } @@ -3644,50 +3675,71 @@ QStringList LinkLabel::propertyLink() const void LinkLabel::onLinkActivated (const QString& s) { Q_UNUSED(s); - LinkSelection* select = new LinkSelection(link); + LinkSelection* select = new LinkSelection(qvariant_cast(link)); QTimer::singleShot(50, select, SLOT(select())); } void LinkLabel::onEditClicked () { - Gui::Dialog::DlgPropertyLink dlg(link, this, 0, isXLink); - if (dlg.exec() == QDialog::Accepted) { - setPropertyLink(dlg.propertyLink()); - /*emit*/ linkChanged(link); + if(!dlg) { + dlg = new DlgPropertyLink(this); + dlg->init(objProp,true); + connect(dlg, SIGNAL(accepted()), this, SLOT(onLinkChanged())); + } else + dlg->init(objProp,false); + dlg->show(); +} + +void LinkLabel::onLinkChanged() { + if(dlg) { + auto links = dlg->currentLinks(); + if(links != dlg->originalLinks()) { + link = QVariant::fromValue(links); + /*emit*/ linkChanged(link); + updatePropertyLink(); + } } } void LinkLabel::resizeEvent(QResizeEvent* e) { editButton->setFixedWidth(e->size().height()); - editButton->setFixedHeight(e->size().height()); } +// -------------------------------------------------------------------- PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkItem) -PropertyLinkItem::PropertyLinkItem():isXLink(false) +PropertyLinkItem::PropertyLinkItem() { } QVariant PropertyLinkItem::toString(const QVariant& prop) const { - QStringList list = prop.toStringList(); - return QVariant(list[2]); + QString res; + if(propertyItems.size()) { + App::DocumentObjectT owner(propertyItems[0]); + res = DlgPropertyLink::formatLinks(owner.getDocument(), + qvariant_cast >(prop)); + } + return res; } QVariant PropertyLinkItem::data(int column, int role) const { if(propertyItems.size() && column == 1 && (role == Qt::TextColorRole || role == Qt::ToolTipRole)) { - auto xlink = Base::freecad_dynamic_cast(propertyItems[0]); - if(xlink) { - if(role==Qt::TextColorRole && xlink->checkRestore()>1) + auto propLink = Base::freecad_dynamic_cast(propertyItems[0]); + if(propLink) { + if(role==Qt::TextColorRole && propLink->checkRestore()>1) return QVariant::fromValue(QColor(0xff,0,0)); else if(role == Qt::ToolTipRole) { - const char *filePath = xlink->getFilePath(); - if(filePath && filePath[0]) - return QVariant::fromValue(QString::fromUtf8(filePath)); + auto xlink = Base::freecad_dynamic_cast(propertyItems[0]); + if(xlink) { + const char *filePath = xlink->getFilePath(); + if(filePath && filePath[0]) + return QVariant::fromValue(QString::fromUtf8(filePath)); + } } } } @@ -3696,365 +3748,55 @@ QVariant PropertyLinkItem::data(int column, int role) const { QVariant PropertyLinkItem::value(const App::Property* prop) const { - assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyLink::getClassTypeId())); + auto propLink = Base::freecad_dynamic_cast(prop); + if(!propLink) + return QVariant(); - auto xlink = Base::freecad_dynamic_cast(prop); - isXLink = xlink!=0; + auto links = DlgPropertyLink::getLinksFromProperty(propLink); + if(links.empty()) + return QVariant(); - const App::PropertyLink* prop_link = static_cast(prop); - App::PropertyContainer* c = prop_link->getContainer(); - - // The list has five mandatory elements: - // - // document name of the container, - // internal name of the linked object, - // label, - // internal name of container, - // property name - // - // and two additional elements if it is a PropertyXLink - // - // subname - // (optional) document name of linked object if it is different from the container - // - - App::DocumentObject* obj = prop_link->getValue(); - QStringList list; - if (obj) { - list << QString::fromLatin1(obj->getDocument()->getName()); - list << QString::fromLatin1(obj->getNameInDocument()); - - std::string _objName; - const char *objName = obj->getNameInDocument(); - auto owner = Base::freecad_dynamic_cast(c); - if(!objName || (owner && owner->getDocument()!=obj->getDocument())) { - _objName = obj->getFullName(); - objName = _objName.c_str(); - } - - if(xlink && xlink->getSubValues().size()) { - int count = 0; - std::stringstream ss; - ss << objName << ' '; - - std::string prevSub; - for(const auto &sub : xlink->getSubValues(false)) { - if(++count > 3) - break; - - if(count>1) - ss << ','; - else - ss << '('; - - auto element = Data::ComplexGeoData::findElementName(sub.c_str()); - if(prevSub.size()==(std::size_t)(element-sub.c_str()) - && boost::starts_with(sub,prevSub)) - { - ss << element; - continue; - } - - prevSub.clear(); - - if(element && element[0]) - prevSub = sub.substr(0,element-sub.c_str()); - - if(count > 1) - ss << ' '; - ss << sub; - } - if(count) { - if(count>3) - ss << "..."; - ss << ')'; - } - list << QString::fromUtf8(ss.str().c_str()); - - }else if(obj->Label.getStrValue() != objName) { - list << QString::fromLatin1("%1 (%2)"). - arg(QString::fromUtf8(obj->Label.getValue()), - QString::fromLatin1(objName)); - } else { - list << QString::fromUtf8(obj->Label.getValue()); - } - } else { - // no object assigned - // the document name - if (c->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* obj = static_cast(c); - list << QString::fromLatin1(obj->getDocument()->getName()); - } - else { - list << QString::fromLatin1(""); - } - - // the internal object name - list << QString::fromLatin1("Null"); - - // the object label - std::string msg; - if(xlink && xlink->checkRestore(&msg)>1) - list << QString::fromUtf8(msg.c_str()); - else - list << QString::fromLatin1(""); - } - - // the name of this object - if (c->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* obj = static_cast(c); - list << QString::fromLatin1(obj->getNameInDocument()); - } - else - list << QString::fromLatin1("Null"); - - list << QString::fromLatin1(prop->getName()); - assert(list.size() == FC_XLINK_VALUE_INDEX); - - if(xlink) { - list << QString::fromUtf8(xlink->getSubName(false)); - auto cobj = dynamic_cast(c); - if(cobj && obj && cobj->getDocument()!=obj->getDocument()) - list << QString::fromLatin1(obj->getDocument()->getName()); - } - - return QVariant(list); + return QVariant::fromValue(links); } void PropertyLinkItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::StringList)) - return; - QStringList items = value.toStringList(); - if (items.size() > 1) { - QString d = items[0]; - QString o = items[1]; - QString data; - if ( o.isEmpty() ) - data = QString::fromLatin1("None"); - else if(isXLink && items.size()>FC_XLINK_VALUE_INDEX+1) { - QString doc; - if(items.size()>=FC_XLINK_VALUE_INDEX+2) - doc = items[FC_XLINK_VALUE_INDEX+1]; - else - doc = d; - data = QString::fromLatin1("(App.getDocument('%1').getObject('%2'),'%3')"). - arg(doc,o,items[FC_XLINK_VALUE_INDEX]); - }else - data = QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d,o); - setPropertyValue(data); - } + auto links = qvariant_cast >(value); + setPropertyValue(DlgPropertyLink::linksToPython(links)); } QWidget* PropertyLinkItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const { - LinkLabel *ll = new LinkLabel(parent, isXLink); + if(propertyItems.empty()) + return 0; + LinkLabel *ll = new LinkLabel(parent, propertyItems.front()); ll->setAutoFillBackground(true); ll->setDisabled(isReadOnly()); - QObject::connect(ll, SIGNAL(linkChanged(const QStringList&)), receiver, method); + QObject::connect(ll, SIGNAL(linkChanged(const QVariant&)), receiver, method); return ll; } void PropertyLinkItem::setEditorData(QWidget *editor, const QVariant& data) const { - QStringList list = data.toStringList(); + (void)data; LinkLabel *ll = static_cast(editor); - ll->setPropertyLink(list); + return ll->updatePropertyLink(); } QVariant PropertyLinkItem::editorData(QWidget *editor) const { LinkLabel *ll = static_cast(editor); - return QVariant(ll->propertyLink()); + return ll->propertyLink(); } // -------------------------------------------------------------------- -LinkListLabel::LinkListLabel (QWidget * parent) : QWidget(parent) -{ - QHBoxLayout *layout = new QHBoxLayout(this); - layout->setMargin(0); - layout->setSpacing(1); - - label = new QLabel(this); - label->setAutoFillBackground(true); - layout->addWidget(label); - - editButton = new QPushButton(QLatin1String("..."), this); - editButton->setToolTip(tr("Change the linked objects")); - layout->addWidget(editButton); - - // setLayout(layout); - connect(editButton, SIGNAL(clicked()), - this, SLOT(onEditClicked())); -} - -LinkListLabel::~LinkListLabel() -{ -} - -void LinkListLabel::setPropertyLinkList(const QVariantList& o) -{ - links = o; - if (links.isEmpty()) { - label->clear(); - } - else if (links.size() == 1) { - QStringList s = links.front().toStringList(); - label->setText(s[2]); - } - else { - QStringList obj; - for (QVariantList::iterator it = links.begin(); it != links.end(); ++it) - obj << it->toStringList()[2]; - label->setText(QString::fromLatin1("[%1]").arg(obj.join(QString::fromLatin1(", ")))); - } -} - -QVariantList LinkListLabel::propertyLinkList() const -{ - return links; -} - -void LinkListLabel::onEditClicked () -{ - QStringList list = links.front().toStringList(); - Gui::Dialog::DlgPropertyLink dlg(list, this); - dlg.setSelectionMode(QAbstractItemView::ExtendedSelection); - if (dlg.exec() == QDialog::Accepted) { - setPropertyLinkList(dlg.propertyLinkList()); - Q_EMIT linkChanged(links); - } -} - -void LinkListLabel::resizeEvent(QResizeEvent* e) -{ - editButton->setFixedWidth(e->size().height()); -} - - PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkListItem) PropertyLinkListItem::PropertyLinkListItem() { } -QVariant PropertyLinkListItem::toString(const QVariant& prop) const -{ - QVariantList list = prop.toList(); - if (list.empty()) { - return QString(); - } - else if (list.size() == 1) { - QStringList item = list.front().toStringList(); - return QString::fromLatin1("%1").arg(item[2]); - } - else { - QStringList obj; - for (QVariantList::iterator it = list.begin(); it != list.end(); ++it) - obj << it->toStringList()[2]; - return QString::fromLatin1("[%1]").arg(obj.join(QString::fromLatin1(", "))); - } -} - -QVariant PropertyLinkListItem::value(const App::Property* prop) const -{ - assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())); - - const App::PropertyLinkList* prop_link = static_cast(prop); - App::PropertyContainer* c = prop_link->getContainer(); - - // the name of this object - QString objName; - if (c->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* obj = static_cast(c); - objName = QString::fromLatin1(obj->getNameInDocument()); - } - else { - objName = QString::fromLatin1("Null"); - } - - // each item is a list of five elements: - //[document name, internal name, label, internal name of container, property name] - // the variant list contains at least one item - std::vector obj = prop_link->getValues(); - QVariantList varList; - if (!obj.empty()) { - for (std::vector::iterator it = obj.begin(); it != obj.end(); ++it) { - QStringList list; - list << QString::fromLatin1((*it)->getDocument()->getName()); - list << QString::fromLatin1((*it)->getNameInDocument()); - list << QString::fromUtf8((*it)->Label.getValue()); - list << objName; - list << QString::fromLatin1(prop->getName()); - varList << list; - } - } - else { - QStringList list; - // no object assigned - // the document name - if (c->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* obj = static_cast(c); - list << QString::fromLatin1(obj->getDocument()->getName()); - } - else { - list << QString::fromLatin1(""); - } - - // the internal object name - list << QString::fromLatin1("Null"); - // the object label - list << QString::fromLatin1(""); - list << objName; - list << QString::fromLatin1(prop->getName()); - varList << list; - } - - return QVariant(varList); -} - -void PropertyLinkListItem::setValue(const QVariant& value) -{ - if (!value.canConvert(QVariant::List)) - return; - QVariantList items = value.toList(); - QStringList data; - for (QVariantList::iterator it = items.begin(); it != items.end(); ++it) { - QStringList list = it->toStringList(); - QString d = list[0]; - QString o = list[1]; - if (!o.isEmpty()) - data << QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d, o); - } - if(data.size()==0) - setPropertyValue(QLatin1String("[]")); - else - setPropertyValue(QString::fromLatin1("[%1]").arg(data.join(QString::fromLatin1(", ")))); -} - -QWidget* PropertyLinkListItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const -{ - LinkListLabel *ll = new LinkListLabel(parent); - ll->setAutoFillBackground(true); - ll->setDisabled(isReadOnly()); - QObject::connect(ll, SIGNAL(linkChanged(const QVariantList&)), receiver, method); - return ll; -} - -void PropertyLinkListItem::setEditorData(QWidget *editor, const QVariant& data) const -{ - QVariantList list = data.toList(); - LinkListLabel *ll = static_cast(editor); - ll->setPropertyLinkList(list); -} - -QVariant PropertyLinkListItem::editorData(QWidget *editor) const -{ - LinkListLabel *ll = static_cast(editor); - return QVariant(ll->propertyLinkList()); -} - // -------------------------------------------------------------------- PropertyItemEditorFactory::PropertyItemEditorFactory() diff --git a/src/Gui/propertyeditor/PropertyItem.h b/src/Gui/propertyeditor/PropertyItem.h index 43c7754441..5027348908 100644 --- a/src/Gui/propertyeditor/PropertyItem.h +++ b/src/Gui/propertyeditor/PropertyItem.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,12 @@ void _class_::init(void) { \ } namespace Gui { -namespace Dialog { class TaskPlacement; } + +namespace Dialog { +class TaskPlacement; +class DlgPropertyLink; +} + namespace PropertyEditor { class PropertyItem; @@ -945,25 +951,26 @@ class LinkSelection : public QObject Q_OBJECT public: - LinkSelection(const QStringList&); + LinkSelection(const App::SubObjectT &); ~LinkSelection(); public Q_SLOTS: void select(); private: - QStringList link; + App::SubObjectT link; }; + class LinkLabel : public QWidget { Q_OBJECT public: - LinkLabel (QWidget * parent = 0, bool xlink = false); + LinkLabel (QWidget * parent, const App::Property *prop); virtual ~LinkLabel(); - void setPropertyLink(const QStringList& o); - QStringList propertyLink() const; + void updatePropertyLink(); + QVariant propertyLink() const; protected: void resizeEvent(QResizeEvent*); @@ -971,15 +978,18 @@ protected: protected Q_SLOTS: void onLinkActivated(const QString&); void onEditClicked(); + void onLinkChanged(); Q_SIGNALS: - void linkChanged(const QStringList&); + void linkChanged(const QVariant&); private: QLabel* label; QPushButton* editButton; - QStringList link; - bool isXLink; + QVariant link; + App::DocumentObjectT objProp; + + Gui::Dialog::DlgPropertyLink* dlg; }; /** @@ -1003,54 +1013,17 @@ protected: protected: PropertyLinkItem(); - -private: - mutable bool isXLink; -}; - -class LinkListLabel : public QWidget -{ - Q_OBJECT - -public: - LinkListLabel (QWidget * parent = 0); - virtual ~LinkListLabel(); - void setPropertyLinkList(const QVariantList& o); - QVariantList propertyLinkList() const; - -protected: - void resizeEvent(QResizeEvent*); - -protected Q_SLOTS: - void onEditClicked(); - -Q_SIGNALS: - void linkChanged(const QVariantList&); - -private: - QLabel* label; - QPushButton* editButton; - QVariantList links; }; /** * Edit properties of link list type. * \author Werner Mayer */ -class GuiExport PropertyLinkListItem: public PropertyItem +class GuiExport PropertyLinkListItem: public PropertyLinkItem { Q_OBJECT PROPERTYITEM_HEADER - virtual QWidget* createEditor(QWidget* parent, const QObject* receiver, const char* method) const; - virtual void setEditorData(QWidget *editor, const QVariant& data) const; - virtual QVariant editorData(QWidget *editor) const; - -protected: - virtual QVariant toString(const QVariant&) const; - virtual QVariant value(const App::Property*) const; - virtual void setValue(const QVariant&); - protected: PropertyLinkListItem(); };