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.

435 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../Application/jucer_Headers.h"
  20. #include "jucer_OpenDocumentManager.h"
  21. #include "../CodeEditor/jucer_ItemPreviewComponent.h"
  22. #include "../Application/jucer_Application.h"
  23. //==============================================================================
  24. class UnknownDocument : public OpenDocumentManager::Document
  25. {
  26. public:
  27. UnknownDocument (Project* p, const File& f)
  28. : project (p), file (f)
  29. {
  30. reloadFromFile();
  31. }
  32. //==============================================================================
  33. struct Type : public OpenDocumentManager::DocumentType
  34. {
  35. bool canOpenFile (const File&) override { return true; }
  36. Document* openFile (Project* p, const File& f) override { return new UnknownDocument (p, f); }
  37. };
  38. //==============================================================================
  39. bool loadedOk() const override { return true; }
  40. bool isForFile (const File& f) const override { return file == f; }
  41. bool isForNode (const ValueTree&) const override { return false; }
  42. bool refersToProject (Project& p) const override { return project == &p; }
  43. Project* getProject() const override { return project; }
  44. bool needsSaving() const override { return false; }
  45. bool save() override { return true; }
  46. bool saveAs() override { return false; }
  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. Component* createEditor() override { return new ItemPreviewComponent (file); }
  52. 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. FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc)
  135. {
  136. if (! doc->needsSaving())
  137. return FileBasedDocument::savedOk;
  138. const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
  139. TRANS("Closing document..."),
  140. TRANS("Do you want to save the changes to \"")
  141. + doc->getName() + "\"?",
  142. TRANS("Save"),
  143. TRANS("Discard changes"),
  144. TRANS("Cancel"));
  145. if (r == 1) // save changes
  146. return doc->save() ? FileBasedDocument::savedOk
  147. : FileBasedDocument::failedToWriteToFile;
  148. if (r == 2) // discard changes
  149. return FileBasedDocument::savedOk;
  150. return FileBasedDocument::userCancelledSave;
  151. }
  152. bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded)
  153. {
  154. if (Document* doc = documents [index])
  155. {
  156. if (saveIfNeeded)
  157. if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk)
  158. return false;
  159. bool canClose = true;
  160. for (int i = listeners.size(); --i >= 0;)
  161. if (DocumentCloseListener* l = listeners[i])
  162. if (! l->documentAboutToClose (doc))
  163. canClose = false;
  164. if (! canClose)
  165. return false;
  166. documents.remove (index);
  167. ProjucerApplication::getCommandManager().commandStatusChanged();
  168. }
  169. return true;
  170. }
  171. bool OpenDocumentManager::closeDocument (Document* document, bool saveIfNeeded)
  172. {
  173. return closeDocument (documents.indexOf (document), saveIfNeeded);
  174. }
  175. void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded)
  176. {
  177. for (int i = documents.size(); --i >= 0;)
  178. if (Document* d = documents[i])
  179. if (d->isForFile (f))
  180. closeDocument (i, saveIfNeeded);
  181. }
  182. bool OpenDocumentManager::closeAll (bool askUserToSave)
  183. {
  184. for (int i = getNumOpenDocuments(); --i >= 0;)
  185. if (! closeDocument (i, askUserToSave))
  186. return false;
  187. return true;
  188. }
  189. bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded)
  190. {
  191. for (int i = documents.size(); --i >= 0;)
  192. if (Document* d = documents[i])
  193. if (d->refersToProject (project))
  194. if (! closeDocument (i, saveIfNeeded))
  195. return false;
  196. return true;
  197. }
  198. bool OpenDocumentManager::anyFilesNeedSaving() const
  199. {
  200. for (int i = documents.size(); --i >= 0;)
  201. if (documents.getUnchecked (i)->needsSaving())
  202. return true;
  203. return false;
  204. }
  205. bool OpenDocumentManager::saveAll()
  206. {
  207. for (int i = documents.size(); --i >= 0;)
  208. {
  209. if (! documents.getUnchecked (i)->save())
  210. return false;
  211. ProjucerApplication::getCommandManager().commandStatusChanged();
  212. }
  213. return true;
  214. }
  215. void OpenDocumentManager::reloadModifiedFiles()
  216. {
  217. for (int i = documents.size(); --i >= 0;)
  218. {
  219. Document* d = documents.getUnchecked (i);
  220. if (d->hasFileBeenModifiedExternally())
  221. d->reloadFromFile();
  222. }
  223. }
  224. void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile)
  225. {
  226. for (int i = documents.size(); --i >= 0;)
  227. {
  228. Document* d = documents.getUnchecked (i);
  229. if (d->isForFile (oldFile))
  230. d->fileHasBeenRenamed (newFile);
  231. }
  232. }
  233. //==============================================================================
  234. RecentDocumentList::RecentDocumentList()
  235. {
  236. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  237. }
  238. RecentDocumentList::~RecentDocumentList()
  239. {
  240. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  241. }
  242. void RecentDocumentList::clear()
  243. {
  244. previousDocs.clear();
  245. nextDocs.clear();
  246. }
  247. void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document)
  248. {
  249. if (document != nullptr && document != getCurrentDocument())
  250. {
  251. nextDocs.clear();
  252. previousDocs.add (document);
  253. }
  254. }
  255. bool RecentDocumentList::canGoToPrevious() const
  256. {
  257. return previousDocs.size() > 1;
  258. }
  259. bool RecentDocumentList::canGoToNext() const
  260. {
  261. return nextDocs.size() > 0;
  262. }
  263. OpenDocumentManager::Document* RecentDocumentList::getPrevious()
  264. {
  265. if (! canGoToPrevious())
  266. return nullptr;
  267. nextDocs.insert (0, previousDocs.removeAndReturn (previousDocs.size() - 1));
  268. return previousDocs.getLast();
  269. }
  270. OpenDocumentManager::Document* RecentDocumentList::getNext()
  271. {
  272. if (! canGoToNext())
  273. return nullptr;
  274. OpenDocumentManager::Document* d = nextDocs.removeAndReturn (0);
  275. previousDocs.add (d);
  276. return d;
  277. }
  278. bool RecentDocumentList::contains (const File& f) const
  279. {
  280. for (int i = previousDocs.size(); --i >= 0;)
  281. if (previousDocs.getUnchecked(i)->getFile() == f)
  282. return true;
  283. return false;
  284. }
  285. OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const
  286. {
  287. for (int i = previousDocs.size(); --i >= 0;)
  288. if (previousDocs.getUnchecked(i) != oneToAvoid)
  289. return previousDocs.getUnchecked(i);
  290. return nullptr;
  291. }
  292. bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
  293. {
  294. previousDocs.removeAllInstancesOf (document);
  295. nextDocs.removeAllInstancesOf (document);
  296. jassert (! previousDocs.contains (document));
  297. jassert (! nextDocs.contains (document));
  298. return true;
  299. }
  300. static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
  301. {
  302. if (xml != nullptr)
  303. {
  304. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  305. forEachXmlChildElementWithTagName (*xml, e, "DOC")
  306. {
  307. const File file (e->getStringAttribute ("file"));
  308. if (file.exists())
  309. {
  310. if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
  311. {
  312. doc->restoreState (e->getStringAttribute ("state"));
  313. list.add (doc);
  314. }
  315. }
  316. }
  317. }
  318. }
  319. void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml)
  320. {
  321. clear();
  322. if (xml.hasTagName ("RECENT_DOCUMENTS"))
  323. {
  324. restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS"));
  325. restoreDocList (project, nextDocs, xml.getChildByName ("NEXT"));
  326. }
  327. }
  328. static void saveDocList (const Array <OpenDocumentManager::Document*>& list, XmlElement& xml)
  329. {
  330. for (int i = 0; i < list.size(); ++i)
  331. {
  332. const OpenDocumentManager::Document& doc = *list.getUnchecked(i);
  333. XmlElement* e = xml.createNewChildElement ("DOC");
  334. e->setAttribute ("file", doc.getFile().getFullPathName());
  335. e->setAttribute ("state", doc.getState());
  336. }
  337. }
  338. XmlElement* RecentDocumentList::createXML() const
  339. {
  340. XmlElement* xml = new XmlElement ("RECENT_DOCUMENTS");
  341. saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
  342. saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
  343. return xml;
  344. }