From cd1c753fa24b8a78e108bf512ccc2d3b0b0ab0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 9 Dec 2016 22:48:46 +0100 Subject: [PATCH] Extensions: Implement persistence --- src/App/Document.cpp | 6 +- src/App/DocumentObject.cpp | 2 +- src/App/DynamicProperty.cpp | 9 ++ src/App/Extension.cpp | 4 +- src/App/Extension.h | 6 ++ src/App/ExtensionContainer.cpp | 145 ++++++++++++++++++++++++++++++++- src/App/ExtensionContainer.h | 15 +++- src/Gui/Document.cpp | 7 +- src/Mod/Test/Document.py | 54 +++++++++++- 9 files changed, 235 insertions(+), 13 deletions(-) diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 9e6a98e8d..b5a880427 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -1153,7 +1153,11 @@ void Document::writeObjects(const std::vector& obj, writer.incInd(); // indentation for 'Object name' for (it = obj.begin(); it != obj.end(); ++it) { - writer.Stream() << writer.ind() << "getNameInDocument() << "\">" << endl; + writer.Stream() << writer.ind() << "getNameInDocument() << "\""; + if((*it)->hasExtensions()) + writer.Stream() << " Extensions=\"True\""; + + writer.Stream() << ">" << endl; (*it)->Save(writer); writer.Stream() << writer.ind() << "" << endl; } diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index 476d87412..346c38491 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -305,7 +305,7 @@ bool DocumentObject::isTouched() const void DocumentObject::Save (Base::Writer &writer) const { writer.ObjectName = this->getNameInDocument(); - App::PropertyContainer::Save(writer); + App::ExtensionContainer::Save(writer); } /** diff --git a/src/App/DynamicProperty.cpp b/src/App/DynamicProperty.cpp index a65f01837..d60e2f761 100644 --- a/src/App/DynamicProperty.cpp +++ b/src/App/DynamicProperty.cpp @@ -331,6 +331,11 @@ std::string DynamicProperty::encodeAttribute(const std::string& str) const void DynamicProperty::Save (Base::Writer &writer) const { + //extenions must be saved first, as they need to be read and initialised before properties (as + //they have their own properties which they need to handle on restore) + if(this->pc->isDerivedFrom(App::ExtensionContainer::getClassTypeId())) + static_cast(this->pc)->saveExtensions(writer); + std::map Map; getPropertyMap(Map); @@ -389,6 +394,10 @@ void DynamicProperty::Save (Base::Writer &writer) const void DynamicProperty::Restore(Base::XMLReader &reader) { + //first all extensions must be initialised so that they can handle their properties + if(this->pc->isDerivedFrom(App::ExtensionContainer::getClassTypeId())) + static_cast(this->pc)->restoreExtensions(reader); + reader.readElement("Properties"); int Cnt = reader.getAttributeAsInteger("Count"); diff --git a/src/App/Extension.cpp b/src/App/Extension.cpp index 1b46b0624..4a348b53c 100644 --- a/src/App/Extension.cpp +++ b/src/App/Extension.cpp @@ -108,12 +108,12 @@ PyObject* Extension::getExtensionPyObject(void) { std::string Extension::name() const { if(m_extensionType.isBad()) - throw Base::Exception("Extension::setExtendedObject: Extension type not set"); + throw Base::Exception("Extension::name: Extension type not set"); std::string temp(m_extensionType.getName()); std::string::size_type pos = temp.find_last_of(":"); - if(pos != std::string::npos) + if(pos != std::string::npos) return temp.substr(pos+1); else return std::string(); diff --git a/src/App/Extension.h b/src/App/Extension.h index 800f52c08..fe68c5862 100644 --- a/src/App/Extension.h +++ b/src/App/Extension.h @@ -253,6 +253,12 @@ public: virtual const char* extensionGetPropertyDocumentation(const char *name) const; //@} + /** @name Persistance */ + //@{ + virtual void extensionSave(Base::Writer&) const {}; + virtual void extensionRestore(Base::XMLReader&) {}; + //@} + /** @name TypeHandling */ //@{ bool extensionIsDerivedFrom(const Base::Type type) const {return getExtensionTypeId().isDerivedFrom(type);} diff --git a/src/App/ExtensionContainer.cpp b/src/App/ExtensionContainer.cpp index e8b4fbeb9..47f100555 100644 --- a/src/App/ExtensionContainer.cpp +++ b/src/App/ExtensionContainer.cpp @@ -83,11 +83,11 @@ bool ExtensionContainer::hasExtension(Base::Type t) const { return true; } -bool ExtensionContainer::hasExtension(const char* name) const { +bool ExtensionContainer::hasExtension(std::string name) const { //and for types derived from it, as they can be cast to the extension for(auto entry : _extensions) { - if(strcmp(entry.second->name().c_str(), name) == 0) + if(entry.second->name() == name) return true; } return false; @@ -110,11 +110,16 @@ Extension* ExtensionContainer::getExtension(Base::Type t) { return result->second; } -Extension* ExtensionContainer::getExtension(const char* name) { +bool ExtensionContainer::hasExtensions() const { + + return !_extensions.empty(); +} + +Extension* ExtensionContainer::getExtension(std::string name) { //and for types derived from it, as they can be cast to the extension for(auto entry : _extensions) { - if(strcmp(entry.second->name().c_str(), name) == 0) + if(entry.second->name() == name) return entry.second; } return nullptr; @@ -273,3 +278,135 @@ void ExtensionContainer::onChanged(const Property* prop) { App::PropertyContainer::onChanged(prop); } + +void ExtensionContainer::Save(Base::Writer& writer) const { + + //Note: save extensions must be called first to ensure that the extension element is always the + // very first inside the object element. That is needed as extension eleent works together with + // an object attribute, and if annother element would be read first the object attributes would be + // cleared. + saveExtensions(writer); + App::PropertyContainer::Save(writer); +} + +void ExtensionContainer::Restore(Base::XMLReader& reader) { + + //restore dynamic extensions. + //Note 1: The extension element must be read first, before all other object elements. That is + // needed as the element works together with an object element attribute, which would be + // cleared if annother attribute is read first + //Note 2: This must happen before the py object of this container is used, as only in the + // pyobject constructor the extension methods are added to the container. + restoreExtensions(reader); + App::PropertyContainer::Restore(reader); +} + +void ExtensionContainer::saveExtensions(Base::Writer& writer) const { + + //we don't save anything if there are no dynamic extensions + if(!hasExtensions()) + return; + + //save dynamic extensions + writer.incInd(); // indentation for 'Extensions' + writer.Stream() << writer.ind() << "" << std::endl; + for(auto entry : _extensions) { + + auto ext = entry.second; + writer.incInd(); // indentation for 'Extension name' + writer.Stream() << writer.ind() << "getExtensionTypeId().getName() <<"\"" + << " name=\"" << ext->name() << "\">" << std::endl; + writer.incInd(); // indentation for the actual Extension + try { + // We must make sure to handle all exceptions accordingly so that + // the project file doesn't get invalidated. In the error case this + // means to proceed instead of aborting the write operation. + ext->extensionSave(writer); + } + catch (const Base::Exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const std::exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const char* e) { + Base::Console().Error("%s\n", e); + } +#ifndef FC_DEBUG + catch (...) { + Base::Console().Error("ExtensionContainer::Save: Unknown C++ exception thrown. Try to continue...\n"); + } +#endif + writer.decInd(); // indentation for the actual extension + writer.Stream() << writer.ind() << "" << std::endl; + writer.decInd(); // indentation for 'Extension name' + } + writer.Stream() << writer.ind() << "" << std::endl; + writer.decInd(); +} + +void ExtensionContainer::restoreExtensions(Base::XMLReader& reader) { + + //Dynamic extensions are optional (also because they are introduced late into the document format) + //and hence it is possible that the element does not exist. As we cannot check for the existance of + //an element a object attribute is set if extensions are available. Here we check that + //attribute, and only if it exists the extensions element will be available. + if(!reader.hasAttribute("Extensions")) + return; + + reader.readElement("Extensions"); + int Cnt = reader.getAttributeAsInteger("Count"); + + for (int i=0 ;i(extension.createInstance()); + //check if this really is a python extension! + if (!ext->isPythonExtension()) { + delete ext; + std::stringstream str; + str << "Extension is not a python addable version: '" << Type << "'" << std::ends; + throw Base::Exception(str.str()); + } + + ext->initExtension(this); + } + if (ext && strcmp(ext->getExtensionTypeId().getName(), Type) == 0) + ext->extensionRestore(reader); + } + catch (const Base::XMLParseException&) { + throw; // re-throw + } + catch (const Base::Exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const std::exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const char* e) { + Base::Console().Error("%s\n", e); + } +#ifndef FC_DEBUG + catch (...) { + Base::Console().Error("ExtensionContainer::Restore: Unknown C++ exception thrown"); + } +#endif + + reader.readEndElement("Extension"); + } + reader.readEndElement("Extensions"); +} diff --git a/src/App/ExtensionContainer.h b/src/App/ExtensionContainer.h index ca13f5b34..303f0e27c 100644 --- a/src/App/ExtensionContainer.h +++ b/src/App/ExtensionContainer.h @@ -29,6 +29,8 @@ #include "PropertyPythonObject.h" #include "DynamicProperty.h" #include +#include +#include namespace App { @@ -123,9 +125,10 @@ public: void registerExtension(Base::Type extension, App::Extension* ext); bool hasExtension(Base::Type) const; //returns first of type (or derived from) and throws otherwise - bool hasExtension(const char* name) const; //this version does not check derived classes + bool hasExtension(std::string name) const; //this version does not check derived classes + bool hasExtensions() const; App::Extension* getExtension(Base::Type); //returns first of type (or derived from) and throws otherwise - App::Extension* getExtension(const char* name); //this version does not check derived classes + App::Extension* getExtension(std::string name); //this version does not check derived classes //returns first of type (or derived from) and throws otherwise template @@ -176,6 +179,14 @@ public: virtual void onChanged(const Property*); + virtual void Save(Base::Writer& writer) const; + virtual void Restore(Base::XMLReader& reader); + + //those methods save/restore the dynamic extenions without handling properties, which is something + //done by the default Save/Restore methods. + void saveExtensions(Base::Writer& writer) const; + void restoreExtensions(Base::XMLReader& reader); + private: //stored extensions std::map _extensions; diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index de438bf0e..e7e7525c8 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -898,8 +898,11 @@ void Document::SaveDocFile (Base::Writer &writer) const ViewProvider* obj = it->second; writer.Stream() << writer.ind() << "getNameInDocument() << "\" " - << "expanded=\"" << (doc->testStatus(App::Expand) ? 1:0) - << "\">" << std::endl; + << "expanded=\"" << (doc->testStatus(App::Expand) ? 1:0) << "\""; + if(obj->hasExtensions()) + writer.Stream() << " Extensions=\"True\""; + + writer.Stream() << ">" << std::endl; obj->Save(writer); writer.Stream() << writer.ind() << "" << std::endl; } diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py index ed49486df..52b68dfb1 100644 --- a/src/Mod/Test/Document.py +++ b/src/Mod/Test/Document.py @@ -196,7 +196,7 @@ class DocumentBasicCases(unittest.TestCase): self.failUnless(len(grp.Group) == 1) self.failUnless(grp.Group[0] == obj) except: - self.failUnless(True) + self.failUnless(False) #test if the method override works class SpecialGroup(): @@ -261,6 +261,24 @@ class DocumentBasicCases(unittest.TestCase): #closing doc FreeCAD.closeDocument("CreateTest") +# class must be defined in global scope to allow it to be reloaded on document open +class SaveRestoreSpecialGroup(): + def __init__(self, obj): + obj.addExtension("App::GroupExtensionPython", self) + obj.Proxy = self + + def allowObject(self, obj): + return False; + +# class must be defined in global scope to allow it to be reloaded on document open +class SaveRestoreSpecialGroupViewProvider(): + def __init__(self, obj): + obj.addExtension("Gui::ViewProviderGroupExtensionPython", self) + obj.Proxy = self + + def testFunction(self): + pass + class DocumentSaveRestoreCases(unittest.TestCase): def setUp(self): self.Doc = FreeCAD.newDocument("SaveRestoreTests") @@ -318,6 +336,40 @@ class DocumentSaveRestoreCases(unittest.TestCase): except: # Okay, no document open self.failUnless(True) + + def testExtensionSaveRestore(self): + # saving and restoring + SaveName = self.TempPath + os.sep + "SaveRestoreExtensions.FCStd" + Doc = FreeCAD.newDocument("SaveRestoreExtensions") + #we try to create a normal python object and add a extension to it + obj = Doc.addObject("App::DocumentObject", "Obj") + grp1 = Doc.addObject("App::DocumentObject", "Extension_1") + grp2 = Doc.addObject("App::FeaturePython", "Extension_2") + + grp1.addExtension("App::GroupExtensionPython", None) + SaveRestoreSpecialGroup(grp2) + if FreeCAD.GuiUp: + SaveRestoreSpecialGroupViewProvider(grp2.ViewObject) + grp2.Group = [obj] + + Doc.saveAs(SaveName) + FreeCAD.closeDocument("SaveRestoreExtensions") + Doc = FreeCAD.open(SaveName) + + self.failUnless(Doc.Extension_1.hasExtension("App::GroupExtension")) + self.failUnless(Doc.Extension_2.hasExtension("App::GroupExtension")) + self.failUnless(Doc.Extension_1.ExtensionProxy is None) + self.failUnless(Doc.Extension_2.ExtensionProxy is not None) + self.failUnless(Doc.Extension_2.Group[0] is Doc.Obj) + self.failUnless(hasattr(Doc.Extension_2.Proxy, 'allowObject')) + self.failUnless(hasattr(Doc.Extension_2.ExtensionProxy, 'allowObject')) + + if FreeCAD.GuiUp: + self.failUnless(Doc.Extension_2.ViewObject.hasExtension("Gui::ViewProviderGroupExtensionPython")) + self.failUnless(hasattr(Doc.Extension_2.ViewObject.Proxy, 'testFunction')) + self.failUnless(hasattr(Doc.Extension_2.ViewObject.ExtensionProxy, 'testFunction')) + + FreeCAD.closeDocument("SaveRestoreExtensions") def tearDown(self): #closing doc