Merge pull request #4952 from realthunder/FixDocumentRestore
Fix external document loading
This commit is contained in:
@@ -55,6 +55,8 @@
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
|
||||
@@ -99,6 +101,7 @@
|
||||
#include "Document.h"
|
||||
#include "DocumentObjectGroup.h"
|
||||
#include "DocumentObjectFileIncluded.h"
|
||||
#include "DocumentObserver.h"
|
||||
#include "InventorObject.h"
|
||||
#include "VRMLObject.h"
|
||||
#include "Annotation.h"
|
||||
@@ -487,6 +490,7 @@ bool Application::closeDocument(const char* name)
|
||||
setActiveDocument((Document*)0);
|
||||
std::unique_ptr<Document> delDoc (pos->second);
|
||||
DocMap.erase( pos );
|
||||
DocFileMap.erase(FileInfo(delDoc->FileName.getValue()).filePath());
|
||||
|
||||
_objCount = -1;
|
||||
|
||||
@@ -566,8 +570,10 @@ int Application::addPendingDocument(const char *FileName, const char *objName, b
|
||||
return -1;
|
||||
assert(FileName && FileName[0]);
|
||||
assert(objName && objName[0]);
|
||||
auto ret = _pendingDocMap.emplace(FileName,std::set<std::string>());
|
||||
ret.first->second.emplace(objName);
|
||||
if(!_docReloadAttempts[FileName].emplace(objName).second)
|
||||
return -1;
|
||||
auto ret = _pendingDocMap.emplace(FileName,std::vector<std::string>());
|
||||
ret.first->second.push_back(objName);
|
||||
if(ret.second) {
|
||||
_pendingDocs.push_back(ret.first->first.c_str());
|
||||
return 1;
|
||||
@@ -623,6 +629,41 @@ Document* Application::openDocument(const char * FileName, bool createView) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Document *Application::getDocumentByPath(const char *path, PathMatchMode checkCanonical) const {
|
||||
if(!path || !path[0])
|
||||
return nullptr;
|
||||
if(DocFileMap.empty()) {
|
||||
for(const auto &v : DocMap) {
|
||||
const auto &file = v.second->FileName.getStrValue();
|
||||
if(file.size())
|
||||
DocFileMap[FileInfo(file.c_str()).filePath()] = v.second;
|
||||
}
|
||||
}
|
||||
auto it = DocFileMap.find(FileInfo(path).filePath());
|
||||
if(it != DocFileMap.end())
|
||||
return it->second;
|
||||
|
||||
if (checkCanonical == PathMatchMode::MatchAbsolute)
|
||||
return nullptr;
|
||||
|
||||
std::string filepath = FileInfo(path).filePath();
|
||||
QString canonicalPath = QFileInfo(QString::fromUtf8(path)).canonicalFilePath();
|
||||
for (const auto &v : DocMap) {
|
||||
QFileInfo fi(QString::fromUtf8(v.second->FileName.getValue()));
|
||||
if (canonicalPath == fi.canonicalFilePath()) {
|
||||
if (checkCanonical == PathMatchMode::MatchCanonical)
|
||||
return v.second;
|
||||
bool samePath = (canonicalPath == QString::fromUtf8(filepath.c_str()));
|
||||
FC_WARN("Identical physical path '" << canonicalPath.toUtf8().constData() << "'\n"
|
||||
<< (samePath?"":" for file '") << (samePath?"":filepath.c_str()) << (samePath?"":"'\n")
|
||||
<< " with existing document '" << v.second->Label.getValue()
|
||||
<< "' in path: '" << v.second->FileName.getValue() << "'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Document*> Application::openDocuments(const std::vector<std::string> &filenames,
|
||||
const std::vector<std::string> *paths,
|
||||
const std::vector<std::string> *labels,
|
||||
@@ -640,6 +681,7 @@ std::vector<Document*> Application::openDocuments(const std::vector<std::string>
|
||||
_pendingDocs.clear();
|
||||
_pendingDocsReopen.clear();
|
||||
_pendingDocMap.clear();
|
||||
_docReloadAttempts.clear();
|
||||
|
||||
signalStartOpenDocument();
|
||||
|
||||
@@ -649,118 +691,162 @@ std::vector<Document*> Application::openDocuments(const std::vector<std::string>
|
||||
for (auto &name : filenames)
|
||||
_pendingDocs.push_back(name.c_str());
|
||||
|
||||
std::map<Document *, DocTiming> newDocs;
|
||||
std::map<DocumentT, DocTiming> timings;
|
||||
|
||||
FC_TIME_INIT(t);
|
||||
|
||||
for (std::size_t count=0;; ++count) {
|
||||
const char *name = _pendingDocs.front();
|
||||
_pendingDocs.pop_front();
|
||||
bool isMainDoc = count < filenames.size();
|
||||
std::vector<DocumentT> openedDocs;
|
||||
|
||||
try {
|
||||
_objCount = -1;
|
||||
std::set<std::string> objNames;
|
||||
if (_allowPartial) {
|
||||
auto it = _pendingDocMap.find(name);
|
||||
if (it != _pendingDocMap.end())
|
||||
objNames.swap(it->second);
|
||||
}
|
||||
int pass = 0;
|
||||
do {
|
||||
std::set<App::DocumentT> newDocs;
|
||||
for (std::size_t count=0;; ++count) {
|
||||
std::string name = std::move(_pendingDocs.front());
|
||||
_pendingDocs.pop_front();
|
||||
bool isMainDoc = (pass == 0 && count < filenames.size());
|
||||
|
||||
FC_TIME_INIT(t1);
|
||||
DocTiming timing;
|
||||
try {
|
||||
_objCount = -1;
|
||||
std::vector<std::string> objNames;
|
||||
if (_allowPartial) {
|
||||
auto it = _pendingDocMap.find(name);
|
||||
if (it != _pendingDocMap.end()) {
|
||||
if(isMainDoc)
|
||||
it->second.clear();
|
||||
else
|
||||
objNames.swap(it->second);
|
||||
_pendingDocMap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
const char *path = name;
|
||||
const char *label = 0;
|
||||
if (isMainDoc) {
|
||||
if (paths && paths->size()>count)
|
||||
path = (*paths)[count].c_str();
|
||||
FC_TIME_INIT(t1);
|
||||
DocTiming timing;
|
||||
|
||||
if (labels && labels->size()>count)
|
||||
label = (*labels)[count].c_str();
|
||||
}
|
||||
const char *path = name.c_str();
|
||||
const char *label = 0;
|
||||
if (isMainDoc) {
|
||||
if (paths && paths->size()>count)
|
||||
path = (*paths)[count].c_str();
|
||||
|
||||
auto doc = openDocumentPrivate(path, name, label, isMainDoc, createView, objNames);
|
||||
FC_DURATION_PLUS(timing.d1,t1);
|
||||
if (doc)
|
||||
newDocs.emplace(doc,timing);
|
||||
if (labels && labels->size()>count)
|
||||
label = (*labels)[count].c_str();
|
||||
}
|
||||
|
||||
auto doc = openDocumentPrivate(path, name.c_str(), label, isMainDoc, createView, std::move(objNames));
|
||||
FC_DURATION_PLUS(timing.d1,t1);
|
||||
if (doc) {
|
||||
timings[doc].d1 += timing.d1;
|
||||
newDocs.emplace(doc);
|
||||
}
|
||||
|
||||
if (isMainDoc)
|
||||
res[count] = doc;
|
||||
_objCount = -1;
|
||||
}
|
||||
catch (const Base::Exception &e) {
|
||||
if (!errs && isMainDoc)
|
||||
throw;
|
||||
if (errs && isMainDoc)
|
||||
(*errs)[count] = e.what();
|
||||
else
|
||||
Console().Error("Exception opening file: %s [%s]\n", name, e.what());
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
if (!errs && isMainDoc)
|
||||
throw;
|
||||
if (errs && isMainDoc)
|
||||
(*errs)[count] = e.what();
|
||||
else
|
||||
Console().Error("Exception opening file: %s [%s]\n", name, e.what());
|
||||
}
|
||||
catch (...) {
|
||||
if (errs) {
|
||||
if (isMainDoc)
|
||||
(*errs)[count] = "unknown error";
|
||||
res[count] = doc;
|
||||
_objCount = -1;
|
||||
}
|
||||
else {
|
||||
_pendingDocs.clear();
|
||||
catch (const Base::Exception &e) {
|
||||
e.ReportException();
|
||||
if (!errs && isMainDoc)
|
||||
throw;
|
||||
if (errs && isMainDoc)
|
||||
(*errs)[count] = e.what();
|
||||
else
|
||||
Console().Error("Exception opening file: %s [%s]\n", name.c_str(), e.what());
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
if (!errs && isMainDoc)
|
||||
throw;
|
||||
if (errs && isMainDoc)
|
||||
(*errs)[count] = e.what();
|
||||
else
|
||||
Console().Error("Exception opening file: %s [%s]\n", name.c_str(), e.what());
|
||||
}
|
||||
catch (...) {
|
||||
if (errs) {
|
||||
if (isMainDoc)
|
||||
(*errs)[count] = "unknown error";
|
||||
}
|
||||
else {
|
||||
_pendingDocs.clear();
|
||||
_pendingDocsReopen.clear();
|
||||
_pendingDocMap.clear();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingDocs.empty()) {
|
||||
if(_pendingDocsReopen.empty())
|
||||
break;
|
||||
_pendingDocs = std::move(_pendingDocsReopen);
|
||||
_pendingDocsReopen.clear();
|
||||
_pendingDocMap.clear();
|
||||
throw;
|
||||
for(const auto &file : _pendingDocs) {
|
||||
auto doc = getDocumentByPath(file.c_str());
|
||||
if(doc)
|
||||
closeDocument(doc->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingDocs.empty()) {
|
||||
if (_pendingDocsReopen.empty())
|
||||
break;
|
||||
_allowPartial = false;
|
||||
_pendingDocs.swap(_pendingDocsReopen);
|
||||
++pass;
|
||||
_pendingDocMap.clear();
|
||||
|
||||
std::vector<Document*> docs;
|
||||
docs.reserve(newDocs.size());
|
||||
for(const auto &d : newDocs) {
|
||||
auto doc = d.getDocument();
|
||||
if(!doc)
|
||||
continue;
|
||||
// Notify PropertyXLink to attach newly opened documents and restore
|
||||
// relevant external links
|
||||
PropertyXLink::restoreDocument(*doc);
|
||||
docs.push_back(doc);
|
||||
}
|
||||
}
|
||||
|
||||
_pendingDocs.clear();
|
||||
_pendingDocsReopen.clear();
|
||||
_pendingDocMap.clear();
|
||||
Base::SequencerLauncher seq("Postprocessing...", docs.size());
|
||||
|
||||
Base::SequencerLauncher seq("Postprocessing...", newDocs.size());
|
||||
|
||||
std::vector<Document*> docs;
|
||||
docs.reserve(newDocs.size());
|
||||
for (auto &v : newDocs) {
|
||||
// Notify PropertyXLink to attach newly opened documents and restore
|
||||
// relevant external links
|
||||
PropertyXLink::restoreDocument(*v.first);
|
||||
docs.push_back(v.first);
|
||||
}
|
||||
|
||||
// After external links has been restored, we can now sort the document
|
||||
// according to their dependency order.
|
||||
docs = Document::getDependentDocuments(docs, true);
|
||||
for (auto it=docs.begin(); it!=docs.end();) {
|
||||
Document *doc = *it;
|
||||
// It is possible that the newly opened document depends on an existing
|
||||
// document, which will be included with the above call to
|
||||
// Document::getDependentDocuments(). Make sure to exclude that.
|
||||
auto dit = newDocs.find(doc);
|
||||
if (dit == newDocs.end()) {
|
||||
it = docs.erase(it);
|
||||
continue;
|
||||
// After external links has been restored, we can now sort the document
|
||||
// according to their dependency order.
|
||||
try {
|
||||
docs = Document::getDependentDocuments(docs, true);
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
++it;
|
||||
FC_TIME_INIT(t1);
|
||||
// Finalize document restoring with the correct order
|
||||
doc->afterRestore(true);
|
||||
FC_DURATION_PLUS(dit->second.d2,t1);
|
||||
seq.next();
|
||||
}
|
||||
for(auto it=docs.begin(); it!=docs.end();) {
|
||||
auto doc = *it;
|
||||
|
||||
// It is possible that the newly opened document depends on an existing
|
||||
// document, which will be included with the above call to
|
||||
// Document::getDependentDocuments(). Make sure to exclude that.
|
||||
if(!newDocs.count(doc)) {
|
||||
it = docs.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &timing = timings[doc];
|
||||
FC_TIME_INIT(t1);
|
||||
// Finalize document restoring with the correct order
|
||||
if(doc->afterRestore(true)) {
|
||||
openedDocs.push_back(doc);
|
||||
it = docs.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
// Here means this is a partial loaded document, and we need to
|
||||
// reload it fully because of touched objects. The reason of
|
||||
// reloading a partial document with touched object is because
|
||||
// partial document is supposed to be readonly, while a
|
||||
// 'touched' object requires recomputation. And an object may
|
||||
// become touched during restoring if externally linked
|
||||
// document time stamp mismatches with the stamp saved.
|
||||
_pendingDocs.push_back(doc->FileName.getValue());
|
||||
_pendingDocMap.erase(doc->FileName.getValue());
|
||||
}
|
||||
FC_DURATION_PLUS(timing.d2,t1);
|
||||
seq.next();
|
||||
}
|
||||
// Close the document for reloading
|
||||
for(const auto doc : docs)
|
||||
closeDocument(doc->getName());
|
||||
|
||||
}while(!_pendingDocs.empty());
|
||||
|
||||
// Set the active document using the first successfully restored main
|
||||
// document (i.e. documents explicitly asked for by caller).
|
||||
@@ -771,14 +857,14 @@ std::vector<Document*> Application::openDocuments(const std::vector<std::string>
|
||||
}
|
||||
}
|
||||
|
||||
for (auto doc : docs) {
|
||||
auto &timing = newDocs[doc];
|
||||
FC_DURATION_LOG(timing.d1, doc->getName() << " restore");
|
||||
FC_DURATION_LOG(timing.d2, doc->getName() << " postprocess");
|
||||
for (auto &doc : openedDocs) {
|
||||
auto &timing = timings[doc];
|
||||
FC_DURATION_LOG(timing.d1, doc.getDocumentName() << " restore");
|
||||
FC_DURATION_LOG(timing.d2, doc.getDocumentName() << " postprocess");
|
||||
}
|
||||
FC_TIME_LOG(t,"total");
|
||||
|
||||
_isRestoring = false;
|
||||
|
||||
signalFinishOpenDocument();
|
||||
return res;
|
||||
}
|
||||
@@ -786,7 +872,7 @@ std::vector<Document*> Application::openDocuments(const std::vector<std::string>
|
||||
Document* Application::openDocumentPrivate(const char * FileName,
|
||||
const char *propFileName, const char *label,
|
||||
bool isMainDoc, bool createView,
|
||||
const std::set<std::string> &objNames)
|
||||
std::vector<std::string> &&objNames)
|
||||
{
|
||||
FileInfo File(FileName);
|
||||
|
||||
@@ -797,55 +883,51 @@ Document* Application::openDocumentPrivate(const char * FileName,
|
||||
}
|
||||
|
||||
// Before creating a new document we check whether the document is already open
|
||||
std::string filepath = File.filePath();
|
||||
QString canonicalPath = QFileInfo(QString::fromUtf8(FileName)).canonicalFilePath();
|
||||
for (std::map<std::string,Document*>::iterator it = DocMap.begin(); it != DocMap.end(); ++it) {
|
||||
// get unique path separators
|
||||
std::string fi = FileInfo(it->second->FileName.getValue()).filePath();
|
||||
if (filepath != fi) {
|
||||
if (canonicalPath == QFileInfo(QString::fromUtf8(fi.c_str())).canonicalFilePath()) {
|
||||
bool samePath = (canonicalPath == QString::fromUtf8(FileName));
|
||||
FC_WARN("Identical physical path '" << canonicalPath.toUtf8().constData() << "'\n"
|
||||
<< (samePath?"":" for file '") << (samePath?"":FileName) << (samePath?"":"'\n")
|
||||
<< " with existing document '" << it->second->Label.getValue()
|
||||
<< "' in path: '" << it->second->FileName.getValue() << "'");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(it->second->testStatus(App::Document::PartialDoc)
|
||||
|| it->second->testStatus(App::Document::PartialRestore)) {
|
||||
auto doc = getDocumentByPath(File.filePath().c_str(), PathMatchMode::MatchCanonicalWarning);
|
||||
if(doc) {
|
||||
if(doc->testStatus(App::Document::PartialDoc)
|
||||
|| doc->testStatus(App::Document::PartialRestore)) {
|
||||
// Here means a document is already partially loaded, but the document
|
||||
// is requested again, either partial or not. We must check if the
|
||||
// document contains the required object
|
||||
|
||||
if(isMainDoc) {
|
||||
// Main document must be open fully, so close and reopen
|
||||
closeDocument(it->first.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
if(_allowPartial) {
|
||||
closeDocument(doc->getName());
|
||||
doc = nullptr;
|
||||
} else if(_allowPartial) {
|
||||
bool reopen = false;
|
||||
for(auto &name : objNames) {
|
||||
auto obj = it->second->getObject(name.c_str());
|
||||
for(const auto &name : objNames) {
|
||||
auto obj = doc->getObject(name.c_str());
|
||||
if(!obj || obj->testStatus(App::PartialObject)) {
|
||||
reopen = true;
|
||||
// NOTE: We are about to reload this document with
|
||||
// extra objects. However, it is possible to repeat
|
||||
// this process several times, if it is linked by
|
||||
// multiple documents and each with a different set of
|
||||
// objects. To partially solve this problem, we do not
|
||||
// close and reopen the document immediately here, but
|
||||
// add it to _pendingDocsReopen to delay reloading.
|
||||
for(auto obj : doc->getObjects())
|
||||
objNames.push_back(obj->getNameInDocument());
|
||||
_pendingDocMap[doc->FileName.getValue()] = std::move(objNames);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!reopen)
|
||||
return 0;
|
||||
}
|
||||
auto &names = _pendingDocMap[FileName];
|
||||
names.clear();
|
||||
_pendingDocsReopen.push_back(FileName);
|
||||
return 0;
|
||||
|
||||
if(doc) {
|
||||
_pendingDocsReopen.emplace_back(FileName);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isMainDoc)
|
||||
return 0;
|
||||
|
||||
return it->second;
|
||||
else if(doc)
|
||||
return doc;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
@@ -867,6 +949,8 @@ Document* Application::openDocumentPrivate(const char * FileName,
|
||||
try {
|
||||
// read the document
|
||||
newDoc->restore(File.filePath().c_str(),true,objNames);
|
||||
if(DocFileMap.size())
|
||||
DocFileMap[FileInfo(newDoc->FileName.getValue()).filePath()] = newDoc;
|
||||
return newDoc;
|
||||
}
|
||||
// if the project file itself is corrupt then
|
||||
@@ -1463,6 +1547,7 @@ void Application::slotStartSaveDocument(const App::Document& doc, const std::str
|
||||
|
||||
void Application::slotFinishSaveDocument(const App::Document& doc, const std::string& filename)
|
||||
{
|
||||
DocFileMap.clear();
|
||||
this->signalFinishSaveDocument(doc, filename);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,33 @@ public:
|
||||
App::Document* getActiveDocument(void) const;
|
||||
/// Retrieve a named document
|
||||
App::Document* getDocument(const char *Name) const;
|
||||
|
||||
/// Path matching mode for getDocumentByPath()
|
||||
enum class PathMatchMode {
|
||||
/// Match by resolving to absolute file path
|
||||
MatchAbsolute = 0,
|
||||
/** Match by absolute path first. If not found then match by resolving
|
||||
* to canonical file path where any intermediate '.' '..' and symlinks
|
||||
* are resolved.
|
||||
*/
|
||||
MatchCanonical = 1,
|
||||
/** Same as MatchCanonical, but if a document is found by canonical
|
||||
* path match, which means the document can be resolved using two
|
||||
* different absolute path, a warning is printed and the found document
|
||||
* is not returned. This is to allow the caller to intentionally load
|
||||
* the same physical file as separate documents.
|
||||
*/
|
||||
MatchCanonicalWarning = 2,
|
||||
};
|
||||
/** Retrieve a document based on file path
|
||||
*
|
||||
* @param path: file path
|
||||
* @param checkCanonical: file path matching mode, @sa PathMatchMode.
|
||||
* @return Return the document found by matching with the given path
|
||||
*/
|
||||
App::Document* getDocumentByPath(const char *path,
|
||||
PathMatchMode checkCanonical = PathMatchMode::MatchAbsolute) const;
|
||||
|
||||
/// gets the (internal) name of the document
|
||||
const char * getDocumentName(const App::Document* ) const;
|
||||
/// get a list of all documents in the application
|
||||
@@ -190,6 +217,8 @@ public:
|
||||
boost::signals2::signal<void (const Document&)> signalStartRestoreDocument;
|
||||
/// signal on restoring Document
|
||||
boost::signals2::signal<void (const Document&)> signalFinishRestoreDocument;
|
||||
/// signal on pending reloading of a partial Document
|
||||
boost::signals2::signal<void (const Document&)> signalPendingReloadDocument;
|
||||
/// signal on starting to save Document
|
||||
boost::signals2::signal<void (const Document&, const std::string&)> signalStartSaveDocument;
|
||||
/// signal on saved Document
|
||||
@@ -441,7 +470,7 @@ protected:
|
||||
|
||||
/// open single document only
|
||||
App::Document* openDocumentPrivate(const char * FileName, const char *propFileName,
|
||||
const char *label, bool isMainDoc, bool createView, const std::set<std::string> &objNames);
|
||||
const char *label, bool isMainDoc, bool createView, std::vector<std::string> &&objNames);
|
||||
|
||||
/// Helper class for App::Document to signal on close/abort transaction
|
||||
class AppExport TransactionSignaller {
|
||||
@@ -559,13 +588,19 @@ private:
|
||||
std::vector<FileTypeItem> _mImportTypes;
|
||||
std::vector<FileTypeItem> _mExportTypes;
|
||||
std::map<std::string,Document*> DocMap;
|
||||
mutable std::map<std::string,Document*> DocFileMap;
|
||||
std::map<std::string,ParameterManager *> mpcPramManager;
|
||||
std::map<std::string,std::string> &_mConfig;
|
||||
App::Document* _pActiveDoc;
|
||||
|
||||
std::deque<const char *> _pendingDocs;
|
||||
std::deque<const char *> _pendingDocsReopen;
|
||||
std::map<std::string,std::set<std::string> > _pendingDocMap;
|
||||
std::deque<std::string> _pendingDocs;
|
||||
std::deque<std::string> _pendingDocsReopen;
|
||||
std::map<std::string,std::vector<std::string> > _pendingDocMap;
|
||||
|
||||
// To prevent infinite recursion of reloading a partial document due a truely
|
||||
// missing object
|
||||
std::map<std::string,std::set<std::string> > _docReloadAttempts;
|
||||
|
||||
bool _isRestoring;
|
||||
bool _allowPartial;
|
||||
bool _isClosingAll;
|
||||
|
||||
@@ -1866,7 +1866,6 @@ void Document::exportObjects(const std::vector<App::DocumentObject*>& obj, std::
|
||||
#define FC_ELEMENT_OBJECT_DEPS "ObjectDeps"
|
||||
#define FC_ATTR_DEP_COUNT "Count"
|
||||
#define FC_ATTR_DEP_OBJ_NAME "Name"
|
||||
#define FC_ATTR_DEP_COUNT "Count"
|
||||
#define FC_ATTR_DEP_ALLOW_PARTIAL "AllowPartial"
|
||||
#define FC_ELEMENT_OBJECT_DEP "Dep"
|
||||
|
||||
@@ -2693,7 +2692,7 @@ bool Document::isAnyRestoring() {
|
||||
|
||||
// Open the document
|
||||
void Document::restore (const char *filename,
|
||||
bool delaySignal, const std::set<std::string> &objNames)
|
||||
bool delaySignal, const std::vector<std::string> &objNames)
|
||||
{
|
||||
clearUndos();
|
||||
d->activeObject = 0;
|
||||
@@ -2752,8 +2751,7 @@ void Document::restore (const char *filename,
|
||||
d->partialLoadObjects.emplace(name,true);
|
||||
try {
|
||||
Document::Restore(reader);
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
} catch (const Base::Exception& e) {
|
||||
Base::Console().Error("Invalid Document.xml: %s\n", e.what());
|
||||
setStatus(Document::RestoreError, true);
|
||||
}
|
||||
@@ -2777,15 +2775,16 @@ void Document::restore (const char *filename,
|
||||
afterRestore(true);
|
||||
}
|
||||
|
||||
void Document::afterRestore(bool checkPartial) {
|
||||
bool Document::afterRestore(bool checkPartial) {
|
||||
Base::FlagToggler<> flag(_IsRestoring,false);
|
||||
if(!afterRestore(d->objectArray,checkPartial)) {
|
||||
FC_WARN("Reload partial document " << getName());
|
||||
restore();
|
||||
return;
|
||||
GetApplication().signalPendingReloadDocument(*this);
|
||||
return false;
|
||||
}
|
||||
GetApplication().signalFinishRestoreDocument(*this);
|
||||
setStatus(Document::Restoring, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Document::afterRestore(const std::vector<DocumentObject *> &objArray, bool checkPartial)
|
||||
@@ -2861,9 +2860,12 @@ bool Document::afterRestore(const std::vector<DocumentObject *> &objArray, bool
|
||||
std::string errMsg;
|
||||
if(link && (res=link->checkRestore(&errMsg))) {
|
||||
d->touchedObjs.insert(obj);
|
||||
if(res==1)
|
||||
if(res==1 || checkPartial) {
|
||||
FC_WARN(obj->getFullName() << '.' << prop->getName() << ": " << errMsg);
|
||||
else {
|
||||
setStatus(Document::LinkStampChanged, true);
|
||||
if(checkPartial)
|
||||
return false;
|
||||
} else {
|
||||
FC_ERR(obj->getFullName() << '.' << prop->getName() << ": " << errMsg);
|
||||
d->addRecomputeLog(errMsg,obj);
|
||||
setStatus(Document::PartialRestore, true);
|
||||
|
||||
@@ -74,7 +74,8 @@ public:
|
||||
PartialDoc = 7,
|
||||
AllowPartialRecompute = 8, // allow recomputing editing object if SkipRecompute is set
|
||||
TempDoc = 9, // Mark as temporary document without prompt for save
|
||||
RestoreError = 10
|
||||
RestoreError = 10,
|
||||
LinkStampChanged = 11, // Indicates during restore time if any linked document's time stamp has changed
|
||||
};
|
||||
|
||||
/** @name Properties */
|
||||
@@ -195,8 +196,8 @@ public:
|
||||
bool saveCopy(const char* file) const;
|
||||
/// Restore the document from the file in Property Path
|
||||
void restore (const char *filename=0,
|
||||
bool delaySignal=false, const std::set<std::string> &objNames={});
|
||||
void afterRestore(bool checkPartial=false);
|
||||
bool delaySignal=false, const std::vector<std::string> &objNames={});
|
||||
bool afterRestore(bool checkPartial=false);
|
||||
bool afterRestore(const std::vector<App::DocumentObject *> &, bool checkPartial=false);
|
||||
enum ExportStatus {
|
||||
NotExporting,
|
||||
|
||||
@@ -62,6 +62,14 @@ public:
|
||||
/*! Assignment operator */
|
||||
void operator=(const std::string&);
|
||||
|
||||
bool operator==(const DocumentT &other) const {
|
||||
return document == other.document;
|
||||
}
|
||||
|
||||
bool operator<(const DocumentT &other) const {
|
||||
return document < other.document;
|
||||
}
|
||||
|
||||
/*! Get a pointer to the document or 0 if it doesn't exist any more. */
|
||||
Document* getDocument() const;
|
||||
/*! Get the name of the document. */
|
||||
|
||||
@@ -2458,6 +2458,7 @@ class App::DocInfo :
|
||||
public:
|
||||
typedef boost::signals2::scoped_connection Connection;
|
||||
Connection connFinishRestoreDocument;
|
||||
Connection connPendingReloadDocument;
|
||||
Connection connDeleteDocument;
|
||||
Connection connSaveDocument;
|
||||
Connection connDeletedObject;
|
||||
@@ -2589,6 +2590,7 @@ public:
|
||||
FC_LOG("deinit " << (pcDoc?pcDoc->getName():filePath()));
|
||||
assert(links.empty());
|
||||
connFinishRestoreDocument.disconnect();
|
||||
connPendingReloadDocument.disconnect();
|
||||
connDeleteDocument.disconnect();
|
||||
connSaveDocument.disconnect();
|
||||
connDeletedObject.disconnect();
|
||||
@@ -2606,6 +2608,8 @@ public:
|
||||
App::Application &app = App::GetApplication();
|
||||
connFinishRestoreDocument = app.signalFinishRestoreDocument.connect(
|
||||
boost::bind(&DocInfo::slotFinishRestoreDocument,this,bp::_1));
|
||||
connPendingReloadDocument = app.signalPendingReloadDocument.connect(
|
||||
boost::bind(&DocInfo::slotFinishRestoreDocument,this,bp::_1));
|
||||
connDeleteDocument = app.signalDeleteDocument.connect(
|
||||
boost::bind(&DocInfo::slotDeleteDocument,this,bp::_1));
|
||||
connSaveDocument = app.signalSaveDocument.connect(
|
||||
@@ -2617,6 +2621,8 @@ public:
|
||||
else{
|
||||
for(App::Document *doc : App::GetApplication().getDocuments()) {
|
||||
if(getFullPath(doc->getFileName()) == fullpath) {
|
||||
if(doc->testStatus(App::Document::PartialDoc) && !doc->getObject(objName))
|
||||
break;
|
||||
attach(doc);
|
||||
return;
|
||||
}
|
||||
@@ -2642,22 +2648,36 @@ public:
|
||||
continue;
|
||||
}
|
||||
auto obj = doc->getObject(link->objectName.c_str());
|
||||
if(!obj)
|
||||
if(obj)
|
||||
link->restoreLink(obj);
|
||||
else if (doc->testStatus(App::Document::PartialDoc)) {
|
||||
App::GetApplication().addPendingDocument(
|
||||
doc->FileName.getValue(),
|
||||
link->objectName.c_str(),
|
||||
false);
|
||||
FC_WARN("reloading partial document '" << doc->FileName.getValue()
|
||||
<< "' due to object " << link->objectName);
|
||||
} else
|
||||
FC_WARN("object '" << link->objectName << "' not found in document '"
|
||||
<< doc->getName() << "'");
|
||||
else
|
||||
link->restoreLink(obj);
|
||||
}
|
||||
for(auto &v : parentLinks) {
|
||||
v.first->setFlag(PropertyLinkBase::LinkRestoring);
|
||||
v.first->aboutToSetValue();
|
||||
for(auto link : v.second) {
|
||||
auto obj = doc->getObject(link->objectName.c_str());
|
||||
if(!obj)
|
||||
if(obj)
|
||||
link->restoreLink(obj);
|
||||
else if (doc->testStatus(App::Document::PartialDoc)) {
|
||||
App::GetApplication().addPendingDocument(
|
||||
doc->FileName.getValue(),
|
||||
link->objectName.c_str(),
|
||||
false);
|
||||
FC_WARN("reloading partial document '" << doc->FileName.getValue()
|
||||
<< "' due to object " << link->objectName);
|
||||
} else
|
||||
FC_WARN("object '" << link->objectName << "' not found in document '"
|
||||
<< doc->getName() << "'");
|
||||
else
|
||||
link->restoreLink(obj);
|
||||
}
|
||||
v.first->hasSetValue();
|
||||
v.first->setFlag(PropertyLinkBase::LinkRestoring,false);
|
||||
@@ -2723,16 +2743,17 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// time stamp changed, touch the linking document. Unfortunately, there
|
||||
// is no way to setModfied() for an App::Document. We don't want to touch
|
||||
// all PropertyXLink for a document, because the linked object is
|
||||
// potentially unchanged. So we just touch at most one.
|
||||
// time stamp changed, touch the linking document.
|
||||
std::set<Document*> docs;
|
||||
for(auto link : links) {
|
||||
auto linkdoc = static_cast<DocumentObject*>(link->getContainer())->getDocument();
|
||||
auto ret = docs.insert(linkdoc);
|
||||
if(ret.second && !linkdoc->isTouched())
|
||||
link->touch();
|
||||
if(ret.second) {
|
||||
// This will signal the Gui::Document to call setModified();
|
||||
FC_LOG("touch document " << linkdoc->getName()
|
||||
<< " on time stamp change of " << link->getFullName());
|
||||
linkdoc->Comment.touch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3473,7 +3494,12 @@ PropertyXLink::getDocumentOutList(App::Document *doc) {
|
||||
std::map<App::Document*,std::set<App::Document*> > ret;
|
||||
for(auto &v : _DocInfoMap) {
|
||||
for(auto link : v.second->links) {
|
||||
if(!v.second->pcDoc) continue;
|
||||
if(!v.second->pcDoc
|
||||
|| link->getScope() == LinkScope::Hidden
|
||||
|| link->testStatus(Property::PropTransient)
|
||||
|| link->testStatus(Property::Transient)
|
||||
|| link->testStatus(Property::PropNoPersist))
|
||||
continue;
|
||||
auto obj = dynamic_cast<App::DocumentObject*>(link->getContainer());
|
||||
if(!obj || !obj->getNameInDocument() || !obj->getDocument())
|
||||
continue;
|
||||
@@ -3493,6 +3519,11 @@ PropertyXLink::getDocumentInList(App::Document *doc) {
|
||||
continue;
|
||||
auto &docs = ret[v.second->pcDoc];
|
||||
for(auto link : v.second->links) {
|
||||
if(link->getScope() == LinkScope::Hidden
|
||||
|| link->testStatus(Property::PropTransient)
|
||||
|| link->testStatus(Property::Transient)
|
||||
|| link->testStatus(Property::PropNoPersist))
|
||||
continue;
|
||||
auto obj = dynamic_cast<App::DocumentObject*>(link->getContainer());
|
||||
if(obj && obj->getNameInDocument() && obj->getDocument())
|
||||
docs.insert(obj->getDocument());
|
||||
@@ -4460,12 +4491,12 @@ void PropertyXLinkContainer::breakLink(App::DocumentObject *obj, bool clear) {
|
||||
}
|
||||
|
||||
int PropertyXLinkContainer::checkRestore(std::string *msg) const {
|
||||
if(_LinkRestored)
|
||||
return 1;
|
||||
for(auto &v : _XLinks) {
|
||||
int res = v.second->checkRestore(msg);
|
||||
if(res)
|
||||
return res;
|
||||
if(_LinkRestored) {
|
||||
for(auto &v : _XLinks) {
|
||||
int res = v.second->checkRestore(msg);
|
||||
if(res)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1499,7 +1499,7 @@ void Document::slotFinishRestoreDocument(const App::Document& doc)
|
||||
}
|
||||
|
||||
// reset modified flag
|
||||
setModified(false);
|
||||
setModified(doc.testStatus(App::Document::LinkStampChanged));
|
||||
}
|
||||
|
||||
void Document::slotShowHidden(const App::Document& doc)
|
||||
|
||||
@@ -196,12 +196,19 @@ void ViewProviderDocumentObject::onChanged(const App::Property* prop)
|
||||
// this is undesired behaviour. So, if this change marks the document as
|
||||
// modified then it must be be reversed.
|
||||
if (!testStatus(Gui::ViewStatus::TouchDocument)) {
|
||||
bool mod = false;
|
||||
if (pcDocument)
|
||||
mod = pcDocument->isModified();
|
||||
// Note: reverting document modified status like that is not
|
||||
// appropriate because we can't tell if there is any other
|
||||
// property being changed due to the change of Visibility here.
|
||||
// Temporary setting the Visibility property as 'NoModify' is
|
||||
// the proper way.
|
||||
Base::ObjectStatusLocker<App::Property::Status,App::Property> guard(
|
||||
App::Property::NoModify, &Visibility);
|
||||
// bool mod = false;
|
||||
// if (pcDocument)
|
||||
// mod = pcDocument->isModified();
|
||||
getObject()->Visibility.setValue(Visibility.getValue());
|
||||
if (pcDocument)
|
||||
pcDocument->setModified(mod);
|
||||
// if (pcDocument)
|
||||
// pcDocument->setModified(mod);
|
||||
}
|
||||
else {
|
||||
getObject()->Visibility.setValue(Visibility.getValue());
|
||||
@@ -215,7 +222,10 @@ void ViewProviderDocumentObject::onChanged(const App::Property* prop)
|
||||
}
|
||||
}
|
||||
|
||||
if (pcDocument && !pcDocument->isModified() && testStatus(Gui::ViewStatus::TouchDocument)) {
|
||||
if (prop && !prop->testStatus(App::Property::NoModify)
|
||||
&& pcDocument
|
||||
&& !pcDocument->isModified()
|
||||
&& testStatus(Gui::ViewStatus::TouchDocument)) {
|
||||
if (prop)
|
||||
FC_LOG(prop->getFullName() << " changed");
|
||||
pcDocument->setModified(true);
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
#include <Base/Parameter.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/TimeInfo.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
@@ -396,6 +397,12 @@ void ViewProviderPartExt::onChanged(const App::Property* prop)
|
||||
// if the object was invisible and has been changed, recreate the visual
|
||||
if (prop == &Visibility && (isUpdateForced() || Visibility.getValue()) && VisualTouched) {
|
||||
updateVisual();
|
||||
// updateVisual() may not be triggered by any change (e.g.
|
||||
// triggered by an external object through forceUpdate()). And
|
||||
// since DiffuseColor is not changed here either, do not falsely set
|
||||
// the document modified status
|
||||
Base::ObjectStatusLocker<App::Property::Status,App::Property> guard(
|
||||
App::Property::NoModify, &DiffuseColor);
|
||||
// The material has to be checked again (#0001736)
|
||||
onChanged(&DiffuseColor);
|
||||
}
|
||||
@@ -1298,8 +1305,8 @@ void ViewProviderPartExt::updateVisual()
|
||||
void ViewProviderPartExt::forceUpdate(bool enable) {
|
||||
if(enable) {
|
||||
if(++forceUpdateCount == 1) {
|
||||
if(!isShow())
|
||||
Visibility.touch();
|
||||
if(!isShow() && VisualTouched)
|
||||
updateVisual();
|
||||
}
|
||||
}else if(forceUpdateCount)
|
||||
--forceUpdateCount;
|
||||
|
||||
Reference in New Issue
Block a user