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.

436 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 "../jucer_Headers.h"
  20. #include "jucer_OpenDocumentManager.h"
  21. #include "jucer_FilePreviewComponent.h"
  22. #include "../Code Editor/jucer_SourceCodeEditor.h"
  23. #include "jucer_Application.h"
  24. //==============================================================================
  25. class UnknownDocument : public OpenDocumentManager::Document
  26. {
  27. public:
  28. UnknownDocument (Project* p, const File& f)
  29. : project (p), file (f)
  30. {
  31. reloadFromFile();
  32. }
  33. //==============================================================================
  34. struct Type : public OpenDocumentManager::DocumentType
  35. {
  36. bool canOpenFile (const File&) override { return true; }
  37. Document* openFile (Project* p, const File& f) override { return new UnknownDocument (p, f); }
  38. };
  39. //==============================================================================
  40. bool loadedOk() const override { return true; }
  41. bool isForFile (const File& f) const override { return file == f; }
  42. bool isForNode (const ValueTree&) const override { return false; }
  43. bool refersToProject (Project& p) const override { return project == &p; }
  44. Project* getProject() const override { return project; }
  45. bool needsSaving() const override { return false; }
  46. bool save() override { return true; }
  47. bool saveAs() override { return false; }
  48. bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); }
  49. void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); }
  50. String getName() const override { return file.getFileName(); }
  51. File getFile() const override { return file; }
  52. Component* createEditor() override { return new ItemPreviewComponent (file); }
  53. Component* createViewer() override { return createEditor(); }
  54. void fileHasBeenRenamed (const File& newFile) override { file = newFile; }
  55. String getState() const override { return {}; }
  56. void restoreState (const String&) override {}
  57. String getType() const override
  58. {
  59. if (file.getFileExtension().isNotEmpty())
  60. return file.getFileExtension() + " file";
  61. jassertfalse;
  62. return "Unknown";
  63. }
  64. private:
  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. FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc)
  136. {
  137. if (! doc->needsSaving())
  138. return FileBasedDocument::savedOk;
  139. const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
  140. TRANS("Closing document..."),
  141. TRANS("Do you want to save the changes to \"")
  142. + doc->getName() + "\"?",
  143. TRANS("Save"),
  144. TRANS("Discard changes"),
  145. TRANS("Cancel"));
  146. if (r == 1) // save changes
  147. return doc->save() ? FileBasedDocument::savedOk
  148. : FileBasedDocument::failedToWriteToFile;
  149. if (r == 2) // discard changes
  150. return FileBasedDocument::savedOk;
  151. return FileBasedDocument::userCancelledSave;
  152. }
  153. bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded)
  154. {
  155. if (Document* doc = documents [index])
  156. {
  157. if (saveIfNeeded)
  158. if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk)
  159. return false;
  160. bool canClose = true;
  161. for (int i = listeners.size(); --i >= 0;)
  162. if (DocumentCloseListener* l = listeners[i])
  163. if (! l->documentAboutToClose (doc))
  164. canClose = false;
  165. if (! canClose)
  166. return false;
  167. documents.remove (index);
  168. ProjucerApplication::getCommandManager().commandStatusChanged();
  169. }
  170. return true;
  171. }
  172. bool OpenDocumentManager::closeDocument (Document* document, bool saveIfNeeded)
  173. {
  174. return closeDocument (documents.indexOf (document), saveIfNeeded);
  175. }
  176. void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded)
  177. {
  178. for (int i = documents.size(); --i >= 0;)
  179. if (Document* d = documents[i])
  180. if (d->isForFile (f))
  181. closeDocument (i, saveIfNeeded);
  182. }
  183. bool OpenDocumentManager::closeAll (bool askUserToSave)
  184. {
  185. for (int i = getNumOpenDocuments(); --i >= 0;)
  186. if (! closeDocument (i, askUserToSave))
  187. return false;
  188. return true;
  189. }
  190. bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded)
  191. {
  192. for (int i = documents.size(); --i >= 0;)
  193. if (Document* d = documents[i])
  194. if (d->refersToProject (project))
  195. if (! closeDocument (i, saveIfNeeded))
  196. return false;
  197. return true;
  198. }
  199. bool OpenDocumentManager::anyFilesNeedSaving() const
  200. {
  201. for (int i = documents.size(); --i >= 0;)
  202. if (documents.getUnchecked (i)->needsSaving())
  203. return true;
  204. return false;
  205. }
  206. bool OpenDocumentManager::saveAll()
  207. {
  208. for (int i = documents.size(); --i >= 0;)
  209. {
  210. if (! documents.getUnchecked (i)->save())
  211. return false;
  212. ProjucerApplication::getCommandManager().commandStatusChanged();
  213. }
  214. return true;
  215. }
  216. void OpenDocumentManager::reloadModifiedFiles()
  217. {
  218. for (int i = documents.size(); --i >= 0;)
  219. {
  220. Document* d = documents.getUnchecked (i);
  221. if (d->hasFileBeenModifiedExternally())
  222. d->reloadFromFile();
  223. }
  224. }
  225. void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile)
  226. {
  227. for (int i = documents.size(); --i >= 0;)
  228. {
  229. Document* d = documents.getUnchecked (i);
  230. if (d->isForFile (oldFile))
  231. d->fileHasBeenRenamed (newFile);
  232. }
  233. }
  234. //==============================================================================
  235. RecentDocumentList::RecentDocumentList()
  236. {
  237. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  238. }
  239. RecentDocumentList::~RecentDocumentList()
  240. {
  241. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  242. }
  243. void RecentDocumentList::clear()
  244. {
  245. previousDocs.clear();
  246. nextDocs.clear();
  247. }
  248. void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document)
  249. {
  250. if (document != nullptr && document != getCurrentDocument())
  251. {
  252. nextDocs.clear();
  253. previousDocs.add (document);
  254. }
  255. }
  256. bool RecentDocumentList::canGoToPrevious() const
  257. {
  258. return previousDocs.size() > 1;
  259. }
  260. bool RecentDocumentList::canGoToNext() const
  261. {
  262. return nextDocs.size() > 0;
  263. }
  264. OpenDocumentManager::Document* RecentDocumentList::getPrevious()
  265. {
  266. if (! canGoToPrevious())
  267. return nullptr;
  268. nextDocs.insert (0, previousDocs.removeAndReturn (previousDocs.size() - 1));
  269. return previousDocs.getLast();
  270. }
  271. OpenDocumentManager::Document* RecentDocumentList::getNext()
  272. {
  273. if (! canGoToNext())
  274. return nullptr;
  275. OpenDocumentManager::Document* d = nextDocs.removeAndReturn (0);
  276. previousDocs.add (d);
  277. return d;
  278. }
  279. bool RecentDocumentList::contains (const File& f) const
  280. {
  281. for (int i = previousDocs.size(); --i >= 0;)
  282. if (previousDocs.getUnchecked(i)->getFile() == f)
  283. return true;
  284. return false;
  285. }
  286. OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const
  287. {
  288. for (int i = previousDocs.size(); --i >= 0;)
  289. if (previousDocs.getUnchecked(i) != oneToAvoid)
  290. return previousDocs.getUnchecked(i);
  291. return nullptr;
  292. }
  293. bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
  294. {
  295. previousDocs.removeAllInstancesOf (document);
  296. nextDocs.removeAllInstancesOf (document);
  297. jassert (! previousDocs.contains (document));
  298. jassert (! nextDocs.contains (document));
  299. return true;
  300. }
  301. static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
  302. {
  303. if (xml != nullptr)
  304. {
  305. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  306. forEachXmlChildElementWithTagName (*xml, e, "DOC")
  307. {
  308. const File file (e->getStringAttribute ("file"));
  309. if (file.exists())
  310. {
  311. if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
  312. {
  313. doc->restoreState (e->getStringAttribute ("state"));
  314. list.add (doc);
  315. }
  316. }
  317. }
  318. }
  319. }
  320. void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml)
  321. {
  322. clear();
  323. if (xml.hasTagName ("RECENT_DOCUMENTS"))
  324. {
  325. restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS"));
  326. restoreDocList (project, nextDocs, xml.getChildByName ("NEXT"));
  327. }
  328. }
  329. static void saveDocList (const Array <OpenDocumentManager::Document*>& list, XmlElement& xml)
  330. {
  331. for (int i = 0; i < list.size(); ++i)
  332. {
  333. const OpenDocumentManager::Document& doc = *list.getUnchecked(i);
  334. XmlElement* e = xml.createNewChildElement ("DOC");
  335. e->setAttribute ("file", doc.getFile().getFullPathName());
  336. e->setAttribute ("state", doc.getState());
  337. }
  338. }
  339. XmlElement* RecentDocumentList::createXML() const
  340. {
  341. XmlElement* xml = new XmlElement ("RECENT_DOCUMENTS");
  342. saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
  343. saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
  344. return xml;
  345. }