/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "jucer_OpenDocumentManager.h" #include "jucer_FilePreviewComponent.h" #include "../Code Editor/jucer_SourceCodeEditor.h" #include "jucer_Application.h" //============================================================================== class UnknownDocument : public OpenDocumentManager::Document { public: UnknownDocument (Project* project_, const File& file_) : project (project_), file (file_) { reloadFromFile(); } //============================================================================== struct Type : public OpenDocumentManager::DocumentType { bool canOpenFile (const File&) { return true; } Document* openFile (Project* project, const File& file) { return new UnknownDocument (project, file); } }; //============================================================================== bool loadedOk() const { return true; } bool isForFile (const File& file_) const { return file == file_; } bool isForNode (const ValueTree& node_) const { return false; } bool refersToProject (Project& p) const { return project == &p; } Project* getProject() const { return project; } bool needsSaving() const { return false; } bool save() { return true; } bool hasFileBeenModifiedExternally() { return fileModificationTime != file.getLastModificationTime(); } void reloadFromFile() { fileModificationTime = file.getLastModificationTime(); } String getName() const { return file.getFileName(); } File getFile() const { return file; } Component* createEditor() { return new ItemPreviewComponent (file); } Component* createViewer() { return createEditor(); } void fileHasBeenRenamed (const File& newFile) { file = newFile; } String getState() const { return String::empty; } void restoreState (const String& state) {} String getType() const { if (file.getFileExtension().isNotEmpty()) return file.getFileExtension() + " file"; jassertfalse return "Unknown"; } private: Project* const project; File file; Time fileModificationTime; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnknownDocument); }; //============================================================================== OpenDocumentManager::OpenDocumentManager() { registerType (new UnknownDocument::Type()); registerType (new SourceCodeDocument::Type()); } OpenDocumentManager::~OpenDocumentManager() { } void OpenDocumentManager::clear() { documents.clear(); types.clear(); } //============================================================================== void OpenDocumentManager::registerType (DocumentType* type) { types.add (type); } //============================================================================== void OpenDocumentManager::addListener (DocumentCloseListener* listener) { listeners.addIfNotAlreadyThere (listener); } void OpenDocumentManager::removeListener (DocumentCloseListener* listener) { listeners.removeFirstMatchingValue (listener); } //============================================================================== bool OpenDocumentManager::canOpenFile (const File& file) { for (int i = types.size(); --i >= 0;) if (types.getUnchecked(i)->canOpenFile (file)) return true; return false; } OpenDocumentManager::Document* OpenDocumentManager::openFile (Project* project, const File& file) { for (int i = documents.size(); --i >= 0;) if (documents.getUnchecked(i)->isForFile (file)) return documents.getUnchecked(i); Document* d = nullptr; for (int i = types.size(); --i >= 0 && d == nullptr;) { if (types.getUnchecked(i)->canOpenFile (file)) { d = types.getUnchecked(i)->openFile (project, file); jassert (d != nullptr); } } jassert (d != nullptr); // should always at least have been picked up by UnknownDocument documents.add (d); commandManager->commandStatusChanged(); return d; } int OpenDocumentManager::getNumOpenDocuments() const { return documents.size(); } OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) const { return documents.getUnchecked (index); } FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc) { if (! doc->needsSaving()) return FileBasedDocument::savedOk; const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon, TRANS("Closing document..."), TRANS("Do you want to save the changes to \"") + doc->getName() + "\"?", TRANS("save"), TRANS("discard changes"), TRANS("cancel")); if (r == 1) { // save changes return doc->save() ? FileBasedDocument::savedOk : FileBasedDocument::failedToWriteToFile; } else if (r == 2) { // discard changes return FileBasedDocument::savedOk; } return FileBasedDocument::userCancelledSave; } bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded) { Document* doc = documents [index]; if (doc != nullptr) { if (saveIfNeeded) { if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk) return false; } for (int i = listeners.size(); --i >= 0;) listeners.getUnchecked(i)->documentAboutToClose (doc); documents.remove (index); commandManager->commandStatusChanged(); } return true; } bool OpenDocumentManager::closeDocument (Document* document, bool saveIfNeeded) { return closeDocument (documents.indexOf (document), saveIfNeeded); } void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded) { for (int i = documents.size(); --i >= 0;) { Document* d = documents.getUnchecked (i); if (d->isForFile (f)) closeDocument (i, saveIfNeeded); } } bool OpenDocumentManager::closeAll (bool askUserToSave) { for (int i = getNumOpenDocuments(); --i >= 0;) if (! closeDocument (i, askUserToSave)) return false; return true; } bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded) { for (int i = documents.size(); --i >= 0;) { Document* d = documents.getUnchecked (i); if (d->refersToProject (project)) { if (! closeDocument (i, saveIfNeeded)) return false; } } return true; } bool OpenDocumentManager::anyFilesNeedSaving() const { for (int i = documents.size(); --i >= 0;) { Document* d = documents.getUnchecked (i); if (d->needsSaving()) return true; } return false; } bool OpenDocumentManager::saveAll() { for (int i = documents.size(); --i >= 0;) { Document* d = documents.getUnchecked (i); if (! d->save()) return false; } return true; } void OpenDocumentManager::reloadModifiedFiles() { for (int i = documents.size(); --i >= 0;) { Document* d = documents.getUnchecked (i); if (d->hasFileBeenModifiedExternally()) d->reloadFromFile(); } } void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile) { for (int i = documents.size(); --i >= 0;) { Document* d = documents.getUnchecked (i); if (d->isForFile (oldFile)) d->fileHasBeenRenamed (newFile); } } //============================================================================== RecentDocumentList::RecentDocumentList() { IntrojucerApp::getApp().openDocumentManager.addListener (this); } RecentDocumentList::~RecentDocumentList() { IntrojucerApp::getApp().openDocumentManager.removeListener (this); } void RecentDocumentList::clear() { previousDocs.clear(); nextDocs.clear(); } void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document) { if (document != nullptr && document != getCurrentDocument()) { nextDocs.clear(); previousDocs.add (document); } } bool RecentDocumentList::canGoToPrevious() const { return previousDocs.size() > 1; } bool RecentDocumentList::canGoToNext() const { return nextDocs.size() > 0; } OpenDocumentManager::Document* RecentDocumentList::getPrevious() { if (! canGoToPrevious()) return nullptr; nextDocs.insert (0, previousDocs.remove (previousDocs.size() - 1)); return previousDocs.getLast(); } OpenDocumentManager::Document* RecentDocumentList::getNext() { if (! canGoToNext()) return nullptr; OpenDocumentManager::Document* d = nextDocs.remove (0); previousDocs.add (d); return d; } OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const { for (int i = previousDocs.size(); --i >= 0;) if (previousDocs.getUnchecked(i) != oneToAvoid) return previousDocs.getUnchecked(i); return nullptr; } void RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document) { previousDocs.removeAllInstancesOf (document); nextDocs.removeAllInstancesOf (document); jassert (! previousDocs.contains (document)); jassert (! nextDocs.contains (document)); } static void restoreDocList (Project& project, Array & list, const XmlElement* xml) { if (xml != nullptr) { OpenDocumentManager& odm = IntrojucerApp::getApp().openDocumentManager; forEachXmlChildElementWithTagName (*xml, e, "DOC") { const File file (e->getStringAttribute ("file")); if (file.exists()) { OpenDocumentManager::Document* doc = odm.openFile (&project, file); if (doc != nullptr) { doc->restoreState (e->getStringAttribute ("state")); list.add (doc); } } } } } void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml) { clear(); if (xml.hasTagName ("RECENT_DOCUMENTS")) { restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS")); restoreDocList (project, nextDocs, xml.getChildByName ("NEXT")); } } static void saveDocList (const Array & list, XmlElement& xml) { for (int i = 0; i < list.size(); ++i) { const OpenDocumentManager::Document& doc = *list.getUnchecked(i); XmlElement* e = xml.createNewChildElement ("DOC"); e->setAttribute ("file", doc.getFile().getFullPathName()); e->setAttribute ("state", doc.getState()); } } XmlElement* RecentDocumentList::createXML() const { XmlElement* xml = new XmlElement ("RECENT_DOCUMENTS"); saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS")); saveDocList (nextDocs, *xml->createNewChildElement ("NEXT")); return xml; }