From 72ae26dfee5a1669e2bfd2542fafaecbaf9490f6 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 23 Dec 2019 11:48:03 +0800 Subject: [PATCH] App/Gui: improve expression binding of PropertyEnumeration The enumeration items are exposed through sub path '.Enum'. When 'ShowAll' is enabled in property view, this sub path is exposed as a sub property item named 'Enum', and can be either manually edited or bound with an expression. --- src/App/Enumeration.cpp | 21 ++- src/App/Property.cpp | 2 + src/App/PropertyStandard.cpp | 174 ++++++++++++++++++------ src/App/PropertyStandard.h | 8 +- src/Gui/ViewProviderDocumentObject.cpp | 3 +- src/Gui/propertyeditor/PropertyItem.cpp | 100 ++++++++++---- src/Gui/propertyeditor/PropertyItem.h | 10 ++ 7 files changed, 241 insertions(+), 77 deletions(-) diff --git a/src/App/Enumeration.cpp b/src/App/Enumeration.cpp index 26b0de9d1b..7a8a1b8643 100644 --- a/src/App/Enumeration.cpp +++ b/src/App/Enumeration.cpp @@ -82,11 +82,9 @@ Enumeration::~Enumeration() void Enumeration::tearDown(void) { // Ugly... - char **plEnums = (char **)_EnumArray; - - // Delete C Strings first - while (*plEnums != NULL) { - free(*(plEnums++)); + for(char **plEnums = (char **)_EnumArray; *plEnums != NULL; ++plEnums) { + // Delete C Strings first + free(*plEnums); } delete [] _EnumArray; @@ -98,6 +96,9 @@ void Enumeration::tearDown(void) void Enumeration::setEnums(const char **plEnums) { + if(plEnums == _EnumArray) + return; + std::string oldValue; bool preserve = (isValid() && plEnums != NULL); if (preserve) { @@ -118,7 +119,11 @@ void Enumeration::setEnums(const char **plEnums) findMaxVal(); // set _index - _index = 0; + if (_index < 0) + _index = 0; + else if (_index > _maxVal) + _index = _maxVal; + if (preserve) { setValue(oldValue); } @@ -311,6 +316,10 @@ Enumeration & Enumeration::operator=(const Enumeration &other) bool Enumeration::operator==(const Enumeration &other) const { + if(_index != other._index) + return false; + if (getCStr() == other.getCStr()) + return true; if (getCStr() == NULL || other.getCStr() == NULL) { return false; } diff --git a/src/App/Property.cpp b/src/App/Property.cpp index d4571eadd6..04b08190b1 100644 --- a/src/App/Property.cpp +++ b/src/App/Property.cpp @@ -71,6 +71,8 @@ std::string Property::getFullName() const { if(myName) { if(father) name = father->getFullName() + "."; + else + name = "?."; name += myName; }else return "?"; diff --git a/src/App/PropertyStandard.cpp b/src/App/PropertyStandard.cpp index 684ca308f5..629f45231d 100644 --- a/src/App/PropertyStandard.cpp +++ b/src/App/PropertyStandard.cpp @@ -304,18 +304,22 @@ PropertyEnumeration::~PropertyEnumeration() void PropertyEnumeration::setEnums(const char **plEnums) { - // Setting the enum is done only once inside the constructor - // but before the current index is already set. So, this needs - // to be preserved. - int index = _enum._index; + // For backward compatibility, if the property container is not attached to + // any document (i.e. its full name starts with '?'), do not notify, or + // else existing code may crash. + bool notify = !boost::starts_with(getFullName(), "?"); + if (notify) + aboutToSetValue(); _enum.setEnums(plEnums); - // Make sure not to set an index out of range - int max = _enum.maxValue(); - _enum._index = std::min(index, max); + if (notify) + hasSetValue(); } void PropertyEnumeration::setEnums(const std::vector &Enums) { + // _enum.setEnums() will preserve old value possible, so no need to do it + // here +#if 0 if (_enum.isValid()) { const std::string &index = getValueAsString(); _enum.setEnums(Enums); @@ -323,6 +327,9 @@ void PropertyEnumeration::setEnums(const std::vector &Enums) } else { _enum.setEnums(Enums); } +#else + setEnumVector(Enums); +#endif } void PropertyEnumeration::setValue(const char *value) @@ -378,6 +385,19 @@ std::vector PropertyEnumeration::getEnumVector() const return _enum.getEnumVector(); } +void PropertyEnumeration::setEnumVector(const std::vector &values) +{ + // For backward compatibility, if the property container is not attached to + // any document (i.e. its full name starts with '?'), do not notify, or + // else existing code may crash. + bool notify = !boost::starts_with(getFullName(), "?"); + if (notify) + aboutToSetValue(); + _enum.setEnums(values); + if (notify) + hasSetValue(); +} + const char ** PropertyEnumeration::getEnums() const { return _enum.getEnums(); @@ -414,6 +434,8 @@ void PropertyEnumeration::Restore(Base::XMLReader &reader) // get the value of my Attribute long val = reader.getAttributeAsInteger("value"); + aboutToSetValue(); + if (reader.hasAttribute("CustomEnum")) { reader.readElement("CustomEnumList"); int count = reader.getAttributeAsInteger("count"); @@ -436,14 +458,21 @@ void PropertyEnumeration::Restore(Base::XMLReader &reader) val = getValue(); } - setValue(val); + _enum.setValue(val); + hasSetValue(); } PyObject * PropertyEnumeration::getPyObject() { if (!_enum.isValid()) { - PyErr_SetString(PyExc_AssertionError, "The enum is empty"); - return nullptr; + // There is legimate use case of having an empty PropertyEnumeration and + // set its enumeration items later. Returning error here cause hasattr() + // to return False even though the property exists. + // + // PyErr_SetString(PyExc_AssertionError, "The enum is empty"); + // return 0; + // + Py_Return; } return Py_BuildValue("s", getValueAsString()); @@ -458,6 +487,7 @@ void PropertyEnumeration::setPyObject(PyObject *value) _enum.setValue(val, true); hasSetValue(); } + return; } else if (PyUnicode_Check(value)) { std::string str = PyUnicode_AsUTF8(value); @@ -467,34 +497,47 @@ void PropertyEnumeration::setPyObject(PyObject *value) hasSetValue(); } else { - std::stringstream out; - out << "'" << str << "' is not part of the enumeration"; - throw Base::ValueError(out.str()); + FC_THROWM(Base::ValueError, "'" << str + << "' is not part of the enumeration in " + << getFullName()); } + return; } else if (PySequence_Check(value)) { - Py_ssize_t nSize = PySequence_Size(value); - std::vector values; - values.resize(nSize); - for (Py_ssize_t i = 0; i < nSize; ++i) { - PyObject *item = PySequence_GetItem(value, i); + try { + std::vector values; - if (PyUnicode_Check(item)) { - values[i] = PyUnicode_AsUTF8(item); - } - else { - std::string error = std::string("type in list must be str or unicode, not "); - throw Base::TypeError(error + item->ob_type->tp_name); + int idx = -1; + Py::Sequence seq(value); + + if(seq.size() == 2) { + Py::Object v(seq[0].ptr()); + if(!v.isString() && v.isSequence()) { + idx = Py::Int(seq[1].ptr()); + seq = v; + } } + + values.resize(seq.size()); + + for (int i = 0; i < seq.size(); ++i) + values[i] = Py::Object(seq[i].ptr()).as_string(); + + aboutToSetValue(); + _enum.setEnums(values); + if (idx>=0) + _enum.setValue(idx,true); + hasSetValue(); + return; + } catch (Py::Exception &) { + Base::PyException e; + e.ReportException(); } - _enum.setEnums(values); - setValue((long)0); - } - else { - std::string error = std::string("type must be int, str or unicode not "); - throw Base::TypeError(error + value->ob_type->tp_name); } + + FC_THROWM(Base::TypeError, "PropertyEnumeration " << getFullName() + << " expects type to be int, string, or list(string), or list(list, int)"); } Property * PropertyEnumeration::Copy() const @@ -504,22 +547,20 @@ Property * PropertyEnumeration::Copy() const void PropertyEnumeration::Paste(const Property &from) { - aboutToSetValue(); - const PropertyEnumeration& prop = dynamic_cast(from); - _enum = prop._enum; - - hasSetValue(); + setValue(prop._enum); } -void PropertyEnumeration::setPathValue(const ObjectIdentifier &path, const boost::any &value) +void PropertyEnumeration::setPathValue(const ObjectIdentifier &, const boost::any &value) { - verifyPath(path); - if (value.type() == typeid(int)) setValue(boost::any_cast(value)); + else if (value.type() == typeid(long)) + setValue(boost::any_cast(value)); else if (value.type() == typeid(double)) setValue(boost::any_cast(value)); + else if (value.type() == typeid(float)) + setValue(boost::any_cast(value)); else if (value.type() == typeid(short)) setValue(boost::any_cast(value)); else if (value.type() == typeid(std::string)) @@ -528,8 +569,61 @@ void PropertyEnumeration::setPathValue(const ObjectIdentifier &path, const boost setValue(boost::any_cast(value)); else if (value.type() == typeid(const char*)) setValue(boost::any_cast(value)); - else - throw bad_cast(); + else { + Base::PyGILStateLocker lock; + Py::Object pyValue = pyObjectFromAny(value); + setPyObject(pyValue.ptr()); + } +} + +bool PropertyEnumeration::setPyPathValue(const ObjectIdentifier &, const Py::Object &value) +{ + setPyObject(value.ptr()); + return true; +} + +const boost::any PropertyEnumeration::getPathValue(const ObjectIdentifier &path) const +{ + std::string p = path.getSubPathStr(); + if (p == ".Enum" || p == ".All") { + Base::PyGILStateLocker lock; + Py::Object res; + getPyPathValue(path, res); + return pyObjectToAny(res,false); + } + else if (p == ".String") { + auto v = getValueAsString(); + return std::string(v?v:""); + } else + return getValue(); +} + +bool PropertyEnumeration::getPyPathValue(const ObjectIdentifier &path, Py::Object &r) const +{ + std::string p = path.getSubPathStr(); + if (p == ".Enum" || p == ".All") { + Base::PyGILStateLocker lock; + Py::Tuple res(_enum.maxValue()+1); + const char **enums = _enum.getEnums(); + PropertyString tmp; + for(int i=0;i<=_enum.maxValue();++i) { + tmp.setValue(enums[i]); + res.setItem(i,Py::asObject(tmp.getPyObject())); + } + if(p == ".Enum") + r = res; + else { + Py::Tuple tuple(2); + tuple.setItem(0, res); + tuple.setItem(1, Py::Int(getValue())); + r = tuple; + } + } else if (p == ".String") { + auto v = getValueAsString(); + r = Py::String(v?v:""); + } else + r = Py::Int(getValue()); + return true; } //************************************************************************** diff --git a/src/App/PropertyStandard.h b/src/App/PropertyStandard.h index aa4557b1a5..49bc7509d3 100644 --- a/src/App/PropertyStandard.h +++ b/src/App/PropertyStandard.h @@ -186,11 +186,13 @@ public: const char * getValueAsString(void) const; /// Returns Enumeration object - Enumeration getEnum(void) const; + const Enumeration &getEnum(void) const; /// get all possible enum values as vector of strings std::vector getEnumVector(void) const; + /// set enum values as vector of strings + void setEnumVector(const std::vector &); /// get the pointer to the enum list const char ** getEnums(void) const; @@ -211,7 +213,9 @@ public: virtual void Paste(const Property &from); virtual void setPathValue(const App::ObjectIdentifier & path, const boost::any & value); - virtual const boost::any getPathValue(const App::ObjectIdentifier & /*path*/) const { return _enum; } + virtual bool setPyPathValue(const App::ObjectIdentifier & path, const Py::Object &value); + virtual const boost::any getPathValue(const App::ObjectIdentifier & /*path*/) const; + virtual bool getPyPathValue(const ObjectIdentifier &path, Py::Object &r) const; private: Enumeration _enum; diff --git a/src/Gui/ViewProviderDocumentObject.cpp b/src/Gui/ViewProviderDocumentObject.cpp index 4c18218c57..208b18b1b8 100644 --- a/src/Gui/ViewProviderDocumentObject.cpp +++ b/src/Gui/ViewProviderDocumentObject.cpp @@ -100,6 +100,7 @@ ViewProviderDocumentObject::ViewProviderDocumentObject() ViewProviderDocumentObject::~ViewProviderDocumentObject() { // Make sure that the property class does not destruct our string list + DisplayMode.setContainer(nullptr); DisplayMode.setEnums(0); } @@ -687,7 +688,7 @@ ViewProviderDocumentObject *ViewProviderDocumentObject::getLinkedViewProvider( std::string ViewProviderDocumentObject::getFullName() const { if(pcObject) return pcObject->getFullName() + ".ViewObject"; - return std::string(); + return std::string("?"); } bool ViewProviderDocumentObject::allowTreeOrderSwap(const App::DocumentObject *child1, const App::DocumentObject *child2) const diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index d3e9cd8076..1c56a23fcc 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -770,7 +770,7 @@ QVariant PropertyFontItem::value(const App::Property* prop) const void PropertyFontItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::String)) + if (hasExpression() || !value.canConvert(QVariant::String)) return; QString val = value.toString(); QString data = QString::fromLatin1("\"%1\"").arg(val); @@ -1270,7 +1270,7 @@ QVariant PropertyBoolItem::value(const App::Property* prop) const void PropertyBoolItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::Bool)) + if (hasExpression() || !value.canConvert(QVariant::Bool)) return; bool val = value.toBool(); QString data = (val ? QLatin1String("True") : QLatin1String("False")); @@ -1377,7 +1377,7 @@ QVariant PropertyVectorItem::value(const App::Property* prop) const void PropertyVectorItem::setValue(const QVariant& value) { - if (!value.canConvert()) + if (hasExpression() || !value.canConvert()) return; const Base::Vector3d& val = value.value(); QString data = QString::fromLatin1("(%1, %2, %3)") @@ -1652,7 +1652,7 @@ QVariant PropertyVectorDistanceItem::value(const App::Property* prop) const void PropertyVectorDistanceItem::setValue(const QVariant& variant) { - if (!variant.canConvert()) + if (hasExpression() || !variant.canConvert()) return; const Base::Vector3d& value = variant.value(); @@ -1869,7 +1869,7 @@ QVariant PropertyMatrixItem::toolTip(const App::Property* prop) const void PropertyMatrixItem::setValue(const QVariant& value) { - if (!value.canConvert()) + if (hasExpression() || !value.canConvert()) return; const Base::Matrix4D& val = value.value(); const int decimals=16; @@ -2644,7 +2644,7 @@ QVariant PropertyPlacementItem::toString(const QVariant& prop) const void PropertyPlacementItem::setValue(const QVariant& value) { - if (!value.canConvert()) + if (hasExpression() || !value.canConvert()) return; // Accept this only if the user changed the axis, angle or position but // not if >this< item loses focus @@ -2711,7 +2711,37 @@ void PropertyPlacementItem::propertyBound() PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyEnumItem) PropertyEnumItem::PropertyEnumItem() + :m_enum(0) { + if(PropertyView::showAll()) { + m_enum = static_cast(PropertyStringListItem::create()); + m_enum->setParent(this); + m_enum->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Enum"))); + this->appendChild(m_enum); + } +} + +void PropertyEnumItem::propertyBound() +{ + if (m_enum && isBound()) + m_enum->bind(App::ObjectIdentifier(getPath())<getTypeId().isDerivedFrom(App::PropertyEnumeration::getClassTypeId())) { + const App::PropertyEnumeration* prop_enum = static_cast(prop); + for(int i=0,last=prop_enum->getEnum().maxValue();i<=last;++i) + res.push_back(QString::fromUtf8(prop_enum->getEnums()[i])); + } + return res; } QVariant PropertyEnumItem::value(const App::Property* prop) const @@ -2719,25 +2749,40 @@ QVariant PropertyEnumItem::value(const App::Property* prop) const assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyEnumeration::getClassTypeId())); const App::PropertyEnumeration* prop_enum = static_cast(prop); - const std::vector& value = prop_enum->getEnumVector(); - long currentItem = prop_enum->getValue(); - - if (currentItem < 0 || currentItem >= static_cast(value.size())) + if(!prop_enum->isValid()) return QVariant(QString()); - return QVariant(QString::fromUtf8(value[currentItem].c_str())); + return QVariant(QString::fromUtf8(prop_enum->getValueAsString())); } void PropertyEnumItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::StringList)) + if (hasExpression()) return; - QStringList items = value.toStringList(); - if (!items.isEmpty()) { - QByteArray val = items.front().toUtf8(); - std::string str = Base::Tools::escapedUnicodeFromUtf8(val); - QString data = QString::fromLatin1("u\"%1\"").arg(QString::fromStdString(str)); - setPropertyValue(data); + + QString data; + + if (value.type() == QVariant::StringList) { + QStringList values = value.toStringList(); + QTextStream str(&data); + str << "["; + for (QStringList::Iterator it = values.begin(); it != values.end(); ++it) { + QString text(*it); + text.replace(QString::fromUtf8("'"),QString::fromUtf8("\\'")); + + std::string pystr = Base::Tools::escapedUnicodeFromUtf8(text.toUtf8()); + pystr = Base::Interpreter().strToPython(pystr.c_str()); + str << "u\"" << pystr.c_str() << "\", "; + } + str << "]"; } + else if (value.canConvert(QVariant::String)) { + QByteArray val = value.toString().toUtf8(); + std::string str = Base::Tools::escapedUnicodeFromUtf8(val); + data = QString::fromLatin1("u\"%1\"").arg(QString::fromStdString(str)); + } + else + return; + setPropertyValue(data); } QWidget* PropertyEnumItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const @@ -2851,7 +2896,7 @@ QVariant PropertyStringListItem::value(const App::Property* prop) const void PropertyStringListItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::StringList)) + if (hasExpression() || !value.canConvert(QVariant::StringList)) return; QStringList values = value.toStringList(); QString data; @@ -2928,7 +2973,7 @@ QVariant PropertyFloatListItem::value(const App::Property* prop) const void PropertyFloatListItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::StringList)) + if (hasExpression() || !value.canConvert(QVariant::StringList)) return; QStringList values = value.toStringList(); QString data; @@ -3003,7 +3048,7 @@ QVariant PropertyIntegerListItem::value(const App::Property* prop) const void PropertyIntegerListItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::StringList)) + if (hasExpression() || !value.canConvert(QVariant::StringList)) return; QStringList values = value.toStringList(); QString data; @@ -3055,7 +3100,7 @@ QVariant PropertyColorItem::value(const App::Property* prop) const void PropertyColorItem::setValue(const QVariant& value) { - if (!value.canConvert()) + if (hasExpression() || !value.canConvert()) return; QColor col = value.value(); App::Color val; val.setValue(col); @@ -3347,7 +3392,7 @@ QVariant PropertyMaterialItem::value(const App::Property* prop) const void PropertyMaterialItem::setValue(const QVariant& value) { - if (!value.canConvert()) + if (hasExpression() || !value.canConvert()) return; Material mat = value.value(); @@ -3779,7 +3824,7 @@ QVariant PropertyMaterialListItem::value(const App::Property* prop) const void PropertyMaterialListItem::setValue(const QVariant& value) { - if (!value.canConvert()) + if (hasExpression() || !value.canConvert()) return; QVariantList list = value.toList(); @@ -3900,7 +3945,7 @@ QVariant PropertyFileItem::value(const App::Property* prop) const void PropertyFileItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::String)) + if (hasExpression() || !value.canConvert(QVariant::String)) return; QString val = value.toString(); QString data = QString::fromLatin1("\"%1\"").arg(val); @@ -3957,7 +4002,7 @@ QVariant PropertyPathItem::value(const App::Property* prop) const void PropertyPathItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::String)) + if (hasExpression() || !value.canConvert(QVariant::String)) return; QString val = value.toString(); QString data = QString::fromLatin1("\"%1\"").arg(val); @@ -4009,7 +4054,7 @@ QVariant PropertyTransientFileItem::value(const App::Property* prop) const void PropertyTransientFileItem::setValue(const QVariant& value) { - if (!value.canConvert(QVariant::String)) + if (hasExpression() || !value.canConvert(QVariant::String)) return; QString val = value.toString(); QString data = QString::fromLatin1("\"%1\"").arg(val); @@ -4267,7 +4312,6 @@ PropertyLinkListItem::PropertyLinkListItem() { } -// -------------------------------------------------------------------- PropertyItemEditorFactory::PropertyItemEditorFactory() { diff --git a/src/Gui/propertyeditor/PropertyItem.h b/src/Gui/propertyeditor/PropertyItem.h index 9483b4d3b6..c409a0be90 100644 --- a/src/Gui/propertyeditor/PropertyItem.h +++ b/src/Gui/propertyeditor/PropertyItem.h @@ -778,6 +778,8 @@ private: PropertyVectorDistanceItem* m_p; }; +class PropertyStringListItem; + /** * Edit properties of enum type. * \author Werner Mayer @@ -785,18 +787,26 @@ private: class GuiExport PropertyEnumItem: public PropertyItem { Q_OBJECT + Q_PROPERTY(QStringList Enum READ getEnum WRITE setEnum DESIGNABLE true USER true) PROPERTYITEM_HEADER virtual QWidget* createEditor(QWidget* parent, const QObject* receiver, const char* method) const; virtual void setEditorData(QWidget *editor, const QVariant& data) const; virtual QVariant editorData(QWidget *editor) const; + QStringList getEnum() const; + void setEnum(QStringList); + protected: virtual QVariant value(const App::Property*) const; virtual void setValue(const QVariant&); + virtual void propertyBound(); protected: PropertyEnumItem(); + +private: + PropertyStringListItem* m_enum; }; /**