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(); };