diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 7d97ec72d9..7e29453ad5 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -367,6 +367,7 @@ set(Gui_MOC_HDRS DAGView/DAGModel.h TaskElementColors.h DlgObjectSelection.h + DlgAddProperty.h ${FreeCADGui_SDK_MOC_HDRS} ) @@ -441,6 +442,7 @@ SET(Gui_UIC_SRCS TaskView/TaskSelectLinkProperty.ui TaskElementColors.ui DlgObjectSelection.ui + DlgAddProperty.ui ) SET(Gui_RES_SRCS @@ -516,6 +518,7 @@ SET(Dialog_CPP_SRCS DocumentRecovery.cpp TaskElementColors.cpp DlgObjectSelection.cpp + DlgAddProperty.cpp ) SET(Dialog_HPP_SRCS @@ -551,6 +554,7 @@ SET(Dialog_HPP_SRCS DocumentRecovery.h TaskElementColors.h DlgObjectSelection.h + DlgAddProperty.h ) SET(Dialog_SRCS @@ -564,6 +568,7 @@ SET(Dialog_SRCS DlgAuthorization.ui DlgDisplayProperties.ui DlgInputDialog.ui + DlgAddProperty.ui DlgLocationAngle.ui DlgLocationPos.ui DlgMacroExecute.ui diff --git a/src/Gui/DlgAddProperty.cpp b/src/Gui/DlgAddProperty.cpp new file mode 100644 index 0000000000..d20e7af9b8 --- /dev/null +++ b/src/Gui/DlgAddProperty.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** + * Copyright (c) 2019 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +#endif + +#include "ui_DlgAddProperty.h" + +#include +#include +#include +#include +#include "MainWindow.h" +#include +#include "DlgAddProperty.h" + + +using namespace Gui; +using namespace Gui::Dialog; + +DlgAddProperty::DlgAddProperty(QWidget* parent, + std::unordered_set &&c) + : QDialog( parent ) + , containers(std::move(c)) + , ui(new Ui_DlgAddProperty) +{ + ui->setupUi(this); + + auto hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/PropertyView"); + auto defType = Base::Type::fromName( + hGrp->GetASCII("NewPropertyType","App::PropertyString").c_str()); + if(defType.isBad()) + defType = App::PropertyString::getClassTypeId(); + + std::vector types; + Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"),types); + for(auto type : types) { + ui->comboType->addItem(QString::fromLatin1(type.getName())); + if(type == defType) + ui->comboType->setCurrentIndex(ui->comboType->count()-1); + } + + ui->edtGroup->setText(QString::fromLatin1( + hGrp->GetASCII("NewPropertyGroup","Base").c_str())); + ui->chkAppend->setChecked(hGrp->GetBool("NewPropertyAppend",true)); +} + +/** + * Destroys the object and frees any allocated resources + */ +DlgAddProperty::~DlgAddProperty() +{ + // no need to delete child widgets, Qt does it all for us +} + +static std::string containerName(const App::PropertyContainer *c) { + auto doc = Base::freecad_dynamic_cast(c); + if(doc) + return doc->getName(); + auto obj = Base::freecad_dynamic_cast(c); + if(obj) + return obj->getFullName(); + auto vpd = Base::freecad_dynamic_cast(c); + if(vpd) + return vpd->getObject()->getFullName(); + return "?"; +} + +void DlgAddProperty::accept() +{ + std::string name = ui->edtName->text().toUtf8().constData(); + std::string group = ui->edtGroup->text().toUtf8().constData(); + if(name.empty() || group.empty() + || name != Base::Tools::getIdentifier(name) + || group != Base::Tools::getIdentifier(group)) + { + QMessageBox::critical(getMainWindow(), + QObject::tr("Invalid name"), + QObject::tr("The property name or group name must only contain alpha numericals,\n" + "underscore, and must not start with a digit.")); + return; + } + + if(ui->chkAppend->isChecked()) + name = group + "_" + name; + + for(auto c : containers) { + if(c->getPropertyByName(name.c_str())) { + QMessageBox::critical(getMainWindow(), + QObject::tr("Invalid name"), + QObject::tr("The property '%1' already exists in '%2'").arg( + QString::fromLatin1(name.c_str()), + QString::fromLatin1(containerName(c).c_str()))); + return; + } + } + + std::string type = ui->comboType->currentText().toLatin1().constData(); + + for(auto it=containers.begin();it!=containers.end();++it) { + try { + (*it)->addDynamicProperty(type.c_str(),name.c_str(), + group.c_str(),ui->edtDoc->toPlainText().toUtf8().constData()); + } catch(Base::Exception &e) { + e.ReportException(); + for(auto it2=containers.begin();it2!=it;++it2) { + try { + (*it)->removeDynamicProperty(name.c_str()); + } catch(Base::Exception &e) { + e.ReportException(); + } + } + QMessageBox::critical(getMainWindow(), + QObject::tr("Add property"), + QObject::tr("Failed to add property to '%1': %2").arg( + QString::fromLatin1(containerName(*it).c_str()), + QString::fromUtf8(e.what()))); + return; + } + } + auto hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/PropertyView"); + hGrp->SetASCII("NewPropertyType",type.c_str()); + hGrp->SetASCII("NewPropertyGroup",group.c_str()); + hGrp->SetBool("NewPropertyAppend",ui->chkAppend->isChecked()); + QDialog::accept(); +} + +#include "moc_DlgAddProperty.cpp" diff --git a/src/Gui/DlgAddProperty.h b/src/Gui/DlgAddProperty.h new file mode 100644 index 0000000000..4f86d4a461 --- /dev/null +++ b/src/Gui/DlgAddProperty.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright (c) 2019 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef GUI_DIALOG_DLGADDPROPERTY_H +#define GUI_DIALOG_DLGADDPROPERTY_H + +#include + +namespace App { +class PropertyContainer; +} + +namespace Gui { +namespace Dialog { + +class Ui_DlgAddProperty; +class GuiExport DlgAddProperty : public QDialog +{ + Q_OBJECT + +public: + DlgAddProperty(QWidget *parent, std::unordered_set &&); + ~DlgAddProperty(); + + virtual void accept() override; + +private: + std::unordered_set containers; + std::unique_ptr ui; +}; + +} // namespace Dialog +} // namespace Gui + +#endif // GUI_DIALOG_DLGADDPROPERTY_H diff --git a/src/Gui/DlgAddProperty.ui b/src/Gui/DlgAddProperty.ui new file mode 100644 index 0000000000..31059b42f6 --- /dev/null +++ b/src/Gui/DlgAddProperty.ui @@ -0,0 +1,119 @@ + + + Gui::Dialog::DlgAddProperty + + + + 0 + 0 + 354 + 258 + + + + Add property + + + + + + Type + + + + + + + + + + Group + + + + + + + + + + Name + + + + + + + + + + Document + + + + + + + + + + Append the group name in front of the property name in the form of 'group'_'name' to avoid conflict with existing property. The prefixed group name will be auto trimmed when shown in the property editor. + + + Append group name + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + comboType + edtGroup + edtName + + + + + buttonBox + accepted() + Gui::Dialog::DlgAddProperty + accept() + + + 199 + 99 + + + 199 + 58 + + + + + buttonBox + rejected() + Gui::Dialog::DlgAddProperty + reject() + + + 199 + 99 + + + 199 + 58 + + + + + diff --git a/src/Gui/DlgExpressionInput.cpp b/src/Gui/DlgExpressionInput.cpp index 39abfe3640..33af3acaa3 100644 --- a/src/Gui/DlgExpressionInput.cpp +++ b/src/Gui/DlgExpressionInput.cpp @@ -90,6 +90,11 @@ DlgExpressionInput::DlgExpressionInput(const App::ObjectIdentifier & _path, ui->horizontalSpacer_3->changeSize(0, 2); ui->verticalLayout->setContentsMargins(9, 9, 9, 9); this->adjustSize(); + // It is strange that (at least on Linux) DlgExpressionInput will shrink + // to be narrower than ui->expression after calling adjustSize() above. + // Why? + if(this->width() < ui->expression->width() + 18) + this->resize(ui->expression->width()+18,this->height()); } ui->expression->setFocus(); } @@ -138,10 +143,12 @@ void DlgExpressionInput::textChanged(const QString &text) if (n) { Base::Quantity value = n->getQuantity(); - if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit) - throw Base::UnitsMismatchError("Unit mismatch between result and required unit"); + if(!impliedUnit.isEmpty()) { + if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit) + throw Base::UnitsMismatchError("Unit mismatch between result and required unit"); - value.setUnit(impliedUnit); + value.setUnit(impliedUnit); + } ui->msg->setText(value.getUserString()); } diff --git a/src/Gui/DlgPropertyLink.cpp b/src/Gui/DlgPropertyLink.cpp index da8b011ebc..b43d688a2e 100644 --- a/src/Gui/DlgPropertyLink.cpp +++ b/src/Gui/DlgPropertyLink.cpp @@ -25,7 +25,7 @@ #ifndef _PreComp_ # include # include -# include +# include # include #endif @@ -34,24 +34,85 @@ #include #include +#include "BitmapFactory.h" #include "DlgPropertyLink.h" #include "Application.h" -#include "ViewProvider.h" +#include "ViewProviderDocumentObject.h" #include "ui_DlgPropertyLink.h" - using namespace Gui::Dialog; /* TRANSLATOR Gui::Dialog::DlgPropertyLink */ -DlgPropertyLink::DlgPropertyLink(const QStringList& list, QWidget* parent, Qt::WindowFlags fl) +DlgPropertyLink::DlgPropertyLink(const QStringList& list, QWidget* parent, Qt::WindowFlags fl, bool xlink) : QDialog(parent, fl), link(list), ui(new Ui_DlgPropertyLink) { #ifdef FC_DEBUG - assert(list.size() >= 5); + 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); - findObjects(ui->checkObjectType->isChecked(), QString()); + ui->typeTree->hide(); + + 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); + } + } + + 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(); + + connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), + this, SLOT(onItemExpanded(QTreeWidgetItem*))); } /** @@ -65,15 +126,14 @@ DlgPropertyLink::~DlgPropertyLink() void DlgPropertyLink::setSelectionMode(QAbstractItemView::SelectionMode mode) { - ui->listWidget->setSelectionMode(mode); - ui->listWidget->clear(); - findObjects(ui->checkObjectType->isChecked(), ui->searchBox->text()); + ui->treeWidget->setSelectionMode(mode); + findObjects(); } void DlgPropertyLink::accept() { - if (ui->listWidget->selectionMode() == QAbstractItemView::SingleSelection) { - QList items = ui->listWidget->selectedItems(); + if (ui->treeWidget->selectionMode() == QAbstractItemView::SingleSelection) { + QList items = ui->treeWidget->selectedItems(); if (items.isEmpty()) { QMessageBox::warning(this, tr("No selection"), tr("Please select an object from the list")); return; @@ -83,149 +143,248 @@ void DlgPropertyLink::accept() 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; +} + QStringList DlgPropertyLink::propertyLink() const { - QList items = ui->listWidget->selectedItems(); + auto items = ui->treeWidget->selectedItems(); if (items.isEmpty()) { return link; } - else { - QStringList list = link; - list[1] = items[0]->data(Qt::UserRole).toString(); - list[2] = items[0]->text(); - if (list[1].isEmpty()) - list[2] = QString::fromUtf8(""); - return list; - } + return getLinkFromItem(link,items[0]); } QVariantList DlgPropertyLink::propertyLinkList() const { QVariantList varList; - QList items = ui->listWidget->selectedItems(); - if (items.isEmpty()) { - varList << link; - } - else { - for (QList::iterator it = items.begin(); it != items.end(); ++it) { - QStringList list = link; - list[1] = (*it)->data(Qt::UserRole).toString(); - list[2] = (*it)->text(); - if (list[1].isEmpty()) - list[2] = QString::fromUtf8(""); - varList << list; - } - } - + QList items = ui->treeWidget->selectedItems(); + for (QList::iterator it = items.begin(); it != items.end(); ++it) + varList << getLinkFromItem(link,*it); return varList; } -void DlgPropertyLink::findObjects(bool on, const QString& searchText) +void DlgPropertyLink::findObjects() { - QString docName = link[0]; // document name - QString objName = link[1]; // internal object name - QString parName = link[3]; // internal object name of the parent of the link property - QString proName = link[4]; // property name + bool filterType = ui->checkObjectType->isChecked(); + ui->treeWidget->clear(); - bool isSingleSelection = (ui->listWidget->selectionMode() == QAbstractItemView::SingleSelection); + 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) { - Base::Type baseType = App::DocumentObject::getClassTypeId(); - if (!on) { - App::DocumentObject* obj = doc->getObject((const char*)objName.toLatin1()); - if (obj) { - Base::Type objType = obj->getTypeId(); - // get only geometric types - if (objType.isDerivedFrom(App::GeoFeature::getClassTypeId())) - baseType = App::GeoFeature::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; - } - } - } // build list of objects names already in property so we can mark them as selected later on - std::vector selectedNames; + std::set selectedNames; - // build ignore list - std::vector ignoreList; - App::DocumentObject* par = doc->getObject((const char*)parName.toLatin1()); - App::Property* prop = par->getPropertyByName((const char*)proName.toLatin1()); - if (prop) { - // for multi-selection we need all objects - if (isSingleSelection) { - ignoreList = par->getOutListOfProperty(prop); - } else { + // Add a "None" entry on top + if (isSingleSelection) { + auto* item = new QTreeWidgetItem(ui->treeWidget); + item->setText(0,tr("None (Remove link)")); + QByteArray ba(""); + item->setData(0,Qt::UserRole, ba); + }else { + 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->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { + 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.push_back((*it)->getNameInDocument()); - } - } - } - - // add the inlist to the ignore list to avoid dependency loops - std::vector inList = par->getInListRecursive(); - ignoreList.insert(ignoreList.end(), inList.begin(), inList.end()); - ignoreList.push_back(par); - } - - // Add a "None" entry on top - QListWidgetItem* item = new QListWidgetItem(ui->listWidget); - item->setText(tr("None (Remove link)")); - QByteArray ba(""); - item->setData(Qt::UserRole, ba); - - std::vector obj = doc->getObjectsOfType(baseType); - for (std::vector::iterator it = obj.begin(); it != obj.end(); ++it) { - Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(*it); - bool nameOk = true; - if (!searchText.isEmpty()) { - QString label = QString::fromUtf8((*it)->Label.getValue()); - if (!label.contains(searchText,Qt::CaseInsensitive)) - nameOk = false; - } - if (vp && nameOk) { - // filter out the objects - if (std::find(ignoreList.begin(), ignoreList.end(), *it) == ignoreList.end()) { - QListWidgetItem* item = new QListWidgetItem(ui->listWidget); - item->setIcon(vp->getIcon()); - item->setText(QString::fromUtf8((*it)->Label.getValue())); - QByteArray ba((*it)->getNameInDocument()); - item->setData(Qt::UserRole, ba); - // mark items as selected if needed - for (std::vector::iterator nit = selectedNames.begin(); nit != selectedNames.end(); ++nit) { - if (strcmp(*nit,(*it)->getNameInDocument()) == 0) { - item->setSelected(true); - break; - } + 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(); + } + } + } +} + +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); + 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); + else + item = new QTreeWidgetItem(ui->treeWidget); + item->setIcon(0, vp->getIcon()); + item->setText(0, QString::fromUtf8((obj)->Label.getValue())); + item->setData(0, Qt::UserRole, QByteArray(obj->getNameInDocument())); + item->setData(0, Qt::UserRole+1, QByteArray(obj->getDocument()->getName())); + if(link.size()>=5) { + item->setChildIndicatorPolicy(obj->hasChildElement()||vp->getChildRoot()? + QTreeWidgetItem::ShowIndicator:QTreeWidgetItem::DontShowIndicator); + } + return item; +} + +void DlgPropertyLink::onItemExpanded(QTreeWidgetItem * item) { + if(link.size()<5 || 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()); + if(!obj) return; + auto vp = Application::Instance->getViewProvider(obj); + if(!vp) return; + for(auto obj : vp->claimChildren()) + createItem(obj,item); } } void DlgPropertyLink::on_checkObjectType_toggled(bool on) { - ui->listWidget->clear(); - findObjects(on, ui->searchBox->text()); + ui->typeTree->setVisible(on); + findObjects(); } -void DlgPropertyLink::on_searchBox_textChanged(const QString& search) -{ - ui->listWidget->clear(); - bool on = ui->checkObjectType->isChecked(); - findObjects(on, search); +void DlgPropertyLink::on_typeTree_itemSelectionChanged() { + types.clear(); + for(auto item : ui->typeTree->selectedItems()) + types.insert(item->text(0).toLatin1().constData()); + findObjects(); } +void DlgPropertyLink::on_searchBox_textChanged(const QString& /*search*/) +{ + findObjects(); +} + +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 8fc5109bd3..e156a68dfd 100644 --- a/src/Gui/DlgPropertyLink.h +++ b/src/Gui/DlgPropertyLink.h @@ -27,6 +27,8 @@ #include #include +#define FC_XLINK_VALUE_INDEX 5 + namespace Gui { namespace Dialog { class Ui_DlgPropertyLink; @@ -35,7 +37,7 @@ class DlgPropertyLink : public QDialog Q_OBJECT public: - DlgPropertyLink(const QStringList& list, QWidget* parent = 0, Qt::WindowFlags fl = 0); + DlgPropertyLink(const QStringList& list, QWidget* parent = 0, Qt::WindowFlags fl = 0, bool xlink=false); ~DlgPropertyLink(); void setSelectionMode(QAbstractItemView::SelectionMode mode); @@ -45,14 +47,21 @@ public: 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); private: - void findObjects(bool on, const QString& searchText); + QTreeWidgetItem *createItem(App::DocumentObject *obj, QTreeWidgetItem *parent); + void findObjects(); private: QStringList link; Ui_DlgPropertyLink* ui; + std::set inList; + std::set types; + bool refreshTypes = true; }; } // namespace Dialog diff --git a/src/Gui/DlgPropertyLink.ui b/src/Gui/DlgPropertyLink.ui index 0766b5cad4..133e3f47ed 100644 --- a/src/Gui/DlgPropertyLink.ui +++ b/src/Gui/DlgPropertyLink.ui @@ -6,15 +6,37 @@ 0 0 - 257 - 428 + 436 + 438 Link + + + + Filter by type + + + + + + true + + + false + + + + 1 + + + + + @@ -32,23 +54,34 @@ - - - - Show all object types - - - - - - - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + QAbstractItemView::ExtendedSelection + + + false + + + false + + + + 1 + + + + diff --git a/src/Gui/ExpressionBinding.cpp b/src/Gui/ExpressionBinding.cpp index 1bd8bbaa84..ef738e0410 100644 --- a/src/Gui/ExpressionBinding.cpp +++ b/src/Gui/ExpressionBinding.cpp @@ -30,10 +30,14 @@ #include #include #include +#include #include #include +#include #include +FC_LOG_LEVEL_INIT("Expression",true,true) + using namespace Gui; using namespace App; @@ -67,10 +71,22 @@ void Gui::ExpressionBinding::setExpression(boost::shared_ptr expr) } lastExpression = getExpression(); + + bool transaction = !App::GetApplication().getActiveTransaction(); + if(transaction) { + std::ostringstream ss; + ss << (expr?"Set":"Discard") << " expression " << docObj->Label.getValue(); + App::GetApplication().setActiveTransaction(ss.str().c_str()); + } + docObj->ExpressionEngine.setValue(path, expr); - + if(m_autoApply) apply(); + + if(transaction) + App::GetApplication().closeActiveTransaction(); + } void ExpressionBinding::bind(const App::ObjectIdentifier &_path) @@ -105,17 +121,34 @@ boost::shared_ptr ExpressionBinding::getExpression() const return docObj->getExpression(path).expression; } -std::string ExpressionBinding::getExpressionString() const +std::string ExpressionBinding::getExpressionString(bool no_throw) const { - if (!getExpression()) - throw Base::RuntimeError("No expression found."); - - return getExpression()->toString(); + try { + if (!getExpression()) + throw Base::RuntimeError("No expression found."); + return getExpression()->toString(); + } catch (Base::Exception &e) { + if(no_throw) + FC_ERR("failed to get expression string: " << e.what()); + else + throw; + } catch (std::exception &e) { + if(no_throw) + FC_ERR("failed to get expression string: " << e.what()); + else + throw; + } catch (...) { + if(no_throw) + FC_ERR("failed to get expression string: unknown exception"); + else + throw; + } + return std::string(); } std::string ExpressionBinding::getEscapedExpressionString() const { - return Base::Tools::escapedUnicodeFromUtf8(getExpressionString().c_str()); + return Base::Tools::escapedUnicodeFromUtf8(getExpressionString(false).c_str()); } QPixmap ExpressionBinding::getIcon(const char* name, const QSize& size) const @@ -142,11 +175,20 @@ bool ExpressionBinding::apply(const std::string & propName) if (!docObj) throw Base::RuntimeError("Document object not found."); + + bool transaction = !App::GetApplication().getActiveTransaction(); + if(transaction) { + std::ostringstream ss; + ss << "Set expression " << docObj->Label.getValue(); + App::GetApplication().setActiveTransaction(ss.str().c_str()); + } Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').%s.setExpression('%s', u'%s')", docObj->getDocument()->getName(), docObj->getNameInDocument(), path.toEscapedString().c_str(), getEscapedExpressionString().c_str()); + if(transaction) + App::GetApplication().closeActiveTransaction(); return true; } else { @@ -156,11 +198,20 @@ bool ExpressionBinding::apply(const std::string & propName) if (!docObj) throw Base::RuntimeError("Document object not found."); - if (lastExpression) + if (lastExpression) { + bool transaction = !App::GetApplication().getActiveTransaction(); + if(transaction) { + std::ostringstream ss; + ss << "Discard expression " << docObj->Label.getValue(); + App::GetApplication().setActiveTransaction(ss.str().c_str()); + } Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').%s.setExpression('%s', None)", docObj->getDocument()->getName(), docObj->getNameInDocument(), path.toEscapedString().c_str()); + if(transaction) + App::GetApplication().closeActiveTransaction(); + } } return false; @@ -183,9 +234,7 @@ bool ExpressionBinding::apply() if (prop->isReadOnly()) return true; - std::string name = docObj->getNameInDocument(); - - return apply("App.ActiveDocument." + name + "." + getPath().toEscapedString()); + return apply(Gui::Command::getObjectCmd(docObj) + "." + getPath().toEscapedString()); } void ExpressionBinding::expressionChange(const ObjectIdentifier& id) { diff --git a/src/Gui/ExpressionBinding.h b/src/Gui/ExpressionBinding.h index 7650737515..4aac7a84a6 100644 --- a/src/Gui/ExpressionBinding.h +++ b/src/Gui/ExpressionBinding.h @@ -58,7 +58,7 @@ public: protected: const App::ObjectIdentifier & getPath() const { return path; } boost::shared_ptr getExpression() const; - std::string getExpressionString() const; + std::string getExpressionString(bool no_throw=true) const; std::string getEscapedExpressionString() const; virtual void setExpression(boost::shared_ptr expr); diff --git a/src/Gui/ExpressionCompleter.cpp b/src/Gui/ExpressionCompleter.cpp index db8c502f80..971b32e15c 100644 --- a/src/Gui/ExpressionCompleter.cpp +++ b/src/Gui/ExpressionCompleter.cpp @@ -5,21 +5,292 @@ #include #include #include +#include #endif +#include + #include +#include #include #include #include +#include #include #include "ExpressionCompleter.h" +#include #include +FC_LOG_LEVEL_INIT("Completer",true,true); + Q_DECLARE_METATYPE(App::ObjectIdentifier); using namespace App; using namespace Gui; +class ExpressionCompleterModel: public QAbstractItemModel { +public: + ExpressionCompleterModel(QObject *parent, const App::DocumentObject *obj) + :QAbstractItemModel(parent) + { + if(obj) { + currentDoc = obj->getDocument()->getName(); + currentObj = obj->getNameInDocument(); + inList = obj->getInListEx(true); + } + } + + // This ExpressionCompleter model works without any pysical items. + // Everything item related is stored inside QModelIndex.InternalPointer/InternalId(), + // using the following Info structure. + // + // The Info contains two indices, one for document and the other for object. + // For 32-bit system, the index is 16bit which limits the size to 64K. For + // 64-bit system, the index is 32bit. + // + // The "virtual" items are organized as a tree. The root items are special, + // which consists of three types in the following order, + // + // * Document, even index contains item using document's name, while + // odd index with quoted document label. + // * Objects of the current document, even index with object's internal + // name, and odd index with quoted object label. + // * Properties of the current object. + // + // Document item contains object item as child, and object item contains + // property item. + // + // The QModelIndex of a root item has both the doc field and obj field set + // to -1, and uses the row as the item index. We can figure out the type of + // the item solely based on this row index. + // + // QModelIndex of a non-root object item has doc field as the document + // index, and obj field set to -1. + // + // QModelIndex of a non-root property item has doc field as the document + // index, and obj field as the object index. + union Info { + struct { + qint32 doc; + qint32 obj; + }d; + struct { + qint16 doc; + qint16 obj; + }d32; + void *ptr; + }; + + static void *infoId(const Info &info) { + if(sizeof(void*) >= sizeof(info)) + return info.ptr; + + Info info32; + info32.d32.doc = (qint16)info.d.doc; + info32.d32.obj = (qint16)info.d.obj; + return info32.ptr; + }; + + static Info getInfo(const QModelIndex &index) { + Info info; + info.ptr = index.internalPointer(); + if(sizeof(void*) >= sizeof(Info)) + return info; + Info res; + res.d.doc = info.d32.doc; + res.d.obj = info.d32.obj; + return res; + } + + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const { + if(role!=Qt::EditRole && role!=Qt::DisplayRole && role!=Qt::UserRole) + return QVariant(); + QVariant v; + Info info = getInfo(index); + _data(info,index.row(),&v,0,role==Qt::UserRole); + FC_TRACE(info.d.doc << "," << info.d.obj << "," << index.row() + << ": " << v.toString().toUtf8().constData()); + return v; + } + + void _data(const Info &info, int row, QVariant *v, int *count, bool sep=false) const { + int idx; + idx = info.d.doc<0?row:info.d.doc; + const auto &docs = App::GetApplication().getDocuments(); + int docSize = (int)docs.size()*2; + int objSize = 0; + int propSize = 0; + std::vector props; + App::Document *doc = 0; + App::DocumentObject *obj = 0; + App::Property *prop = 0; + if(idx>=0 && idxgetObjects(); + objSize = (int)objs.size()*2; + if(idx>=0 && idxgetObject(currentObj.c_str()); + if(cobj) { + idx -= objSize; + if(info.d.doc<0) + row = idx; + cobj->getPropertyList(props); + propSize = (int)props.size(); + if(idx >= propSize) + return; + if(idx>=0) { + obj = cobj; + prop = props[idx]; + } + } + } + } + if(info.d.doc<0) { + if(count) + *count = docSize + objSize + propSize; + if(idx>=0 && v) { + QString res; + if(prop) + res = QString::fromLatin1(prop->getName()); + else if(obj) { + if(idx & 1) + res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str()); + else + res = QString::fromLatin1(obj->getNameInDocument()); + if(sep) + res += QLatin1Char('.'); + }else { + if(idx & 1) + res = QString::fromUtf8(quote(doc->Label.getStrValue()).c_str()); + else + res = QString::fromLatin1(doc->getName()); + if(sep) + res += QLatin1Char('#'); + } + v->setValue(res); + } + return; + } + + if(!obj) { + idx = info.d.obj<0?row:info.d.obj; + const auto &objs = doc->getObjects(); + objSize = (int)objs.size()*2; + if(idx<0 || idx>=objSize || inList.count(obj)) + return; + obj = objs[idx/2]; + if(info.d.obj<0) { + if(count) + *count = objSize; + if(v) { + QString res; + if(idx&1) + res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str()); + else + res = QString::fromLatin1(obj->getNameInDocument()); + if(sep) + res += QLatin1Char('.'); + v->setValue(res); + } + return; + } + } + + if(!prop) { + idx = row; + obj->getPropertyList(props); + propSize = (int)props.size(); + if(idx<0 || idx>=propSize) + return; + prop = props[idx]; + if(count) + *count = propSize; + } + if(v) + *v = QString::fromLatin1(prop->getName()); + return; + } + + QModelIndex parent(const QModelIndex & index) const { + if(!index.isValid()) + return QModelIndex(); + Info info; + Info parentInfo; + info = parentInfo = getInfo(index); + if(info.d.obj>=0) { + parentInfo.d.obj = -1; + return createIndex(info.d.obj,0,infoId(parentInfo)); + } + if(info.d.doc>=0) { + parentInfo.d.doc = -1; + return createIndex(info.d.doc,0,infoId(parentInfo)); + } + return QModelIndex(); + } + + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const { + if(row<0) + return QModelIndex(); + Info info; + if(!parent.isValid()) { + info.d.doc = -1; + info.d.obj = -1; + }else{ + info = getInfo(parent); + if(info.d.doc<=0) + info.d.doc = parent.row(); + else if(info.d.obj<=0) + info.d.obj = parent.row(); + else + return QModelIndex(); + } + return createIndex(row,column,infoId(info)); + } + + int rowCount(const QModelIndex & parent = QModelIndex()) const { + const auto &docs = App::GetApplication().getDocuments(); + Info info; + int row = 0; + if(!parent.isValid()) { + info.d.doc = -1; + info.d.obj = -1; + row = -1; + }else{ + info = getInfo(parent); + if(info.d.doc<0) + info.d.doc = parent.row(); + else if(info.d.obj<0) + info.d.obj = parent.row(); + else + return 0; + } + int count = 0; + _data(info,row,0,&count); + FC_TRACE(info.d.doc << "," << info.d.obj << "," << row << " row count " << count); + return count; + } + + int columnCount(const QModelIndex &) const { + return 1; + } + +private: + std::set inList; + std::string currentDoc; + std::string currentObj; +}; + /** * @brief Construct an ExpressionCompleter object. * @param currentDoc Current document to generate the model from. @@ -27,208 +298,88 @@ using namespace Gui; * @param parent Parent object owning the completer. */ -ExpressionCompleter::ExpressionCompleter(const App::Document * currentDoc, const App::DocumentObject * currentDocObj, QObject *parent) - : QCompleter(parent), prefixStart(0) +ExpressionCompleter::ExpressionCompleter(const App::DocumentObject * currentDocObj, QObject *parent) + : QCompleter(parent), prefixStart(0), currentObj(currentDocObj) { - QStandardItemModel* model = new QStandardItemModel(this); - - std::vector docs = App::GetApplication().getDocuments(); - std::vector::const_iterator di = docs.begin(); - - std::vector deps; - if (currentDocObj) - deps = currentDocObj->getInList(); - std::set forbidden; - - for (std::vector::const_iterator it = deps.begin(); it != deps.end(); ++it) - forbidden.insert(*it); - - /* Create tree with full path to all objects */ - while (di != docs.end()) { - QStandardItem* docItem = new QStandardItem(QString::fromLatin1((*di)->getName())); - - docItem->setData(QString::fromLatin1((*di)->getName()) + QString::fromLatin1("#"), Qt::UserRole); - createModelForDocument(*di, docItem, forbidden); - - model->appendRow(docItem); - - ++di; - } - - /* Create branch with current document object */ - - if (currentDocObj) { - createModelForDocument(currentDocObj->getDocument(), model->invisibleRootItem(), forbidden); - createModelForDocumentObject(currentDocObj, model->invisibleRootItem()); - } - else { - if (currentDoc) - createModelForDocument(currentDoc, model->invisibleRootItem(), forbidden); - } - - setModel(model); - setCaseSensitivity(Qt::CaseInsensitive); } -/** - * @brief Create model node given document, the parent and forbidden node. - * @param doc Document - * @param parent Parent item - * @param forbidden Forbidden document objects; typically those that will create a loop in the DAG if used. - */ +void ExpressionCompleter::init() { + if(model()) + return; -void ExpressionCompleter::createModelForDocument(const App::Document * doc, QStandardItem * parent, - const std::set & forbidden) { - std::vector docObjs = doc->getObjects(); - std::vector::const_iterator doi = docObjs.begin(); - - while (doi != docObjs.end()) { - std::set::const_iterator it = forbidden.find(*doi); - - // Skip? - if (it != forbidden.end()) { - ++doi; - continue; - } - - QStandardItem* docObjItem = new QStandardItem(QString::fromLatin1((*doi)->getNameInDocument())); - - docObjItem->setData(QString::fromLatin1((*doi)->getNameInDocument()) + QString::fromLatin1("."), Qt::UserRole); - createModelForDocumentObject(*doi, docObjItem); - parent->appendRow(docObjItem); - - if (strcmp((*doi)->getNameInDocument(), (*doi)->Label.getValue()) != 0) { - std::string label = (*doi)->Label.getValue(); - - if (!ExpressionParser::isTokenAnIndentifier(label)) - label = quote(label); - - docObjItem = new QStandardItem(QString::fromUtf8(label.c_str())); - - docObjItem->setData( QString::fromUtf8(label.c_str()) + QString::fromLatin1("."), Qt::UserRole); - createModelForDocumentObject(*doi, docObjItem); - parent->appendRow(docObjItem); - } - - ++doi; - } + setModel(new ExpressionCompleterModel(this,currentObj.getObject())); } -/** - * @brief Create model nodes for document object - * @param docObj Document object - * @param parent Parent item - */ - -void ExpressionCompleter::createModelForDocumentObject(const DocumentObject * docObj, QStandardItem * parent) -{ - std::vector props; - docObj->getPropertyList(props); - - std::vector::const_iterator pi = props.begin(); - while (pi != props.end()) { - - // Skip all types of links - if ((*pi)->isDerivedFrom(App::PropertyLink::getClassTypeId()) || - (*pi)->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) { - ++pi; - continue; - } - - createModelForPaths(*pi, parent); - ++pi; - } -} - -/** - * @brief Create nodes for a property. - * @param prop - * @param docObjItem - */ - -void ExpressionCompleter::createModelForPaths(const App::Property * prop, QStandardItem *docObjItem) -{ - std::vector paths; - std::vector::const_iterator ppi; - - prop->getPaths(paths); - - for (ppi = paths.begin(); ppi != paths.end(); ++ppi) { - QStandardItem* pathItem = new QStandardItem(Base::Tools::fromStdString(ppi->toString())); - - QVariant value; - - value.setValue(*ppi); - pathItem->setData(value, Qt::UserRole); - - docObjItem->appendRow(pathItem); - } -} QString ExpressionCompleter::pathFromIndex ( const QModelIndex & index ) const { - QStandardItemModel * m = static_cast(model()); - - if (m->data(index, Qt::UserRole).canConvert()) { - App::ObjectIdentifier p = m->data(index, Qt::UserRole).value(); - QString pStr = Base::Tools::fromStdString(p.toString()); - - QString parentStr; - QModelIndex parent = index.parent(); - while (parent.isValid()) { - QString thisParentStr = m->data(parent, Qt::UserRole).toString(); - - parentStr = thisParentStr + parentStr; - - parent = parent.parent(); - } - - return parentStr + pStr; - } - else if (m->data(index, Qt::UserRole).canConvert()) { - QModelIndex parent = index; - QString parentStr; - - while (parent.isValid()) { - QString thisParentStr = m->data(parent, Qt::UserRole).toString(); - - parentStr = thisParentStr + parentStr; - - parent = parent.parent(); - } - - return parentStr; - } - else + auto m = model(); + if(!m || !index.isValid()) return QString(); + + QString res; + auto parent = index; + do { + res = m->data(parent, Qt::UserRole).toString() + res; + parent = parent.parent(); + }while(parent.isValid()); + + auto info = ExpressionCompleterModel::getInfo(index); + FC_TRACE("join path " << info.d.doc << "," << info.d.obj << "," << index.row() + << ": " << res.toUtf8().constData()); + return res; } -QStringList ExpressionCompleter::splitPath ( const QString & path ) const +QStringList ExpressionCompleter::splitPath ( const QString & input ) const { - try { - App::ObjectIdentifier p = ObjectIdentifier::parse(0, path.toUtf8().constData()); - QStringList l; + QStringList l; + std::string path = input.toUtf8().constData(); + if(path.empty()) + return l; + + int retry = 0; + std::string trim; + while(1) { + try { + App::ObjectIdentifier p = ObjectIdentifier::parse( + currentObj.getObject(), path); - if (p.getProperty()) { - for (int i = 0; i < p.numComponents(); ++i) - l << Base::Tools::fromStdString(p.getPropertyComponent(i).toString()); - return l; - } - else { std::vector sl = p.getStringList(); std::vector::const_iterator sli = sl.begin(); - + if(retry && sl.size()) + sl.pop_back(); + if(trim.size() && boost::ends_with(sl.back(),trim)) + sl.back().resize(sl.back().size()-trim.size()); while (sli != sl.end()) { l << Base::Tools::fromStdString(*sli); ++sli; } - + FC_TRACE("split path " << path + << " -> " << l.join(QLatin1String("/")).toUtf8().constData()); return l; } - } - catch (const Base::Exception &) { - return QStringList() << path; + catch (const Base::Exception &e) { + FC_TRACE("split path " << path << " error: " << e.what()); + if(!retry) { + char last = path[path.size()-1]; + if(last!='#' && last!='.' && path.find('#')!=std::string::npos) { + path += "._self"; + ++retry; + continue; + } + }else if(retry==1) { + path.resize(path.size()-6); + char last = path[path.size()-1]; + if(last!='.' && last!='<' && path.find("#<<")!=std::string::npos) { + path += ">>._self"; + ++retry; + trim = ">>"; + continue; + } + } + return QStringList() << input; + } } } @@ -292,7 +443,7 @@ void ExpressionCompleter::slotUpdate(const QString & prefix) ExpressionLineEdit::ExpressionLineEdit(QWidget *parent) : QLineEdit(parent) , completer(0) - , block(false) + , block(true) { connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(slotTextChanged(const QString&))); } @@ -305,7 +456,7 @@ void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDo } if (currentDocObj != 0) { - completer = new ExpressionCompleter(currentDocObj->getDocument(), currentDocObj, this); + completer = new ExpressionCompleter(currentDocObj, this); completer->setWidget(this); completer->setCaseSensitivity(Qt::CaseInsensitive); connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString))); @@ -338,10 +489,79 @@ void ExpressionLineEdit::slotCompleteText(const QString & completionPrefix) QString before(text().left(start)); QString after(text().mid(cursorPosition())); - block = true; + Base::FlagToggler flag(block,false); setText(before + completionPrefix + after); setCursorPosition(QString(before + completionPrefix).length()); - block = false; +} + +void ExpressionLineEdit::keyPressEvent(QKeyEvent *e) { + Base::FlagToggler flag(block,true); + QLineEdit::keyPressEvent(e); +} + + +/////////////////////////////////////////////////////////////////////// + +ExpressionTextEdit::ExpressionTextEdit(QWidget *parent) + : QPlainTextEdit(parent) + , completer(0) + , block(true) +{ + connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); +} + +void ExpressionTextEdit::setDocumentObject(const App::DocumentObject * currentDocObj) +{ + if (completer) { + delete completer; + completer = 0; + } + + if (currentDocObj != 0) { + completer = new ExpressionCompleter(currentDocObj, this); + completer->setWidget(this); + completer->setCaseSensitivity(Qt::CaseInsensitive); + connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString))); + connect(completer, SIGNAL(highlighted(QString)), this, SLOT(slotCompleteText(QString))); + connect(this, SIGNAL(textChanged2(QString)), completer, SLOT(slotUpdate(QString))); + } +} + +bool ExpressionTextEdit::completerActive() const +{ + return completer && completer->popup() && completer->popup()->isVisible(); +} + +void ExpressionTextEdit::hideCompleter() +{ + if (completer && completer->popup()) + completer->popup()->setVisible(false); +} + +void ExpressionTextEdit::slotTextChanged() +{ + if (!block) { + QTextCursor cursor = textCursor(); + Q_EMIT textChanged2(cursor.block().text().left(cursor.positionInBlock())); + } +} + +void ExpressionTextEdit::slotCompleteText(const QString & completionPrefix) +{ + QTextCursor cursor = textCursor(); + int start = completer->getPrefixStart(); + int pos = cursor.positionInBlock(); + if(pos>=start) { + Base::FlagToggler flag(block,false); + if(pos>start) + cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor,pos-start); + cursor.insertText(completionPrefix); + } +} + +void ExpressionTextEdit::keyPressEvent(QKeyEvent *e) { + Base::FlagToggler flag(block,true); + QPlainTextEdit::keyPressEvent(e); } #include "moc_ExpressionCompleter.cpp" diff --git a/src/Gui/ExpressionCompleter.h b/src/Gui/ExpressionCompleter.h index e5911d1379..77ed9ecbe2 100644 --- a/src/Gui/ExpressionCompleter.h +++ b/src/Gui/ExpressionCompleter.h @@ -4,7 +4,10 @@ #include #include #include +#include #include +#include +#include class QStandardItem; @@ -25,7 +28,7 @@ class GuiExport ExpressionCompleter : public QCompleter { Q_OBJECT public: - ExpressionCompleter(const App::Document * currentDoc, const App::DocumentObject * currentDocObj, QObject *parent = 0); + ExpressionCompleter(const App::DocumentObject * currentDocObj, QObject *parent = 0); int getPrefixStart() const { return prefixStart; } @@ -33,14 +36,12 @@ public Q_SLOTS: void slotUpdate(const QString &prefix); private: - void createModelForDocument(const App::Document * doc, QStandardItem * parent, const std::set &forbidden); - void createModelForDocumentObject(const App::DocumentObject * docObj, QStandardItem * parent); - void createModelForPaths(const App::Property * prop, QStandardItem *docObjItem); - + void init(); virtual QString pathFromIndex ( const QModelIndex & index ) const; virtual QStringList splitPath ( const QString & path ) const; int prefixStart; + App::DocumentObjectT currentObj; }; @@ -56,6 +57,27 @@ Q_SIGNALS: public Q_SLOTS: void slotTextChanged(const QString & text); void slotCompleteText(const QString & completionPrefix); +protected: + void keyPressEvent(QKeyEvent * event); +private: + ExpressionCompleter * completer; + bool block; +}; + +class GuiExport ExpressionTextEdit : public QPlainTextEdit { + Q_OBJECT +public: + ExpressionTextEdit(QWidget *parent = 0); + void setDocumentObject(const App::DocumentObject *currentDocObj); + bool completerActive() const; + void hideCompleter(); +protected: + void keyPressEvent(QKeyEvent * event); +Q_SIGNALS: + void textChanged2(QString text); +public Q_SLOTS: + void slotTextChanged(); + void slotCompleteText(const QString & completionPrefix); private: ExpressionCompleter * completer; bool block; diff --git a/src/Gui/PropertyView.cpp b/src/Gui/PropertyView.cpp index fed192f12d..ba4daa0554 100644 --- a/src/Gui/PropertyView.cpp +++ b/src/Gui/PropertyView.cpp @@ -26,6 +26,7 @@ # include # include # include +# include #endif #include @@ -38,12 +39,17 @@ #include #include #include +#include #include "PropertyView.h" #include "Application.h" +#include "MainWindow.h" #include "Document.h" #include "BitmapFactory.h" #include "ViewProvider.h" +#include "ViewProviderDocumentObject.h" +#include "Tree.h" +#include "ViewParams.h" #include "propertyeditor/PropertyEditor.h" @@ -52,6 +58,14 @@ using namespace Gui; using namespace Gui::DockWnd; using namespace Gui::PropertyEditor; +static ParameterGrp::handle _GetParam() { + static ParameterGrp::handle hGrp; + if(!hGrp) { + hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/PropertyView"); + } + return hGrp; +} /* TRANSLATOR Gui::PropertyView */ @@ -61,12 +75,16 @@ using namespace Gui::PropertyEditor; * in two tabs. */ PropertyView::PropertyView(QWidget *parent) - : QWidget(parent) + : QWidget(parent),SelectionObserver(false) { QGridLayout* pLayout = new QGridLayout( this ); pLayout->setSpacing(0); pLayout->setMargin (0); + timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), this, SLOT(onTimer())); + tabs = new QTabWidget (this); tabs->setObjectName(QString::fromUtf8("propertyTab")); tabs->setTabPosition(QTabWidget::South); @@ -83,14 +101,10 @@ PropertyView::PropertyView(QWidget *parent) propertyEditorData->setAutomaticDocumentUpdate(true); tabs->addTab(propertyEditorData, tr("Data")); - ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("PropertyView"); - if ( hGrp ) { - int preferredTab = hGrp->GetInt("LastTabIndex", 1); + int preferredTab = _GetParam()->GetInt("LastTabIndex", 1); - if ( preferredTab > 0 && preferredTab < tabs->count() ) - tabs->setCurrentIndex(preferredTab); - } + if ( preferredTab > 0 && preferredTab < tabs->count() ) + tabs->setCurrentIndex(preferredTab); // connect after adding all tabs, so adding doesn't thrash the parameter connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); @@ -109,10 +123,19 @@ PropertyView::PropertyView(QWidget *parent) (&PropertyView::slotRemoveDynamicProperty, this, _1)); this->connectPropChange = App::GetApplication().signalChangePropertyEditor.connect(boost::bind - (&PropertyView::slotChangePropertyEditor, this, _1)); + (&PropertyView::slotChangePropertyEditor, this, _1, _2)); + this->connectUndoDocument = + App::GetApplication().signalUndoDocument.connect(boost::bind + (&PropertyView::slotRollback, this)); + this->connectRedoDocument = + App::GetApplication().signalRedoDocument.connect(boost::bind + (&PropertyView::slotRollback, this)); this->connectActiveDoc = Application::Instance->signalActiveDocument.connect(boost::bind (&PropertyView::slotActiveDocument, this, _1)); + this->connectDelDocument = + Application::Instance->signalDeleteDocument.connect( + boost::bind(&PropertyView::slotDeleteDocument, this, _1)); } PropertyView::~PropertyView() @@ -122,7 +145,60 @@ PropertyView::~PropertyView() this->connectPropAppend.disconnect(); this->connectPropRemove.disconnect(); this->connectPropChange.disconnect(); + this->connectUndoDocument.disconnect(); + this->connectRedoDocument.disconnect(); this->connectActiveDoc.disconnect(); + this->connectDelDocument.disconnect(); +} + +static bool _ShowAll; + +bool PropertyView::showAll() { + return _ShowAll; +} + +void PropertyView::setShowAll(bool enable) { + if(_ShowAll != enable) { + _ShowAll = enable; + for(auto view : getMainWindow()->findChildren()) { + if(view->isVisible()) + view->onTimer(); + } + } +} + +void PropertyView::hideEvent(QHideEvent *ev) { + this->timer->stop(); + this->detachSelection(); + // clear the properties before hiding. + propertyEditorData->buildUp(); + propertyEditorView->buildUp(); + clearPropertyItemSelection(); + QWidget::hideEvent(ev); +} + +void PropertyView::showEvent(QShowEvent *ev) { + this->attachSelection(); + this->timer->start(100); + QWidget::showEvent(ev); +} + +void PropertyView::clearPropertyItemSelection() { + QModelIndex index; + propertyEditorData->clearSelection(); + propertyEditorData->setCurrentIndex(index); + propertyEditorView->clearSelection(); + propertyEditorView->setCurrentIndex(index); +} + +void PropertyView::slotRollback() { + // PropertyItemDelegate will setup application active transaction on + // entering edit mode, and close active transaction when exit editing. But, + // when the user clicks undo/redo button while editing some property, the + // current active transaction will be closed by design, which cause further + // editing to be not recorded. Hence, we force unselect any property item on + // undo/redo + clearPropertyItemSelection(); } void PropertyView::slotChangePropertyData(const App::DocumentObject&, const App::Property& prop) @@ -135,32 +211,33 @@ void PropertyView::slotChangePropertyView(const Gui::ViewProvider&, const App::P propertyEditorView->updateProperty(prop); } +bool PropertyView::isPropertyHidden(const App::Property *prop) { + return prop && !showAll() && + ((prop->getType() & App::Prop_Hidden) || prop->testStatus(App::Property::Hidden)); +} + void PropertyView::slotAppendDynamicProperty(const App::Property& prop) { - App::PropertyContainer* parent = prop.getContainer(); - if (parent->isHidden(&prop)) + if (isPropertyHidden(&prop)) return; - if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - propertyEditorData->appendProperty(prop); - } - else if (parent->isDerivedFrom(Gui::ViewProvider::getClassTypeId())) { - propertyEditorView->appendProperty(prop); + if (propertyEditorData->appendProperty(prop) + || propertyEditorView->appendProperty(prop)) + { + timer->start(100); } } void PropertyView::slotRemoveDynamicProperty(const App::Property& prop) { App::PropertyContainer* parent = prop.getContainer(); - if (parent && parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { + if(propertyEditorData->propOwners.count(parent)) propertyEditorData->removeProperty(prop); - } - else if (parent && parent->isDerivedFrom(Gui::ViewProvider::getClassTypeId())) { + else if(propertyEditorView->propOwners.count(parent)) propertyEditorView->removeProperty(prop); - } } -void PropertyView::slotChangePropertyEditor(const App::Property& prop) +void PropertyView::slotChangePropertyEditor(const App::Document &, const App::Property& prop) { App::PropertyContainer* parent = prop.getContainer(); if (parent && parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { @@ -171,25 +248,27 @@ void PropertyView::slotChangePropertyEditor(const App::Property& prop) } } +void PropertyView::slotDeleteDocument(const Gui::Document &doc) { + if(propertyEditorData->propOwners.count(doc.getDocument())) { + propertyEditorView->buildUp(); + propertyEditorData->buildUp(); + clearPropertyItemSelection(); + } +} + void PropertyView::slotActiveDocument(const Gui::Document &doc) { - // allow to disable the auto-deactivation - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - bool enableEditor = hGrp->GetBool("EnablePropertyViewForInactiveDocument", false); - if (enableEditor) { + checkEnable(doc.getDocument()->getName()); +} + +void PropertyView::checkEnable(const char *doc) { + if(ViewParams::instance()->getEnablePropertyViewForInactiveDocument()) { setEnabled(true); return; } - // check if at least one selected object is part of the active document - std::vector array = Gui::Selection().getCompleteSelection(); - for (std::vector::const_iterator it = array.begin(); it != array.end(); ++it) { - if (Gui::Application::Instance->getDocument(it->pDoc) == &doc) { - enableEditor = true; - break; - } - } - setEnabled(enableEditor || array.empty()); + setEnabled(!Selection().hasSelection() + || Selection().hasSelection(doc,false)); } struct PropertyView::PropInfo @@ -217,53 +296,119 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg) msg.Type != SelectionChanges::ClrSelection) return; - // allow to disable the auto-deactivation - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - bool enableEditor = hGrp->GetBool("EnablePropertyViewForInactiveDocument", false); - Gui::Document *activeDoc = Application::Instance->activeDocument(); + // clear the properties. + propertyEditorData->buildUp(); + propertyEditorView->buildUp(); + clearPropertyItemSelection(); + timer->start(100); +} + +void PropertyView::onTimer() { + + propertyEditorData->buildUp(); + propertyEditorView->buildUp(); + clearPropertyItemSelection(); + timer->stop(); + + if(!Gui::Selection().hasSelection()) { + auto gdoc = TreeWidget::selectedDocument(); + if(!gdoc || !gdoc->getDocument()) + return; + + PropertyModel::PropertyList docProps; + + auto doc = gdoc->getDocument(); + std::vector props; + doc->getPropertyList(props); + for(auto prop : props) + docProps.emplace_back(prop->getName(), + std::vector(1,prop)); + propertyEditorData->buildUp(std::move(docProps)); + tabs->setCurrentIndex(1); + return; + } + + std::set objSet; // group the properties by std::vector propDataMap; std::vector propViewMap; - std::vector array = Gui::Selection().getCompleteSelection(); - for (std::vector::const_iterator it = array.begin(); it != array.end(); ++it) { - App::DocumentObject *ob=0; - ViewProvider *vp=0; + bool checkLink = true; + ViewProviderDocumentObject *vpLast = 0; + const auto &array = Gui::Selection().getCompleteSelection(false); + for(auto &sel : array) { + if(!sel.pObject) continue; + App::DocumentObject *parent = 0; + App::DocumentObject *ob = sel.pObject->resolve(sel.SubName,&parent); + if(!ob) continue; + + // App::Link should be able to handle special case below now, besides, the new + // support of plain group in App::Link breaks because of the code below +#if 0 + if(parent) { + auto parentVp = Application::Instance->getViewProvider(parent); + if(parentVp) { + // For special case where the SubName reference can resolve to + // a non-child object (e.g. link array element), the tree view + // will select the parent instead. So we shall show the + // property of the parent as well. + bool found = false; + for(auto child : parentVp->claimChildren()) { + if(ob == child) { + found = true; + break; + } + } + if(!found) + ob = parent; + } + } +#endif + + // Do not process an object more than once + if(!objSet.insert(ob).second) + continue; std::vector dataList; std::map viewList; - if ((*it).pObject) { - (*it).pObject->getPropertyList(dataList); - ob = (*it).pObject; - // get also the properties of the associated view provider - Gui::Document* doc = Gui::Application::Instance->getDocument(it->pDoc); - vp = doc->getViewProvider((*it).pObject); - if(!vp) continue; - // get the properties as map here because it doesn't matter to have them sorted alphabetically - vp->getPropertyMap(viewList); - if (activeDoc == doc) { - enableEditor = true; - } + auto vp = Application::Instance->getViewProvider(ob); + if(!vp) { + checkLink = false; + ob->getPropertyList(dataList); + continue; } + if(vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + auto cvp = static_cast(vp); + if(vpLast && cvp!=vpLast) + checkLink = false; + vpLast = cvp; + } + + ob->getPropertyList(dataList); + + // get the properties as map here because it doesn't matter to have them sorted alphabetically + vp->getPropertyMap(viewList); + // store the properties with as key in a map std::vector::iterator pt; if (ob) { for (pt = dataList.begin(); pt != dataList.end(); ++pt) { + if (isPropertyHidden(*pt)) + continue; + PropInfo nameType; - nameType.propName = ob->getPropertyName(*pt); + nameType.propName = (*pt)->getName(); nameType.propId = (*pt)->getTypeId().getKey(); - if (!ob->isHidden(*pt)) { - std::vector::iterator pi = std::find_if(propDataMap.begin(), propDataMap.end(), PropFind(nameType)); - if (pi != propDataMap.end()) { - pi->propList.push_back(*pt); - } - else { - nameType.propList.push_back(*pt); - propDataMap.push_back(nameType); - } + std::vector::iterator pi = std::find_if(propDataMap.begin(), propDataMap.end(), PropFind(nameType)); + if (pi != propDataMap.end()) { + pi->propList.push_back(*pt); + } + else { + nameType.propList.push_back(*pt); + propDataMap.push_back(nameType); } } } @@ -271,19 +416,20 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg) if (vp) { std::map::iterator pt; for (pt = viewList.begin(); pt != viewList.end(); ++pt) { + if (isPropertyHidden(pt->second)) + continue; + PropInfo nameType; nameType.propName = pt->first; nameType.propId = pt->second->getTypeId().getKey(); - if (!vp->isHidden(pt->second)) { - std::vector::iterator pi = std::find_if(propViewMap.begin(), propViewMap.end(), PropFind(nameType)); - if (pi != propViewMap.end()) { - pi->propList.push_back(pt->second); - } - else { - nameType.propList.push_back(pt->second); - propViewMap.push_back(nameType); - } + std::vector::iterator pi = std::find_if(propViewMap.begin(), propViewMap.end(), PropFind(nameType)); + if (pi != propViewMap.end()) { + pi->propList.push_back(pt->second); + } + else { + nameType.propList.push_back(pt->second); + propViewMap.push_back(nameType); } } } @@ -294,32 +440,69 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg) // name and id std::vector::const_iterator it; PropertyModel::PropertyList dataProps; - for (it = propDataMap.begin(); it != propDataMap.end(); ++it) { - if (it->propList.size() == array.size()) { - dataProps.push_back(std::make_pair(it->propName, it->propList)); - } - } - propertyEditorData->buildUp(dataProps); - PropertyModel::PropertyList viewProps; - for (it = propViewMap.begin(); it != propViewMap.end(); ++it) { - if (it->propList.size() == array.size()) { - viewProps.push_back(std::make_pair(it->propName, it->propList)); + + if(checkLink && vpLast) { + // In case the only selected object is a link, insert the link's own + // property before the linked object + App::DocumentObject *obj = vpLast->getObject(); + auto linked = obj; + if(obj && obj->canLinkProperties() && (linked=obj->getLinkedObject(true))!=obj && linked) { + std::vector dataList; + std::map propMap; + obj->getPropertyMap(propMap); + linked->getPropertyList(dataList); + for(auto prop : dataList) { + if(isPropertyHidden(prop)) + continue; + std::string name(prop->getName()); + auto it = propMap.find(name); + if(it!=propMap.end() && !isPropertyHidden(it->second)) + continue; + std::vector v(1,prop); + dataProps.emplace_back(name+"*", v); + } + auto vpLinked = Application::Instance->getViewProvider(linked); + if(vpLinked) { + propMap.clear(); + vpLast->getPropertyMap(propMap); + dataList.clear(); + vpLinked->getPropertyList(dataList); + for(auto prop : dataList) { + if(isPropertyHidden(prop)) + continue; + std::string name(prop->getName()); + auto it = propMap.find(name); + if(it!=propMap.end() && !isPropertyHidden(it->second)) + continue; + std::vector v(1,prop); + viewProps.emplace_back(name+"*", v); + } + } } } - propertyEditorView->buildUp(viewProps); + + for (it = propDataMap.begin(); it != propDataMap.end(); ++it) { + if (it->propList.size() == array.size()) + dataProps.push_back(std::make_pair(it->propName, it->propList)); + } + + propertyEditorData->buildUp(std::move(dataProps)); + + for (it = propViewMap.begin(); it != propViewMap.end(); ++it) { + if (it->propList.size() == array.size()) + viewProps.push_back(std::make_pair(it->propName, it->propList)); + } + + propertyEditorView->buildUp(std::move(viewProps)); // make sure the editors are enabled/disabled properly - setEnabled(enableEditor || array.empty()); + checkEnable(); } void PropertyView::tabChanged(int index) { - ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("PropertyView"); - if (hGrp) { - hGrp->SetInt("LastTabIndex", index); - } + _GetParam()->SetInt("LastTabIndex",index); } void PropertyView::changeEvent(QEvent *e) diff --git a/src/Gui/PropertyView.h b/src/Gui/PropertyView.h index 0feff23a10..a865e7631e 100644 --- a/src/Gui/PropertyView.h +++ b/src/Gui/PropertyView.h @@ -64,13 +64,20 @@ public: Gui::PropertyEditor::PropertyEditor* propertyEditorView; Gui::PropertyEditor::PropertyEditor* propertyEditorData; + void clearPropertyItemSelection(); + static bool showAll(); + static void setShowAll(bool); + static bool isPropertyHidden(const App::Property *); public Q_SLOTS: /// Stores a preference for the last tab selected void tabChanged(int index); + void onTimer(); protected: void changeEvent(QEvent *e); + void showEvent(QShowEvent *) override; + void hideEvent(QHideEvent *) override; private: void onSelectionChanged(const SelectionChanges& msg); @@ -78,8 +85,12 @@ private: void slotChangePropertyView(const Gui::ViewProvider&, const App::Property&); void slotAppendDynamicProperty(const App::Property&); void slotRemoveDynamicProperty(const App::Property&); - void slotChangePropertyEditor(const App::Property&); + void slotChangePropertyEditor(const App::Document&, const App::Property&); + void slotRollback(); void slotActiveDocument(const Gui::Document&); + void slotDeleteDocument(const Gui::Document&); + + void checkEnable(const char *doc = 0); private: struct PropInfo; @@ -90,8 +101,12 @@ private: Connection connectPropAppend; Connection connectPropRemove; Connection connectPropChange; + Connection connectUndoDocument; + Connection connectRedoDocument; Connection connectActiveDoc; + Connection connectDelDocument; QTabWidget* tabs; + QTimer* timer; }; namespace DockWnd { diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index d60fada5f4..0ae0be28a2 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -38,18 +38,26 @@ # include # include # include +# include #endif +#include #include #include +#include +#include "Command.h" #include "Widgets.h" #include "Application.h" #include "Action.h" #include "PrefWidgets.h" #include "BitmapFactory.h" +#include "DlgExpressionInput.h" +#include "QuantitySpinBox_p.h" using namespace Gui; +using namespace App; +using namespace Base; /** * Constructs an empty command view with parent \a parent. @@ -1403,4 +1411,173 @@ void LabelEditor::setInputType(InputType t) this->type = t; } +// -------------------------------------------------------------------- + +ExpLineEdit::ExpLineEdit(QWidget* parent, bool expressionOnly) + : QLineEdit(parent), autoClose(expressionOnly) +{ + defaultPalette = palette(); + + /* Icon for f(x) */ + QFontMetrics fm(font()); + int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); + iconHeight = fm.height() - frameWidth; + iconLabel = new ExpressionLabel(this); + iconLabel->setCursor(Qt::ArrowCursor); + QPixmap pixmap = getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight)); + iconLabel->setPixmap(pixmap); + iconLabel->setStyleSheet(QString::fromLatin1("QLabel { border: none; padding: 0px; padding-top: %2px; width: %1px; height: %1px }").arg(iconHeight).arg(frameWidth/2)); + iconLabel->hide(); + setStyleSheet(QString::fromLatin1("QLineEdit { padding-right: %1px } ").arg(iconHeight+frameWidth)); + + QObject::connect(iconLabel, SIGNAL(clicked()), this, SLOT(openFormulaDialog())); + if(expressionOnly) + QMetaObject::invokeMethod(this, "openFormulaDialog", Qt::QueuedConnection, QGenericReturnArgument()); +} + +bool ExpLineEdit::apply(const std::string& propName) { + + if (!ExpressionBinding::apply(propName)) { + QString val = QString::fromUtf8(Base::Interpreter().strToPython(text().toUtf8()).c_str()); + Gui::Command::doCommand(Gui::Command::Doc, "%s = \"%s\"", propName.c_str(), val.constData()); + return true; + } + else + return false; +} + +void ExpLineEdit::bind(const ObjectIdentifier& _path) { + + ExpressionBinding::bind(_path); + + int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); + setStyleSheet(QString::fromLatin1("QLineEdit { padding-right: %1px } ").arg(iconLabel->sizeHint().width() + frameWidth + 1)); + + iconLabel->show(); +} + +void ExpLineEdit::setExpression(boost::shared_ptr expr) +{ + Q_ASSERT(isBound()); + + try { + ExpressionBinding::setExpression(expr); + } + catch (const Base::Exception & e) { + setReadOnly(true); + QPalette p(palette()); + p.setColor(QPalette::Active, QPalette::Text, Qt::red); + setPalette(p); + iconLabel->setToolTip(QString::fromLatin1(e.what())); + } +} + +void ExpLineEdit::onChange() { + + if (getExpression()) { + std::unique_ptr result(getExpression()->eval()); + if(result->isDerivedFrom(App::StringExpression::getClassTypeId())) + setText(QString::fromUtf8(static_cast( + result.get())->getText().c_str())); + else + setText(QString::fromUtf8(result->toString().c_str())); + setReadOnly(true); + iconLabel->setPixmap(getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight))); + + QPalette p(palette()); + p.setColor(QPalette::Text, Qt::lightGray); + setPalette(p); + setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + } + else { + setReadOnly(false); + iconLabel->setPixmap(getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight))); + QPalette p(palette()); + p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); + setPalette(p); + + } + iconLabel->setToolTip(QString()); +} + +void ExpLineEdit::resizeEvent(QResizeEvent * event) +{ + QLineEdit::resizeEvent(event); + + int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); + + QSize sz = iconLabel->sizeHint(); + iconLabel->move(rect().right() - frameWidth - sz.width(), 0); + + try { + if (isBound() && getExpression()) { + setReadOnly(true); + QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight)); + iconLabel->setPixmap(pixmap); + + QPalette p(palette()); + p.setColor(QPalette::Text, Qt::lightGray); + setPalette(p); + setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + } + else { + setReadOnly(false); + QPixmap pixmap = getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight)); + iconLabel->setPixmap(pixmap); + + QPalette p(palette()); + p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text)); + setPalette(p); + + } + iconLabel->setToolTip(QString()); + } + catch (const Base::Exception & e) { + setReadOnly(true); + QPalette p(palette()); + p.setColor(QPalette::Active, QPalette::Text, Qt::red); + setPalette(p); + iconLabel->setToolTip(QString::fromLatin1(e.what())); + } +} + +void ExpLineEdit::openFormulaDialog() +{ + Q_ASSERT(isBound()); + + Gui::Dialog::DlgExpressionInput* box = new Gui::Dialog::DlgExpressionInput( + getPath(), getExpression(),Unit(), this); + connect(box, SIGNAL(finished(int)), this, SLOT(finishFormulaDialog())); + box->show(); + + QPoint pos = mapToGlobal(QPoint(0,0)); + box->move(pos-box->expressionPosition()); + box->setExpressionInputSize(width(), height()); +} + +void ExpLineEdit::finishFormulaDialog() +{ + Gui::Dialog::DlgExpressionInput* box = qobject_cast(sender()); + if (!box) { + qWarning() << "Sender is not a Gui::Dialog::DlgExpressionInput"; + return; + } + + if (box->result() == QDialog::Accepted) + setExpression(box->getExpression()); + else if (box->discardedFormula()) + setExpression(boost::shared_ptr()); + + box->deleteLater(); + + if(autoClose) + this->deleteLater(); +} + +void ExpLineEdit::keyPressEvent(QKeyEvent *event) +{ + if (!hasExpression()) + QLineEdit::keyPressEvent(event); +} + #include "moc_Widgets.cpp" diff --git a/src/Gui/Widgets.h b/src/Gui/Widgets.h index e14b055ae5..0fd06bb59c 100644 --- a/src/Gui/Widgets.h +++ b/src/Gui/Widgets.h @@ -34,6 +34,8 @@ #include #include #include +#include +#include "ExpressionBinding.h" namespace Gui { class PrefCheckBox; @@ -449,6 +451,33 @@ private: QPushButton *button; }; +/** + * The ExpLineEdit class provides a lineedit that support expressing binding. + * \author realthunder + */ +class GuiExport ExpLineEdit : public QLineEdit, public ExpressionBinding +{ + Q_OBJECT + +public: + ExpLineEdit ( QWidget * parent=0, bool expressionOnly=false ); + + void setExpression(boost::shared_ptr expr); + void bind(const App::ObjectIdentifier &_path); + bool apply(const std::string &propName); + + void keyPressEvent(QKeyEvent *event); + void resizeEvent(QResizeEvent *event); + +private Q_SLOTS: + void finishFormulaDialog(); + void openFormulaDialog(); + virtual void onChange(); + +private: + bool autoClose; +}; + } // namespace Gui #endif // GUI_WIDGETS_H diff --git a/src/Gui/propertyeditor/PropertyEditor.cpp b/src/Gui/propertyeditor/PropertyEditor.cpp index 9f79f73230..c20717add6 100644 --- a/src/Gui/propertyeditor/PropertyEditor.cpp +++ b/src/Gui/propertyeditor/PropertyEditor.cpp @@ -26,24 +26,38 @@ #ifndef _PreComp_ # include # include +# include +# include +# include +# include +# include #endif #include +#include #include #include +#include "MainWindow.h" +#include "DlgAddProperty.h" #include "PropertyEditor.h" #include "PropertyItemDelegate.h" #include "PropertyModel.h" +#include "PropertyView.h" + +FC_LOG_LEVEL_INIT("PropertyView",true,true); using namespace Gui::PropertyEditor; PropertyEditor::PropertyEditor(QWidget *parent) - : QTreeView(parent), autoupdate(false), committing(false), delaybuild(false) + : QTreeView(parent), autoupdate(false), committing(false), delaybuild(false), binding(false) { propertyModel = new PropertyModel(this); setModel(propertyModel); - PropertyItemDelegate* delegate = new PropertyItemDelegate(this); + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + delegate = new PropertyItemDelegate(this); delegate->setItemEditorFactory(new PropertyItemEditorFactory); setItemDelegate(delegate); @@ -54,8 +68,10 @@ PropertyEditor::PropertyEditor(QWidget *parent) this->background = opt.palette.dark(); this->groupColor = opt.palette.color(QPalette::BrightText); - connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onItemActivated(QModelIndex))); - connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(onItemActivated(QModelIndex))); + this->setSelectionMode(QAbstractItemView::ExtendedSelection); + + connect(this, SIGNAL(activated(const QModelIndex &)), this, SLOT(onItemActivated(const QModelIndex &))); + connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(onItemActivated(const QModelIndex &))); } PropertyEditor::~PropertyEditor() @@ -137,14 +153,19 @@ void PropertyEditor::commitData (QWidget * editor) void PropertyEditor::editorDestroyed (QObject * editor) { + delegate->editorClosed(0,QAbstractItemDelegate::NoHint); QTreeView::editorDestroyed(editor); } void PropertyEditor::currentChanged ( const QModelIndex & current, const QModelIndex & previous ) { + FC_LOG("current changed " << current.row()<<","<buddy(previous)); + + // if (previous.isValid()) + // closePersistentEditor(model()->buddy(previous)); // DO NOT activate editor here, use onItemActivate() which response to // signals of activated and clicked. @@ -153,16 +174,62 @@ void PropertyEditor::currentChanged ( const QModelIndex & current, const QModelI // openPersistentEditor(model()->buddy(current)); } +void PropertyEditor::setupTransaction(const QModelIndex &index) { + if(!autoupdate) + return; + if(this->state()!=EditingState) { + FC_LOG("editor not editing"); + return; + } + auto &app = App::GetApplication(); + if(app.getActiveTransaction()) { + FC_LOG("editor already transacting " << app.getActiveTransaction()); + return; + } + PropertyItem* item = static_cast(index.internalPointer()); + auto items = item->getPropertyData(); + for(auto propItem=item->parent();items.empty() && propItem;propItem=propItem->parent()) + items = propItem->getPropertyData(); + if(items.empty()) { + FC_LOG("editor no item"); + return; + } + auto prop = items[0]; + auto parent = prop->getContainer(); + auto obj = Base::freecad_dynamic_cast(parent); + if(!obj || !obj->getDocument()) { + FC_LOG("invalid object"); + return; + } + if(obj->getDocument()->hasPendingTransaction()) { + FC_LOG("pending transaction"); + return; + } + std::ostringstream str; + str << tr("Edit").toUtf8().constData() << ' '; + for(auto prop : items) { + if(prop->getContainer()!=obj) { + obj = 0; + break; + } + } + if(obj && obj->getNameInDocument()) + str << obj->getNameInDocument() << '.'; + else + str << tr("property").toUtf8().constData() << ' '; + str << prop->getName(); + if(items.size()>1) + str << "..."; + app.setActiveTransaction(str.str().c_str()); + FC_LOG("editor transaction " << app.getActiveTransaction()); +} + void PropertyEditor::onItemActivated ( const QModelIndex & index ) { - if (autoupdate) { - PropertyItem* property = static_cast(index.internalPointer()); - QString edit = tr("Edit %1").arg(property->propertyName()); - App::Document* doc = App::GetApplication().getActiveDocument(); - if (doc) - doc->openTransaction(edit.toUtf8()); - } - openPersistentEditor(model()->buddy(index)); + if(index.column() != 1) + return; + edit(model()->buddy(index),AllEditTriggers,0); + setupTransaction(index); } void PropertyEditor::closeEditor (QWidget * editor, QAbstractItemDelegate::EndEditHint hint) @@ -171,35 +238,38 @@ void PropertyEditor::closeEditor (QWidget * editor, QAbstractItemDelegate::EndEd App::Document* doc = App::GetApplication().getActiveDocument(); if (doc) { if (!doc->isTransactionEmpty()) { - doc->commitTransaction(); // Between opening and committing a transaction a recompute // could already have been done if (doc->isTouched()) doc->recompute(); } - else { - doc->abortTransaction(); - } } + App::GetApplication().closeActiveTransaction(); } + QModelIndex indexSaved = currentIndex(); + FC_LOG("index saved " << indexSaved.row() << ", " << indexSaved.column()); + QTreeView::closeEditor(editor, hint); - // If after closing the editor this widget is still in editing state - // then a new editor must have been created. So, a transaction must be - // opened, too. - if (autoupdate && this->state() == EditingState) { - App::Document* doc = App::GetApplication().getActiveDocument(); - if (doc) { - QString edit; - QModelIndex index = currentIndex(); - if (index.isValid()) { - PropertyItem* property = static_cast(index.internalPointer()); - edit = tr("Edit %1").arg(property->propertyName()); - } - doc->openTransaction(edit.toUtf8()); + QModelIndex lastIndex; + while(this->state()!=EditingState) { + QModelIndex index; + if (hint == QAbstractItemDelegate::EditNextItem) { + index = moveCursor(MoveDown,Qt::NoModifier); + } else if(hint == QAbstractItemDelegate::EditPreviousItem) { + index = moveCursor(MoveUp,Qt::NoModifier); + } else + break; + if(!index.isValid() || index==lastIndex) { + setCurrentIndex(indexSaved); + break; } + lastIndex = index; + setCurrentIndex(index); + edit(index,AllEditTriggers,0); } + setupTransaction(currentIndex()); } void PropertyEditor::reset() @@ -237,7 +307,7 @@ void PropertyEditor::drawBranches(QPainter *painter, const QRect &rect, const QM //painter->setPen(savedPen); } -void PropertyEditor::buildUp(const PropertyModel::PropertyList& props) +void PropertyEditor::buildUp(PropertyModel::PropertyList &&props) { if (committing) { Base::Console().Warning("While committing the data to the property the selection has changed.\n"); @@ -255,7 +325,12 @@ void PropertyEditor::buildUp(const PropertyModel::PropertyList& props) this->setCurrentIndex(index); } - propList = props; + propList = std::move(props); + propOwners.clear(); + for(auto &v : propList) { + for(auto prop : v.second) + propOwners.insert(prop->getContainer()); + } } void PropertyEditor::updateProperty(const App::Property& prop) @@ -271,7 +346,7 @@ void PropertyEditor::setEditorMode(const QModelIndex & parent, int start, int en for (int i=start; i<=end; i++) { QModelIndex item = propertyModel->index(i, column, parent); PropertyItem* propItem = static_cast(item.internalPointer()); - if (propItem && propItem->testStatus(App::Property::Hidden)) { + if (!PropertyView::showAll() && propItem && propItem->testStatus(App::Property::Hidden)) { setRowHidden (i, parent, true); } if (propItem && propItem->isSeparator()) { @@ -285,10 +360,10 @@ void PropertyEditor::updateEditorMode(const App::Property& prop) { // check if the parent object is selected std::string editor = prop.getEditorName(); - if (editor.empty()) + if (!PropertyView::showAll() && editor.empty()) return; - bool hidden = prop.testStatus(App::Property::Hidden); + bool hidden = PropertyView::isPropertyHidden(&prop); bool readOnly = prop.testStatus(App::Property::ReadOnly); int column = 1; @@ -324,34 +399,8 @@ void PropertyEditor::updateItemEditor(bool enable, int column, const QModelIndex } } -void PropertyEditor::appendProperty(const App::Property& prop) -{ - // check if the parent object is selected - std::string editor = prop.getEditorName(); - if (editor.empty()) - return; - App::PropertyContainer* parent = prop.getContainer(); - std::string context = prop.getName(); - - bool canAddProperty = (!propList.empty()); - for (PropertyModel::PropertyList::iterator it = propList.begin(); it != propList.end(); ++it) { - if (it->second.empty() || it->second.size() > 1) { - canAddProperty = false; - break; - } - else if (it->second.front()->getContainer() != parent) { - canAddProperty = false; - break; - } - } - - if (canAddProperty) { - std::vector list; - list.push_back(const_cast(&prop)); - std::pair< std::string, std::vector > pair = std::make_pair(context, list); - propList.push_back(pair); - propertyModel->appendProperty(prop); - } +bool PropertyEditor::appendProperty(const App::Property& prop) { + return !!propOwners.count(prop.getContainer()); } void PropertyEditor::removeProperty(const App::Property& prop) @@ -371,4 +420,166 @@ void PropertyEditor::removeProperty(const App::Property& prop) } } +enum MenuAction { + MA_ShowAll, + MA_Expression, + MA_RemoveProp, + MA_AddProp, + MA_Transient, + MA_Output, + MA_NoRecompute, + MA_ReadOnly, + MA_Hidden, + MA_Touched, + MA_EvalOnRestore, +}; + +void PropertyEditor::contextMenuEvent(QContextMenuEvent *) { + QMenu menu; + QAction *showAll = menu.addAction(tr("Show all")); + showAll->setCheckable(true); + showAll->setChecked(PropertyView::showAll()); + showAll->setData(QVariant(MA_ShowAll)); + + auto contextIndex = currentIndex(); + + std::unordered_set props; + + if(PropertyView::showAll()) { + for(auto index : selectedIndexes()) { + auto item = static_cast(index.internalPointer()); + if(item->isSeparator()) + continue; + for(auto parent=item;parent;parent=item->parent()) { + const auto &ps = parent->getPropertyData(); + if(ps.size()) { + props.insert(ps.begin(),ps.end()); + break; + } + } + } + + if(props.size()) + menu.addAction(tr("Add property"))->setData(QVariant(MA_AddProp)); + + bool canRemove = !props.empty(); + unsigned long propType = 0; + unsigned long propStatus = 0xffffffff; + for(auto prop : props) { + propType |= prop->getType(); + propStatus &= prop->getStatus(); + if(!prop->testStatus(App::Property::PropDynamic) + || prop->testStatus(App::Property::LockDynamic)) + { + canRemove = false; + } + } + if(canRemove) + menu.addAction(tr("Remove property"))->setData(QVariant(MA_RemoveProp)); + + if(props.size() == 1) { + auto item = static_cast(contextIndex.internalPointer()); + auto prop = *props.begin(); + if(item->isBound() + && !prop->isDerivedFrom(App::PropertyExpressionEngine::getClassTypeId()) + && !prop->isReadOnly() + && !(prop->getType() & App::Prop_ReadOnly)) + { + contextIndex = propertyModel->buddy(contextIndex); + setCurrentIndex(contextIndex); + menu.addSeparator(); + menu.addAction(tr("Expression..."))->setData(QVariant(MA_Expression)); + } + } + + if(props.size()) { + menu.addSeparator(); + + QAction *action; + QString text; +#define _ACTION_SETUP(_name) do {\ + text = tr(#_name);\ + action = menu.addAction(text);\ + action->setData(QVariant(MA_##_name));\ + action->setCheckable(true);\ + if(propStatus & (1<setChecked(true);\ + }while(0) +#define ACTION_SETUP(_name) do {\ + _ACTION_SETUP(_name);\ + if(propType & App::Prop_##_name) {\ + action->setText(text + QString::fromLatin1(" *"));\ + action->setChecked(true);\ + }\ + }while(0) + + ACTION_SETUP(Hidden); + ACTION_SETUP(Output); + ACTION_SETUP(NoRecompute); + ACTION_SETUP(ReadOnly); + ACTION_SETUP(Transient); + _ACTION_SETUP(Touched); + _ACTION_SETUP(EvalOnRestore); + } + } + + auto action = menu.exec(QCursor::pos()); + if(!action) + return; + + switch(action->data().toInt()) { + case MA_ShowAll: + PropertyView::setShowAll(action->isChecked()); + return; +#define ACTION_CHECK(_name) \ + case MA_##_name:\ + for(auto prop : props) \ + prop->setStatus(App::Property::_name,action->isChecked());\ + break + ACTION_CHECK(Transient); + ACTION_CHECK(ReadOnly); + ACTION_CHECK(Output); + ACTION_CHECK(Hidden); + ACTION_CHECK(EvalOnRestore); + case MA_Touched: + for(auto prop : props) { + if(action->isChecked()) + prop->touch(); + else + prop->purgeTouched(); + } + break; + case MA_Expression: + if(contextIndex == currentIndex()) { + closePersistentEditor(contextIndex); + Base::FlagToggler<> flag(binding); + edit(contextIndex,AllEditTriggers,0); + } + break; + case MA_AddProp: { + App::AutoTransaction committer("Add property"); + std::unordered_set containers; + for(auto prop : props) + containers.insert(prop->getContainer()); + Gui::Dialog::DlgAddProperty dlg( + Gui::getMainWindow(),std::move(containers)); + dlg.exec(); + return; + } + case MA_RemoveProp: { + App::AutoTransaction committer("Remove property"); + for(auto prop : props) { + try { + prop->getContainer()->removeDynamicProperty(prop->getName()); + }catch(Base::Exception &e) { + e.ReportException(); + } + } + break; + } + default: + break; + } +} + #include "moc_PropertyEditor.cpp" diff --git a/src/Gui/propertyeditor/PropertyEditor.h b/src/Gui/propertyeditor/PropertyEditor.h index e78a30f291..a64371d407 100644 --- a/src/Gui/propertyeditor/PropertyEditor.h +++ b/src/Gui/propertyeditor/PropertyEditor.h @@ -27,9 +27,11 @@ #include #include #include +#include #include +#include #include "PropertyItem.h" #include "PropertyModel.h" @@ -38,8 +40,12 @@ class Property; } namespace Gui { + +class PropertyView; + namespace PropertyEditor { +class PropertyItemDelegate; class PropertyModel; /*! Put this into the .qss file after Gui--PropertyEditor--PropertyEditor @@ -66,10 +72,10 @@ public: ~PropertyEditor(); /** Builds up the list view with the properties. */ - void buildUp(const PropertyModel::PropertyList& props); + void buildUp(PropertyModel::PropertyList &&props = PropertyModel::PropertyList()); void updateProperty(const App::Property&); void updateEditorMode(const App::Property&); - void appendProperty(const App::Property&); + bool appendProperty(const App::Property&); void removeProperty(const App::Property&); void setAutomaticDocumentUpdate(bool); bool isAutomaticDocumentUpdate(bool) const; @@ -81,7 +87,9 @@ public: QColor groupTextColor() const; void setGroupTextColor(const QColor& c); -public Q_SLOTS: + bool isBinding() const { return binding; } + +protected Q_SLOTS: void onItemActivated(const QModelIndex &index); protected: @@ -92,21 +100,29 @@ protected: virtual void rowsInserted (const QModelIndex & parent, int start, int end); virtual void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const; virtual QStyleOptionViewItem viewOptions() const; + virtual void contextMenuEvent(QContextMenuEvent *event); virtual bool event(QEvent*); private: void setEditorMode(const QModelIndex & parent, int start, int end); void updateItemEditor(bool enable, int column, const QModelIndex& parent); + void setupTransaction(const QModelIndex &); private: + PropertyItemDelegate *delegate; PropertyModel* propertyModel; QStringList selectedProperty; PropertyModel::PropertyList propList; + std::unordered_set propOwners; bool autoupdate; bool committing; bool delaybuild; QColor groupColor; QBrush background; + + bool binding; + + friend class Gui::PropertyView; }; } //namespace PropertyEditor diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index d93b068545..49d1cb07f9 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,7 @@ #include #include "PropertyItem.h" +#include "PropertyView.h" #include using namespace Gui::PropertyEditor; @@ -84,10 +86,11 @@ PropertyItem* PropertyItemFactory::createPropertyItem (const char* sName) const } // ---------------------------------------------------- +Q_DECLARE_METATYPE(Py::Object) PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyItem) -PropertyItem::PropertyItem() : parentItem(0), readonly(false), cleared(false) +PropertyItem::PropertyItem() : parentItem(0), readonly(false), cleared(false), linked(false) { precision = Base::UnitsApi::getDecimals(); setAutoApply(true); @@ -262,6 +265,18 @@ bool PropertyItem::isReadOnly() const return readonly; } +void PropertyItem::setLinked(bool l) +{ + linked = l; + for (QList::iterator it = childItems.begin(); it != childItems.end(); ++it) + (*it)->setLinked(l); +} + +bool PropertyItem::isLinked() const +{ + return linked; +} + bool PropertyItem::testStatus(App::Property::Status pos) const { std::vector::const_iterator it; @@ -301,7 +316,47 @@ QVariant PropertyItem::decoration(const QVariant&) const QVariant PropertyItem::toString(const QVariant& prop) const { - return prop; + if(prop != QVariant() || propertyItems.size()!=1) + return prop; + Base::PyGILStateLocker lock; + Py::Object pyobj(propertyItems[0]->getPyObject(),true); + std::ostringstream ss; + if(pyobj.isNone()) + ss << ""; + else if(pyobj.isSequence()) { + ss << '['; + Py::Sequence seq(pyobj); + bool first = true; + size_t i=0; + for(i=0;i<2 && igetContainer(); + QString propPrefix = QString::fromLatin1(parent->getPropertyPrefix()); if (parent->getTypeId() == App::Document::getClassTypeId()) { App::Document* doc = static_cast(parent); QString docName = QString::fromLatin1(App::GetApplication().getDocumentName(doc)); - QString propName = QString::fromLatin1(parent->getPropertyName(prop)); - return QString::fromLatin1("FreeCAD.getDocument(\"%1\").%2").arg(docName, propName); + QString propName = QString::fromLatin1(prop->getName()); + return QString::fromLatin1("FreeCAD.getDocument(\"%1\").%3%2").arg(docName).arg(propName).arg(propPrefix); } if (parent->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) { App::DocumentObject* obj = static_cast(parent); App::Document* doc = obj->getDocument(); QString docName = QString::fromLatin1(App::GetApplication().getDocumentName(doc)); QString objName = QString::fromLatin1(obj->getNameInDocument()); - QString propName = QString::fromLatin1(parent->getPropertyName(prop)); - return QString::fromLatin1("FreeCAD.getDocument(\"%1\").getObject(\"%2\").%3") - .arg(docName, objName, propName); + QString propName = QString::fromLatin1(prop->getName()); + return QString::fromLatin1("FreeCAD.getDocument(\"%1\").getObject(\"%2\").%4%3") + .arg(docName,objName,propName,propPrefix); } auto* vp = dynamic_cast(parent); if (vp) { @@ -337,9 +393,9 @@ QString PropertyItem::pythonIdentifier(const App::Property* prop) const App::Document* doc = obj->getDocument(); QString docName = QString::fromLatin1(App::GetApplication().getDocumentName(doc)); QString objName = QString::fromLatin1(obj->getNameInDocument()); - QString propName = QString::fromLatin1(parent->getPropertyName(prop)); - return QString::fromLatin1("FreeCADGui.getDocument(\"%1\").getObject(\"%2\").%3") - .arg(docName, objName, propName); + QString propName = QString::fromLatin1(prop->getName()); + return QString::fromLatin1("FreeCADGui.getDocument(\"%1\").getObject(\"%2\").%4%3") + .arg(docName,objName,propName,propPrefix); } return QString(); } @@ -358,6 +414,34 @@ QVariant PropertyItem::editorData(QWidget * /*editor*/) const return QVariant(); } +QWidget* PropertyItem::createExpressionEditor(QWidget* parent, const QObject* receiver, const char* method) const +{ + if(!isBound()) + return 0; + ExpLineEdit *le = new ExpLineEdit(parent,true); + le->setFrame(false); + le->setReadOnly(true); + QObject::connect(le, SIGNAL(textChanged(const QString&)), receiver, method); + le->bind(getPath()); + le->setAutoApply(autoApply()); + return le; +} + +void PropertyItem::setExpressionEditorData(QWidget *editor, const QVariant& data) const +{ + QLineEdit *le = qobject_cast(editor); + if(le) + le->setText(data.toString()); +} + +QVariant PropertyItem::expressionEditorData(QWidget *editor) const +{ + QLineEdit *le = qobject_cast(editor); + if(le) + return QVariant(le->text()); + return QVariant(); +} + QString PropertyItem::propertyName() const { if (propName.isEmpty()) @@ -417,13 +501,32 @@ QVariant PropertyItem::data(int column, int role) const { // property name if (column == 0) { + if (role == Qt::BackgroundRole) { + if(PropertyView::showAll() + && propertyItems.size() == 1 + && propertyItems.front()->testStatus(App::Property::PropDynamic) + && !propertyItems.front()->testStatus(App::Property::LockDynamic)) + { + return QBrush(QColor(0xFF,0xFF,0x99)); + } + return QVariant(); + } if (role == Qt::DisplayRole) return displayName(); // no properties set if (propertyItems.empty()) return QVariant(); - else if (role == Qt::ToolTipRole) - return toolTip(propertyItems[0]); + else if (role == Qt::ToolTipRole) { + if(!PropertyView::showAll()) + return toolTip(propertyItems[0]); + QString type = QString::fromLatin1("Type: %1").arg( + QString::fromLatin1(propertyItems[0]->getTypeId().getName())); + QString doc = toolTip(propertyItems[0]).toString(); + if(doc.size()) + return type + QLatin1String("\n\n") + doc; + return type; + } else if (role == Qt::TextColorRole && linked) + return QVariant::fromValue(QColor(0,0x80,0)); else return QVariant(); } @@ -456,7 +559,11 @@ QVariant PropertyItem::data(int column, int role) const return toString(value(propertyItems[0])); else if (role == Qt::ToolTipRole) return toolTip(propertyItems[0]); - else + else if( role == Qt::TextColorRole) { + if(hasExpression()) + return QVariant::fromValue(QColor(0,0,255.0)); + return QVariant(); + } else return QVariant(); } } @@ -544,20 +651,27 @@ QVariant PropertyStringItem::value(const App::Property* prop) const void PropertyStringItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::String)) - return; - QString val = value.toString(); - val = QString::fromUtf8(Base::Interpreter().strToPython(val.toUtf8()).c_str()); - QString data = QString::fromLatin1("\"%1\"").arg(val); - setPropertyValue(data); + if(!hasExpression()) { + if (!value.canConvert(QVariant::String)) + return; + QString val = value.toString(); + val = QString::fromUtf8(Base::Interpreter().strToPython(val.toUtf8()).c_str()); + QString data = QString::fromLatin1("\"%1\"").arg(val); + setPropertyValue(data); + } } QWidget* PropertyStringItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const { - QLineEdit *le = new QLineEdit(parent); + ExpLineEdit *le = new ExpLineEdit(parent); le->setFrame(false); le->setReadOnly(isReadOnly()); QObject::connect(le, SIGNAL(textChanged(const QString&)), receiver, method); + if(isBound()) { + le->bind(getPath()); + le->setAutoApply(autoApply()); + } + return le; } @@ -3381,14 +3495,15 @@ void LinkSelection::select() { Gui::Selection().clearSelection(); Gui::Selection().addSelection((const char*)link[0].toLatin1(), - (const char*)link[1].toLatin1()); + (const char*)link[1].toLatin1(), + link.size()>=6?(const char*)link[5].toUtf8():0); this->deleteLater(); } // --------------------------------------------------------------- -LinkLabel::LinkLabel (QWidget * parent) : QWidget(parent) -{ +LinkLabel::LinkLabel (QWidget * parent, bool xlink) : QWidget(parent), isXLink(xlink) +{ QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); layout->setSpacing(1); @@ -3447,7 +3562,7 @@ void LinkLabel::onLinkActivated (const QString& s) void LinkLabel::onEditClicked () { - Gui::Dialog::DlgPropertyLink dlg(link, this); + Gui::Dialog::DlgPropertyLink dlg(link, this, 0, isXLink); if (dlg.exec() == QDialog::Accepted) { setPropertyLink(dlg.propertyLink()); /*emit*/ linkChanged(link); @@ -3463,7 +3578,7 @@ void LinkLabel::resizeEvent(QResizeEvent* e) PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkItem) -PropertyLinkItem::PropertyLinkItem() +PropertyLinkItem::PropertyLinkItem():isXLink(false) { } @@ -3473,23 +3588,72 @@ QVariant PropertyLinkItem::toString(const QVariant& prop) const return QVariant(list[2]); } +QVariant PropertyLinkItem::data(int column, int role) const { + if(propertyItems.size() && column == 1 && role == Qt::TextColorRole) { + auto xlink = Base::freecad_dynamic_cast(propertyItems[0]); + if(xlink && xlink->checkRestore()>1) + return QVariant::fromValue(QColor(0xff,0,0)); + } + return PropertyItem::data(column,role); +} + QVariant PropertyLinkItem::value(const App::Property* prop) const { assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyLink::getClassTypeId())); + auto xlink = Base::freecad_dynamic_cast(prop); + isXLink = xlink!=0; + const App::PropertyLink* prop_link = static_cast(prop); App::PropertyContainer* c = prop_link->getContainer(); - // the list has five elements: - // [document name, internal name, label, internal name of container, property name] + // 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()); - list << QString::fromUtf8(obj->Label.getValue()); - } - else { + + 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->getSubName(false)[0]) { + auto subObj = obj->getSubObject(xlink->getSubName(false)); + if(subObj) + list << QString::fromLatin1("%1 (%2.%3)"). + arg(QString::fromUtf8(subObj->Label.getValue()), + QString::fromLatin1(objName), + QString::fromUtf8(xlink->getSubName(false))); + else + list << QString::fromLatin1("%1.%2"). + arg(QString::fromLatin1(objName), + QString::fromUtf8(xlink->getSubName(false))); + }else if(objName!=obj->Label.getValue()) { + 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())) { @@ -3502,8 +3666,13 @@ QVariant PropertyLinkItem::value(const App::Property* prop) const // the internal object name list << QString::fromLatin1("Null"); + // the object label - list << QString::fromLatin1(""); + std::string msg; + if(xlink && xlink->checkRestore(&msg)>1) + list << QString::fromUtf8(msg.c_str()); + else + list << QString::fromLatin1(""); } // the name of this object @@ -3511,11 +3680,18 @@ QVariant PropertyLinkItem::value(const App::Property* prop) const App::DocumentObject* obj = static_cast(c); list << QString::fromLatin1(obj->getNameInDocument()); } - else { + 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); } @@ -3531,15 +3707,23 @@ void PropertyLinkItem::setValue(const QVariant& value) QString data; if ( o.isEmpty() ) data = QString::fromLatin1("None"); - else - data = QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d, o); + 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); } } QWidget* PropertyLinkItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const { - LinkLabel *ll = new LinkLabel(parent); + LinkLabel *ll = new LinkLabel(parent, isXLink); ll->setAutoFillBackground(true); ll->setDisabled(isReadOnly()); QObject::connect(ll, SIGNAL(linkChanged(const QStringList&)), receiver, method); @@ -3718,8 +3902,10 @@ void PropertyLinkListItem::setValue(const QVariant& value) if (!o.isEmpty()) data << QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d, o); } - - setPropertyValue(QString::fromLatin1("[%1]").arg(data.join(QString::fromLatin1(", ")))); + 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 diff --git a/src/Gui/propertyeditor/PropertyItem.h b/src/Gui/propertyeditor/PropertyItem.h index 98185f96bd..b262e827c3 100644 --- a/src/Gui/propertyeditor/PropertyItem.h +++ b/src/Gui/propertyeditor/PropertyItem.h @@ -125,6 +125,10 @@ public: virtual QVariant editorData(QWidget *editor) const; virtual bool isSeparator() const { return false; } + QWidget* createExpressionEditor(QWidget* parent, const QObject* receiver, const char* method) const; + void setExpressionEditorData(QWidget *editor, const QVariant& data) const; + QVariant expressionEditorData(QWidget *editor) const; + /**override the bind functions to ensure we issue the propertyBound() call, which is then overloaded by childs which like to be informed of a binding*/ virtual void bind(const App::Property& prop); @@ -145,13 +149,16 @@ public: void setDecimals(int); int decimals() const; + void setLinked(bool); + bool isLinked() const; + PropertyItem *child(int row); int childCount() const; int columnCount() const; QString propertyName() const; void setPropertyName(const QString&); void setPropertyValue(const QString&); - QVariant data(int column, int role) const; + virtual QVariant data(int column, int role) const; bool setData (const QVariant& value); Qt::ItemFlags flags(int column) const; int row() const; @@ -169,16 +176,16 @@ protected: virtual void initialize(); QString pythonIdentifier(const App::Property*) const; -private: +protected: QString propName; QString displayText; - QVariant propData; std::vector propertyItems; PropertyItem *parentItem; QList childItems; bool readonly; int precision; bool cleared; + bool linked; }; /** @@ -948,7 +955,7 @@ class LinkLabel : public QWidget Q_OBJECT public: - LinkLabel (QWidget * parent = 0); + LinkLabel (QWidget * parent = 0, bool xlink = false); virtual ~LinkLabel(); void setPropertyLink(const QStringList& o); QStringList propertyLink() const; @@ -967,6 +974,7 @@ private: QLabel* label; QPushButton* editButton; QStringList link; + bool isXLink; }; /** @@ -986,9 +994,13 @@ protected: virtual QVariant toString(const QVariant&) const; virtual QVariant value(const App::Property*) const; virtual void setValue(const QVariant&); + virtual QVariant data(int column, int role) const override; protected: PropertyLinkItem(); + +private: + mutable bool isXLink; }; class LinkListLabel : public QWidget diff --git a/src/Gui/propertyeditor/PropertyItemDelegate.cpp b/src/Gui/propertyeditor/PropertyItemDelegate.cpp index bb9a11c5f8..562c4d9750 100644 --- a/src/Gui/propertyeditor/PropertyItemDelegate.cpp +++ b/src/Gui/propertyeditor/PropertyItemDelegate.cpp @@ -29,14 +29,23 @@ # include #endif +#include +#include +#include +#include +#include #include "PropertyItemDelegate.h" #include "PropertyItem.h" +#include "PropertyEditor.h" + +FC_LOG_LEVEL_INIT("PropertyView",true,true); using namespace Gui::PropertyEditor; PropertyItemDelegate::PropertyItemDelegate(QObject* parent) - : QItemDelegate(parent), pressed(false) + : QItemDelegate(parent), expressionEditor(0) + , pressed(false), changed(false) { connect(this, SIGNAL(closeEditor(QWidget*, QAbstractItemDelegate::EndEditHint)), this, SLOT(editorClosed(QWidget*, QAbstractItemDelegate::EndEditHint))); @@ -116,9 +125,21 @@ bool PropertyItemDelegate::editorEvent (QEvent * event, QAbstractItemModel* mode void PropertyItemDelegate::editorClosed(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) { +#if 0 + int id = 0; + auto &app = App::GetApplication(); + const char *name = app.getActiveTransaction(&id); + if(id && id==activeTransactionID) { + FC_LOG("editor close transaction " << name); + app.closeActiveTransaction(false,id); + activeTransactionID = 0; + } + FC_LOG("editor close " << editor); +#endif + // don't close the editor when pressing Tab or Shift+Tab // https://forum.freecadweb.org/viewtopic.php?f=3&t=34627#p290957 - if (hint != EditNextItem && hint != EditPreviousItem) + if (editor && hint != EditNextItem && hint != EditPreviousItem) editor->close(); } @@ -131,7 +152,16 @@ QWidget * PropertyItemDelegate::createEditor (QWidget * parent, const QStyleOpti PropertyItem *childItem = static_cast(index.internalPointer()); if (!childItem) return 0; - QWidget* editor = childItem->createEditor(parent, this, SLOT(valueChanged())); + + FC_LOG("create editor " << index.row() << "," << index.column()); + + PropertyEditor *parentEditor = qobject_cast(this->parent()); + QWidget* editor; + expressionEditor = 0; + if(parentEditor && parentEditor->isBinding()) + expressionEditor = editor = childItem->createExpressionEditor(parent, this, SLOT(valueChanged())); + else + editor = childItem->createEditor(parent, this, SLOT(valueChanged())); if (editor) // Make sure the editor background is painted so the cell content doesn't show through editor->setAutoFillBackground(true); if (editor && childItem->isReadOnly()) @@ -144,14 +174,17 @@ QWidget * PropertyItemDelegate::createEditor (QWidget * parent, const QStyleOpti editor->setFocus(); } this->pressed = false; + return editor; } void PropertyItemDelegate::valueChanged() { QWidget* editor = qobject_cast(sender()); - if (editor) + if (editor) { + Base::FlagToggler<> flag(changed); commitData(editor); + } } void PropertyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const @@ -161,17 +194,24 @@ void PropertyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &ind QVariant data = index.data(Qt::EditRole); PropertyItem *childItem = static_cast(index.internalPointer()); editor->blockSignals(true); - childItem->setEditorData(editor, data); + if(expressionEditor == editor) + childItem->setExpressionEditorData(editor, data); + else + childItem->setEditorData(editor, data); editor->blockSignals(false); return; } void PropertyItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { - if (!index.isValid()) + if (!index.isValid() || !changed) return; PropertyItem *childItem = static_cast(index.internalPointer()); - QVariant data = childItem->editorData(editor); + QVariant data; + if(expressionEditor == editor) + data = childItem->expressionEditorData(editor); + else + data = childItem->editorData(editor); model->setData(index, data, Qt::EditRole); } diff --git a/src/Gui/propertyeditor/PropertyItemDelegate.h b/src/Gui/propertyeditor/PropertyItemDelegate.h index d19c712c74..96c24a3c89 100644 --- a/src/Gui/propertyeditor/PropertyItemDelegate.h +++ b/src/Gui/propertyeditor/PropertyItemDelegate.h @@ -50,7 +50,9 @@ public Q_SLOTS: void editorClosed (QWidget * editor, QAbstractItemDelegate::EndEditHint hint); private: + mutable QWidget *expressionEditor; mutable bool pressed; + bool changed; }; } // namespace PropertyEditor diff --git a/src/Gui/propertyeditor/PropertyModel.cpp b/src/Gui/propertyeditor/PropertyModel.cpp index a96454aa20..41254c5801 100644 --- a/src/Gui/propertyeditor/PropertyModel.cpp +++ b/src/Gui/propertyeditor/PropertyModel.cpp @@ -27,8 +27,11 @@ # include #endif +#include + #include "PropertyModel.h" #include "PropertyItem.h" +#include "PropertyView.h" using namespace Gui::PropertyEditor; @@ -206,6 +209,24 @@ QModelIndex PropertyModel::propertyIndexFromPath(const QStringList& path) const return parent; } +struct PropItemInfo { + const std::string &name; + const std::vector &props; + + PropItemInfo(const std::string &n, const std::vector &p) + :name(n),props(p) + {} +}; + +static void setPropertyItemName(PropertyItem *item, const char *propName, QString groupName) { + QString name = QString::fromLatin1(propName); + if(name.size()>groupName.size()+1 + && name.startsWith(groupName + QLatin1Char('_'))) + name = name.right(name.size()-groupName.size()-1); + + item->setPropertyName(name); +} + void PropertyModel::buildUp(const PropertyModel::PropertyList& props) { beginResetModel(); @@ -214,42 +235,47 @@ void PropertyModel::buildUp(const PropertyModel::PropertyList& props) rootItem->reset(); // sort the properties into their groups - std::map > > propGroup; + std::map > propGroup; PropertyModel::PropertyList::const_iterator jt; for (jt = props.begin(); jt != props.end(); ++jt) { App::Property* prop = jt->second.front(); const char* group = prop->getGroup(); bool isEmpty = (group == 0 || group[0] == '\0'); std::string grp = isEmpty ? QT_TRANSLATE_NOOP("App::Property", "Base") : group; - propGroup[grp].push_back(jt->second); + propGroup[grp].emplace_back(jt->first,jt->second); } - std::map > > - ::const_iterator kt; - for (kt = propGroup.begin(); kt != propGroup.end(); ++kt) { + for (auto kt = propGroup.begin(); kt != propGroup.end(); ++kt) { // set group item PropertyItem* group = static_cast(PropertySeparatorItem::create()); group->setParent(rootItem); rootItem->appendChild(group); - group->setPropertyName(QString::fromLatin1(kt->first.c_str())); + QString groupName = QString::fromLatin1(kt->first.c_str()); + group->setPropertyName(groupName); // setup the items for the properties - std::vector >::const_iterator it; - for (it = kt->second.begin(); it != kt->second.end(); ++it) { - App::Property* prop = it->front(); - QString editor = QString::fromLatin1(prop->getEditorName()); - if (!editor.isEmpty()) { - PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(prop->getEditorName()); + for (auto it = kt->second.begin(); it != kt->second.end(); ++it) { + const auto &info = *it; + App::Property* prop = info.props.front(); + std::string editor(prop->getEditorName()); + if(editor.empty() && PropertyView::showAll()) + editor = "Gui::PropertyEditor::PropertyItem"; + if (!editor.empty()) { + PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(editor.c_str()); if (!item) { - qWarning("No property item for type %s found\n", prop->getEditorName()); + qWarning("No property item for type %s found\n", editor.c_str()); continue; } else { + if(boost::ends_with(info.name,"*")) + item->setLinked(true); PropertyItem* child = (PropertyItem*)item; child->setParent(rootItem); rootItem->appendChild(child); - child->setPropertyName(QString::fromLatin1(prop->getName())); - child->setPropertyData(*it); + + setPropertyItemName(child,prop->getName(),groupName); + + child->setPropertyData(info.props); } } } @@ -279,11 +305,13 @@ void PropertyModel::updateProperty(const App::Property& prop) void PropertyModel::appendProperty(const App::Property& prop) { - QString editor = QString::fromLatin1(prop.getEditorName()); - if (!editor.isEmpty()) { - PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(prop.getEditorName()); + std::string editor(prop.getEditorName()); + if(editor.empty() && PropertyView::showAll()) + editor = "Gui::PropertyEditor::PropertyItem"; + if (!editor.empty()) { + PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(editor.c_str()); if (!item) { - qWarning("No property item for type %s found\n", prop.getEditorName()); + qWarning("No property item for type %s found\n", editor.c_str()); return; } @@ -354,7 +382,8 @@ void PropertyModel::appendProperty(const App::Property& prop) std::vector data; data.push_back(const_cast(&prop)); - item->setPropertyName(QString::fromLatin1(prop.getName())); + + setPropertyItemName(item,prop.getName(),groupName); item->setPropertyData(data); endInsertRows();