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.

533 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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 final : public OpenDocumentManager::Document
  24. {
  25. public:
  26. UnknownDocument (Project* p, const File& f)
  27. : project (p), file (f)
  28. {
  29. handleReloadFromFile();
  30. }
  31. //==============================================================================
  32. struct Type final : 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 { handleReloadFromFile(); }
  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. void handleReloadFromFile() { fileModificationTime = file.getLastModificationTime(); }
  65. Project* const project;
  66. File file;
  67. Time fileModificationTime;
  68. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnknownDocument)
  69. };
  70. //==============================================================================
  71. OpenDocumentManager::DocumentType* createGUIDocumentType();
  72. OpenDocumentManager::OpenDocumentManager()
  73. {
  74. registerType (new UnknownDocument::Type());
  75. registerType (new SourceCodeDocument::Type());
  76. registerType (createGUIDocumentType());
  77. }
  78. OpenDocumentManager::~OpenDocumentManager()
  79. {
  80. }
  81. void OpenDocumentManager::clear()
  82. {
  83. documents.clear();
  84. types.clear();
  85. }
  86. //==============================================================================
  87. void OpenDocumentManager::registerType (DocumentType* type, int index)
  88. {
  89. types.insert (index, type);
  90. }
  91. //==============================================================================
  92. void OpenDocumentManager::addListener (DocumentCloseListener* listener)
  93. {
  94. listeners.addIfNotAlreadyThere (listener);
  95. }
  96. void OpenDocumentManager::removeListener (DocumentCloseListener* listener)
  97. {
  98. listeners.removeFirstMatchingValue (listener);
  99. }
  100. //==============================================================================
  101. bool OpenDocumentManager::canOpenFile (const File& file)
  102. {
  103. for (int i = types.size(); --i >= 0;)
  104. if (types.getUnchecked (i)->canOpenFile (file))
  105. return true;
  106. return false;
  107. }
  108. OpenDocumentManager::Document* OpenDocumentManager::openFile (Project* project, const File& file)
  109. {
  110. for (int i = documents.size(); --i >= 0;)
  111. if (documents.getUnchecked (i)->isForFile (file))
  112. return documents.getUnchecked (i);
  113. Document* d = nullptr;
  114. for (int i = types.size(); --i >= 0 && d == nullptr;)
  115. {
  116. if (types.getUnchecked (i)->canOpenFile (file))
  117. {
  118. d = types.getUnchecked (i)->openFile (project, file);
  119. jassert (d != nullptr);
  120. }
  121. }
  122. jassert (d != nullptr); // should always at least have been picked up by UnknownDocument
  123. documents.add (d);
  124. ProjucerApplication::getCommandManager().commandStatusChanged();
  125. return d;
  126. }
  127. int OpenDocumentManager::getNumOpenDocuments() const
  128. {
  129. return documents.size();
  130. }
  131. OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) const
  132. {
  133. return documents.getUnchecked (index);
  134. }
  135. void OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc,
  136. std::function<void (FileBasedDocument::SaveResult)> callback)
  137. {
  138. if (! doc->needsSaving())
  139. {
  140. NullCheckedInvocation::invoke (callback, FileBasedDocument::savedOk);
  141. return;
  142. }
  143. auto options = MessageBoxOptions::makeOptionsYesNoCancel (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. messageBox = AlertWindow::showScopedAsync (options, [parent = WeakReference<OpenDocumentManager> { this }, doc, callback] (int r)
  151. {
  152. if (parent == nullptr)
  153. return;
  154. if (r == 1)
  155. {
  156. doc->saveAsync ([parent, callback] (bool hasSaved)
  157. {
  158. if (parent == nullptr)
  159. return;
  160. NullCheckedInvocation::invoke (callback, hasSaved ? FileBasedDocument::savedOk : FileBasedDocument::failedToWriteToFile);
  161. });
  162. return;
  163. }
  164. NullCheckedInvocation::invoke (callback, r == 2 ? FileBasedDocument::savedOk : FileBasedDocument::userCancelledSave);
  165. });
  166. }
  167. bool OpenDocumentManager::closeDocumentWithoutSaving (Document* doc)
  168. {
  169. if (documents.contains (doc))
  170. {
  171. bool canClose = true;
  172. for (int i = listeners.size(); --i >= 0;)
  173. if (auto* l = listeners[i])
  174. if (! l->documentAboutToClose (doc))
  175. canClose = false;
  176. if (! canClose)
  177. return false;
  178. documents.removeObject (doc);
  179. ProjucerApplication::getCommandManager().commandStatusChanged();
  180. }
  181. return true;
  182. }
  183. void OpenDocumentManager::closeDocumentAsync (Document* doc, SaveIfNeeded saveIfNeeded, std::function<void (bool)> callback)
  184. {
  185. if (! documents.contains (doc))
  186. {
  187. NullCheckedInvocation::invoke (callback, true);
  188. return;
  189. }
  190. if (saveIfNeeded == SaveIfNeeded::yes)
  191. {
  192. saveIfNeededAndUserAgrees (doc,
  193. [parent = WeakReference<OpenDocumentManager> { this }, doc, callback] (FileBasedDocument::SaveResult result)
  194. {
  195. if (parent == nullptr)
  196. return;
  197. if (result != FileBasedDocument::savedOk)
  198. {
  199. NullCheckedInvocation::invoke (callback, false);
  200. return;
  201. }
  202. auto closed = parent->closeDocumentWithoutSaving (doc);
  203. NullCheckedInvocation::invoke (callback, closed);
  204. });
  205. return;
  206. }
  207. auto closed = closeDocumentWithoutSaving (doc);
  208. NullCheckedInvocation::invoke (callback, closed);
  209. }
  210. void OpenDocumentManager::closeFileWithoutSaving (const File& f)
  211. {
  212. for (int i = documents.size(); --i >= 0;)
  213. if (auto* d = documents[i])
  214. if (d->isForFile (f))
  215. closeDocumentWithoutSaving (d);
  216. }
  217. static void closeLastAsyncRecusrsive (WeakReference<OpenDocumentManager> parent,
  218. OpenDocumentManager::SaveIfNeeded askUserToSave,
  219. std::function<void (bool)> callback)
  220. {
  221. auto lastIndex = parent->getNumOpenDocuments() - 1;
  222. if (lastIndex < 0)
  223. {
  224. NullCheckedInvocation::invoke (callback, true);
  225. return;
  226. }
  227. parent->closeDocumentAsync (parent->getOpenDocument (lastIndex),
  228. askUserToSave,
  229. [parent, askUserToSave, callback] (bool closedSuccessfully)
  230. {
  231. if (parent == nullptr)
  232. return;
  233. if (! closedSuccessfully)
  234. {
  235. NullCheckedInvocation::invoke (callback, false);
  236. return;
  237. }
  238. closeLastAsyncRecusrsive (parent, askUserToSave, std::move (callback));
  239. });
  240. }
  241. void OpenDocumentManager::closeAllAsync (SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  242. {
  243. closeLastAsyncRecusrsive (this, askUserToSave, std::move (callback));
  244. }
  245. void OpenDocumentManager::closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager> parent,
  246. Project* project,
  247. SaveIfNeeded askUserToSave,
  248. std::function<void (bool)> callback)
  249. {
  250. for (int i = documents.size(); --i >= 0;)
  251. {
  252. if (auto* d = documents[i])
  253. {
  254. if (d->getProject() == project)
  255. {
  256. closeDocumentAsync (d, askUserToSave, [parent, project, askUserToSave, callback] (bool closedSuccessfully)
  257. {
  258. if (parent == nullptr)
  259. return;
  260. if (! closedSuccessfully)
  261. {
  262. NullCheckedInvocation::invoke (callback, false);
  263. return;
  264. }
  265. parent->closeLastDocumentUsingProjectRecursive (parent, project, askUserToSave, std::move (callback));
  266. });
  267. return;
  268. }
  269. }
  270. }
  271. NullCheckedInvocation::invoke (callback, true);
  272. }
  273. void OpenDocumentManager::closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  274. {
  275. WeakReference<OpenDocumentManager> parent { this };
  276. closeLastDocumentUsingProjectRecursive (parent, &project, askUserToSave, std::move (callback));
  277. }
  278. void OpenDocumentManager::closeAllDocumentsUsingProjectWithoutSaving (Project& project)
  279. {
  280. for (int i = documents.size(); --i >= 0;)
  281. if (Document* d = documents[i])
  282. if (d->refersToProject (project))
  283. closeDocumentWithoutSaving (d);
  284. }
  285. bool OpenDocumentManager::anyFilesNeedSaving() const
  286. {
  287. for (int i = documents.size(); --i >= 0;)
  288. if (documents.getUnchecked (i)->needsSaving())
  289. return true;
  290. return false;
  291. }
  292. void OpenDocumentManager::saveAllSyncWithoutAsking()
  293. {
  294. for (int i = documents.size(); --i >= 0;)
  295. {
  296. if (documents.getUnchecked (i)->saveSyncWithoutAsking())
  297. ProjucerApplication::getCommandManager().commandStatusChanged();
  298. }
  299. }
  300. void OpenDocumentManager::reloadModifiedFiles()
  301. {
  302. for (int i = documents.size(); --i >= 0;)
  303. {
  304. Document* d = documents.getUnchecked (i);
  305. if (d->hasFileBeenModifiedExternally())
  306. d->reloadFromFile();
  307. }
  308. }
  309. void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile)
  310. {
  311. for (int i = documents.size(); --i >= 0;)
  312. {
  313. Document* d = documents.getUnchecked (i);
  314. if (d->isForFile (oldFile))
  315. d->fileHasBeenRenamed (newFile);
  316. }
  317. }
  318. //==============================================================================
  319. RecentDocumentList::RecentDocumentList()
  320. {
  321. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  322. }
  323. RecentDocumentList::~RecentDocumentList()
  324. {
  325. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  326. }
  327. void RecentDocumentList::clear()
  328. {
  329. previousDocs.clear();
  330. nextDocs.clear();
  331. }
  332. void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document)
  333. {
  334. if (document != nullptr && document != getCurrentDocument())
  335. {
  336. nextDocs.clear();
  337. previousDocs.add (document);
  338. }
  339. }
  340. bool RecentDocumentList::canGoToPrevious() const
  341. {
  342. return previousDocs.size() > 1;
  343. }
  344. bool RecentDocumentList::canGoToNext() const
  345. {
  346. return nextDocs.size() > 0;
  347. }
  348. OpenDocumentManager::Document* RecentDocumentList::getPrevious()
  349. {
  350. if (! canGoToPrevious())
  351. return nullptr;
  352. nextDocs.insert (0, previousDocs.removeAndReturn (previousDocs.size() - 1));
  353. return previousDocs.getLast();
  354. }
  355. OpenDocumentManager::Document* RecentDocumentList::getNext()
  356. {
  357. if (! canGoToNext())
  358. return nullptr;
  359. OpenDocumentManager::Document* d = nextDocs.removeAndReturn (0);
  360. previousDocs.add (d);
  361. return d;
  362. }
  363. bool RecentDocumentList::contains (const File& f) const
  364. {
  365. for (int i = previousDocs.size(); --i >= 0;)
  366. if (previousDocs.getUnchecked (i)->getFile() == f)
  367. return true;
  368. return false;
  369. }
  370. OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const
  371. {
  372. for (int i = previousDocs.size(); --i >= 0;)
  373. if (previousDocs.getUnchecked (i) != oneToAvoid)
  374. return previousDocs.getUnchecked (i);
  375. return nullptr;
  376. }
  377. bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
  378. {
  379. previousDocs.removeAllInstancesOf (document);
  380. nextDocs.removeAllInstancesOf (document);
  381. jassert (! previousDocs.contains (document));
  382. jassert (! nextDocs.contains (document));
  383. return true;
  384. }
  385. static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
  386. {
  387. if (xml != nullptr)
  388. {
  389. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  390. for (auto* e : xml->getChildWithTagNameIterator ("DOC"))
  391. {
  392. const File file (e->getStringAttribute ("file"));
  393. if (file.exists())
  394. {
  395. if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
  396. {
  397. doc->restoreState (e->getStringAttribute ("state"));
  398. list.add (doc);
  399. }
  400. }
  401. }
  402. }
  403. }
  404. void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml)
  405. {
  406. clear();
  407. if (xml.hasTagName ("RECENT_DOCUMENTS"))
  408. {
  409. restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS"));
  410. restoreDocList (project, nextDocs, xml.getChildByName ("NEXT"));
  411. }
  412. }
  413. static void saveDocList (const Array <OpenDocumentManager::Document*>& list, XmlElement& xml)
  414. {
  415. for (int i = 0; i < list.size(); ++i)
  416. {
  417. const OpenDocumentManager::Document& doc = *list.getUnchecked (i);
  418. XmlElement* e = xml.createNewChildElement ("DOC");
  419. e->setAttribute ("file", doc.getFile().getFullPathName());
  420. e->setAttribute ("state", doc.getState());
  421. }
  422. }
  423. std::unique_ptr<XmlElement> RecentDocumentList::createXML() const
  424. {
  425. auto xml = std::make_unique<XmlElement> ("RECENT_DOCUMENTS");
  426. saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
  427. saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
  428. return xml;
  429. }