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.

545 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. if (callback != nullptr)
  208. callback (parent->closeDocumentWithoutSaving (doc));
  209. });
  210. return;
  211. }
  212. if (callback != nullptr)
  213. callback (closeDocumentWithoutSaving (doc));
  214. }
  215. void OpenDocumentManager::closeFileWithoutSaving (const File& f)
  216. {
  217. for (int i = documents.size(); --i >= 0;)
  218. if (auto* d = documents[i])
  219. if (d->isForFile (f))
  220. closeDocumentWithoutSaving (d);
  221. }
  222. static void closeLastAsyncRecusrsive (WeakReference<OpenDocumentManager> parent,
  223. OpenDocumentManager::SaveIfNeeded askUserToSave,
  224. std::function<void (bool)> callback)
  225. {
  226. auto lastIndex = parent->getNumOpenDocuments() - 1;
  227. if (lastIndex < 0)
  228. {
  229. if (callback != nullptr)
  230. callback (true);
  231. return;
  232. }
  233. parent->closeDocumentAsync (parent->getOpenDocument (lastIndex),
  234. askUserToSave,
  235. [parent, askUserToSave, callback] (bool closedSuccessfully)
  236. {
  237. if (parent == nullptr)
  238. return;
  239. if (! closedSuccessfully)
  240. {
  241. if (callback != nullptr)
  242. callback (false);
  243. return;
  244. }
  245. closeLastAsyncRecusrsive (parent, askUserToSave, std::move (callback));
  246. });
  247. }
  248. void OpenDocumentManager::closeAllAsync (SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  249. {
  250. closeLastAsyncRecusrsive (this, askUserToSave, std::move (callback));
  251. }
  252. void OpenDocumentManager::closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager> parent,
  253. Project* project,
  254. SaveIfNeeded askUserToSave,
  255. std::function<void (bool)> callback)
  256. {
  257. for (int i = documents.size(); --i >= 0;)
  258. {
  259. if (auto* d = documents[i])
  260. {
  261. if (d->getProject() == project)
  262. {
  263. closeDocumentAsync (d, askUserToSave, [parent, project, askUserToSave, callback] (bool closedSuccessfully)
  264. {
  265. if (parent == nullptr)
  266. return;
  267. if (! closedSuccessfully)
  268. {
  269. if (callback != nullptr)
  270. callback (false);
  271. return;
  272. }
  273. parent->closeLastDocumentUsingProjectRecursive (parent, project, askUserToSave, std::move (callback));
  274. });
  275. return;
  276. }
  277. }
  278. }
  279. if (callback != nullptr)
  280. callback (true);
  281. }
  282. void OpenDocumentManager::closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  283. {
  284. WeakReference<OpenDocumentManager> parent { this };
  285. closeLastDocumentUsingProjectRecursive (parent, &project, askUserToSave, std::move (callback));
  286. }
  287. void OpenDocumentManager::closeAllDocumentsUsingProjectWithoutSaving (Project& project)
  288. {
  289. for (int i = documents.size(); --i >= 0;)
  290. if (Document* d = documents[i])
  291. if (d->refersToProject (project))
  292. closeDocumentWithoutSaving (d);
  293. }
  294. bool OpenDocumentManager::anyFilesNeedSaving() const
  295. {
  296. for (int i = documents.size(); --i >= 0;)
  297. if (documents.getUnchecked (i)->needsSaving())
  298. return true;
  299. return false;
  300. }
  301. void OpenDocumentManager::saveAllSyncWithoutAsking()
  302. {
  303. for (int i = documents.size(); --i >= 0;)
  304. {
  305. if (documents.getUnchecked (i)->saveSyncWithoutAsking())
  306. ProjucerApplication::getCommandManager().commandStatusChanged();
  307. }
  308. }
  309. void OpenDocumentManager::reloadModifiedFiles()
  310. {
  311. for (int i = documents.size(); --i >= 0;)
  312. {
  313. Document* d = documents.getUnchecked (i);
  314. if (d->hasFileBeenModifiedExternally())
  315. d->reloadFromFile();
  316. }
  317. }
  318. void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile)
  319. {
  320. for (int i = documents.size(); --i >= 0;)
  321. {
  322. Document* d = documents.getUnchecked (i);
  323. if (d->isForFile (oldFile))
  324. d->fileHasBeenRenamed (newFile);
  325. }
  326. }
  327. //==============================================================================
  328. RecentDocumentList::RecentDocumentList()
  329. {
  330. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  331. }
  332. RecentDocumentList::~RecentDocumentList()
  333. {
  334. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  335. }
  336. void RecentDocumentList::clear()
  337. {
  338. previousDocs.clear();
  339. nextDocs.clear();
  340. }
  341. void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document)
  342. {
  343. if (document != nullptr && document != getCurrentDocument())
  344. {
  345. nextDocs.clear();
  346. previousDocs.add (document);
  347. }
  348. }
  349. bool RecentDocumentList::canGoToPrevious() const
  350. {
  351. return previousDocs.size() > 1;
  352. }
  353. bool RecentDocumentList::canGoToNext() const
  354. {
  355. return nextDocs.size() > 0;
  356. }
  357. OpenDocumentManager::Document* RecentDocumentList::getPrevious()
  358. {
  359. if (! canGoToPrevious())
  360. return nullptr;
  361. nextDocs.insert (0, previousDocs.removeAndReturn (previousDocs.size() - 1));
  362. return previousDocs.getLast();
  363. }
  364. OpenDocumentManager::Document* RecentDocumentList::getNext()
  365. {
  366. if (! canGoToNext())
  367. return nullptr;
  368. OpenDocumentManager::Document* d = nextDocs.removeAndReturn (0);
  369. previousDocs.add (d);
  370. return d;
  371. }
  372. bool RecentDocumentList::contains (const File& f) const
  373. {
  374. for (int i = previousDocs.size(); --i >= 0;)
  375. if (previousDocs.getUnchecked(i)->getFile() == f)
  376. return true;
  377. return false;
  378. }
  379. OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const
  380. {
  381. for (int i = previousDocs.size(); --i >= 0;)
  382. if (previousDocs.getUnchecked(i) != oneToAvoid)
  383. return previousDocs.getUnchecked(i);
  384. return nullptr;
  385. }
  386. bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
  387. {
  388. previousDocs.removeAllInstancesOf (document);
  389. nextDocs.removeAllInstancesOf (document);
  390. jassert (! previousDocs.contains (document));
  391. jassert (! nextDocs.contains (document));
  392. return true;
  393. }
  394. static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
  395. {
  396. if (xml != nullptr)
  397. {
  398. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  399. for (auto* e : xml->getChildWithTagNameIterator ("DOC"))
  400. {
  401. const File file (e->getStringAttribute ("file"));
  402. if (file.exists())
  403. {
  404. if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
  405. {
  406. doc->restoreState (e->getStringAttribute ("state"));
  407. list.add (doc);
  408. }
  409. }
  410. }
  411. }
  412. }
  413. void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml)
  414. {
  415. clear();
  416. if (xml.hasTagName ("RECENT_DOCUMENTS"))
  417. {
  418. restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS"));
  419. restoreDocList (project, nextDocs, xml.getChildByName ("NEXT"));
  420. }
  421. }
  422. static void saveDocList (const Array <OpenDocumentManager::Document*>& list, XmlElement& xml)
  423. {
  424. for (int i = 0; i < list.size(); ++i)
  425. {
  426. const OpenDocumentManager::Document& doc = *list.getUnchecked(i);
  427. XmlElement* e = xml.createNewChildElement ("DOC");
  428. e->setAttribute ("file", doc.getFile().getFullPathName());
  429. e->setAttribute ("state", doc.getState());
  430. }
  431. }
  432. std::unique_ptr<XmlElement> RecentDocumentList::createXML() const
  433. {
  434. auto xml = std::make_unique<XmlElement> ("RECENT_DOCUMENTS");
  435. saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
  436. saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
  437. return xml;
  438. }