The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

549 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../Application/jucer_Headers.h"
  19. #include "jucer_OpenDocumentManager.h"
  20. #include "../CodeEditor/jucer_ItemPreviewComponent.h"
  21. #include "../Application/jucer_Application.h"
  22. //==============================================================================
  23. class UnknownDocument : public OpenDocumentManager::Document
  24. {
  25. public:
  26. UnknownDocument (Project* p, const File& f)
  27. : project (p), file (f)
  28. {
  29. reloadFromFile();
  30. }
  31. //==============================================================================
  32. struct Type : public OpenDocumentManager::DocumentType
  33. {
  34. bool canOpenFile (const File&) override { return true; }
  35. Document* openFile (Project* p, const File& f) override { return new UnknownDocument (p, f); }
  36. };
  37. //==============================================================================
  38. bool loadedOk() const override { return true; }
  39. bool isForFile (const File& f) const override { return file == f; }
  40. bool isForNode (const ValueTree&) const override { return false; }
  41. bool refersToProject (Project& p) const override { return project == &p; }
  42. Project* getProject() const override { return project; }
  43. bool needsSaving() const override { return false; }
  44. bool saveSyncWithoutAsking() override { return true; }
  45. void saveAsync (std::function<void (bool)>) override {}
  46. void saveAsAsync (std::function<void (bool)>) override {}
  47. bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); }
  48. void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); }
  49. String getName() const override { return file.getFileName(); }
  50. File getFile() const override { return file; }
  51. std::unique_ptr<Component> createEditor() override { return std::make_unique<ItemPreviewComponent> (file); }
  52. std::unique_ptr<Component> createViewer() override { return createEditor(); }
  53. void fileHasBeenRenamed (const File& newFile) override { file = newFile; }
  54. String getState() const override { return {}; }
  55. void restoreState (const String&) override {}
  56. String getType() const override
  57. {
  58. if (file.getFileExtension().isNotEmpty())
  59. return file.getFileExtension() + " file";
  60. jassertfalse;
  61. return "Unknown";
  62. }
  63. private:
  64. Project* const project;
  65. File file;
  66. Time fileModificationTime;
  67. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnknownDocument)
  68. };
  69. //==============================================================================
  70. OpenDocumentManager::DocumentType* createGUIDocumentType();
  71. OpenDocumentManager::OpenDocumentManager()
  72. {
  73. registerType (new UnknownDocument::Type());
  74. registerType (new SourceCodeDocument::Type());
  75. registerType (createGUIDocumentType());
  76. }
  77. OpenDocumentManager::~OpenDocumentManager()
  78. {
  79. }
  80. void OpenDocumentManager::clear()
  81. {
  82. documents.clear();
  83. types.clear();
  84. }
  85. //==============================================================================
  86. void OpenDocumentManager::registerType (DocumentType* type, int index)
  87. {
  88. types.insert (index, type);
  89. }
  90. //==============================================================================
  91. void OpenDocumentManager::addListener (DocumentCloseListener* listener)
  92. {
  93. listeners.addIfNotAlreadyThere (listener);
  94. }
  95. void OpenDocumentManager::removeListener (DocumentCloseListener* listener)
  96. {
  97. listeners.removeFirstMatchingValue (listener);
  98. }
  99. //==============================================================================
  100. bool OpenDocumentManager::canOpenFile (const File& file)
  101. {
  102. for (int i = types.size(); --i >= 0;)
  103. if (types.getUnchecked(i)->canOpenFile (file))
  104. return true;
  105. return false;
  106. }
  107. OpenDocumentManager::Document* OpenDocumentManager::openFile (Project* project, const File& file)
  108. {
  109. for (int i = documents.size(); --i >= 0;)
  110. if (documents.getUnchecked(i)->isForFile (file))
  111. return documents.getUnchecked(i);
  112. Document* d = nullptr;
  113. for (int i = types.size(); --i >= 0 && d == nullptr;)
  114. {
  115. if (types.getUnchecked(i)->canOpenFile (file))
  116. {
  117. d = types.getUnchecked(i)->openFile (project, file);
  118. jassert (d != nullptr);
  119. }
  120. }
  121. jassert (d != nullptr); // should always at least have been picked up by UnknownDocument
  122. documents.add (d);
  123. ProjucerApplication::getCommandManager().commandStatusChanged();
  124. return d;
  125. }
  126. int OpenDocumentManager::getNumOpenDocuments() const
  127. {
  128. return documents.size();
  129. }
  130. OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) const
  131. {
  132. return documents.getUnchecked (index);
  133. }
  134. void OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc,
  135. std::function<void (FileBasedDocument::SaveResult)> callback)
  136. {
  137. if (! doc->needsSaving())
  138. {
  139. if (callback != nullptr)
  140. callback (FileBasedDocument::savedOk);
  141. return;
  142. }
  143. AlertWindow::showYesNoCancelBox (MessageBoxIconType::QuestionIcon,
  144. TRANS("Closing document..."),
  145. TRANS("Do you want to save the changes to \"")
  146. + doc->getName() + "\"?",
  147. TRANS("Save"),
  148. TRANS("Discard changes"),
  149. TRANS("Cancel"),
  150. nullptr,
  151. ModalCallbackFunction::create ([parent = WeakReference<OpenDocumentManager> { this }, doc, callback] (int r)
  152. {
  153. if (parent == nullptr)
  154. return;
  155. if (r == 1)
  156. {
  157. doc->saveAsync ([parent, callback] (bool hasSaved)
  158. {
  159. if (parent == nullptr)
  160. return;
  161. if (callback != nullptr)
  162. callback (hasSaved ? FileBasedDocument::savedOk : FileBasedDocument::failedToWriteToFile);
  163. });
  164. return;
  165. }
  166. if (callback != nullptr)
  167. callback (r == 2 ? FileBasedDocument::savedOk : FileBasedDocument::userCancelledSave);
  168. }));
  169. }
  170. bool OpenDocumentManager::closeDocumentWithoutSaving (Document* doc)
  171. {
  172. if (documents.contains (doc))
  173. {
  174. bool canClose = true;
  175. for (int i = listeners.size(); --i >= 0;)
  176. if (auto* l = listeners[i])
  177. if (! l->documentAboutToClose (doc))
  178. canClose = false;
  179. if (! canClose)
  180. return false;
  181. documents.removeObject (doc);
  182. ProjucerApplication::getCommandManager().commandStatusChanged();
  183. }
  184. return true;
  185. }
  186. void OpenDocumentManager::closeDocumentAsync (Document* doc, SaveIfNeeded saveIfNeeded, std::function<void (bool)> callback)
  187. {
  188. if (! documents.contains (doc))
  189. {
  190. if (callback != nullptr)
  191. callback (true);
  192. return;
  193. }
  194. if (saveIfNeeded == SaveIfNeeded::yes)
  195. {
  196. saveIfNeededAndUserAgrees (doc,
  197. [parent = WeakReference<OpenDocumentManager> { this }, doc, callback] (FileBasedDocument::SaveResult result)
  198. {
  199. if (parent == nullptr)
  200. return;
  201. if (result != FileBasedDocument::savedOk)
  202. {
  203. if (callback != nullptr)
  204. callback (false);
  205. return;
  206. }
  207. auto closed = parent->closeDocumentWithoutSaving (doc);
  208. if (callback != nullptr)
  209. callback (closed);
  210. });
  211. return;
  212. }
  213. auto closed = closeDocumentWithoutSaving (doc);
  214. if (callback != nullptr)
  215. callback (closed);
  216. }
  217. void OpenDocumentManager::closeFileWithoutSaving (const File& f)
  218. {
  219. for (int i = documents.size(); --i >= 0;)
  220. if (auto* d = documents[i])
  221. if (d->isForFile (f))
  222. closeDocumentWithoutSaving (d);
  223. }
  224. static void closeLastAsyncRecusrsive (WeakReference<OpenDocumentManager> parent,
  225. OpenDocumentManager::SaveIfNeeded askUserToSave,
  226. std::function<void (bool)> callback)
  227. {
  228. auto lastIndex = parent->getNumOpenDocuments() - 1;
  229. if (lastIndex < 0)
  230. {
  231. if (callback != nullptr)
  232. callback (true);
  233. return;
  234. }
  235. parent->closeDocumentAsync (parent->getOpenDocument (lastIndex),
  236. askUserToSave,
  237. [parent, askUserToSave, callback] (bool closedSuccessfully)
  238. {
  239. if (parent == nullptr)
  240. return;
  241. if (! closedSuccessfully)
  242. {
  243. if (callback != nullptr)
  244. callback (false);
  245. return;
  246. }
  247. closeLastAsyncRecusrsive (parent, askUserToSave, std::move (callback));
  248. });
  249. }
  250. void OpenDocumentManager::closeAllAsync (SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  251. {
  252. closeLastAsyncRecusrsive (this, askUserToSave, std::move (callback));
  253. }
  254. void OpenDocumentManager::closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager> parent,
  255. Project* project,
  256. SaveIfNeeded askUserToSave,
  257. std::function<void (bool)> callback)
  258. {
  259. for (int i = documents.size(); --i >= 0;)
  260. {
  261. if (auto* d = documents[i])
  262. {
  263. if (d->getProject() == project)
  264. {
  265. closeDocumentAsync (d, askUserToSave, [parent, project, askUserToSave, callback] (bool closedSuccessfully)
  266. {
  267. if (parent == nullptr)
  268. return;
  269. if (! closedSuccessfully)
  270. {
  271. if (callback != nullptr)
  272. callback (false);
  273. return;
  274. }
  275. parent->closeLastDocumentUsingProjectRecursive (parent, project, askUserToSave, std::move (callback));
  276. });
  277. return;
  278. }
  279. }
  280. }
  281. if (callback != nullptr)
  282. callback (true);
  283. }
  284. void OpenDocumentManager::closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  285. {
  286. WeakReference<OpenDocumentManager> parent { this };
  287. closeLastDocumentUsingProjectRecursive (parent, &project, askUserToSave, std::move (callback));
  288. }
  289. void OpenDocumentManager::closeAllDocumentsUsingProjectWithoutSaving (Project& project)
  290. {
  291. for (int i = documents.size(); --i >= 0;)
  292. if (Document* d = documents[i])
  293. if (d->refersToProject (project))
  294. closeDocumentWithoutSaving (d);
  295. }
  296. bool OpenDocumentManager::anyFilesNeedSaving() const
  297. {
  298. for (int i = documents.size(); --i >= 0;)
  299. if (documents.getUnchecked (i)->needsSaving())
  300. return true;
  301. return false;
  302. }
  303. void OpenDocumentManager::saveAllSyncWithoutAsking()
  304. {
  305. for (int i = documents.size(); --i >= 0;)
  306. {
  307. if (documents.getUnchecked (i)->saveSyncWithoutAsking())
  308. ProjucerApplication::getCommandManager().commandStatusChanged();
  309. }
  310. }
  311. void OpenDocumentManager::reloadModifiedFiles()
  312. {
  313. for (int i = documents.size(); --i >= 0;)
  314. {
  315. Document* d = documents.getUnchecked (i);
  316. if (d->hasFileBeenModifiedExternally())
  317. d->reloadFromFile();
  318. }
  319. }
  320. void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile)
  321. {
  322. for (int i = documents.size(); --i >= 0;)
  323. {
  324. Document* d = documents.getUnchecked (i);
  325. if (d->isForFile (oldFile))
  326. d->fileHasBeenRenamed (newFile);
  327. }
  328. }
  329. //==============================================================================
  330. RecentDocumentList::RecentDocumentList()
  331. {
  332. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  333. }
  334. RecentDocumentList::~RecentDocumentList()
  335. {
  336. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  337. }
  338. void RecentDocumentList::clear()
  339. {
  340. previousDocs.clear();
  341. nextDocs.clear();
  342. }
  343. void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document)
  344. {
  345. if (document != nullptr && document != getCurrentDocument())
  346. {
  347. nextDocs.clear();
  348. previousDocs.add (document);
  349. }
  350. }
  351. bool RecentDocumentList::canGoToPrevious() const
  352. {
  353. return previousDocs.size() > 1;
  354. }
  355. bool RecentDocumentList::canGoToNext() const
  356. {
  357. return nextDocs.size() > 0;
  358. }
  359. OpenDocumentManager::Document* RecentDocumentList::getPrevious()
  360. {
  361. if (! canGoToPrevious())
  362. return nullptr;
  363. nextDocs.insert (0, previousDocs.removeAndReturn (previousDocs.size() - 1));
  364. return previousDocs.getLast();
  365. }
  366. OpenDocumentManager::Document* RecentDocumentList::getNext()
  367. {
  368. if (! canGoToNext())
  369. return nullptr;
  370. OpenDocumentManager::Document* d = nextDocs.removeAndReturn (0);
  371. previousDocs.add (d);
  372. return d;
  373. }
  374. bool RecentDocumentList::contains (const File& f) const
  375. {
  376. for (int i = previousDocs.size(); --i >= 0;)
  377. if (previousDocs.getUnchecked(i)->getFile() == f)
  378. return true;
  379. return false;
  380. }
  381. OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const
  382. {
  383. for (int i = previousDocs.size(); --i >= 0;)
  384. if (previousDocs.getUnchecked(i) != oneToAvoid)
  385. return previousDocs.getUnchecked(i);
  386. return nullptr;
  387. }
  388. bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
  389. {
  390. previousDocs.removeAllInstancesOf (document);
  391. nextDocs.removeAllInstancesOf (document);
  392. jassert (! previousDocs.contains (document));
  393. jassert (! nextDocs.contains (document));
  394. return true;
  395. }
  396. static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
  397. {
  398. if (xml != nullptr)
  399. {
  400. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  401. for (auto* e : xml->getChildWithTagNameIterator ("DOC"))
  402. {
  403. const File file (e->getStringAttribute ("file"));
  404. if (file.exists())
  405. {
  406. if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
  407. {
  408. doc->restoreState (e->getStringAttribute ("state"));
  409. list.add (doc);
  410. }
  411. }
  412. }
  413. }
  414. }
  415. void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml)
  416. {
  417. clear();
  418. if (xml.hasTagName ("RECENT_DOCUMENTS"))
  419. {
  420. restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS"));
  421. restoreDocList (project, nextDocs, xml.getChildByName ("NEXT"));
  422. }
  423. }
  424. static void saveDocList (const Array <OpenDocumentManager::Document*>& list, XmlElement& xml)
  425. {
  426. for (int i = 0; i < list.size(); ++i)
  427. {
  428. const OpenDocumentManager::Document& doc = *list.getUnchecked(i);
  429. XmlElement* e = xml.createNewChildElement ("DOC");
  430. e->setAttribute ("file", doc.getFile().getFullPathName());
  431. e->setAttribute ("state", doc.getState());
  432. }
  433. }
  434. std::unique_ptr<XmlElement> RecentDocumentList::createXML() const
  435. {
  436. auto xml = std::make_unique<XmlElement> ("RECENT_DOCUMENTS");
  437. saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
  438. saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
  439. return xml;
  440. }