// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2002 Jürgen Riegel * * * * 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 SRC_APP_DOCUMENT_H_ #define SRC_APP_DOCUMENT_H_ #include #include #include #include #include #include #include "PropertyContainer.h" #include "PropertyLinks.h" #include "PropertyStandard.h" #include "ExportInfo.h" #include #include #include #include #include namespace Base { class Writer; } namespace App { enum class AddObjectOption { None = 0, SetNewStatus = 1, SetPartialStatus = 2, UnsetPartialStatus = 4, DoSetup = 8, ActivateObject = 16 }; using AddObjectOptions = Base::Flags; enum class RemoveObjectOption { None = 0, MayRemoveWhileRecomputing = 1, MayDestroyOutOfTransaction = 2, DestroyOnRollback = 4, PreserveChildrenVisibility = 8 }; using RemoveObjectOptions = Base::Flags; } ENABLE_BITMASK_OPERATORS(App::AddObjectOption) ENABLE_BITMASK_OPERATORS(App::RemoveObjectOption) namespace App { class TransactionalObject; class DocumentObject; class DocumentObjectExecReturn; class Document; class DocumentPy; class Application; class Transaction; class StringHasher; using StringHasherRef = Base::Reference; /** * @brief The document class * @ingroup DocumentGroup * @details For a more high-level discussion see the topic @ref DocumentGroup "Document". */ class AppExport Document: public PropertyContainer { PROPERTY_HEADER_WITH_OVERRIDE(App::Document); public: // clang-format off enum Status { SkipRecompute = 0, KeepTrailingDigits = 1, Closable = 2, Restoring = 3, Recomputing = 4, PartialRestore = 5, Importing = 6, PartialDoc = 7, AllowPartialRecompute = 8, // allow recomputing editing object if SkipRecompute is set TempDoc = 9, // Mark as temporary document without prompt for save RestoreError = 10, LinkStampChanged = 11, // Indicates during restore time if any linked document's time stamp has changed IgnoreErrorOnRecompute = 12, // Don't report errors if the recompute failed RecomputeOnRestore = 13, // Mark pending recompute on restore for migration purposes MigrateLCS = 14 // Migrate local coordinate system of older versions }; // clang-format on // NOLINTBEGIN /** @name Properties */ //@{ /// holds the long name of the document (utf-8 coded) PropertyString Label; /// full qualified (with path) file name (utf-8 coded) PropertyString FileName; /// creators name (utf-8) PropertyString CreatedBy; PropertyString CreationDate; /// user last modified the document PropertyString LastModifiedBy; PropertyString LastModifiedDate; /// company name UTF8(optional) PropertyString Company; /// Unit System PropertyEnumeration UnitSystem; /// long comment or description (UTF8 with line breaks) PropertyString Comment; /// Id e.g. Part number PropertyString Id; /// unique identifier of the document PropertyUUID Uid; /// Full name of the licence e.g. "Creative Commons Attribution". See https://spdx.org/licenses/ PropertyString License; /// License description/contract URL PropertyString LicenseURL; /// Meta descriptions PropertyMap Meta; /// Material descriptions, used and defined in the Material module. PropertyMap Material; /// read-only name of the temp dir created when the document is opened PropertyString TransientDir; /// Tip object of the document (if any) PropertyLink Tip; /// Tip object of the document (if any) PropertyString TipName; /// Whether to show hidden items in TreeView PropertyBool ShowHidden; /// Whether to use hasher on topological naming PropertyBool UseHasher; //@} /** @name Signals of the document */ //@{ // clang-format off /// signal before changing an doc property fastsignals::signal signalBeforeChange; /// signal on changed doc property fastsignals::signal signalChanged; /// signal on new Object fastsignals::signal signalNewObject; /// signal on deleted Object fastsignals::signal signalDeletedObject; /// signal before changing an Object fastsignals::signal signalBeforeChangeObject; /// signal on changed Object fastsignals::signal signalChangedObject; /// signal on manually called DocumentObject::touch() fastsignals::signal signalTouchedObject; /// signal on relabeled Object fastsignals::signal signalRelabelObject; /// signal on activated Object fastsignals::signal signalActivatedObject; /// signal on created object fastsignals::signal signalTransactionAppend; /// signal on removed object fastsignals::signal signalTransactionRemove; /// signal on undo fastsignals::signal signalUndo; /// signal on redo fastsignals::signal signalRedo; /** signal on load/save document * this signal is given when the document gets streamed. * you can use this hook to write additional information in * the file (like the Gui::Document does). */ fastsignals::signal signalSaveDocument; fastsignals::signal signalRestoreDocument; fastsignals::signal&, Base::Writer&)> signalExportObjects; fastsignals::signal&, Base::Writer&)> signalExportViewObjects; fastsignals::signal&, Base::XMLReader&)> signalImportObjects; fastsignals::signal&, Base::Reader&, const std::map&)> signalImportViewObjects; fastsignals::signal&)> signalFinishImportObjects; // signal starting a save action to a file fastsignals::signal signalStartSave; // signal finishing a save action to a file fastsignals::signal signalFinishSave; fastsignals::signal signalBeforeRecompute; fastsignals::signal&)> signalRecomputed; fastsignals::signal signalRecomputedObject; // signal a new opened transaction fastsignals::signal signalOpenTransaction; // signal a committed transaction fastsignals::signal signalCommitTransaction; // signal an aborted transaction fastsignals::signal signalAbortTransaction; fastsignals::signal&)> signalSkipRecompute; fastsignals::signal signalFinishRestoreObject; fastsignals::signal signalChangePropertyEditor; fastsignals::signal signalLinkXsetValue; // clang-format on //@} // NOLINTEND using PreRecomputeHook = std::function; void setPreRecomputeHook(const PreRecomputeHook& hook); void clearDocument(); /** @name File handling of the document */ //@{ /// Save the Document under a new Name // void saveAs (const char* Name); /// Save the document to the file in Property Path bool save(); bool saveAs(const char* file); bool saveCopy(const char* file) const; /// Restore the document from the file in Property Path void restore(const char* filename = nullptr, bool delaySignal = false, const std::vector& objNames = {}); bool afterRestore(bool checkPartial = false); bool afterRestore(const std::vector&, bool checkPartial = false); enum ExportStatus { NotExporting, Exporting, }; ExportStatus isExporting(const DocumentObject* obj) const; ExportInfo exportInfo() const; void setExportInfo(const ExportInfo& info); void exportObjects(const std::vector&, std::ostream&); void exportGraphviz(std::ostream&) const; std::vector importObjects(Base::XMLReader& reader); /** Import any externally linked objects * * @param objs: input list of objects. Only objects belonging to this document will * be checked for external links. And all found external linked object will be imported * to this document. Link type properties of those input objects will be automatically * reassigned to the imported objects. Note that the link properties of other objects * in the document but not included in the input list, will not be affected even if they * point to some object beining imported. To import all objects, simply pass in all objects * of this document. * * @return the list of imported objects */ std::vector importLinks(const std::vector& objs = {}); /// Opens the document from its file name // void open (void); /// Is the document already saved to a file? bool isSaved() const; /// Get the document name const char* getName() const; /// Get program version the project file was created with const char* getProgramVersion() const; /** Returned filename * * For saved document, this will be the content stored in property * 'Filename'. For unsaved temporary file, this will be the content of * property 'TransientDir'. */ const char* getFileName() const; //@} void Save(Base::Writer& writer) const override; void Restore(Base::XMLReader& reader) override; /// returns the complete document memory consumption, including all managed DocObjects and Undo /// Redo. unsigned int getMemSize() const override; /** @name Object handling */ //@{ /** Add a feature of sType with sName (ASCII) to this document and set it active. * Unicode names are set through the Label property. * @param sType the type of created object * @param pObjectName if nonNULL use that name otherwise generate a new unique name based on the * \a sType * @param isNew if false don't call the \c DocumentObject::setupObject() callback (default * is true) * @param viewType override object's view provider name * @param isPartial indicate if this object is meant to be partially loaded */ DocumentObject* addObject(const char* sType, const char* pObjectName = nullptr, bool isNew = true, const char* viewType = nullptr, bool isPartial = false); //@{ /** Add a feature of T type with sName (ASCII) to this document and set it active. * Unicode names are set through the Label property. * @param pObjectName if nonNULL use that name otherwise generate a new unique name based on the * \a sType * @param isNew if false don't call the \c DocumentObject::setupObject() callback (default * is true) * @param viewType override object's view provider name * @param isPartial indicate if this object is meant to be partially loaded */ template T* addObject(const char* pObjectName = nullptr, bool isNew = true, const char* viewType = nullptr, bool isPartial = false); /** Add an array of features of the given types and names. * Unicode names are set through the Label property. * @param sType The type of created object * @param objectNames A list of object names * @param isNew If false don't call the \c DocumentObject::setupObject() callback (default * is true) */ std::vector addObjects(const char* sType, const std::vector& objectNames, bool isNew = true); /// Remove a feature out of the document void removeObject(const char* sName); /** Add an existing feature with sName (ASCII) to this document and set it active. * Unicode names are set through the Label property. * This is an overloaded function of the function above and can be used to create * a feature outside and add it to the document afterwards. * \note The passed feature must not yet be added to a document, otherwise an exception * is raised. */ void addObject(DocumentObject*, const char* pObjectName = nullptr); /// returns whether this is actually contains the DocumentObject. /// Testing the DocumentObject's pDoc pointer is not sufficient because the object /// removeObject and _removeObject leave _pDoc unchanged bool containsObject(const DocumentObject*) const; /** Copy objects from another document to this document * * @param objs * @param recursive: if true, then all objects this object depends on are * copied as well. By default \a recursive is false. * * @param returnAll: if true, return all copied objects including those * auto included by recursive searching. If false, then only return the * copied object corresponding to the input objects. * * @return Returns the list of objects copied. */ std::vector copyObject(const std::vector& objs, bool recursive = false, bool returnAll = false); /** Move an object from another document to this document * If \a recursive is true then all objects this object depends on * are moved as well. By default \a recursive is false. * Returns the moved object itself or 0 if the object is already part of this * document.. */ DocumentObject* moveObject(DocumentObject* obj, bool recursive = false); /// Returns the active Object of this document DocumentObject* getActiveObject() const; /// Returns a Object of this document DocumentObject* getObject(const char* Name) const; /// Returns a Object of this document by its id DocumentObject* getObjectByID(long id) const; /// Returns true if the DocumentObject is contained in this document bool isIn(const DocumentObject* pFeat) const; /// Returns a Name of an Object or 0 const char *getObjectName(const DocumentObject* pFeat) const; /// Returns a Name for a new Object or empty if proposedName is null or empty. std::string getUniqueObjectName(const char* proposedName) const; /// Returns a name different from any of the Labels of any objects in this document, based on the given modelName. std::string getStandardObjectLabel(const char* modelName, int d) const; /// Determine if a given DocumentObject Name and a proposed Label are based on the same base name bool haveSameBaseName(const std::string& name, const std::string& label); /// Returns a list of document's objects including the dependencies std::vector getDependingObjects() const; /// Returns a list of all Objects const std::vector& getObjects() const; std::vector getObjectsOfType(const Base::Type& typeId) const; std::vector getObjectsOfType(const std::vector& types) const; /// Returns all object with given extensions. If derived=true also all objects with extensions /// derived from the given one std::vector getObjectsWithExtension(const Base::Type& typeId, bool derived = true) const; std::vector findObjects(const Base::Type& typeId, const char* objname, const char* label) const; /// Returns an array with the correct types already. template inline std::vector getObjectsOfType() const; template inline int countObjectsOfType() const; int countObjectsOfType(const char* typeName) const; /// get the number of objects in the document int countObjects() const; //@} /** @name methods for modification and state handling */ //@{ /// Remove all modifications. After this call The document becomes Valid again. void purgeTouched(); /// check if there is any touched object in this document bool isTouched() const; /// check if there is any object must execute in this document bool mustExecute() const; /// returns all touched objects std::vector getTouched() const; /// set the document to be closable, this is on by default. void setClosable(bool); /// check whether the document can be closed bool isClosable() const; /// set the document to autoCreated, this is off by default. void setAutoCreated(bool); /// check whether the document is autoCreated. bool isAutoCreated() const; /** Recompute touched features and return the number of recalculated features * * @param objs: specify a sub set of objects to recompute. If empty, then * all object in this document is checked for recompute * @param force * @param hasError * @param options */ int recompute(const std::vector& objs = {}, bool force = false, bool* hasError = nullptr, int options = 0); /// Recompute only one feature bool recomputeFeature(DocumentObject* Feat, bool recursive = false); /// get the text of the error of a specified object const char* getErrorDescription(const DocumentObject*) const; /// return the status bits bool testStatus(Status pos) const; /// set the status bits void setStatus(Status pos, bool on); //@} /** @name methods for the UNDO REDO and Transaction handling * * Introduce a new concept of transaction ID. Each transaction must be * unique inside the document. Multiple transactions from different * documents can be grouped together with the same transaction ID. * * When undo, Gui component can query getAvailableUndo(id) to see if it is * possible to undo with a given ID. If there more than one undo * transactions, meaning that there are other transactions before the given * ID. The Gui component shall ask user if they want to undo multiple steps. * And if the user agrees, call undo(id) to unroll all transaction before * and including the one with the given ID. Same applies for redo. * * The new transaction ID describe here is fully backward compatible. * Calling the APIs with a default id=0 gives the original behavior. */ //@{ /// switch the level of Undo/Redo void setUndoMode(int iMode); /// switch the level of Undo/Redo int getUndoMode() const; /// switch the transaction mode void setTransactionMode(int iMode); /** Open a new command Undo/Redo, an UTF-8 name can be specified * * @param name: transaction name * * This function calls Application::setActiveTransaction(name) instead * to setup a potential transaction which will only be created if there is * actual changes. */ void openTransaction(const char* name = nullptr); /// Rename the current transaction if the id matches void renameTransaction(const char* name, int id) const; /// Commit the Command transaction. Do nothing If there is no Command transaction open. void commitTransaction(); /// Abort the actually running transaction. void abortTransaction() const; /// Check if a transaction is open bool hasPendingTransaction() const; /// Return the undo/redo transaction ID starting from the back int getTransactionID(bool undo, unsigned pos = 0) const; /// Check if a transaction is open and its list is empty. /// If no transaction is open true is returned. bool isTransactionEmpty() const; /// Set the Undo limit in Byte! void setUndoLimit(unsigned int UndoMemSize = 0); /// Returns the actual memory consumption of the Undo redo stuff. unsigned int getUndoMemSize() const; /// Set the Undo limit as stack size void setMaxUndoStackSize(unsigned int UndoMaxStackSize = 20); // NOLINT /// Set the Undo limit as stack size unsigned int getMaxUndoStackSize() const; /// Remove all stored Undos and Redos void clearUndos(); /// Returns the number of stored Undos. If greater than 0 Undo will be effective. int getAvailableUndos(int id = 0) const; /// Returns a list of the Undo names std::vector getAvailableUndoNames() const; /// Will UNDO one step, returns False if no undo was done (Undos == 0). bool undo(int id = 0); /// Returns the number of stored Redos. If greater than 0 Redo will be effective. int getAvailableRedos(int id = 0) const; /// Returns a list of the Redo names. std::vector getAvailableRedoNames() const; /// Will REDO one step, returns False if no redo was done (Redos == 0). bool redo(int id = 0); /// returns true if the document is in an Transaction phase, e.g. currently performing a /// redo/undo or rollback bool isPerformingTransaction() const; /// \internal add or remove property from a transactional object void addOrRemovePropertyOfObject(TransactionalObject*, const Property* prop, bool add); void renamePropertyOfObject(TransactionalObject*, const Property* prop, const char* newName); //@} /** @name dependency stuff */ //@{ /// write GraphViz file void writeDependencyGraphViz(std::ostream& out); /// checks if the graph is directed and has no cycles static bool checkOnCycle(); /// get a list of all objects linking to the given object std::vector getInList(const DocumentObject* me) const; /// Option bit flags used by getDepenencyList() enum DependencyOption { /// Return topological sorted list DepSort = 1, /// Do no include object linked by PropertyXLink, as it can handle external link DepNoXLinked = 2, /// Raise exception on cycles DepNoCycle = 4, }; /** Get a complete list of all objects the given objects depend on. * * This function is defined as static because it accepts objects from * different documents, and the returned list will contain dependent * objects from all relevant documents * * @param objs: input objects to query for dependency. * @param options: See DependencyOption */ static std::vector getDependencyList(const std::vector& objs, int options = 0); std::vector getDependentDocuments(bool sort = true); static std::vector getDependentDocuments(std::vector docs, bool sort); // set Changed // void setChanged(DocumentObject* change); /// get a list of topological sorted objects (https://en.wikipedia.org/wiki/Topological_sorting) std::vector topologicalSort() const; /// get all root objects (objects no other one reference too) std::vector getRootObjects() const; /// get all tree root objects (objects that are at the root of the object tree) std::vector getRootObjectsIgnoreLinks() const; /// get all possible paths from one object to another following the OutList std::vector> getPathsByOutList(const DocumentObject* from, const DocumentObject* to) const; //@} /** Called by a property during save to store its StringHasher * * @param hasher: the input hasher * @return Returns a pair. The boolean indicates if the * StringHasher has been added before. The integer is the hasher index. * * The StringHasher object is designed to be shared among multiple objects. * We must not save duplicate copies of the same hasher, and must be * able to restore with the same sharing relationship. This function returns * whether the hasher has been added before by other objects, and the index * of the hasher. If the hasher has not been added before, the object must * save the hasher by calling StringHasher::Save */ std::pair addStringHasher(const StringHasherRef& hasher) const; /** Called by property to restore its StringHasher * * @param index: the index previously returned by calling addStringHasher() * during save. Or if is negative, then return document's own string hasher. * * @return Return the resulting string hasher. * * The caller is responsible for restoring the hasher if the caller is the first * owner of the hasher, i.e. if addStringHasher() returns true during save. */ StringHasherRef getStringHasher(int index = -1) const; /** Return the links to a given object * * @param links: holds the links found * @param obj: the linked object. If NULL, then all links are returned. * @param options: @sa GetLinkOption * @param maxCount: limit the number of links returned, 0 means no limit * @param objs: optional objects to search for, if empty, then all objects * of this document are searched. */ void getLinksTo(std::set& links, const DocumentObject* obj, int options, int maxCount = 0, const std::vector& objs = {}) const; /// Check if there is any link to the given object bool hasLinksTo(const DocumentObject* obj) const; /// Called by objects during restore to ask for recompute void addRecomputeObject(DocumentObject* obj); const std::string& getOldLabel() const { return oldLabel; } /// Function called to signal that an object identifier has been renamed void renameObjectIdentifiers( const std::map& paths, const std::function& selector = [](const DocumentObject*) { return true; }); PyObject* getPyObject() override; std::string getFullName() const override; /// Indicate if there is any document restoring/importing static bool isAnyRestoring(); void registerLabel(const std ::string& newLabel); void unregisterLabel(const std::string& oldLabel); bool containsLabel(const std::string& label); std::string makeUniqueLabel(const std::string& modelLabel); friend class Application; /// because of transaction handling friend class TransactionalObject; friend class DocumentObject; friend class Transaction; friend class TransactionDocumentObject; /// Destruction ~Document() override; protected: /// Construction explicit Document(const char* documentName = ""); void _removeObject(DocumentObject* pcObject, RemoveObjectOptions options = RemoveObjectOption::DestroyOnRollback | RemoveObjectOption::PreserveChildrenVisibility); void _addObject(DocumentObject* pcObject, const char* pObjectName, AddObjectOptions options = AddObjectOption::ActivateObject, const char* viewType = nullptr); /// checks if a valid transaction is open void _checkTransaction(DocumentObject* pcDelObj, const Property* What, int line); void breakDependency(DocumentObject* pcObject, bool clear); std::vector readObjects(Base::XMLReader& reader); void writeObjects(const std::vector&, Base::Writer& writer) const; bool saveToFile(const char* filename) const; int countObjectsOfType(const Base::Type& typeId) const; void onBeforeChange(const Property* prop) override; void onChanged(const Property* prop) override; /// callback from the Document objects before property will be changed void onBeforeChangeProperty(const TransactionalObject* Who, const Property* What); /// callback from the Document objects after property was changed void onChangedProperty(const DocumentObject* Who, const Property* What); /// helper which Recompute only this feature /// @return 0 if succeeded, 1 if failed, -1 if aborted by user. int _recomputeFeature(DocumentObject* Feat); void _clearRedos(); /// refresh the internal dependency graph void _rebuildDependencyList( const std::vector& objs = std::vector()); std::string getTransientDirectoryName(const std::string& uuid, const std::string& filename) const; /** Open a new command Undo/Redo, an UTF-8 name can be specified * * @param name: transaction name * @param id: transaction ID, if 0 then the ID is auto generated. * * @return: Return the ID of the new transaction. * * This function creates an actual transaction regardless of Application * AutoTransaction setting. */ int _openTransaction(const char* name = nullptr, int id = 0); /// Internally called by Application to commit the Command transaction. void _commitTransaction(bool notify = false); /// Internally called by Application to abort the running transaction. void _abortTransaction(); private: void changePropertyOfObject(TransactionalObject* obj, const Property* prop, const std::function& changeFunc); private: // # Data Member of the document // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ std::list mUndoTransactions; std::map mUndoMap; std::list mRedoTransactions; std::map mRedoMap; struct DocumentP* d; std::string oldLabel; std::string myName; bool autoCreated; // Flag to know if the document was automatically created at startup }; template inline std::vector Document::getObjectsOfType() const { std::vector type; const std::vector obj = this->getObjectsOfType(T::getClassTypeId()); type.reserve(obj.size()); for (auto it : obj) { type.push_back(static_cast(it)); } return type; } template inline int Document::countObjectsOfType() const { static_assert(std::is_base_of_v, "T must be derived from App::DocumentObject"); return this->countObjectsOfType(T::getClassTypeId()); } template T* Document::addObject(const char* pObjectName, bool isNew, const char* viewType, bool isPartial) { static_assert(std::is_base_of::value, "T must be derived from DocumentObject"); return static_cast(addObject(T::getClassName(), pObjectName, isNew, viewType, isPartial)); } } // namespace App #endif // SRC_APP_DOCUMENT_H_