git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,65 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_DocumentEditorComponent.h"
#include "../Application/jucer_Application.h"
#include "../Project/UI/jucer_ProjectContentComponent.h"
//==============================================================================
DocumentEditorComponent::DocumentEditorComponent (OpenDocumentManager::Document* doc)
: document (doc)
{
ProjucerApplication::getApp().openDocumentManager.addListener (this);
}
DocumentEditorComponent::~DocumentEditorComponent()
{
ProjucerApplication::getApp().openDocumentManager.removeListener (this);
}
bool DocumentEditorComponent::documentAboutToClose (OpenDocumentManager::Document* closingDoc)
{
if (document == closingDoc)
{
jassert (document != nullptr);
if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->hideDocument (document);
}
return true;
}
void DocumentEditorComponent::setEditedState (bool hasBeenEdited)
{
if (hasBeenEdited != lastEditedState)
{
if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->refreshProjectTreeFileStatuses();
lastEditedState = hasBeenEdited;
}
}

View File

@ -0,0 +1,51 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_OpenDocumentManager.h"
//==============================================================================
class DocumentEditorComponent : public Component,
private OpenDocumentManager::DocumentCloseListener
{
public:
//==============================================================================
DocumentEditorComponent (OpenDocumentManager::Document* document);
~DocumentEditorComponent() override;
OpenDocumentManager::Document* getDocument() const { return document; }
protected:
OpenDocumentManager::Document* document;
bool lastEditedState = false;
void setEditedState (bool hasBeenEdited);
private:
bool documentAboutToClose (OpenDocumentManager::Document*) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DocumentEditorComponent)
};

View File

@ -0,0 +1,117 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ItemPreviewComponent : public Component
{
public:
ItemPreviewComponent (const File& f) : file (f)
{
setOpaque (true);
tryToLoadImage();
}
void paint (Graphics& g) override
{
g.fillAll (findColour (backgroundColourId));
if (drawable != nullptr)
{
auto contentBounds = drawable->getDrawableBounds();
if (auto* dc = dynamic_cast<DrawableComposite*> (drawable.get()))
{
auto r = dc->getContentArea();
if (! r.isEmpty())
contentBounds = r;
}
auto area = RectanglePlacement (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize)
.appliedTo (contentBounds, Rectangle<float> (4.0f, 22.0f, (float) getWidth() - 8.0f, (float) getHeight() - 26.0f));
Path p;
p.addRectangle (area);
DropShadow (Colours::black.withAlpha (0.5f), 6, Point<int> (0, 1)).drawForPath (g, p);
g.fillCheckerBoard (area, 24.0f, 24.0f, Colour (0xffffffff), Colour (0xffeeeeee));
drawable->draw (g, 1.0f, RectanglePlacement (RectanglePlacement::stretchToFit)
.getTransformToFit (contentBounds, area.toFloat()));
}
g.setFont (Font (14.0f, Font::bold));
g.setColour (findColour (defaultTextColourId));
g.drawMultiLineText (facts.joinIntoString ("\n"), 10, 15, getWidth() - 16);
}
private:
StringArray facts;
File file;
std::unique_ptr<Drawable> drawable;
void tryToLoadImage()
{
facts.clear();
facts.add (file.getFullPathName());
drawable.reset();
if (auto input = std::unique_ptr<FileInputStream> (file.createInputStream()))
{
auto totalSize = input->getTotalLength();
String formatName;
if (auto* format = ImageFileFormat::findImageFormatForStream (*input))
formatName = " " + format->getFormatName();
input.reset();
auto image = ImageCache::getFromFile (file);
if (image.isValid())
{
auto* d = new DrawableImage();
d->setImage (image);
drawable.reset (d);
facts.add (String (image.getWidth()) + " x " + String (image.getHeight()) + formatName);
}
if (totalSize > 0)
facts.add (File::descriptionOfSizeInBytes (totalSize));
}
if (drawable == nullptr)
if (auto svg = parseXML (file))
drawable = Drawable::createFromSVG (*svg);
facts.removeEmptyStrings (true);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemPreviewComponent)
};

View File

@ -0,0 +1,544 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_OpenDocumentManager.h"
#include "../CodeEditor/jucer_ItemPreviewComponent.h"
#include "../Application/jucer_Application.h"
//==============================================================================
class UnknownDocument : public OpenDocumentManager::Document
{
public:
UnknownDocument (Project* p, const File& f)
: project (p), file (f)
{
reloadFromFile();
}
//==============================================================================
struct Type : public OpenDocumentManager::DocumentType
{
bool canOpenFile (const File&) override { return true; }
Document* openFile (Project* p, const File& f) override { return new UnknownDocument (p, f); }
};
//==============================================================================
bool loadedOk() const override { return true; }
bool isForFile (const File& f) const override { return file == f; }
bool isForNode (const ValueTree&) const override { return false; }
bool refersToProject (Project& p) const override { return project == &p; }
Project* getProject() const override { return project; }
bool needsSaving() const override { return false; }
bool saveSyncWithoutAsking() override { return true; }
void saveAsync (std::function<void (bool)>) override {}
void saveAsAsync (std::function<void (bool)>) override {}
bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); }
void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); }
String getName() const override { return file.getFileName(); }
File getFile() const override { return file; }
std::unique_ptr<Component> createEditor() override { return std::make_unique<ItemPreviewComponent> (file); }
std::unique_ptr<Component> createViewer() override { return createEditor(); }
void fileHasBeenRenamed (const File& newFile) override { file = newFile; }
String getState() const override { return {}; }
void restoreState (const String&) override {}
String getType() const override
{
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::DocumentType* createGUIDocumentType();
OpenDocumentManager::OpenDocumentManager()
{
registerType (new UnknownDocument::Type());
registerType (new SourceCodeDocument::Type());
registerType (createGUIDocumentType());
}
OpenDocumentManager::~OpenDocumentManager()
{
}
void OpenDocumentManager::clear()
{
documents.clear();
types.clear();
}
//==============================================================================
void OpenDocumentManager::registerType (DocumentType* type, int index)
{
types.insert (index, 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);
ProjucerApplication::getCommandManager().commandStatusChanged();
return d;
}
int OpenDocumentManager::getNumOpenDocuments() const
{
return documents.size();
}
OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) const
{
return documents.getUnchecked (index);
}
void OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc,
std::function<void (FileBasedDocument::SaveResult)> callback)
{
if (! doc->needsSaving())
{
if (callback != nullptr)
callback (FileBasedDocument::savedOk);
return;
}
AlertWindow::showYesNoCancelBox (MessageBoxIconType::QuestionIcon,
TRANS("Closing document..."),
TRANS("Do you want to save the changes to \"")
+ doc->getName() + "\"?",
TRANS("Save"),
TRANS("Discard changes"),
TRANS("Cancel"),
nullptr,
ModalCallbackFunction::create ([parent = WeakReference<OpenDocumentManager> { this }, doc, callback] (int r)
{
if (parent == nullptr)
return;
if (r == 1)
{
doc->saveAsync ([parent, callback] (bool hasSaved)
{
if (parent == nullptr)
return;
if (callback != nullptr)
callback (hasSaved ? FileBasedDocument::savedOk : FileBasedDocument::failedToWriteToFile);
});
return;
}
if (callback != nullptr)
callback (r == 2 ? FileBasedDocument::savedOk : FileBasedDocument::userCancelledSave);
}));
}
bool OpenDocumentManager::closeDocumentWithoutSaving (Document* doc)
{
if (documents.contains (doc))
{
bool canClose = true;
for (int i = listeners.size(); --i >= 0;)
if (auto* l = listeners[i])
if (! l->documentAboutToClose (doc))
canClose = false;
if (! canClose)
return false;
documents.removeObject (doc);
ProjucerApplication::getCommandManager().commandStatusChanged();
}
return true;
}
void OpenDocumentManager::closeDocumentAsync (Document* doc, SaveIfNeeded saveIfNeeded, std::function<void (bool)> callback)
{
if (! documents.contains (doc))
{
if (callback != nullptr)
callback (true);
return;
}
if (saveIfNeeded == SaveIfNeeded::yes)
{
saveIfNeededAndUserAgrees (doc,
[parent = WeakReference<OpenDocumentManager> { this }, doc, callback] (FileBasedDocument::SaveResult result)
{
if (parent == nullptr)
return;
if (result != FileBasedDocument::savedOk)
{
if (callback != nullptr)
callback (false);
return;
}
if (callback != nullptr)
callback (parent->closeDocumentWithoutSaving (doc));
});
return;
}
if (callback != nullptr)
callback (closeDocumentWithoutSaving (doc));
}
void OpenDocumentManager::closeFileWithoutSaving (const File& f)
{
for (int i = documents.size(); --i >= 0;)
if (auto* d = documents[i])
if (d->isForFile (f))
closeDocumentWithoutSaving (d);
}
static void closeLastAsyncRecusrsive (WeakReference<OpenDocumentManager> parent,
OpenDocumentManager::SaveIfNeeded askUserToSave,
std::function<void (bool)> callback)
{
auto lastIndex = parent->getNumOpenDocuments() - 1;
if (lastIndex < 0)
{
if (callback != nullptr)
callback (true);
return;
}
parent->closeDocumentAsync (parent->getOpenDocument (lastIndex),
askUserToSave,
[parent, askUserToSave, callback] (bool closedSuccessfully)
{
if (parent == nullptr)
return;
if (! closedSuccessfully)
{
if (callback != nullptr)
callback (false);
return;
}
closeLastAsyncRecusrsive (parent, askUserToSave, std::move (callback));
});
}
void OpenDocumentManager::closeAllAsync (SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
{
closeLastAsyncRecusrsive (this, askUserToSave, std::move (callback));
}
void OpenDocumentManager::closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager> parent,
Project* project,
SaveIfNeeded askUserToSave,
std::function<void (bool)> callback)
{
for (int i = documents.size(); --i >= 0;)
{
if (auto* d = documents[i])
{
if (d->getProject() == project)
{
closeDocumentAsync (d, askUserToSave, [parent, project, askUserToSave, callback] (bool closedSuccessfully)
{
if (parent == nullptr)
return;
if (! closedSuccessfully)
{
if (callback != nullptr)
callback (false);
return;
}
parent->closeLastDocumentUsingProjectRecursive (parent, project, askUserToSave, std::move (callback));
});
return;
}
}
}
if (callback != nullptr)
callback (true);
}
void OpenDocumentManager::closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
{
WeakReference<OpenDocumentManager> parent { this };
closeLastDocumentUsingProjectRecursive (parent, &project, askUserToSave, std::move (callback));
}
void OpenDocumentManager::closeAllDocumentsUsingProjectWithoutSaving (Project& project)
{
for (int i = documents.size(); --i >= 0;)
if (Document* d = documents[i])
if (d->refersToProject (project))
closeDocumentWithoutSaving (d);
}
bool OpenDocumentManager::anyFilesNeedSaving() const
{
for (int i = documents.size(); --i >= 0;)
if (documents.getUnchecked (i)->needsSaving())
return true;
return false;
}
void OpenDocumentManager::saveAllSyncWithoutAsking()
{
for (int i = documents.size(); --i >= 0;)
{
if (documents.getUnchecked (i)->saveSyncWithoutAsking())
ProjucerApplication::getCommandManager().commandStatusChanged();
}
}
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()
{
ProjucerApplication::getApp().openDocumentManager.addListener (this);
}
RecentDocumentList::~RecentDocumentList()
{
ProjucerApplication::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.removeAndReturn (previousDocs.size() - 1));
return previousDocs.getLast();
}
OpenDocumentManager::Document* RecentDocumentList::getNext()
{
if (! canGoToNext())
return nullptr;
OpenDocumentManager::Document* d = nextDocs.removeAndReturn (0);
previousDocs.add (d);
return d;
}
bool RecentDocumentList::contains (const File& f) const
{
for (int i = previousDocs.size(); --i >= 0;)
if (previousDocs.getUnchecked(i)->getFile() == f)
return true;
return false;
}
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;
}
bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
{
previousDocs.removeAllInstancesOf (document);
nextDocs.removeAllInstancesOf (document);
jassert (! previousDocs.contains (document));
jassert (! nextDocs.contains (document));
return true;
}
static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
{
if (xml != nullptr)
{
OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
for (auto* e : xml->getChildWithTagNameIterator ("DOC"))
{
const File file (e->getStringAttribute ("file"));
if (file.exists())
{
if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
{
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 <OpenDocumentManager::Document*>& 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());
}
}
std::unique_ptr<XmlElement> RecentDocumentList::createXML() const
{
auto xml = std::make_unique<XmlElement> ("RECENT_DOCUMENTS");
saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
return xml;
}

View File

@ -0,0 +1,167 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Project/jucer_Project.h"
//==============================================================================
class OpenDocumentManager
{
public:
//==============================================================================
OpenDocumentManager();
~OpenDocumentManager();
//==============================================================================
class Document
{
public:
Document() {}
virtual ~Document() {}
virtual bool loadedOk() const = 0;
virtual bool isForFile (const File& file) const = 0;
virtual bool isForNode (const ValueTree& node) const = 0;
virtual bool refersToProject (Project& project) const = 0;
virtual Project* getProject() const = 0;
virtual String getName() const = 0;
virtual String getType() const = 0;
virtual File getFile() const = 0;
virtual bool needsSaving() const = 0;
virtual bool saveSyncWithoutAsking() = 0;
virtual void saveAsync (std::function<void (bool)>) = 0;
virtual void saveAsAsync (std::function<void (bool)>) = 0;
virtual bool hasFileBeenModifiedExternally() = 0;
virtual void reloadFromFile() = 0;
virtual std::unique_ptr<Component> createEditor() = 0;
virtual std::unique_ptr<Component> createViewer() = 0;
virtual void fileHasBeenRenamed (const File& newFile) = 0;
virtual String getState() const = 0;
virtual void restoreState (const String& state) = 0;
virtual File getCounterpartFile() const { return {}; }
};
//==============================================================================
int getNumOpenDocuments() const;
Document* getOpenDocument (int index) const;
void clear();
enum class SaveIfNeeded { no, yes };
bool canOpenFile (const File& file);
Document* openFile (Project* project, const File& file);
void closeDocumentAsync (Document* document, SaveIfNeeded saveIfNeeded, std::function<void (bool)> callback);
bool closeDocumentWithoutSaving (Document* document);
void closeAllAsync (SaveIfNeeded askUserToSave, std::function<void (bool)> callback);
void closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function<void (bool)> callback);
void closeAllDocumentsUsingProjectWithoutSaving (Project& project);
void closeFileWithoutSaving (const File& f);
bool anyFilesNeedSaving() const;
void saveAllSyncWithoutAsking();
void saveIfNeededAndUserAgrees (Document* doc, std::function<void (FileBasedDocument::SaveResult)>);
void reloadModifiedFiles();
void fileHasBeenRenamed (const File& oldFile, const File& newFile);
//==============================================================================
class DocumentCloseListener
{
public:
DocumentCloseListener() {}
virtual ~DocumentCloseListener() {}
// return false to force it to stop.
virtual bool documentAboutToClose (Document* document) = 0;
};
void addListener (DocumentCloseListener*);
void removeListener (DocumentCloseListener*);
//==============================================================================
class DocumentType
{
public:
DocumentType() {}
virtual ~DocumentType() {}
virtual bool canOpenFile (const File& file) = 0;
virtual Document* openFile (Project* project, const File& file) = 0;
};
void registerType (DocumentType* type, int index = -1);
private:
//==============================================================================
void closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager>,
Project*,
SaveIfNeeded,
std::function<void (bool)>);
//==============================================================================
OwnedArray<DocumentType> types;
OwnedArray<Document> documents;
Array<DocumentCloseListener*> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenDocumentManager)
JUCE_DECLARE_WEAK_REFERENCEABLE (OpenDocumentManager)
};
//==============================================================================
class RecentDocumentList : private OpenDocumentManager::DocumentCloseListener
{
public:
RecentDocumentList();
~RecentDocumentList();
void clear();
void newDocumentOpened (OpenDocumentManager::Document* document);
OpenDocumentManager::Document* getCurrentDocument() const { return previousDocs.getLast(); }
bool canGoToPrevious() const;
bool canGoToNext() const;
bool contains (const File&) const;
OpenDocumentManager::Document* getPrevious();
OpenDocumentManager::Document* getNext();
OpenDocumentManager::Document* getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const;
void restoreFromXML (Project& project, const XmlElement& xml);
std::unique_ptr<XmlElement> createXML() const;
private:
bool documentAboutToClose (OpenDocumentManager::Document*);
Array<OpenDocumentManager::Document*> previousDocs, nextDocs;
};

View File

@ -0,0 +1,697 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_SourceCodeEditor.h"
#include "../Application/jucer_Application.h"
//==============================================================================
SourceCodeDocument::SourceCodeDocument (Project* p, const File& f)
: modDetector (f), project (p)
{
}
CodeDocument& SourceCodeDocument::getCodeDocument()
{
if (codeDoc == nullptr)
{
codeDoc.reset (new CodeDocument());
reloadInternal();
codeDoc->clearUndoHistory();
}
return *codeDoc;
}
std::unique_ptr<Component> SourceCodeDocument::createEditor()
{
auto e = std::make_unique<SourceCodeEditor> (this, getCodeDocument());
applyLastState (*(e->editor));
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move")
return std::move (e);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
void SourceCodeDocument::reloadFromFile()
{
getCodeDocument();
reloadInternal();
}
void SourceCodeDocument::reloadInternal()
{
jassert (codeDoc != nullptr);
modDetector.updateHash();
auto fileContent = getFile().loadFileAsString();
auto lineFeed = getLineFeedForFile (fileContent);
if (lineFeed.isEmpty())
{
if (project != nullptr)
lineFeed = project->getProjectLineFeed();
else
lineFeed = "\r\n";
}
codeDoc->setNewLineCharacters (lineFeed);
codeDoc->applyChanges (fileContent);
codeDoc->setSavePoint();
}
static bool writeCodeDocToFile (const File& file, CodeDocument& doc)
{
TemporaryFile temp (file);
{
FileOutputStream fo (temp.getFile());
if (! (fo.openedOk() && doc.writeToStream (fo)))
return false;
}
return temp.overwriteTargetFileWithTemporary();
}
bool SourceCodeDocument::saveSyncWithoutAsking()
{
if (writeCodeDocToFile (getFile(), getCodeDocument()))
{
getCodeDocument().setSavePoint();
modDetector.updateHash();
return true;
}
return false;
}
void SourceCodeDocument::saveAsync (std::function<void (bool)> callback)
{
callback (saveSyncWithoutAsking());
}
void SourceCodeDocument::saveAsAsync (std::function<void (bool)> callback)
{
chooser = std::make_unique<FileChooser> (TRANS("Save As..."), getFile(), "*");
auto flags = FileBrowserComponent::saveMode
| FileBrowserComponent::canSelectFiles
| FileBrowserComponent::warnAboutOverwriting;
chooser->launchAsync (flags, [this, callback] (const FileChooser& fc)
{
if (fc.getResult() == File{})
{
callback (true);
return;
}
callback (writeCodeDocToFile (fc.getResult(), getCodeDocument()));
});
}
void SourceCodeDocument::updateLastState (CodeEditorComponent& editor)
{
lastState.reset (new CodeEditorComponent::State (editor));
}
void SourceCodeDocument::applyLastState (CodeEditorComponent& editor) const
{
if (lastState != nullptr)
lastState->restoreState (editor);
}
//==============================================================================
SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, CodeDocument& codeDocument)
: DocumentEditorComponent (doc)
{
GenericCodeEditorComponent* ed = nullptr;
auto file = document->getFile();
if (fileNeedsCppSyntaxHighlighting (file))
{
ed = new CppCodeEditorComponent (file, codeDocument);
}
else
{
CodeTokeniser* tokeniser = nullptr;
if (file.hasFileExtension ("xml;svg"))
{
static XmlTokeniser xmlTokeniser;
tokeniser = &xmlTokeniser;
}
if (file.hasFileExtension ("lua"))
{
static LuaTokeniser luaTokeniser;
tokeniser = &luaTokeniser;
}
ed = new GenericCodeEditorComponent (file, codeDocument, tokeniser);
}
setEditor (ed);
}
SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, GenericCodeEditorComponent* ed)
: DocumentEditorComponent (doc)
{
setEditor (ed);
}
SourceCodeEditor::~SourceCodeEditor()
{
if (editor != nullptr)
editor->getDocument().removeListener (this);
getAppSettings().appearance.settings.removeListener (this);
if (auto* doc = dynamic_cast<SourceCodeDocument*> (getDocument()))
doc->updateLastState (*editor);
}
void SourceCodeEditor::setEditor (GenericCodeEditorComponent* newEditor)
{
if (editor != nullptr)
editor->getDocument().removeListener (this);
editor.reset (newEditor);
addAndMakeVisible (newEditor);
editor->setFont (AppearanceSettings::getDefaultCodeFont());
editor->setTabSize (4, true);
updateColourScheme();
getAppSettings().appearance.settings.addListener (this);
editor->getDocument().addListener (this);
}
void SourceCodeEditor::scrollToKeepRangeOnScreen (Range<int> range)
{
auto space = jmin (10, editor->getNumLinesOnScreen() / 3);
const CodeDocument::Position start (editor->getDocument(), range.getStart());
const CodeDocument::Position end (editor->getDocument(), range.getEnd());
editor->scrollToKeepLinesOnScreen ({ start.getLineNumber() - space, end.getLineNumber() + space });
}
void SourceCodeEditor::highlight (Range<int> range, bool cursorAtStart)
{
scrollToKeepRangeOnScreen (range);
if (cursorAtStart)
{
editor->moveCaretTo (CodeDocument::Position (editor->getDocument(), range.getEnd()), false);
editor->moveCaretTo (CodeDocument::Position (editor->getDocument(), range.getStart()), true);
}
else
{
editor->setHighlightedRegion (range);
}
}
void SourceCodeEditor::resized()
{
editor->setBounds (getLocalBounds());
}
void SourceCodeEditor::updateColourScheme()
{
getAppSettings().appearance.applyToCodeEditor (*editor);
}
void SourceCodeEditor::checkSaveState()
{
setEditedState (getDocument()->needsSaving());
}
void SourceCodeEditor::lookAndFeelChanged()
{
updateColourScheme();
}
void SourceCodeEditor::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateColourScheme(); }
void SourceCodeEditor::valueTreeChildAdded (ValueTree&, ValueTree&) { updateColourScheme(); }
void SourceCodeEditor::valueTreeChildRemoved (ValueTree&, ValueTree&, int) { updateColourScheme(); }
void SourceCodeEditor::valueTreeChildOrderChanged (ValueTree&, int, int) { updateColourScheme(); }
void SourceCodeEditor::valueTreeParentChanged (ValueTree&) { updateColourScheme(); }
void SourceCodeEditor::valueTreeRedirected (ValueTree&) { updateColourScheme(); }
void SourceCodeEditor::codeDocumentTextInserted (const String&, int) { checkSaveState(); }
void SourceCodeEditor::codeDocumentTextDeleted (int, int) { checkSaveState(); }
//==============================================================================
GenericCodeEditorComponent::GenericCodeEditorComponent (const File& f, CodeDocument& codeDocument,
CodeTokeniser* tokeniser)
: CodeEditorComponent (codeDocument, tokeniser), file (f)
{
setScrollbarThickness (6);
setCommandManager (&ProjucerApplication::getCommandManager());
}
GenericCodeEditorComponent::~GenericCodeEditorComponent() {}
enum
{
showInFinderID = 0x2fe821e3,
insertComponentID = 0x2fe821e4
};
void GenericCodeEditorComponent::addPopupMenuItems (PopupMenu& menu, const MouseEvent* e)
{
menu.addItem (showInFinderID,
#if JUCE_MAC
"Reveal " + file.getFileName() + " in Finder");
#else
"Reveal " + file.getFileName() + " in Explorer");
#endif
menu.addSeparator();
CodeEditorComponent::addPopupMenuItems (menu, e);
}
void GenericCodeEditorComponent::performPopupMenuAction (int menuItemID)
{
if (menuItemID == showInFinderID)
file.revealToUser();
else
CodeEditorComponent::performPopupMenuAction (menuItemID);
}
void GenericCodeEditorComponent::getAllCommands (Array <CommandID>& commands)
{
CodeEditorComponent::getAllCommands (commands);
const CommandID ids[] = { CommandIDs::showFindPanel,
CommandIDs::findSelection,
CommandIDs::findNext,
CommandIDs::findPrevious };
commands.addArray (ids, numElementsInArray (ids));
}
void GenericCodeEditorComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
{
auto anythingSelected = isHighlightActive();
switch (commandID)
{
case CommandIDs::showFindPanel:
result.setInfo (TRANS ("Find"), TRANS ("Searches for text in the current document."), "Editing", 0);
result.defaultKeypresses.add (KeyPress ('f', ModifierKeys::commandModifier, 0));
break;
case CommandIDs::findSelection:
result.setInfo (TRANS ("Find Selection"), TRANS ("Searches for the currently selected text."), "Editing", 0);
result.setActive (anythingSelected);
result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
break;
case CommandIDs::findNext:
result.setInfo (TRANS ("Find Next"), TRANS ("Searches for the next occurrence of the current search-term."), "Editing", 0);
result.defaultKeypresses.add (KeyPress ('g', ModifierKeys::commandModifier, 0));
break;
case CommandIDs::findPrevious:
result.setInfo (TRANS ("Find Previous"), TRANS ("Searches for the previous occurrence of the current search-term."), "Editing", 0);
result.defaultKeypresses.add (KeyPress ('g', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
result.defaultKeypresses.add (KeyPress ('d', ModifierKeys::commandModifier, 0));
break;
default:
CodeEditorComponent::getCommandInfo (commandID, result);
break;
}
}
bool GenericCodeEditorComponent::perform (const InvocationInfo& info)
{
switch (info.commandID)
{
case CommandIDs::showFindPanel: showFindPanel(); return true;
case CommandIDs::findSelection: findSelection(); return true;
case CommandIDs::findNext: findNext (true, true); return true;
case CommandIDs::findPrevious: findNext (false, false); return true;
default: break;
}
return CodeEditorComponent::perform (info);
}
void GenericCodeEditorComponent::addListener (GenericCodeEditorComponent::Listener* listener)
{
listeners.add (listener);
}
void GenericCodeEditorComponent::removeListener (GenericCodeEditorComponent::Listener* listener)
{
listeners.remove (listener);
}
//==============================================================================
class GenericCodeEditorComponent::FindPanel : public Component
{
public:
FindPanel()
{
editor.setColour (CaretComponent::caretColourId, Colours::black);
addAndMakeVisible (editor);
label.setColour (Label::textColourId, Colours::white);
label.attachToComponent (&editor, false);
addAndMakeVisible (caseButton);
caseButton.setColour (ToggleButton::textColourId, Colours::white);
caseButton.setToggleState (isCaseSensitiveSearch(), dontSendNotification);
caseButton.onClick = [this] { setCaseSensitiveSearch (caseButton.getToggleState()); };
findPrev.setConnectedEdges (Button::ConnectedOnRight);
findNext.setConnectedEdges (Button::ConnectedOnLeft);
addAndMakeVisible (findPrev);
addAndMakeVisible (findNext);
setWantsKeyboardFocus (false);
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
findPrev.setWantsKeyboardFocus (false);
findNext.setWantsKeyboardFocus (false);
editor.setText (getSearchString());
editor.onTextChange = [this] { changeSearchString(); };
editor.onReturnKey = [] { ProjucerApplication::getCommandManager().invokeDirectly (CommandIDs::findNext, true); };
editor.onEscapeKey = [this]
{
if (auto* ed = getOwner())
ed->hideFindPanel();
};
}
void setCommandManager (ApplicationCommandManager* cm)
{
findPrev.setCommandToTrigger (cm, CommandIDs::findPrevious, true);
findNext.setCommandToTrigger (cm, CommandIDs::findNext, true);
}
void paint (Graphics& g) override
{
Path outline;
outline.addRoundedRectangle (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 8.0f);
g.setColour (Colours::black.withAlpha (0.6f));
g.fillPath (outline);
g.setColour (Colours::white.withAlpha (0.8f));
g.strokePath (outline, PathStrokeType (1.0f));
}
void resized() override
{
int y = 30;
editor.setBounds (10, y, getWidth() - 20, 24);
y += 30;
caseButton.setBounds (10, y, getWidth() / 2 - 10, 22);
findNext.setBounds (getWidth() - 40, y, 30, 22);
findPrev.setBounds (getWidth() - 70, y, 30, 22);
}
void changeSearchString()
{
setSearchString (editor.getText());
if (auto* ed = getOwner())
ed->findNext (true, false);
}
GenericCodeEditorComponent* getOwner() const
{
return findParentComponentOfClass <GenericCodeEditorComponent>();
}
TextEditor editor;
Label label { {}, "Find:" };
ToggleButton caseButton { "Case-sensitive" };
TextButton findPrev { "<" },
findNext { ">" };
};
void GenericCodeEditorComponent::resized()
{
CodeEditorComponent::resized();
if (findPanel != nullptr)
{
findPanel->setSize (jmin (260, getWidth() - 32), 100);
findPanel->setTopRightPosition (getWidth() - 16, 8);
}
}
void GenericCodeEditorComponent::showFindPanel()
{
if (findPanel == nullptr)
{
findPanel.reset (new FindPanel());
findPanel->setCommandManager (&ProjucerApplication::getCommandManager());
addAndMakeVisible (findPanel.get());
resized();
}
if (findPanel != nullptr)
{
findPanel->editor.grabKeyboardFocus();
findPanel->editor.selectAll();
}
}
void GenericCodeEditorComponent::hideFindPanel()
{
findPanel.reset();
}
void GenericCodeEditorComponent::findSelection()
{
auto selected = getTextInRange (getHighlightedRegion());
if (selected.isNotEmpty())
{
setSearchString (selected);
findNext (true, true);
}
}
void GenericCodeEditorComponent::findNext (bool forwards, bool skipCurrentSelection)
{
auto highlight = getHighlightedRegion();
const CodeDocument::Position startPos (getDocument(), skipCurrentSelection ? highlight.getEnd()
: highlight.getStart());
auto lineNum = startPos.getLineNumber();
auto linePos = startPos.getIndexInLine();
auto totalLines = getDocument().getNumLines();
auto searchText = getSearchString();
auto caseSensitive = isCaseSensitiveSearch();
for (auto linesToSearch = totalLines; --linesToSearch >= 0;)
{
auto line = getDocument().getLine (lineNum);
int index;
if (forwards)
{
index = caseSensitive ? line.indexOf (linePos, searchText)
: line.indexOfIgnoreCase (linePos, searchText);
}
else
{
if (linePos >= 0)
line = line.substring (0, linePos);
index = caseSensitive ? line.lastIndexOf (searchText)
: line.lastIndexOfIgnoreCase (searchText);
}
if (index >= 0)
{
const CodeDocument::Position p (getDocument(), lineNum, index);
selectRegion (p, p.movedBy (searchText.length()));
break;
}
if (forwards)
{
linePos = 0;
lineNum = (lineNum + 1) % totalLines;
}
else
{
if (--lineNum < 0)
lineNum = totalLines - 1;
linePos = -1;
}
}
}
void GenericCodeEditorComponent::handleEscapeKey()
{
CodeEditorComponent::handleEscapeKey();
hideFindPanel();
}
void GenericCodeEditorComponent::editorViewportPositionChanged()
{
CodeEditorComponent::editorViewportPositionChanged();
listeners.call ([this] (Listener& l) { l.codeEditorViewportMoved (*this); });
}
//==============================================================================
static CPlusPlusCodeTokeniser cppTokeniser;
CppCodeEditorComponent::CppCodeEditorComponent (const File& f, CodeDocument& doc)
: GenericCodeEditorComponent (f, doc, &cppTokeniser)
{
}
CppCodeEditorComponent::~CppCodeEditorComponent() {}
void CppCodeEditorComponent::handleReturnKey()
{
GenericCodeEditorComponent::handleReturnKey();
auto pos = getCaretPos();
String blockIndent, lastLineIndent;
CodeHelpers::getIndentForCurrentBlock (pos, getTabString (getTabSize()), blockIndent, lastLineIndent);
auto remainderOfBrokenLine = pos.getLineText();
auto numLeadingWSChars = CodeHelpers::getLeadingWhitespace (remainderOfBrokenLine).length();
if (numLeadingWSChars > 0)
getDocument().deleteSection (pos, pos.movedBy (numLeadingWSChars));
if (remainderOfBrokenLine.trimStart().startsWithChar ('}'))
insertTextAtCaret (blockIndent);
else
insertTextAtCaret (lastLineIndent);
auto previousLine = pos.movedByLines (-1).getLineText();
auto trimmedPreviousLine = previousLine.trim();
if ((trimmedPreviousLine.startsWith ("if ")
|| trimmedPreviousLine.startsWith ("if(")
|| trimmedPreviousLine.startsWith ("for ")
|| trimmedPreviousLine.startsWith ("for(")
|| trimmedPreviousLine.startsWith ("while(")
|| trimmedPreviousLine.startsWith ("while "))
&& trimmedPreviousLine.endsWithChar (')'))
{
insertTabAtCaret();
}
}
void CppCodeEditorComponent::insertTextAtCaret (const String& newText)
{
if (getHighlightedRegion().isEmpty())
{
auto pos = getCaretPos();
if ((newText == "{" || newText == "}")
&& pos.getLineNumber() > 0
&& pos.getLineText().trim().isEmpty())
{
moveCaretToStartOfLine (true);
String blockIndent, lastLineIndent;
if (CodeHelpers::getIndentForCurrentBlock (pos, getTabString (getTabSize()), blockIndent, lastLineIndent))
{
GenericCodeEditorComponent::insertTextAtCaret (blockIndent);
if (newText == "{")
insertTabAtCaret();
}
}
}
GenericCodeEditorComponent::insertTextAtCaret (newText);
}
void CppCodeEditorComponent::addPopupMenuItems (PopupMenu& menu, const MouseEvent* e)
{
GenericCodeEditorComponent::addPopupMenuItems (menu, e);
menu.addSeparator();
menu.addItem (insertComponentID, TRANS("Insert code for a new Component class..."));
}
void CppCodeEditorComponent::performPopupMenuAction (int menuItemID)
{
if (menuItemID == insertComponentID)
insertComponentClass();
GenericCodeEditorComponent::performPopupMenuAction (menuItemID);
}
void CppCodeEditorComponent::insertComponentClass()
{
asyncAlertWindow = std::make_unique<AlertWindow> (TRANS ("Insert a new Component class"),
TRANS ("Please enter a name for the new class"),
MessageBoxIconType::NoIcon,
nullptr);
const String classNameField { "Class Name" };
asyncAlertWindow->addTextEditor (classNameField, String(), String(), false);
asyncAlertWindow->addButton (TRANS ("Insert Code"), 1, KeyPress (KeyPress::returnKey));
asyncAlertWindow->addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey));
asyncAlertWindow->enterModalState (true,
ModalCallbackFunction::create ([parent = SafePointer<CppCodeEditorComponent> { this }, classNameField] (int result)
{
if (parent == nullptr)
return;
auto& aw = *(parent->asyncAlertWindow);
aw.exitModalState (result);
aw.setVisible (false);
if (result == 0)
return;
auto className = aw.getTextEditorContents (classNameField).trim();
if (className == build_tools::makeValidIdentifier (className, false, true, false))
{
String code (BinaryData::jucer_InlineComponentTemplate_h);
code = code.replace ("%%component_class%%", className);
parent->insertTextAtCaret (code);
return;
}
parent->insertComponentClass();
}));
}

View File

@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_DocumentEditorComponent.h"
//==============================================================================
class SourceCodeDocument : public OpenDocumentManager::Document
{
public:
SourceCodeDocument (Project*, const File&);
bool loadedOk() const override { return true; }
bool isForFile (const File& file) const override { return getFile() == file; }
bool isForNode (const ValueTree&) const override { return false; }
bool refersToProject (Project& p) const override { return project == &p; }
Project* getProject() const override { return project; }
String getName() const override { return getFile().getFileName(); }
String getType() const override { return getFile().getFileExtension() + " file"; }
File getFile() const override { return modDetector.getFile(); }
bool needsSaving() const override { return codeDoc != nullptr && codeDoc->hasChangedSinceSavePoint(); }
bool hasFileBeenModifiedExternally() override { return modDetector.hasBeenModified(); }
void fileHasBeenRenamed (const File& newFile) override { modDetector.fileHasBeenRenamed (newFile); }
String getState() const override { return lastState != nullptr ? lastState->toString() : String(); }
void restoreState (const String& state) override { lastState.reset (new CodeEditorComponent::State (state)); }
File getCounterpartFile() const override
{
auto file = getFile();
if (file.hasFileExtension (sourceFileExtensions))
{
static const char* extensions[] = { "h", "hpp", "hxx", "hh", nullptr };
return findCounterpart (file, extensions);
}
if (file.hasFileExtension (headerFileExtensions))
{
static const char* extensions[] = { "cpp", "mm", "cc", "cxx", "c", "m", nullptr };
return findCounterpart (file, extensions);
}
return {};
}
static File findCounterpart (const File& file, const char** extensions)
{
while (*extensions != nullptr)
{
auto f = file.withFileExtension (*extensions++);
if (f.existsAsFile())
return f;
}
return {};
}
void reloadFromFile() override;
bool saveSyncWithoutAsking() override;
void saveAsync (std::function<void (bool)>) override;
void saveAsAsync (std::function<void (bool)>) override;
std::unique_ptr<Component> createEditor() override;
std::unique_ptr<Component> createViewer() override { return createEditor(); }
void updateLastState (CodeEditorComponent&);
void applyLastState (CodeEditorComponent&) const;
CodeDocument& getCodeDocument();
//==============================================================================
struct Type : public OpenDocumentManager::DocumentType
{
bool canOpenFile (const File& file) override
{
if (file.hasFileExtension (sourceOrHeaderFileExtensions)
|| file.hasFileExtension ("txt;inc;tcc;xml;plist;rtf;html;htm;php;py;rb;cs"))
return true;
MemoryBlock mb;
if (file.loadFileAsData (mb)
&& seemsToBeText (static_cast<const char*> (mb.getData()), (int) mb.getSize())
&& ! file.hasFileExtension ("svg"))
return true;
return false;
}
static bool seemsToBeText (const char* const chars, const int num) noexcept
{
for (int i = 0; i < num; ++i)
{
const char c = chars[i];
if ((c < 32 && c != '\t' && c != '\r' && c != '\n') || chars[i] > 126)
return false;
}
return true;
}
Document* openFile (Project* p, const File& file) override { return new SourceCodeDocument (p, file); }
};
protected:
FileModificationDetector modDetector;
std::unique_ptr<CodeDocument> codeDoc;
Project* project;
std::unique_ptr<CodeEditorComponent::State> lastState;
void reloadInternal();
private:
std::unique_ptr<FileChooser> chooser;
};
class GenericCodeEditorComponent;
//==============================================================================
class SourceCodeEditor : public DocumentEditorComponent,
private ValueTree::Listener,
private CodeDocument::Listener
{
public:
SourceCodeEditor (OpenDocumentManager::Document*, CodeDocument&);
SourceCodeEditor (OpenDocumentManager::Document*, GenericCodeEditorComponent*);
~SourceCodeEditor() override;
void scrollToKeepRangeOnScreen (Range<int> range);
void highlight (Range<int> range, bool cursorAtStart);
std::unique_ptr<GenericCodeEditorComponent> editor;
private:
void resized() override;
void lookAndFeelChanged() override;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
void valueTreeChildOrderChanged (ValueTree&, int, int) override;
void valueTreeParentChanged (ValueTree&) override;
void valueTreeRedirected (ValueTree&) override;
void codeDocumentTextInserted (const String&, int) override;
void codeDocumentTextDeleted (int, int) override;
void setEditor (GenericCodeEditorComponent*);
void updateColourScheme();
void checkSaveState();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SourceCodeEditor)
};
//==============================================================================
class GenericCodeEditorComponent : public CodeEditorComponent
{
public:
GenericCodeEditorComponent (const File&, CodeDocument&, CodeTokeniser*);
~GenericCodeEditorComponent() override;
void addPopupMenuItems (PopupMenu&, const MouseEvent*) override;
void performPopupMenuAction (int menuItemID) override;
void getAllCommands (Array<CommandID>&) override;
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
bool perform (const InvocationInfo&) override;
void showFindPanel();
void hideFindPanel();
void findSelection();
void findNext (bool forwards, bool skipCurrentSelection);
void handleEscapeKey() override;
void editorViewportPositionChanged() override;
void resized() override;
static String getSearchString() { return getAppSettings().getGlobalProperties().getValue ("searchString"); }
static void setSearchString (const String& s) { getAppSettings().getGlobalProperties().setValue ("searchString", s); }
static bool isCaseSensitiveSearch() { return getAppSettings().getGlobalProperties().getBoolValue ("searchCaseSensitive"); }
static void setCaseSensitiveSearch (bool b) { getAppSettings().getGlobalProperties().setValue ("searchCaseSensitive", b); }
struct Listener
{
virtual ~Listener() {}
virtual void codeEditorViewportMoved (CodeEditorComponent&) = 0;
};
void addListener (Listener* listener);
void removeListener (Listener* listener);
private:
File file;
class FindPanel;
std::unique_ptr<FindPanel> findPanel;
ListenerList<Listener> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericCodeEditorComponent)
};
//==============================================================================
class CppCodeEditorComponent : public GenericCodeEditorComponent
{
public:
CppCodeEditorComponent (const File&, CodeDocument&);
~CppCodeEditorComponent() override;
void addPopupMenuItems (PopupMenu&, const MouseEvent*) override;
void performPopupMenuAction (int menuItemID) override;
void handleReturnKey() override;
void insertTextAtCaret (const String& newText) override;
private:
void insertComponentClass();
std::unique_ptr<AlertWindow> asyncAlertWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent)
};