1 
2 
3 #include "toonz/tproject.h"
4 
5 // TnzLib includes
6 #include "toonz/sceneproperties.h"
7 #include "toonz/toonzscene.h"
8 #include "toonz/txsheet.h"
9 #include "toonz/observer.h"
10 #include "toonz/toonzfolders.h"
11 #include "toonz/cleanupparameters.h"
12 
13 // TnzBase includes
14 #include "tenv.h"
15 
16 // TnzCore includes
17 #include "tsystem.h"
18 #include "tstream.h"
19 #include "tfilepath_io.h"
20 #include "tconvert.h"
21 
22 // Qt includes
23 #include <QFileInfo>
24 #include <QDir>
25 
26 // STD includes
27 #include <fstream>
28 #include <stdlib.h>
29 
30 using namespace std;
31 
32 //===================================================================
33 
34 /* Version-related strings added to project files, in reversed chronological
35  * order */
36 const std::wstring prjSuffix[4] = {L"_otprj", L"_prj63ml", L"_prj6", L"_prj"};
37 const std::wstring xmlExt       = L".xml";
38 const int prjSuffixCount        = 4;
39 
40 //===================================================================
41 /*! Default inputs folder: is used to save all scanned immage.*/
42 const std::string
43     TProject::Inputs = "inputs",
44     /*! Default drawings folder: is used to save all tlv and pli levels.*/
45     TProject::Drawings = "drawings",
46     /*! Default scenes folder: is used to save all scenes.*/
47     TProject::Scenes = "scenes",
48     /*! Default scripts folder: is used to save all the script.*/
49     TProject::Scripts = "scripts",
50     /*! Default extras folder: is used to save all imported images and levels
51        not pli or tlv.*/
52     TProject::Extras = "extras",
53     /*! Default outputs folder: is used to save all rendered scenes.*/
54     TProject::Outputs = "outputs",
55     /*! Default palettes folder: is used for color design (色指定)*/
56     TProject::Palettes = "palettes";
57 //! Default project name
58 const TFilePath TProject::SandboxProjectName("sandbox");
59 
60 TProjectP currentProject;
61 
62 //===================================================================
63 
64 namespace {
65 
66 //===================================================================
67 //
68 // helper functions
69 //
70 //===================================================================
71 
makeRelative(TFilePath ref,TFilePath fp)72 TFilePath makeRelative(TFilePath ref, TFilePath fp) {
73   if (!fp.isAbsolute()) return fp;
74   TFilePath dots;
75   for (;;) {
76     if (ref.isAncestorOf(fp)) {
77       TFilePath relativePath = dots + (fp - ref);
78       return relativePath;
79     }
80     if (ref.isRoot()) return fp;
81     ref  = ref.getParentDir();
82     dots = dots + "..";
83   }
84 }
85 
86 //-------------------------------------------------------------------
87 
makeAbsolute(TFilePath ref,TFilePath fp)88 TFilePath makeAbsolute(TFilePath ref, TFilePath fp) {
89   if (fp.isAbsolute()) return fp;
90   const TFilePath twoDots("..");
91   while (twoDots.isAncestorOf(fp)) {
92     TFilePath refParent = ref.getParentDir();
93     if (refParent == TFilePath()) break;  // non dovrebbe succedere
94     ref = refParent;
95     fp  = fp - twoDots;
96   }
97   fp = ref + fp;
98 
99   return fp;
100 }
101 
102 //===================================================================
103 
104 TEnv::StringVar currentProjectPath("CurrentProject", "");
105 
106 //===================================================================
107 
108 //! Returns the project suffix (ie anything beyond the last '_' included)
109 //! of the passed file path.
getProjectSuffix(const TFilePath & path)110 std::wstring getProjectSuffix(const TFilePath &path) {
111   const std::wstring &name = path.getWideName();
112   int idx                  = name.find_last_of(L'_');
113   if (idx == string::npos) return L"";
114 
115   return name.substr(idx);
116 }
117 
118 //-------------------------------------------------------------------
119 
120 /*! Looks in the directory for a project file.  If nothing found, returns a
121  * blank TFilePath
122  */
getProjectFile(const TFilePath & fp)123 TFilePath getProjectFile(const TFilePath &fp) {
124   const std::wstring &fpName     = fp.getWideName();
125   const std::wstring &folderName = fp.getParentDir().getWideName();
126   QDir dir(fp.getQString());
127   for (int i = 0; i < prjSuffixCount; ++i) {
128     TFilePath path = fp + (fpName + prjSuffix[i] + xmlExt);
129     if (TFileStatus(path).doesExist()) return path;
130 
131     QStringList filters;
132     filters << "*" + QString::fromStdWString(prjSuffix[i] + xmlExt);
133     QStringList prjfiles =
134         dir.entryList(filters, QDir::Files, (QDir::Time | QDir::Reversed));
135     if (prjfiles.size()) return fp + TFilePath(prjfiles[0]);
136   }
137 
138   return TFilePath();
139 }
140 
141 //-------------------------------------------------------------------
142 
143 //! In case the supplied path has an old version suffix,
144 //! this function updates it to the most recent; otherwise,
145 //! it is left untouched.
getLatestVersionProjectPath(const TFilePath & path)146 TFilePath getLatestVersionProjectPath(const TFilePath &path) {
147   const std::wstring &suffix = getProjectSuffix(path);
148   for (int i = 1; i < prjSuffixCount; ++i)
149     if (suffix == prjSuffix[i]) {
150       const std::wstring &name = path.getWideName();
151       int pos                  = name.size() - suffix.size();
152       return path.withName(path.getWideName().substr(0, pos) + prjSuffix[0]);
153     }
154 
155   return path;
156 }
157 
158 //===================================================================
159 
160 /*! Return project path with right suffix.
161                 VERSION 6.0: _prj6.xml;
162                 PRECEDENT VERSION: _prj.xml.
163                 If in \b folder exists ONLY old version project path return old
164    version path;
165                 otherwise return 6.0 version project path.
166 */
searchProjectPath(TFilePath folder)167 TFilePath searchProjectPath(TFilePath folder) {
168   assert(folder.isAbsolute());
169   wstring projectName = folder.getWideName();
170 
171   // Search for the first available project file, starting from the most recent.
172   TFilePath projectPath = getProjectFile(folder);
173   if (projectPath != TFilePath()) return projectPath;
174 
175   // If none exist in the folder, build the name with the most recent suffix
176   return folder + TFilePath(projectName + prjSuffix[0] + xmlExt);
177 }
178 
179 //===================================================================
180 
isFolderUnderVersionControl(const TFilePath & folderPath)181 bool isFolderUnderVersionControl(const TFilePath &folderPath) {
182   QDir dir(QString::fromStdWString(folderPath.getWideString()));
183   if (dir.entryList(QDir::AllDirs | QDir::Hidden).contains(".svn")) return true;
184   // For SVN 1.7 and greater, check parent directories to see if it's under
185   // version control
186   while (dir.cdUp()) {
187     if (dir.entryList(QDir::AllDirs | QDir::Hidden).contains(".svn"))
188       return true;
189   }
190 
191   return false;
192 }
193 
194 //===================================================================
195 
hideOlderProjectFiles(const TFilePath & folderPath)196 void hideOlderProjectFiles(const TFilePath &folderPath) {
197   const std::wstring &name = folderPath.getWideName();
198 
199   TFilePath path;
200   for (int i = 1; i < prjSuffixCount; ++i) {
201     path = folderPath + (name + prjSuffix[i] + xmlExt);
202     if (TFileStatus(path).doesExist())
203       TSystem::renameFile(path.withType("xml_"), path);
204   }
205 }
206 
207 }  // namespace
208 
209 //===================================================================
210 
211 //===================================================================
212 //
213 // TProject
214 //
215 
216 //-------------------------------------------------------------------
217 
218 /*! \class TProject tproject.h
219         \brief Define and handle a toonz project.
220 
221     A toonz project is identified by a project name that matches a folder with
222    the same name in the project root.\n
223         The project folder can contains saveral other folders.
224         By default, five folders are created: Inputs, Drawings, Scenes, Extras
225    and Outputs.
226         Each of this folders can be renamed using the setFolder(string name,
227    TFilePath path) method.
228         Usually, the \b name parameter is chosen from inputs, drawings, scenes,
229    extras and outputs;
230         the \b path parameter contains the folder that can have a different name
231    from them.
232         All association between names and folder are kept in a mapping.\n
233         A folder of a project can be constant or scene dependent. A constant
234    folder is used by
235         every scene created in the project to save or load data. A scene
236    dependent folder is used only by the scene
237         from which the folder depends. A scene dependent folder contains the
238    string "$scene" in its path.
239 
240         \code
241         e.g.
242         Scene path: "...\\prodA\\episode1\\scenes\\sceneA.tzn"
243         Drawings scene dependent folder:
244    "...\\prodA\\episode1\\$scene\\drawings"
245         Drawings folder path: "...\\prodA\\episode1\\SceneA\\drawings"
246         \endcode
247         \n\n
248         By default, from the toonz installation, exist always a toonz project
249    called "sandbox".
250         \see TProjectManager, TSceneProperties.
251 */
252 
253 /*! \fn TFilePath TProject::getName() const
254         Returns the name of the project.
255         \code
256         e.g. "prodA\\episode1"
257         \endcode
258 */
259 
260 /*! \fn TFilePath TProject::getProjectPath() const
261         Returns the path of the project. It is an absolute path.
262         \code
263         e.g. "prodA\\episode1" -> "...\\prodA\\episode1\\episode1_prj.xml"
264         \endcode
265   */
266 
267 /*! \fn TFilePath TProject::getProjectFolder() const
268         Returns the project folder path. It is an absolute path.
269         \code
270         e.g. "prodA\\episode1" -> "...\\prodA\\episode1\\"
271         \endcode
272   */
273 
274 /*! \fn const TSceneProperties &TProject::getSceneProperties() const
275     Returns the scene properties of the project.
276     \see TSceneProperties
277         */
278 
279 /*! \fn void TProject::save()
280         Saves the project.
281         Is equivalent to save(getProjectPath()).
282         The project is saved as a xml file.\n
283         Uses TProjectManager and TOStream.
284         \note Exceptions can be thrown.
285         \see TProjectManager and TOStream.
286   */
287 
TProject()288 TProject::TProject() : m_name(), m_path(), m_sprop(new TSceneProperties()) {}
289 
290 //-------------------------------------------------------------------
291 
~TProject()292 TProject::~TProject() { delete m_sprop; }
293 
294 //-------------------------------------------------------------------
295 /*! Associates the \b name to the specified \b path.
296         \code
297         e.g. setFolder(TProject::Drawings, TFilePath("C:\\temp\\drawings"))
298         \endcode
299         Usually, the \b name parameter is chosen from inputs, drawings, scenes,
300    extras and outputs;
301         the \b path contains the folder that can have a different name from
302    them.
303         Every association between names and paths is contained in a mapping.
304         \note Not absolute path are thought relative to the project folder.
305 */
setFolder(string name,TFilePath path)306 void TProject::setFolder(string name, TFilePath path) {
307   std::map<std::string, TFilePath>::iterator it;
308   it = m_folders.find(name);
309   if (it == m_folders.end()) {
310     m_folderNames.push_back(name);
311     m_folders[name] = path;
312   } else {
313     it->second = path;
314   }
315 }
316 
317 //-------------------------------------------------------------------
318 /*! Create a folder named with \b name.
319         Call the setFolder(name,TFilePath(name)) method.\n
320         e.g. setFolder(TProject::Drawings) is equivalent to
321    setFolder(TProject::Drawings, TFilePath("drawings"))\n
322         The resulting is "..\\projectFolder\\drawings"
323 */
setFolder(string name)324 void TProject::setFolder(string name) { setFolder(name, TFilePath(name)); }
325 
326 //-------------------------------------------------------------------
327 /*! Returns the path of the folder named with \b name.\n
328         Returns TFilePath() if there isn't a folder named with \b name.
329         \note The returned path could be a relative path if \b absolute is
330    false.
331 */
getFolder(string name,bool absolute) const332 TFilePath TProject::getFolder(string name, bool absolute) const {
333   std::map<std::string, TFilePath>::const_iterator it;
334   it = m_folders.find(name);
335   if (it != m_folders.end())
336     return (absolute) ? makeAbsolute(getProjectFolder(), it->second)
337                       : it->second;
338   else
339     return TFilePath();
340 }
341 
342 //-------------------------------------------------------------------
343 /*! Returns the path of the Scene folder.\n
344         The Scene folder contains all the saved scene. The returned path is an
345    absolute path.
346 */
getScenesPath() const347 TFilePath TProject::getScenesPath() const {
348   TFilePath scenes = getFolder(Scenes);
349   return makeAbsolute(getProjectFolder(), scenes);
350 }
351 
352 //-------------------------------------------------------------------
353 /*! Returns the path of the folder indexed with \b index.\n
354         Returns TFilePath() if there isn't a folder indexed with \b index.
355         \note The returned path could be a relative path.
356 */
getFolder(int index) const357 TFilePath TProject::getFolder(int index) const {
358   if (0 <= index && index < (int)m_folderNames.size())
359     return getFolder(m_folderNames[index]);
360   else
361     return TFilePath();
362 }
363 
364 //-------------------------------------------------------------------
365 /*! Returns true if the folder indexed with \b index isn't scene dependent.\n
366         A scene dependent folder is a folder containing "$scene" in its path.
367 */
isConstantFolder(int index) const368 bool TProject::isConstantFolder(int index) const {
369   TFilePath fp = getFolder(index);
370   return fp.getWideString().find(L"$scene") == wstring::npos;
371 }
372 
373 //-------------------------------------------------------------------
374 //! Returns the number of the folders contained in the project folder.
getFolderCount() const375 int TProject::getFolderCount() const { return m_folders.size(); }
376 
377 //-------------------------------------------------------------------
378 //! Returns the name of the folder indexed with \b index.
getFolderName(int index) const379 string TProject::getFolderName(int index) const {
380   if (0 <= index && index < (int)m_folderNames.size())
381     return m_folderNames[index];
382   else
383     return "";
384 }
385 
386 //-------------------------------------------------------------------
387 
388 /*! Returns the index of the folder named with \b folderName.\n
389         If a folder named with \b folderName doesn't exist, return -1.
390 */
getFolderIndex(string name) const391 int TProject::getFolderIndex(string name) const {
392   std::vector<std::string>::const_iterator it;
393   it = std::find(m_folderNames.begin(), m_folderNames.end(), name);
394   if (it == m_folderNames.end()) return -1;
395   return std::distance(it, m_folderNames.begin());
396 }
397 
398 //-------------------------------------------------------------------
399 /*! Returns true if this project is the current project.
400         Uses \b TProjectManager.
401         \see TProjectManager
402 */
isCurrent() const403 bool TProject::isCurrent() const {
404   TFilePath currentProjectPath =
405       TProjectManager::instance()->getCurrentProjectPath();
406   if (getProjectPath() == currentProjectPath)
407     return true;
408   else
409     return getLatestVersionProjectPath(currentProjectPath) ==
410            getLatestVersionProjectPath(getProjectPath());
411 }
412 
413 //-------------------------------------------------------------------
414 /*! Set the scene properties \b sprop to the project.
415     \see TSceneProperties*/
setSceneProperties(const TSceneProperties & sprop)416 void TProject::setSceneProperties(const TSceneProperties &sprop) {
417   m_sprop->assign(&sprop);
418 }
419 
420 //-------------------------------------------------------------------
421 /*! Returns the absolute path of \b fp.
422         If \b fp contains "$project", replaces it with the name of the project.
423         \note the returned path can contain "$scene"
424 */
decode(TFilePath fp) const425 TFilePath TProject::decode(TFilePath fp) const {
426   for (;;) {
427     wstring fpstr = fp.getWideString();
428     int j         = fpstr.find(L"$project");
429     if (j == (int)wstring::npos) break;
430     fpstr.replace(j, 8, getName().getWideString());
431     fp = TFilePath(fpstr);
432   }
433   return makeAbsolute(getProjectFolder(), fp);
434 }
435 
436 //-------------------------------------------------------------------
437 
setUseScenePath(string folderName,bool on)438 void TProject::setUseScenePath(string folderName, bool on) {
439   m_useScenePathFlags[folderName] = on;
440 }
441 
442 //-------------------------------------------------------------------
443 
getUseScenePath(string folderName) const444 bool TProject::getUseScenePath(string folderName) const {
445   std::map<std::string, bool>::const_iterator it;
446   it = m_useScenePathFlags.find(folderName);
447   return it != m_useScenePathFlags.end() ? it->second : false;
448 }
449 
450 //-------------------------------------------------------------------
451 /*! Returns the index of the folder specified in the path \b folderDir.
452         Returns -1 if \b folderDir isn't a folder of the project.
453 */
getFolderIndexFromPath(const TFilePath & folderDir)454 int TProject::getFolderIndexFromPath(const TFilePath &folderDir) {
455   TFilePath scenePath          = decode(getFolder(Scenes));
456   bool sceneDependentScenePath = false;
457   if (scenePath.getName().find("$scene") != string::npos) {
458     scenePath               = scenePath.getParentDir();
459     sceneDependentScenePath = true;
460   }
461   int folderIndex;
462   for (folderIndex = 0; folderIndex < getFolderCount(); folderIndex++)
463     if (isConstantFolder(folderIndex)) {
464       TFilePath fp = decode(getFolder(folderIndex));
465       if (fp == folderDir) return folderIndex;
466     } else {
467       TFilePath fp = decode(getFolder(folderIndex));
468       wstring a    = fp.getWideString();
469       wstring b    = folderDir.getWideString();
470       int alen     = a.length();
471       int blen     = b.length();
472       int i        = a.find(L"$scene");
473       assert(i != (int)wstring::npos);
474       if (i == (int)wstring::npos) continue;
475       int j = i + 1;
476       while (j < alen && isalnum(a[j])) j++;
477       // a.substr(i,j-i) == "$scenexxxx"
478       int k = j + blen - alen;
479       if (!(0 <= i && i < k && k <= blen)) continue;
480       assert(i < blen);
481       if (i > 0 && a.substr(0, i) != b.substr(0, i)) continue;
482       if (k < blen && (j >= alen || a.substr(j) != b.substr(k))) continue;
483       wstring v = b.substr(i, k - i);
484       TFilePath scene(v + L".tnz");
485       if (sceneDependentScenePath)
486         scene = scenePath + scene.getWideName() + scene;
487       else
488         scene = scenePath + scene;
489       if (TFileStatus(scene).doesExist()) return folderIndex;
490     }
491   return -1;
492 }
493 
494 //-------------------------------------------------------------------
495 /*! Returns the folder's name of the specified TFilePath \b folderDir.\n
496         Returns the empty string if \b folderDir isn't a folder of the
497    project.*/
getFolderNameFromPath(const TFilePath & folderDir)498 wstring TProject::getFolderNameFromPath(const TFilePath &folderDir) {
499   int index = getFolderIndexFromPath(folderDir);
500   if (index < 0) return L"";
501   if (getFolder(index).isAbsolute())
502     return ::to_wstring("+" + getFolderName(index));
503   else
504     return folderDir.getWideName();
505 }
506 
507 //-------------------------------------------------------------------
508 /*! Saves the project in the specified path.
509         The TfilePath fp must be an absolute path. The project is saved as a xml
510    file.\n
511         Uses TProjectManager and TOStream.
512         \note Exceptions can be thrown.
513         \see TProjectManager and TOStream.
514 */
save(const TFilePath & projectPath)515 bool TProject::save(const TFilePath &projectPath) {
516   assert(isAProjectPath(projectPath));
517 
518   TProjectManager *pm     = TProjectManager::instance();
519   m_name                  = pm->projectPathToProjectName(projectPath);
520   m_path                  = getLatestVersionProjectPath(projectPath);
521   TFilePath projectFolder = projectPath.getParentDir();
522 
523   if (!TFileStatus(projectFolder).doesExist()) {
524     try {
525       TSystem::mkDir(projectFolder);
526     } catch (...) {
527       return false;
528     }
529   }
530 
531   TFilePath sceneFolder    = decode(getFolder(TProject::Scenes));
532   TFilePath scenesDescPath = sceneFolder + "scenes.xml";
533 
534   TFileStatus fs(projectPath);
535   if (fs.doesExist() && !fs.isWritable()) {
536     throw TSystemException(
537         projectPath,
538         "Cannot save the project settings. The file is read-only.");
539     return false;
540   }
541   TFileStatus fs2(scenesDescPath);
542   if (fs2.doesExist() && !fs2.isWritable()) {
543     throw TSystemException(
544         projectPath,
545         "Cannot save the project settings. The scenes file is read-only.");
546     return false;
547   }
548 
549   TOStream os(m_path);
550   os.openChild("project");
551   os.openChild("version");
552   os << 70 << 1;    // Standard version signature:
553   os.closeChild();  //   <Major Toonz version number * 10>.<Major version
554                     //   advancement>
555   os.openChild("folders");
556   int i = 0;
557   for (i = 0; i < getFolderCount(); i++) {
558     TFilePath folderRelativePath = getFolder(i);
559     if (folderRelativePath == TFilePath()) continue;
560     std::map<std::string, string> attr;
561     string folderName = getFolderName(i);
562     attr["name"]      = folderName;
563     attr["path"]      = ::to_string(folderRelativePath);  // escape()
564     if (getUseScenePath(folderName)) attr["useScenePath"] = "yes";
565     os.openCloseChild("folder", attr);
566   }
567   os.closeChild();
568 
569   os.openChild("sceneProperties");
570   getSceneProperties().saveData(os);
571   os.closeChild();
572   os.closeChild();
573 
574   // crea (se necessario) le directory relative ai vari folder
575   for (i = 0; i < getFolderCount(); i++)
576     if (isConstantFolder(i)) {
577       TFilePath fp = getFolder(i);
578       if (fp == TFilePath()) continue;
579       fp = decode(fp);
580       // if(!fp.isAbsolute()) fp = projectFolder + fp;
581       if (!TFileStatus(fp).doesExist()) {
582         try {
583           TSystem::mkDir(fp);
584         } catch (...) {
585         }
586       }
587     }
588 
589   /*-- +scenes だけでなく、全てのProject Folderにscenes.xmlを生成する --*/
590   std::vector<std::string> foldernames;
591   pm->getFolderNames(foldernames);
592   for (int f = 0; f < foldernames.size(); f++) {
593     TFilePath folderpath = decode(getFolder(foldernames.at(f)));
594     if (folderpath.isEmpty() || !isConstantFolder(f)) continue;
595 
596     TFilePath xmlPath = folderpath + "scenes.xml";
597     TFileStatus xmlfs(xmlPath);
598     if (xmlfs.doesExist() && !xmlfs.isWritable()) continue;
599 
600     TFilePath relativeProjectFolder =
601         makeRelative(folderpath, m_path.getParentDir());
602 
603     TOStream os2(xmlPath);
604     std::map<std::string, string> attr;
605     attr["type"] = "projectFolder";
606     os2.openChild("parentProject", attr);
607     os2 << relativeProjectFolder;
608     os2.closeChild();
609   }
610 
611   // The project has been successfully saved. In case there are other
612   // project files from older Toonz project versions, those files are
613   // renamed so that older Toonz versions can no longer 'see' it.
614   if (!isFolderUnderVersionControl(projectFolder))
615     hideOlderProjectFiles(projectFolder);
616 
617   return true;
618 }
619 
620 //-------------------------------------------------------------------
621 
save()622 bool TProject::save() { return save(m_path); }
623 
624 //-------------------------------------------------------------------
625 /*! Loads the project specified in \b projectPath.\n
626         \b projectPath must be an absolute path.
627 */
load(const TFilePath & projectPath)628 void TProject::load(const TFilePath &projectPath) {
629   assert(isAProjectPath(projectPath));
630 
631   TFilePath latestProjectPath = getLatestVersionProjectPath(projectPath);
632   TFilePath inputProjectPath  = searchProjectPath(projectPath.getParentDir());
633 
634   TProjectManager *pm = TProjectManager::instance();
635   m_name              = pm->projectPathToProjectName(latestProjectPath);
636   m_path              = latestProjectPath;
637 
638   m_folderNames.clear();
639   m_folders.clear();
640   m_useScenePathFlags.clear();
641   delete m_sprop;
642   m_sprop = new TSceneProperties();
643 
644   // Read the project
645   TIStream is(inputProjectPath);
646   if (!is) return;
647 
648   string tagName;
649   if (!is.matchTag(tagName) || tagName != "project") return;
650 
651   while (is.matchTag(tagName)) {
652     if (tagName == "folders") {
653       while (is.matchTag(tagName)) {
654         if (tagName == "folder") {
655           string name = is.getTagAttribute("name");
656           TFilePath path(is.getTagAttribute("path"));
657           setFolder(name, path);
658           string useScenePath = is.getTagAttribute("useScenePath");
659           setUseScenePath(name, useScenePath == "yes");
660         } else
661           throw TException("expected <folder>");
662       }
663       is.matchEndTag();
664     } else if (tagName == "version") {
665       int major, minor;
666       is >> major >> minor;
667       is.setVersion(VersionNumber(major, minor));
668       is.matchEndTag();
669     } else if (tagName == "sceneProperties") {
670       TSceneProperties sprop;
671       try {
672         sprop.loadData(is, true);
673       } catch (...) {
674       }
675       setSceneProperties(sprop);
676       is.matchEndTag();
677     }
678   }
679 }
680 
681 //-------------------------------------------------------------------
682 /*! Returns true if the specified path is a project path.\n
683         A project path must be absolute, must be an xml file and must have the
684    name of the
685         parent root with either one of the version-dependent suffixes "_prj*".\n
686         \code
687         e.g. "C:\\Toonz 5.2 stuff\\projects\\prodA\\episode1\\episode1_prj.xml"
688    is a project path.
689         \endcode
690 */
isAProjectPath(const TFilePath & fp)691 bool TProject::isAProjectPath(const TFilePath &fp) {
692   if (fp.isAbsolute() && fp.getType() == "xml") {
693     const std::wstring &fpName = fp.getWideName();
694     for (int i = 0; i < prjSuffixCount; ++i)
695       if (fpName.find(prjSuffix[i]) != std::wstring::npos) return true;
696   }
697 
698   return false;
699 }
700 
701 //-------------------------------------------------------------------
702 
703 namespace {
704 
705 /*
706 class SimpleProject final : public TProject {
707 public:
708   SimpleProject() : TProject(TFilePath("___simpleProject")) {
709   }
710 
711 };
712 */
713 }  // namespace
714 
715 //===================================================================
716 //
717 // TProjectManager
718 //
719 //-------------------------------------------------------------------
720 
721 /*! \class TProjectManager tproject.h
722         \brief Manages all toonz projects. The class provides all needed method
723    to retrieve projects paths, names
724         and folders.
725 
726         It is possible to handle more than one project root.
727         The class maintains a container this purpose. All the projects roots
728    must be set by hand in the windows registery. By default, only one project
729    root is created when toonz is installed.\n The project root container can be
730    updated using addProjectsRoot(const TFilePath &root),
731    addDefaultProjectsRoot() methods.
732 
733         The class maintains also information about the current project. The
734    class provides all needed method to retrieve the current project path, name
735    and folder. \see TProject
736 
737 */
738 
739 /*! \fn bool TProjectManager::isTabModeEnabled() const
740         Returns the tab mode.
741         \note the tab mode is used for Tab Application
742 */
743 
744 /*! \fn void TProjectManager::enableTabMode(bool tabMode)
745         Set the tab mode to the passed \b tabMode.
746         \note the tab mode is used for Tab Application
747 */
748 
TProjectManager()749 TProjectManager::TProjectManager() : m_tabMode(false), m_tabKidsMode(false) {}
750 
751 //-------------------------------------------------------------------
752 
~TProjectManager()753 TProjectManager::~TProjectManager() {}
754 
755 //-------------------------------------------------------------------
756 /*! Returns the instance to the TProjectManager.\n
757         If an instance doesn't exist, creates one.*/
instance()758 TProjectManager *TProjectManager::instance() {
759   static TProjectManager _instance;
760   return &_instance;
761 }
762 
763 //-------------------------------------------------------------------
764 /*! Adds the specified folder \b fp in the projecs roots container.\n
765         If \b fp is already contained in the container, the method does nothing.
766         \note \b fp must be a folder and not a file path.*/
addProjectsRoot(const TFilePath & root)767 void TProjectManager::addProjectsRoot(const TFilePath &root) {
768   // assert(TFileStatus(root).isDirectory());
769   if (std::find(m_projectsRoots.begin(), m_projectsRoots.end(), root) ==
770       m_projectsRoots.end())
771     m_projectsRoots.push_back(root);
772 }
773 
774 //-------------------------------------------------------------------
775 
776 /*! Adds the specified folder \b fp in the version control projecs roots
777    container.\n
778         If \b fp is already contained in the container, the method does nothing.
779         \note \b fp must be a folder and not a file path.*/
addSVNProjectsRoot(const TFilePath & root)780 void TProjectManager::addSVNProjectsRoot(const TFilePath &root) {
781   assert(TFileStatus(root).isDirectory());
782   if (std::find(m_svnProjectsRoots.begin(), m_svnProjectsRoots.end(), root) ==
783       m_svnProjectsRoots.end())
784     m_svnProjectsRoots.push_back(root);
785 }
786 
787 //-------------------------------------------------------------------
788 
addDefaultProjectsRoot()789 void TProjectManager::addDefaultProjectsRoot() {
790   addProjectsRoot(TEnv::getStuffDir() + "projects");
791 }
792 
793 //-------------------------------------------------------------------
794 
getCurrentProjectRoot()795 TFilePath TProjectManager::getCurrentProjectRoot() {
796   TFilePath currentProjectPath = getCurrentProjectPath();
797   int i;
798   for (i = 0; i < (int)m_projectsRoots.size(); i++)
799     if (m_projectsRoots[i].isAncestorOf(currentProjectPath))
800       return m_projectsRoots[i];
801   for (i = 0; i < (int)m_svnProjectsRoots.size(); i++)
802     if (m_svnProjectsRoots[i].isAncestorOf(currentProjectPath))
803       return m_svnProjectsRoots[i];
804   if (m_projectsRoots.empty())
805     addDefaultProjectsRoot();  // shouldn't be necessary
806   return m_projectsRoots[0];
807 }
808 
809 //-------------------------------------------------------------------
810 /*! Returns the name of the specified \b projectPath.
811         \note projectPath must be an absolute path.
812 */
projectPathToProjectName(const TFilePath & projectPath)813 TFilePath TProjectManager::projectPathToProjectName(
814     const TFilePath &projectPath) {
815   assert(projectPath.isAbsolute());
816   TFilePath projectFolder = projectPath.getParentDir();
817   if (m_projectsRoots.empty()) addDefaultProjectsRoot();
818 
819   std::wstring fpName = projectPath.getWideName();
820   for (int i = 0; i < prjSuffixCount; ++i) {
821     //	  std::wstring::size_type const i = fpName.find(prjSuffix[i]);
822     if (fpName.find(prjSuffix[i]) != std::wstring::npos)
823       return TFilePath(fpName.substr(0, fpName.find(prjSuffix[i])));
824   }
825 
826   int i;
827   for (i = 0; i < (int)m_projectsRoots.size(); i++) {
828     if (m_projectsRoots[i].isAncestorOf(projectFolder))
829       return projectFolder - m_projectsRoots[i];
830   }
831   for (i = 0; i < (int)m_svnProjectsRoots.size(); i++) {
832     if (m_svnProjectsRoots[i].isAncestorOf(projectFolder))
833       return projectFolder - m_svnProjectsRoots[i];
834   }
835   // non dovrei mai arrivare qui: il progetto non sta sotto un project root
836   return projectFolder.withoutParentDir();
837 }
838 
839 //-------------------------------------------------------------------
840 /*! Returns an absolute path of the specified \b projectName.\n
841         \note The returned project path is always computed used the first
842    project root in the container.*/
projectNameToProjectPath(const TFilePath & projectName)843 TFilePath TProjectManager::projectNameToProjectPath(
844     const TFilePath &projectName) {
845   assert(!TProject::isAProjectPath(projectName));
846   assert(!projectName.isAbsolute());
847   if (m_projectsRoots.empty()) addDefaultProjectsRoot();
848   if (projectName == TProject::SandboxProjectName)
849     return searchProjectPath(TEnv::getStuffDir() + projectName);
850   return searchProjectPath(m_projectsRoots[0] + projectName);
851 }
852 
853 //-------------------------------------------------------------------
854 /*! Returns the absolute path of the project file respect to the specified \b
855    projectFolder.\n
856         \note \b projectName must be an absolute path.*/
projectFolderToProjectPath(const TFilePath & projectFolder)857 TFilePath TProjectManager::projectFolderToProjectPath(
858     const TFilePath &projectFolder) {
859   assert(projectFolder.isAbsolute());
860   return searchProjectPath(projectFolder);
861 }
862 
863 //-------------------------------------------------------------------
864 /*! Returns the absolute path of the specified \b projectName only if the
865    project already exist.\n
866         Returns TFilePath() if a project with the specified \b projectName
867    doesn't exist.\n
868         \note \b projectName must be a relative path.*/
getProjectPathByName(const TFilePath & projectName)869 TFilePath TProjectManager::getProjectPathByName(const TFilePath &projectName) {
870   assert(!TProject::isAProjectPath(projectName));
871   assert(!projectName.isAbsolute());
872   // TFilePath relativeProjectPath = projectName + (projectName.getName() +
873   // projectPathSuffix);
874   if (m_projectsRoots.empty()) addDefaultProjectsRoot();
875   if (projectName == TProject::SandboxProjectName)
876     return searchProjectPath(TEnv::getStuffDir() + projectName);
877   int i, n = (int)m_projectsRoots.size();
878   for (i = 0; i < n; i++) {
879     TFilePath projectPath = searchProjectPath(m_projectsRoots[i] + projectName);
880     assert(TProject::isAProjectPath(projectPath));
881     if (TFileStatus(projectPath).doesExist()) return projectPath;
882   }
883   for (i = 0; i < (int)m_svnProjectsRoots.size(); i++) {
884     TFilePath projectPath =
885         searchProjectPath(m_svnProjectsRoots[i] + projectName);
886     assert(TProject::isAProjectPath(projectPath));
887     if (TFileStatus(projectPath).doesExist()) return projectPath;
888   }
889   return TFilePath();
890 }
891 
892 //-------------------------------------------------------------------
893 
getProjectPathByProjectFolder(const TFilePath & projectFolder)894 TFilePath TProjectManager::getProjectPathByProjectFolder(
895     const TFilePath &projectFolder) {
896   assert(projectFolder.isAbsolute());
897   TFilePath projectPath = searchProjectPath(projectFolder);
898   return projectPathToProjectName(projectPath);
899 }
900 
901 //-------------------------------------------------------------------
902 /*! Gets all project folder names and put them in the passed vector \b names.
903         \note All previous data contained in \b names are lost.*/
904 
getFolderNames(std::vector<std::string> & names)905 void TProjectManager::getFolderNames(std::vector<std::string> &names) {
906   names.clear();
907   TFilePath fp = ToonzFolder::getProfileFolder() + "project_folders.txt";
908   try {
909     Tifstream is(fp);
910     if (is)
911       for (;;) {
912         char buffer[1024];
913         is.getline(buffer, sizeof(buffer));
914         if (is.eof()) break;
915         char *s = buffer;
916         while (*s == ' ' || *s == '\t') s++;  // skips blanks
917         char *t = s;
918         while (*t && *t != '\r' && *t != '\n') t++;  // reads up to end of line
919         while (t > s && (t[-1] == ' ' || t[-1] == '\t'))
920           t--;  // remove trailing blanks
921         t[0] = '\0';
922         if (s[0]) names.push_back(string(s));
923       }
924   } catch (...) {
925   }
926   const std::string stdNames[] = {TProject::Inputs,  TProject::Drawings,
927                                   TProject::Scenes,  TProject::Extras,
928                                   TProject::Outputs, TProject::Scripts};
929   for (auto const &name : stdNames) {
930     // se il nome non e' gia' stato inserito lo aggiungo
931     if (std::find(names.begin(), names.end(), name) == names.end())
932       names.push_back(name);
933   }
934 }
935 
936 //-------------------------------------------------------------------
937 /*! Set the the path \b fp as current project path.\n
938         \b fp must be an absolute path.*/
setCurrentProjectPath(const TFilePath & fp)939 void TProjectManager::setCurrentProjectPath(const TFilePath &fp) {
940   assert(TProject::isAProjectPath(fp));
941   currentProjectPath = ::to_string(fp.getWideString());
942   currentProject     = TProjectP();
943   notifyListeners();
944 }
945 
946 //-------------------------------------------------------------------
947 /*! Returns the current project path.\n
948         The project path, usually, is set in key registry. If a current
949    project path isn't set,
950         TProject::SandboxProjectName is set as current project.
951 */
getCurrentProjectPath()952 TFilePath TProjectManager::getCurrentProjectPath() {
953   TFilePath fp(currentProjectPath);
954   if (fp == TFilePath())
955     fp = projectNameToProjectPath(TProject::SandboxProjectName);
956   if (!TProject::isAProjectPath(fp)) {
957     // in Toonz 5.1 e precedenti era un project name
958     if (!fp.isAbsolute()) fp = getProjectPathByName(fp);
959   }
960   fp = searchProjectPath(fp.getParentDir());
961   if (!TFileStatus(fp).doesExist())
962     fp = projectNameToProjectPath(TProject::SandboxProjectName);
963   fp       = getLatestVersionProjectPath(fp);
964   string s = ::to_string(fp);
965   if (s != (string)currentProjectPath) currentProjectPath = s;
966   return fp;
967 }
968 
969 //-------------------------------------------------------------------
970 /*! Returns the current TProject.\n
971         If a current TProject() doesn't exist, load the project in the the
972    current project path.
973 */
getCurrentProject()974 TProjectP TProjectManager::getCurrentProject() {
975   if (currentProject.getPointer() == 0) {
976     TFilePath fp = getCurrentProjectPath();
977     assert(TProject::isAProjectPath(fp));
978     currentProject = new TProject();
979     currentProject->load(fp);
980   }
981   return currentProject;
982 }
983 
984 //-------------------------------------------------------------------
985 /*! Returns the TProjectP in which the specified \b scenePath is saved.\n
986         Returns 0 if \b scenePath isn't a valid scene, or isn't saved in a valid
987    folder of a project root.
988         \note \b scenePath must be an absolute path.\n
989         Creates a new TProject. The caller gets ownership.*/
loadSceneProject(const TFilePath & scenePath)990 TProjectP TProjectManager::loadSceneProject(const TFilePath &scenePath) {
991   // cerca il file scenes.xml nella stessa directory della scena
992   // oppure in una
993   // directory superiore
994 
995   TFilePath folder = scenePath.getParentDir();
996   TFilePath sceneDesc;
997   bool found = true;
998   for (;;) {
999     sceneDesc = folder + "scenes.xml";
1000     if (TFileStatus(sceneDesc).doesExist()) break;
1001     if (folder.isRoot()) {
1002       found = false;
1003       break;
1004     }
1005     folder = folder.getParentDir();
1006   }
1007 
1008   // legge il path (o il nome) del progetto
1009   TFilePath projectPath;
1010   if (found) {
1011     try {
1012       TIStream is(sceneDesc);
1013       string tagName;
1014       is.matchTag(tagName);
1015       string type = is.getTagAttribute("type");
1016       TFilePath projectFolderPath;
1017       is >> projectFolderPath;
1018       if (type == "") {
1019         projectFolderPath = TFilePath("..");
1020       }
1021       is.matchEndTag();
1022       projectPath = makeAbsolute(folder, projectFolderPath);
1023 
1024       TFilePath path = getProjectFile(projectPath);
1025 
1026       projectPath = path;
1027 
1028     } catch (...) {
1029     }
1030     if (projectPath == TFilePath()) return 0;
1031   } else
1032     projectPath = getSandboxProjectPath();
1033 
1034   if (!TProject::isAProjectPath(projectPath)) {
1035     // in Toonz 5.1 e precedenti era un project name
1036     if (!projectPath.isAbsolute())
1037       projectPath = getProjectPathByName(projectPath);
1038     else
1039       return 0;
1040   }
1041   if (!TFileStatus(projectPath).doesExist()) return 0;
1042 
1043   TProject *project = new TProject();
1044   project->load(projectPath);
1045   return project;
1046 }
1047 
1048 //-------------------------------------------------------------------
1049 
notifyListeners()1050 void TProjectManager::notifyListeners() {
1051   for (std::set<Listener *>::iterator i = m_listeners.begin();
1052        i != m_listeners.end(); ++i)
1053     (*i)->onProjectSwitched();
1054 }
1055 
1056 //-------------------------------------------------------------------
1057 
notifyProjectChanged()1058 void TProjectManager::notifyProjectChanged() {
1059   for (std::set<Listener *>::iterator i = m_listeners.begin();
1060        i != m_listeners.end(); ++i)
1061     (*i)->onProjectChanged();
1062 }
1063 
1064 //-------------------------------------------------------------------
1065 /*! Adds \b listener to the listeners container.*/
addListener(Listener * listener)1066 void TProjectManager::addListener(Listener *listener) {
1067   m_listeners.insert(listener);
1068 }
1069 
1070 //-------------------------------------------------------------------
1071 /*! Removes \b listener from the listeners container.*/
removeListener(Listener * listener)1072 void TProjectManager::removeListener(Listener *listener) {
1073   m_listeners.erase(listener);
1074 }
1075 
1076 //-------------------------------------------------------------------
1077 /*! Initializes the specified \b scene using the TSceneProperties of the current
1078    project.\n
1079         \see TSceneProperties
1080 */
initializeScene(ToonzScene * scene)1081 void TProjectManager::initializeScene(ToonzScene *scene) {
1082   TProject *project       = scene->getProject();
1083   TSceneProperties *sprop = scene->getProperties();
1084 
1085   TFilePath currentProjectPath = getCurrentProjectPath();
1086   project->load(currentProjectPath);
1087 
1088   sprop->assign(&project->getSceneProperties());
1089   CleanupParameters::GlobalParameters.assign(
1090       project->getSceneProperties().getCleanupParameters());
1091 
1092   // scene->setProject(this);
1093   scene->setUntitled();
1094   sprop->cloneCamerasTo(scene->getTopXsheet()->getStageObjectTree());
1095   sprop->onInitialize();
1096   // scene->save(scene->getScenePath());
1097 }
1098 
1099 //-------------------------------------------------------------------
1100 /*! Saves the TSceneProperties of the specified scene in the current project.*/
saveTemplate(ToonzScene * scene)1101 void TProjectManager::saveTemplate(ToonzScene *scene) {
1102   TSceneProperties props;
1103   props.assign(scene->getProperties());
1104   props.cloneCamerasFrom(scene->getXsheet()->getStageObjectTree());
1105 
1106   // camera capture's "save in" path is saved in env, not in the project
1107   props.setCameraCaptureSaveInPath(TFilePath());
1108 
1109   TProjectP currentProject = getCurrentProject();
1110   currentProject->setSceneProperties(props);
1111   currentProject->save();
1112 }
1113 
1114 //-------------------------------------------------------------------
1115 /*! Creates the standard project folder "sandbox" if it doesn't exist.*/
createSandboxIfNeeded()1116 void TProjectManager::createSandboxIfNeeded() {
1117   TFilePath path = getSandboxProjectPath();
1118   if (!TFileStatus(path).doesExist()) {
1119     TProjectP project = createStandardProject();
1120     try {
1121       project->save(path);
1122     } catch (...) {
1123     }
1124   }
1125 }
1126 
1127 //-------------------------------------------------------------------
1128 /*! Create a standard project.\n
1129         A standard project is a project containing the standard named and
1130    constant folder.
1131         \see TProject. */
createStandardProject()1132 TProjectP TProjectManager::createStandardProject() {
1133   TProject *project = new TProject();
1134   // set default folders (+drawings, ecc.)
1135   std::vector<std::string> names;
1136   getFolderNames(names);
1137   std::vector<std::string>::iterator it;
1138   for (it = names.begin(); it != names.end(); ++it) project->setFolder(*it);
1139   return project;
1140 }
1141 
1142 //! Return the absolute path of the standard folder "sandbox".
getSandboxProjectFolder()1143 TFilePath TProjectManager::getSandboxProjectFolder() {
1144   return getSandboxProjectPath().getParentDir();
1145 }
1146 //! Return the absolute path of the standard project "sandbox_prj6.xml" file.
getSandboxProjectPath()1147 TFilePath TProjectManager::getSandboxProjectPath() {
1148   return getProjectPathByName(TProject::SandboxProjectName);
1149 }
1150 
isProject(const TFilePath & projectFolder)1151 bool TProjectManager::isProject(const TFilePath &projectFolder) {
1152   TFilePath projectPath = projectFolderToProjectPath(projectFolder);
1153   return TFileStatus(projectPath).doesExist();
1154 }
1155