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.

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