mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Fix C++23 compilation
This commit is contained in:
parent
b77249ad52
commit
5ce2fc388e
16 changed files with 817 additions and 760 deletions
|
|
@ -36,6 +36,158 @@
|
||||||
#include "jucer_Application.h"
|
#include "jucer_Application.h"
|
||||||
#include "jucer_AutoUpdater.h"
|
#include "jucer_AutoUpdater.h"
|
||||||
|
|
||||||
|
class DownloadAndInstallThread final : private ThreadWithProgressWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DownloadAndInstallThread (const VersionInfo::Asset& a, const File& t, std::function<void (Result)>&& cb)
|
||||||
|
: ThreadWithProgressWindow ("Downloading New Version", true, true),
|
||||||
|
asset (a), targetFolder (t), completionCallback (std::move (cb))
|
||||||
|
{
|
||||||
|
launchThread (Priority::low);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run() override
|
||||||
|
{
|
||||||
|
setProgress (-1.0);
|
||||||
|
|
||||||
|
MemoryBlock zipData;
|
||||||
|
auto result = download (zipData);
|
||||||
|
|
||||||
|
if (result.wasOk() && ! threadShouldExit())
|
||||||
|
result = install (zipData);
|
||||||
|
|
||||||
|
MessageManager::callAsync ([result, callback = completionCallback]
|
||||||
|
{
|
||||||
|
callback (result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Result download (MemoryBlock& dest)
|
||||||
|
{
|
||||||
|
setStatusMessage ("Downloading...");
|
||||||
|
|
||||||
|
int statusCode = 0;
|
||||||
|
auto inStream = VersionInfo::createInputStreamForAsset (asset, statusCode);
|
||||||
|
|
||||||
|
if (inStream != nullptr && statusCode == 200)
|
||||||
|
{
|
||||||
|
int64 total = 0;
|
||||||
|
MemoryOutputStream mo (dest, true);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (threadShouldExit())
|
||||||
|
return Result::fail ("Cancelled");
|
||||||
|
|
||||||
|
auto written = mo.writeFromInputStream (*inStream, 8192);
|
||||||
|
|
||||||
|
if (written == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
total += written;
|
||||||
|
|
||||||
|
setStatusMessage ("Downloading... " + File::descriptionOfSizeInBytes (total));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result::ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result::fail ("Failed to download from: " + asset.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result install (const MemoryBlock& data)
|
||||||
|
{
|
||||||
|
setStatusMessage ("Installing...");
|
||||||
|
|
||||||
|
MemoryInputStream input (data, false);
|
||||||
|
ZipFile zip (input);
|
||||||
|
|
||||||
|
if (zip.getNumEntries() == 0)
|
||||||
|
return Result::fail ("The downloaded file was not a valid JUCE file!");
|
||||||
|
|
||||||
|
struct ScopedDownloadFolder
|
||||||
|
{
|
||||||
|
explicit ScopedDownloadFolder (const File& installTargetFolder)
|
||||||
|
{
|
||||||
|
folder = installTargetFolder.getSiblingFile (installTargetFolder.getFileNameWithoutExtension() + "_download").getNonexistentSibling();
|
||||||
|
jassert (folder.createDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedDownloadFolder() { folder.deleteRecursively(); }
|
||||||
|
|
||||||
|
File folder;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScopedDownloadFolder unzipTarget (targetFolder);
|
||||||
|
|
||||||
|
if (! unzipTarget.folder.isDirectory())
|
||||||
|
return Result::fail ("Couldn't create a temporary folder to unzip the new version!");
|
||||||
|
|
||||||
|
auto r = zip.uncompressTo (unzipTarget.folder);
|
||||||
|
|
||||||
|
if (r.failed())
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (threadShouldExit())
|
||||||
|
return Result::fail ("Cancelled");
|
||||||
|
|
||||||
|
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC
|
||||||
|
r = setFilePermissions (unzipTarget.folder, zip);
|
||||||
|
|
||||||
|
if (r.failed())
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (threadShouldExit())
|
||||||
|
return Result::fail ("Cancelled");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (targetFolder.exists())
|
||||||
|
{
|
||||||
|
auto oldFolder = targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling();
|
||||||
|
|
||||||
|
if (! targetFolder.moveFileTo (oldFolder))
|
||||||
|
return Result::fail ("Could not remove the existing folder!\n\n"
|
||||||
|
"This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n"
|
||||||
|
"Please select a folder that is writable by the current user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! unzipTarget.folder.getChildFile ("JUCE").moveFileTo (targetFolder))
|
||||||
|
return Result::fail ("Could not overwrite the existing folder!\n\n"
|
||||||
|
"This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n"
|
||||||
|
"Please select a folder that is writable by the current user.");
|
||||||
|
|
||||||
|
return Result::ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result setFilePermissions (const File& root, const ZipFile& zip)
|
||||||
|
{
|
||||||
|
constexpr uint32 executableFlag = (1 << 22);
|
||||||
|
|
||||||
|
for (int i = 0; i < zip.getNumEntries(); ++i)
|
||||||
|
{
|
||||||
|
auto* entry = zip.getEntry (i);
|
||||||
|
|
||||||
|
if ((entry->externalFileAttributes & executableFlag) != 0 && entry->filename.getLastCharacter() != '/')
|
||||||
|
{
|
||||||
|
auto exeFile = root.getChildFile (entry->filename);
|
||||||
|
|
||||||
|
if (! exeFile.exists())
|
||||||
|
return Result::fail ("Failed to find executable file when setting permissions " + exeFile.getFileName());
|
||||||
|
|
||||||
|
if (! exeFile.setExecutePermission (true))
|
||||||
|
return Result::fail ("Failed to set executable file permission for " + exeFile.getFileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result::ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
VersionInfo::Asset asset;
|
||||||
|
File targetFolder;
|
||||||
|
std::function<void (Result)> completionCallback;
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater()
|
LatestVersionCheckerAndUpdater::LatestVersionCheckerAndUpdater()
|
||||||
: Thread ("VersionChecker")
|
: Thread ("VersionChecker")
|
||||||
|
|
@ -394,158 +546,6 @@ void LatestVersionCheckerAndUpdater::addNotificationToOpenProjects (const Versio
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class DownloadAndInstallThread final : private ThreadWithProgressWindow
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DownloadAndInstallThread (const VersionInfo::Asset& a, const File& t, std::function<void (Result)>&& cb)
|
|
||||||
: ThreadWithProgressWindow ("Downloading New Version", true, true),
|
|
||||||
asset (a), targetFolder (t), completionCallback (std::move (cb))
|
|
||||||
{
|
|
||||||
launchThread (Priority::low);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
setProgress (-1.0);
|
|
||||||
|
|
||||||
MemoryBlock zipData;
|
|
||||||
auto result = download (zipData);
|
|
||||||
|
|
||||||
if (result.wasOk() && ! threadShouldExit())
|
|
||||||
result = install (zipData);
|
|
||||||
|
|
||||||
MessageManager::callAsync ([result, callback = completionCallback]
|
|
||||||
{
|
|
||||||
callback (result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Result download (MemoryBlock& dest)
|
|
||||||
{
|
|
||||||
setStatusMessage ("Downloading...");
|
|
||||||
|
|
||||||
int statusCode = 0;
|
|
||||||
auto inStream = VersionInfo::createInputStreamForAsset (asset, statusCode);
|
|
||||||
|
|
||||||
if (inStream != nullptr && statusCode == 200)
|
|
||||||
{
|
|
||||||
int64 total = 0;
|
|
||||||
MemoryOutputStream mo (dest, true);
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (threadShouldExit())
|
|
||||||
return Result::fail ("Cancelled");
|
|
||||||
|
|
||||||
auto written = mo.writeFromInputStream (*inStream, 8192);
|
|
||||||
|
|
||||||
if (written == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
total += written;
|
|
||||||
|
|
||||||
setStatusMessage ("Downloading... " + File::descriptionOfSizeInBytes (total));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result::ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result::fail ("Failed to download from: " + asset.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result install (const MemoryBlock& data)
|
|
||||||
{
|
|
||||||
setStatusMessage ("Installing...");
|
|
||||||
|
|
||||||
MemoryInputStream input (data, false);
|
|
||||||
ZipFile zip (input);
|
|
||||||
|
|
||||||
if (zip.getNumEntries() == 0)
|
|
||||||
return Result::fail ("The downloaded file was not a valid JUCE file!");
|
|
||||||
|
|
||||||
struct ScopedDownloadFolder
|
|
||||||
{
|
|
||||||
explicit ScopedDownloadFolder (const File& installTargetFolder)
|
|
||||||
{
|
|
||||||
folder = installTargetFolder.getSiblingFile (installTargetFolder.getFileNameWithoutExtension() + "_download").getNonexistentSibling();
|
|
||||||
jassert (folder.createDirectory());
|
|
||||||
}
|
|
||||||
|
|
||||||
~ScopedDownloadFolder() { folder.deleteRecursively(); }
|
|
||||||
|
|
||||||
File folder;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScopedDownloadFolder unzipTarget (targetFolder);
|
|
||||||
|
|
||||||
if (! unzipTarget.folder.isDirectory())
|
|
||||||
return Result::fail ("Couldn't create a temporary folder to unzip the new version!");
|
|
||||||
|
|
||||||
auto r = zip.uncompressTo (unzipTarget.folder);
|
|
||||||
|
|
||||||
if (r.failed())
|
|
||||||
return r;
|
|
||||||
|
|
||||||
if (threadShouldExit())
|
|
||||||
return Result::fail ("Cancelled");
|
|
||||||
|
|
||||||
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC
|
|
||||||
r = setFilePermissions (unzipTarget.folder, zip);
|
|
||||||
|
|
||||||
if (r.failed())
|
|
||||||
return r;
|
|
||||||
|
|
||||||
if (threadShouldExit())
|
|
||||||
return Result::fail ("Cancelled");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (targetFolder.exists())
|
|
||||||
{
|
|
||||||
auto oldFolder = targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling();
|
|
||||||
|
|
||||||
if (! targetFolder.moveFileTo (oldFolder))
|
|
||||||
return Result::fail ("Could not remove the existing folder!\n\n"
|
|
||||||
"This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n"
|
|
||||||
"Please select a folder that is writable by the current user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! unzipTarget.folder.getChildFile ("JUCE").moveFileTo (targetFolder))
|
|
||||||
return Result::fail ("Could not overwrite the existing folder!\n\n"
|
|
||||||
"This may happen if you are trying to download into a directory that requires administrator privileges to modify.\n"
|
|
||||||
"Please select a folder that is writable by the current user.");
|
|
||||||
|
|
||||||
return Result::ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result setFilePermissions (const File& root, const ZipFile& zip)
|
|
||||||
{
|
|
||||||
constexpr uint32 executableFlag = (1 << 22);
|
|
||||||
|
|
||||||
for (int i = 0; i < zip.getNumEntries(); ++i)
|
|
||||||
{
|
|
||||||
auto* entry = zip.getEntry (i);
|
|
||||||
|
|
||||||
if ((entry->externalFileAttributes & executableFlag) != 0 && entry->filename.getLastCharacter() != '/')
|
|
||||||
{
|
|
||||||
auto exeFile = root.getChildFile (entry->filename);
|
|
||||||
|
|
||||||
if (! exeFile.exists())
|
|
||||||
return Result::fail ("Failed to find executable file when setting permissions " + exeFile.getFileName());
|
|
||||||
|
|
||||||
if (! exeFile.setExecutePermission (true))
|
|
||||||
return Result::fail ("Failed to set executable file permission for " + exeFile.getFileName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result::ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
VersionInfo::Asset asset;
|
|
||||||
File targetFolder;
|
|
||||||
std::function<void (Result)> completionCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void restartProcess (const File& targetFolder)
|
static void restartProcess (const File& targetFolder)
|
||||||
{
|
{
|
||||||
#if JUCE_MAC || JUCE_LINUX || JUCE_BSD
|
#if JUCE_MAC || JUCE_LINUX || JUCE_BSD
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,89 @@ void SourceCodeEditor::valueTreeRedirected (ValueTree&)
|
||||||
void SourceCodeEditor::codeDocumentTextInserted (const String&, int) { checkSaveState(); }
|
void SourceCodeEditor::codeDocumentTextInserted (const String&, int) { checkSaveState(); }
|
||||||
void SourceCodeEditor::codeDocumentTextDeleted (int, int) { checkSaveState(); }
|
void SourceCodeEditor::codeDocumentTextDeleted (int, int) { checkSaveState(); }
|
||||||
|
|
||||||
|
class GenericCodeEditorComponent::FindPanel final : 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 { ">" };
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
GenericCodeEditorComponent::GenericCodeEditorComponent (const File& f, CodeDocument& codeDocument,
|
GenericCodeEditorComponent::GenericCodeEditorComponent (const File& f, CodeDocument& codeDocument,
|
||||||
CodeTokeniser* tokeniser)
|
CodeTokeniser* tokeniser)
|
||||||
|
|
@ -384,89 +467,6 @@ void GenericCodeEditorComponent::removeListener (GenericCodeEditorComponent::Lis
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class GenericCodeEditorComponent::FindPanel final : 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()
|
void GenericCodeEditorComponent::resized()
|
||||||
{
|
{
|
||||||
CodeEditorComponent::resized();
|
CodeEditorComponent::resized();
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
#include "juce_audio_devices.h"
|
#include "juce_audio_devices.h"
|
||||||
|
|
||||||
#include "audio_io/juce_SampleRateHelpers.cpp"
|
#include "audio_io/juce_SampleRateHelpers.cpp"
|
||||||
#include "midi_io/juce_MidiDevices.cpp"
|
#include "midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp"
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if JUCE_MAC || JUCE_IOS
|
#if JUCE_MAC || JUCE_IOS
|
||||||
|
|
@ -258,6 +258,8 @@ namespace juce
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "midi_io/juce_MidiDevices.cpp"
|
||||||
|
|
||||||
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
|
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
|
||||||
namespace juce
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
This file is part of the JUCE framework.
|
||||||
|
Copyright (c) Raw Material Software Limited
|
||||||
|
|
||||||
|
JUCE is an open source framework subject to commercial or open source
|
||||||
|
licensing.
|
||||||
|
|
||||||
|
By downloading, installing, or using the JUCE framework, or combining the
|
||||||
|
JUCE framework with any other source code, object code, content or any other
|
||||||
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||||
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||||
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||||
|
do not agree to the terms of these agreements, we will not license the JUCE
|
||||||
|
framework to you, and you must discontinue the installation or download
|
||||||
|
process and cease use of the JUCE framework.
|
||||||
|
|
||||||
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||||
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||||
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
You may also use this code under the terms of the AGPLv3:
|
||||||
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||||
|
|
||||||
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||||
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||||
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace juce
|
||||||
|
{
|
||||||
|
|
||||||
|
class MidiDeviceListConnectionBroadcaster final : private AsyncUpdater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~MidiDeviceListConnectionBroadcaster() override
|
||||||
|
{
|
||||||
|
cancelPendingUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiDeviceListConnection::Key add (std::function<void()> callback)
|
||||||
|
{
|
||||||
|
JUCE_ASSERT_MESSAGE_THREAD
|
||||||
|
return callbacks.emplace (key++, std::move (callback)).first->first;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove (const MidiDeviceListConnection::Key k)
|
||||||
|
{
|
||||||
|
JUCE_ASSERT_MESSAGE_THREAD
|
||||||
|
callbacks.erase (k);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notify()
|
||||||
|
{
|
||||||
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||||
|
{
|
||||||
|
cancelPendingUpdate();
|
||||||
|
|
||||||
|
const State newState;
|
||||||
|
|
||||||
|
if (std::exchange (lastNotifiedState, newState) != newState)
|
||||||
|
for (auto it = callbacks.begin(); it != callbacks.end();)
|
||||||
|
NullCheckedInvocation::invoke ((it++)->second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
triggerAsyncUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto& get()
|
||||||
|
{
|
||||||
|
static MidiDeviceListConnectionBroadcaster result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MidiDeviceListConnectionBroadcaster() = default;
|
||||||
|
|
||||||
|
class State
|
||||||
|
{
|
||||||
|
Array<MidiDeviceInfo> ins = MidiInput::getAvailableDevices(),
|
||||||
|
outs = MidiOutput::getAvailableDevices();
|
||||||
|
auto tie() const
|
||||||
|
{
|
||||||
|
return std::tie (ins, outs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool operator== (const State& other) const
|
||||||
|
{
|
||||||
|
return tie() == other.tie();
|
||||||
|
}
|
||||||
|
bool operator!= (const State& other) const
|
||||||
|
{
|
||||||
|
return tie() != other.tie();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void handleAsyncUpdate() override
|
||||||
|
{
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<MidiDeviceListConnection::Key, std::function<void()>> callbacks;
|
||||||
|
State lastNotifiedState;
|
||||||
|
MidiDeviceListConnection::Key key = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace juce
|
||||||
|
|
@ -35,74 +35,6 @@
|
||||||
namespace juce
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
class MidiDeviceListConnectionBroadcaster final : private AsyncUpdater
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
~MidiDeviceListConnectionBroadcaster() override
|
|
||||||
{
|
|
||||||
cancelPendingUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
MidiDeviceListConnection::Key add (std::function<void()> callback)
|
|
||||||
{
|
|
||||||
JUCE_ASSERT_MESSAGE_THREAD
|
|
||||||
return callbacks.emplace (key++, std::move (callback)).first->first;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove (const MidiDeviceListConnection::Key k)
|
|
||||||
{
|
|
||||||
JUCE_ASSERT_MESSAGE_THREAD
|
|
||||||
callbacks.erase (k);
|
|
||||||
}
|
|
||||||
|
|
||||||
void notify()
|
|
||||||
{
|
|
||||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
|
||||||
{
|
|
||||||
cancelPendingUpdate();
|
|
||||||
|
|
||||||
const State newState;
|
|
||||||
|
|
||||||
if (std::exchange (lastNotifiedState, newState) != newState)
|
|
||||||
for (auto it = callbacks.begin(); it != callbacks.end();)
|
|
||||||
NullCheckedInvocation::invoke ((it++)->second);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
triggerAsyncUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto& get()
|
|
||||||
{
|
|
||||||
static MidiDeviceListConnectionBroadcaster result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
MidiDeviceListConnectionBroadcaster() = default;
|
|
||||||
|
|
||||||
class State
|
|
||||||
{
|
|
||||||
Array<MidiDeviceInfo> ins = MidiInput::getAvailableDevices(), outs = MidiOutput::getAvailableDevices();
|
|
||||||
auto tie() const { return std::tie (ins, outs); }
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool operator== (const State& other) const { return tie() == other.tie(); }
|
|
||||||
bool operator!= (const State& other) const { return tie() != other.tie(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
void handleAsyncUpdate() override
|
|
||||||
{
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<MidiDeviceListConnection::Key, std::function<void()>> callbacks;
|
|
||||||
State lastNotifiedState;
|
|
||||||
MidiDeviceListConnection::Key key = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
MidiDeviceListConnection::~MidiDeviceListConnection() noexcept
|
MidiDeviceListConnection::~MidiDeviceListConnection() noexcept
|
||||||
{
|
{
|
||||||
if (broadcaster != nullptr)
|
if (broadcaster != nullptr)
|
||||||
|
|
|
||||||
|
|
@ -148,247 +148,6 @@ public:
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableModel)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableModel)
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit,
|
|
||||||
const File& deadMansPedal, PropertiesFile* const props,
|
|
||||||
bool allowPluginsWhichRequireAsynchronousInstantiation)
|
|
||||||
: formatManager (manager),
|
|
||||||
list (listToEdit),
|
|
||||||
deadMansPedalFile (deadMansPedal),
|
|
||||||
optionsButton ("Options..."),
|
|
||||||
propertiesToUse (props),
|
|
||||||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation),
|
|
||||||
numThreads (allowAsync ? 1 : 0)
|
|
||||||
{
|
|
||||||
tableModel.reset (new TableModel (*this, listToEdit));
|
|
||||||
|
|
||||||
TableHeaderComponent& header = table.getHeader();
|
|
||||||
|
|
||||||
header.addColumn (TRANS ("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards);
|
|
||||||
header.addColumn (TRANS ("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable);
|
|
||||||
header.addColumn (TRANS ("Category"), TableModel::categoryCol, 100, 100, 200);
|
|
||||||
header.addColumn (TRANS ("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300);
|
|
||||||
header.addColumn (TRANS ("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable);
|
|
||||||
|
|
||||||
table.setHeaderHeight (22);
|
|
||||||
table.setRowHeight (20);
|
|
||||||
table.setModel (tableModel.get());
|
|
||||||
table.setMultipleSelectionEnabled (true);
|
|
||||||
addAndMakeVisible (table);
|
|
||||||
|
|
||||||
addAndMakeVisible (optionsButton);
|
|
||||||
optionsButton.onClick = [this]
|
|
||||||
{
|
|
||||||
createOptionsMenu().showMenuAsync (PopupMenu::Options()
|
|
||||||
.withDeletionCheck (*this)
|
|
||||||
.withTargetComponent (optionsButton));
|
|
||||||
};
|
|
||||||
|
|
||||||
optionsButton.setTriggeredOnMouseDown (true);
|
|
||||||
|
|
||||||
setSize (400, 600);
|
|
||||||
list.addChangeListener (this);
|
|
||||||
updateList();
|
|
||||||
table.getHeader().reSortTable();
|
|
||||||
|
|
||||||
PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile);
|
|
||||||
deadMansPedalFile.deleteFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginListComponent::~PluginListComponent()
|
|
||||||
{
|
|
||||||
list.removeChangeListener (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::setOptionsButtonText (const String& newText)
|
|
||||||
{
|
|
||||||
optionsButton.setButtonText (newText);
|
|
||||||
resized();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::setScanDialogText (const String& title, const String& content)
|
|
||||||
{
|
|
||||||
dialogTitle = title;
|
|
||||||
dialogText = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::setNumberOfThreadsForScanning (int num)
|
|
||||||
{
|
|
||||||
numThreads = num;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::resized()
|
|
||||||
{
|
|
||||||
auto r = getLocalBounds().reduced (2);
|
|
||||||
|
|
||||||
if (optionsButton.isVisible())
|
|
||||||
{
|
|
||||||
optionsButton.setBounds (r.removeFromBottom (24));
|
|
||||||
optionsButton.changeWidthToFitText (24);
|
|
||||||
r.removeFromBottom (3);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.setBounds (r);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::changeListenerCallback (ChangeBroadcaster*)
|
|
||||||
{
|
|
||||||
table.getHeader().reSortTable();
|
|
||||||
updateList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::updateList()
|
|
||||||
{
|
|
||||||
table.updateContent();
|
|
||||||
table.repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::removeSelectedPlugins()
|
|
||||||
{
|
|
||||||
auto selected = table.getSelectedRows();
|
|
||||||
|
|
||||||
for (int i = table.getNumRows(); --i >= 0;)
|
|
||||||
if (selected.contains (i))
|
|
||||||
removePluginItem (i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::setTableModel (TableListBoxModel* model)
|
|
||||||
{
|
|
||||||
table.setModel (nullptr);
|
|
||||||
tableModel.reset (model);
|
|
||||||
table.setModel (tableModel.get());
|
|
||||||
|
|
||||||
table.getHeader().reSortTable();
|
|
||||||
table.updateContent();
|
|
||||||
table.repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool canShowFolderForPlugin (KnownPluginList& list, int index)
|
|
||||||
{
|
|
||||||
return File::createFileWithoutCheckingPath (list.getTypes()[index].fileOrIdentifier).exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void showFolderForPlugin (KnownPluginList& list, int index)
|
|
||||||
{
|
|
||||||
if (canShowFolderForPlugin (list, index))
|
|
||||||
File (list.getTypes()[index].fileOrIdentifier).revealToUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::removeMissingPlugins()
|
|
||||||
{
|
|
||||||
auto types = list.getTypes();
|
|
||||||
|
|
||||||
for (int i = types.size(); --i >= 0;)
|
|
||||||
{
|
|
||||||
auto type = types.getUnchecked (i);
|
|
||||||
|
|
||||||
if (! formatManager.doesPluginStillExist (type))
|
|
||||||
list.removeType (type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::removePluginItem (int index)
|
|
||||||
{
|
|
||||||
if (index < list.getNumTypes())
|
|
||||||
list.removeType (list.getTypes()[index]);
|
|
||||||
else
|
|
||||||
list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupMenu PluginListComponent::createOptionsMenu()
|
|
||||||
{
|
|
||||||
PopupMenu menu;
|
|
||||||
menu.addItem (PopupMenu::Item (TRANS ("Clear list"))
|
|
||||||
.setAction ([this] { list.clear(); }));
|
|
||||||
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
for (auto format : formatManager.getFormats())
|
|
||||||
if (format->canScanForPlugins())
|
|
||||||
menu.addItem (PopupMenu::Item ("Remove all " + format->getName() + " plug-ins")
|
|
||||||
.setEnabled (! list.getTypesForFormat (*format).isEmpty())
|
|
||||||
.setAction ([this, format]
|
|
||||||
{
|
|
||||||
for (auto& pd : list.getTypesForFormat (*format))
|
|
||||||
list.removeType (pd);
|
|
||||||
}));
|
|
||||||
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
menu.addItem (PopupMenu::Item (TRANS ("Remove selected plug-in from list"))
|
|
||||||
.setEnabled (table.getNumSelectedRows() > 0)
|
|
||||||
.setAction ([this] { removeSelectedPlugins(); }));
|
|
||||||
|
|
||||||
menu.addItem (PopupMenu::Item (TRANS ("Remove any plug-ins whose files no longer exist"))
|
|
||||||
.setAction ([this] { removeMissingPlugins(); }));
|
|
||||||
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
auto selectedRow = table.getSelectedRow();
|
|
||||||
|
|
||||||
menu.addItem (PopupMenu::Item (TRANS ("Show folder containing selected plug-in"))
|
|
||||||
.setEnabled (canShowFolderForPlugin (list, selectedRow))
|
|
||||||
.setAction ([this, selectedRow] { showFolderForPlugin (list, selectedRow); }));
|
|
||||||
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
for (auto format : formatManager.getFormats())
|
|
||||||
if (format->canScanForPlugins())
|
|
||||||
menu.addItem (PopupMenu::Item ("Scan for new or updated " + format->getName() + " plug-ins")
|
|
||||||
.setAction ([this, format] { scanFor (*format); }));
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupMenu PluginListComponent::createMenuForRow (int rowNumber)
|
|
||||||
{
|
|
||||||
PopupMenu menu;
|
|
||||||
|
|
||||||
if (rowNumber >= 0 && rowNumber < tableModel->getNumRows())
|
|
||||||
{
|
|
||||||
menu.addItem (PopupMenu::Item (TRANS ("Remove plug-in from list"))
|
|
||||||
.setAction ([this, rowNumber] { removePluginItem (rowNumber); }));
|
|
||||||
|
|
||||||
menu.addItem (PopupMenu::Item (TRANS ("Show folder containing plug-in"))
|
|
||||||
.setEnabled (canShowFolderForPlugin (list, rowNumber))
|
|
||||||
.setAction ([this, rowNumber] { showFolderForPlugin (list, rowNumber); }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::filesDropped (const StringArray& files, int, int)
|
|
||||||
{
|
|
||||||
OwnedArray<PluginDescription> typesFound;
|
|
||||||
list.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSearchPath PluginListComponent::getLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format)
|
|
||||||
{
|
|
||||||
auto key = "lastPluginScanPath_" + format.getName();
|
|
||||||
|
|
||||||
if (properties.containsKey (key) && properties.getValue (key, {}).trim().isEmpty())
|
|
||||||
properties.removeValue (key);
|
|
||||||
|
|
||||||
return FileSearchPath (properties.getValue (key, format.getDefaultLocationsToSearch().toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginListComponent::setLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format,
|
|
||||||
const FileSearchPath& newPath)
|
|
||||||
{
|
|
||||||
auto key = "lastPluginScanPath_" + format.getName();
|
|
||||||
|
|
||||||
if (newPath.getNumPaths() == 0)
|
|
||||||
properties.removeValue (key);
|
|
||||||
else
|
|
||||||
properties.setValue (key, newPath.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class PluginListComponent::Scanner final : private Timer
|
class PluginListComponent::Scanner final : private Timer
|
||||||
{
|
{
|
||||||
|
|
@ -638,6 +397,248 @@ private:
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Scanner)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Scanner)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit,
|
||||||
|
const File& deadMansPedal, PropertiesFile* const props,
|
||||||
|
bool allowPluginsWhichRequireAsynchronousInstantiation)
|
||||||
|
: formatManager (manager),
|
||||||
|
list (listToEdit),
|
||||||
|
deadMansPedalFile (deadMansPedal),
|
||||||
|
optionsButton ("Options..."),
|
||||||
|
propertiesToUse (props),
|
||||||
|
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation),
|
||||||
|
numThreads (allowAsync ? 1 : 0)
|
||||||
|
{
|
||||||
|
tableModel.reset (new TableModel (*this, listToEdit));
|
||||||
|
|
||||||
|
TableHeaderComponent& header = table.getHeader();
|
||||||
|
|
||||||
|
header.addColumn (TRANS ("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards);
|
||||||
|
header.addColumn (TRANS ("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable);
|
||||||
|
header.addColumn (TRANS ("Category"), TableModel::categoryCol, 100, 100, 200);
|
||||||
|
header.addColumn (TRANS ("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300);
|
||||||
|
header.addColumn (TRANS ("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable);
|
||||||
|
|
||||||
|
table.setHeaderHeight (22);
|
||||||
|
table.setRowHeight (20);
|
||||||
|
table.setModel (tableModel.get());
|
||||||
|
table.setMultipleSelectionEnabled (true);
|
||||||
|
addAndMakeVisible (table);
|
||||||
|
|
||||||
|
addAndMakeVisible (optionsButton);
|
||||||
|
optionsButton.onClick = [this]
|
||||||
|
{
|
||||||
|
createOptionsMenu().showMenuAsync (PopupMenu::Options()
|
||||||
|
.withDeletionCheck (*this)
|
||||||
|
.withTargetComponent (optionsButton));
|
||||||
|
};
|
||||||
|
|
||||||
|
optionsButton.setTriggeredOnMouseDown (true);
|
||||||
|
|
||||||
|
setSize (400, 600);
|
||||||
|
list.addChangeListener (this);
|
||||||
|
updateList();
|
||||||
|
table.getHeader().reSortTable();
|
||||||
|
|
||||||
|
PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile);
|
||||||
|
deadMansPedalFile.deleteFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginListComponent::~PluginListComponent()
|
||||||
|
{
|
||||||
|
list.removeChangeListener (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::setOptionsButtonText (const String& newText)
|
||||||
|
{
|
||||||
|
optionsButton.setButtonText (newText);
|
||||||
|
resized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::setScanDialogText (const String& title, const String& content)
|
||||||
|
{
|
||||||
|
dialogTitle = title;
|
||||||
|
dialogText = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::setNumberOfThreadsForScanning (int num)
|
||||||
|
{
|
||||||
|
numThreads = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::resized()
|
||||||
|
{
|
||||||
|
auto r = getLocalBounds().reduced (2);
|
||||||
|
|
||||||
|
if (optionsButton.isVisible())
|
||||||
|
{
|
||||||
|
optionsButton.setBounds (r.removeFromBottom (24));
|
||||||
|
optionsButton.changeWidthToFitText (24);
|
||||||
|
r.removeFromBottom (3);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.setBounds (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::changeListenerCallback (ChangeBroadcaster*)
|
||||||
|
{
|
||||||
|
table.getHeader().reSortTable();
|
||||||
|
updateList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::updateList()
|
||||||
|
{
|
||||||
|
table.updateContent();
|
||||||
|
table.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::removeSelectedPlugins()
|
||||||
|
{
|
||||||
|
auto selected = table.getSelectedRows();
|
||||||
|
|
||||||
|
for (int i = table.getNumRows(); --i >= 0;)
|
||||||
|
if (selected.contains (i))
|
||||||
|
removePluginItem (i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::setTableModel (TableListBoxModel* model)
|
||||||
|
{
|
||||||
|
table.setModel (nullptr);
|
||||||
|
tableModel.reset (model);
|
||||||
|
table.setModel (tableModel.get());
|
||||||
|
|
||||||
|
table.getHeader().reSortTable();
|
||||||
|
table.updateContent();
|
||||||
|
table.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canShowFolderForPlugin (KnownPluginList& list, int index)
|
||||||
|
{
|
||||||
|
return File::createFileWithoutCheckingPath (list.getTypes()[index].fileOrIdentifier).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showFolderForPlugin (KnownPluginList& list, int index)
|
||||||
|
{
|
||||||
|
if (canShowFolderForPlugin (list, index))
|
||||||
|
File (list.getTypes()[index].fileOrIdentifier).revealToUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::removeMissingPlugins()
|
||||||
|
{
|
||||||
|
auto types = list.getTypes();
|
||||||
|
|
||||||
|
for (int i = types.size(); --i >= 0;)
|
||||||
|
{
|
||||||
|
auto type = types.getUnchecked (i);
|
||||||
|
|
||||||
|
if (! formatManager.doesPluginStillExist (type))
|
||||||
|
list.removeType (type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::removePluginItem (int index)
|
||||||
|
{
|
||||||
|
if (index < list.getNumTypes())
|
||||||
|
list.removeType (list.getTypes()[index]);
|
||||||
|
else
|
||||||
|
list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenu PluginListComponent::createOptionsMenu()
|
||||||
|
{
|
||||||
|
PopupMenu menu;
|
||||||
|
menu.addItem (PopupMenu::Item (TRANS ("Clear list"))
|
||||||
|
.setAction ([this] { list.clear(); }));
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
for (auto format : formatManager.getFormats())
|
||||||
|
if (format->canScanForPlugins())
|
||||||
|
menu.addItem (PopupMenu::Item ("Remove all " + format->getName() + " plug-ins")
|
||||||
|
.setEnabled (! list.getTypesForFormat (*format).isEmpty())
|
||||||
|
.setAction ([this, format]
|
||||||
|
{
|
||||||
|
for (auto& pd : list.getTypesForFormat (*format))
|
||||||
|
list.removeType (pd);
|
||||||
|
}));
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
menu.addItem (PopupMenu::Item (TRANS ("Remove selected plug-in from list"))
|
||||||
|
.setEnabled (table.getNumSelectedRows() > 0)
|
||||||
|
.setAction ([this] { removeSelectedPlugins(); }));
|
||||||
|
|
||||||
|
menu.addItem (PopupMenu::Item (TRANS ("Remove any plug-ins whose files no longer exist"))
|
||||||
|
.setAction ([this] { removeMissingPlugins(); }));
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
auto selectedRow = table.getSelectedRow();
|
||||||
|
|
||||||
|
menu.addItem (PopupMenu::Item (TRANS ("Show folder containing selected plug-in"))
|
||||||
|
.setEnabled (canShowFolderForPlugin (list, selectedRow))
|
||||||
|
.setAction ([this, selectedRow] { showFolderForPlugin (list, selectedRow); }));
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
for (auto format : formatManager.getFormats())
|
||||||
|
if (format->canScanForPlugins())
|
||||||
|
menu.addItem (PopupMenu::Item ("Scan for new or updated " + format->getName() + " plug-ins")
|
||||||
|
.setAction ([this, format] { scanFor (*format); }));
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenu PluginListComponent::createMenuForRow (int rowNumber)
|
||||||
|
{
|
||||||
|
PopupMenu menu;
|
||||||
|
|
||||||
|
if (rowNumber >= 0 && rowNumber < tableModel->getNumRows())
|
||||||
|
{
|
||||||
|
menu.addItem (PopupMenu::Item (TRANS ("Remove plug-in from list"))
|
||||||
|
.setAction ([this, rowNumber] { removePluginItem (rowNumber); }));
|
||||||
|
|
||||||
|
menu.addItem (PopupMenu::Item (TRANS ("Show folder containing plug-in"))
|
||||||
|
.setEnabled (canShowFolderForPlugin (list, rowNumber))
|
||||||
|
.setAction ([this, rowNumber] { showFolderForPlugin (list, rowNumber); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::filesDropped (const StringArray& files, int, int)
|
||||||
|
{
|
||||||
|
OwnedArray<PluginDescription> typesFound;
|
||||||
|
list.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSearchPath PluginListComponent::getLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format)
|
||||||
|
{
|
||||||
|
auto key = "lastPluginScanPath_" + format.getName();
|
||||||
|
|
||||||
|
if (properties.containsKey (key) && properties.getValue (key, {}).trim().isEmpty())
|
||||||
|
properties.removeValue (key);
|
||||||
|
|
||||||
|
return FileSearchPath (properties.getValue (key, format.getDefaultLocationsToSearch().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginListComponent::setLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format,
|
||||||
|
const FileSearchPath& newPath)
|
||||||
|
{
|
||||||
|
auto key = "lastPluginScanPath_" + format.getName();
|
||||||
|
|
||||||
|
if (newPath.getNumPaths() == 0)
|
||||||
|
properties.removeValue (key);
|
||||||
|
else
|
||||||
|
properties.setValue (key, newPath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
void PluginListComponent::scanFor (AudioPluginFormat& format)
|
void PluginListComponent::scanFor (AudioPluginFormat& format)
|
||||||
{
|
{
|
||||||
scanFor (format, StringArray());
|
scanFor (format, StringArray());
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,14 @@
|
||||||
#include "misc/juce_ConsoleApplication.cpp"
|
#include "misc/juce_ConsoleApplication.cpp"
|
||||||
#include "misc/juce_ScopeGuard.cpp"
|
#include "misc/juce_ScopeGuard.cpp"
|
||||||
#include "network/juce_MACAddress.cpp"
|
#include "network/juce_MACAddress.cpp"
|
||||||
|
|
||||||
|
#if ! JUCE_WINDOWS
|
||||||
|
#include "native/juce_SharedCode_posix.h"
|
||||||
|
#include "native/juce_NamedPipe_posix.cpp"
|
||||||
|
#else
|
||||||
|
#include "native/juce_Files_windows.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "network/juce_NamedPipe.cpp"
|
#include "network/juce_NamedPipe.cpp"
|
||||||
#include "network/juce_Socket.cpp"
|
#include "network/juce_Socket.cpp"
|
||||||
#include "network/juce_IPAddress.cpp"
|
#include "network/juce_IPAddress.cpp"
|
||||||
|
|
@ -197,12 +205,8 @@
|
||||||
#include "native/juce_PlatformTimerListener.h"
|
#include "native/juce_PlatformTimerListener.h"
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if ! JUCE_WINDOWS
|
#if ! JUCE_WINDOWS && (! JUCE_ANDROID || __ANDROID_API__ >= 24)
|
||||||
#include "native/juce_SharedCode_posix.h"
|
#include "native/juce_IPAddress_posix.h"
|
||||||
#include "native/juce_NamedPipe_posix.cpp"
|
|
||||||
#if ! JUCE_ANDROID || __ANDROID_API__ >= 24
|
|
||||||
#include "native/juce_IPAddress_posix.h"
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -218,7 +222,6 @@
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#elif JUCE_WINDOWS
|
#elif JUCE_WINDOWS
|
||||||
#include "native/juce_Files_windows.cpp"
|
|
||||||
#include "native/juce_Network_windows.cpp"
|
#include "native/juce_Network_windows.cpp"
|
||||||
#include "native/juce_Registry_windows.cpp"
|
#include "native/juce_Registry_windows.cpp"
|
||||||
#include "native/juce_SystemStats_windows.cpp"
|
#include "native/juce_SystemStats_windows.cpp"
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ bool File::setAsCurrentWorkingDirectory() const
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
// The unix siginterrupt function is deprecated - this does the same job.
|
// The unix siginterrupt function is deprecated - this does the same job.
|
||||||
int juce_siginterrupt ([[maybe_unused]] int sig, [[maybe_unused]] int flag)
|
inline int juce_siginterrupt ([[maybe_unused]] int sig, [[maybe_unused]] int flag)
|
||||||
{
|
{
|
||||||
#if JUCE_WASM
|
#if JUCE_WASM
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,6 @@ static void handleCrash (int signum)
|
||||||
globalCrashHandler ((void*) (pointer_sized_int) signum);
|
globalCrashHandler ((void*) (pointer_sized_int) signum);
|
||||||
::kill (getpid(), SIGKILL);
|
::kill (getpid(), SIGKILL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int juce_siginterrupt (int sig, int flag);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler)
|
void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler)
|
||||||
|
|
|
||||||
|
|
@ -42,18 +42,6 @@ JUCEApplicationBase* JUCEApplicationBase::appInstance = nullptr;
|
||||||
void* JUCEApplicationBase::iOSCustomDelegate = nullptr;
|
void* JUCEApplicationBase::iOSCustomDelegate = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JUCEApplicationBase::JUCEApplicationBase()
|
|
||||||
{
|
|
||||||
jassert (isStandaloneApp() && appInstance == nullptr);
|
|
||||||
appInstance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
JUCEApplicationBase::~JUCEApplicationBase()
|
|
||||||
{
|
|
||||||
jassert (appInstance == this);
|
|
||||||
appInstance = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept
|
void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept
|
||||||
{
|
{
|
||||||
appReturnValue = newReturnValue;
|
appReturnValue = newReturnValue;
|
||||||
|
|
@ -154,6 +142,18 @@ bool JUCEApplicationBase::sendCommandLineToPreexistingInstance()
|
||||||
struct JUCEApplicationBase::MultipleInstanceHandler {};
|
struct JUCEApplicationBase::MultipleInstanceHandler {};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
JUCEApplicationBase::JUCEApplicationBase()
|
||||||
|
{
|
||||||
|
jassert (isStandaloneApp() && appInstance == nullptr);
|
||||||
|
appInstance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
JUCEApplicationBase::~JUCEApplicationBase()
|
||||||
|
{
|
||||||
|
jassert (appInstance == this);
|
||||||
|
appInstance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if JUCE_ANDROID
|
#if JUCE_ANDROID
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if JUCE_MAC
|
#if JUCE_MAC
|
||||||
#import <QuartzCore/QuartzCore.h>
|
#import <QuartzCore/QuartzCore.h>
|
||||||
|
#include <CoreImage/CIRenderDestination.h>
|
||||||
#include <CoreText/CTFont.h>
|
#include <CoreText/CTFont.h>
|
||||||
|
|
||||||
#elif JUCE_WINDOWS
|
#elif JUCE_WINDOWS
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,50 @@ private:
|
||||||
JUCE_DECLARE_NON_COPYABLE (MouseListenerList)
|
JUCE_DECLARE_NON_COPYABLE (MouseListenerList)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Component::EffectState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit EffectState (ImageEffectFilter& i) : effect (&i) {}
|
||||||
|
|
||||||
|
ImageEffectFilter& getEffect() const
|
||||||
|
{
|
||||||
|
return *effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setEffect (ImageEffectFilter& i)
|
||||||
|
{
|
||||||
|
return std::exchange (effect, &i) != &i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel)
|
||||||
|
{
|
||||||
|
auto scale = g.getInternalContext().getPhysicalPixelScaleFactor();
|
||||||
|
auto scaledBounds = c.getLocalBounds() * scale;
|
||||||
|
|
||||||
|
if (effectImage.getBounds() != scaledBounds)
|
||||||
|
effectImage = Image { c.isOpaque() ? Image::RGB : Image::ARGB, scaledBounds.getWidth(), scaledBounds.getHeight(), false };
|
||||||
|
|
||||||
|
if (! c.isOpaque())
|
||||||
|
effectImage.clear (effectImage.getBounds());
|
||||||
|
|
||||||
|
{
|
||||||
|
Graphics g2 (effectImage);
|
||||||
|
g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) c.getWidth(),
|
||||||
|
(float) scaledBounds.getHeight() / (float) c.getHeight()));
|
||||||
|
c.paintComponentAndChildren (g2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Graphics::ScopedSaveState ss (g);
|
||||||
|
|
||||||
|
g.addTransform (AffineTransform::scale (1.0f / scale));
|
||||||
|
effect->applyEffect (effectImage, g, scale, ignoreAlphaLevel ? 1.0f : c.getAlpha());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Image effectImage;
|
||||||
|
ImageEffectFilter* effect;
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
Component::Component() noexcept
|
Component::Component() noexcept
|
||||||
: componentFlags (0)
|
: componentFlags (0)
|
||||||
|
|
@ -1695,50 +1739,6 @@ void Component::paintComponentAndChildren (Graphics& g)
|
||||||
paintOverChildren (g);
|
paintOverChildren (g);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Component::EffectState
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit EffectState (ImageEffectFilter& i) : effect (&i) {}
|
|
||||||
|
|
||||||
ImageEffectFilter& getEffect() const
|
|
||||||
{
|
|
||||||
return *effect;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool setEffect (ImageEffectFilter& i)
|
|
||||||
{
|
|
||||||
return std::exchange (effect, &i) != &i;
|
|
||||||
}
|
|
||||||
|
|
||||||
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel)
|
|
||||||
{
|
|
||||||
auto scale = g.getInternalContext().getPhysicalPixelScaleFactor();
|
|
||||||
auto scaledBounds = c.getLocalBounds() * scale;
|
|
||||||
|
|
||||||
if (effectImage.getBounds() != scaledBounds)
|
|
||||||
effectImage = Image { c.isOpaque() ? Image::RGB : Image::ARGB, scaledBounds.getWidth(), scaledBounds.getHeight(), false };
|
|
||||||
|
|
||||||
if (! c.isOpaque())
|
|
||||||
effectImage.clear (effectImage.getBounds());
|
|
||||||
|
|
||||||
{
|
|
||||||
Graphics g2 (effectImage);
|
|
||||||
g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) c.getWidth(),
|
|
||||||
(float) scaledBounds.getHeight() / (float) c.getHeight()));
|
|
||||||
c.paintComponentAndChildren (g2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Graphics::ScopedSaveState ss (g);
|
|
||||||
|
|
||||||
g.addTransform (AffineTransform::scale (1.0f / scale));
|
|
||||||
effect->applyEffect (effectImage, g, scale, ignoreAlphaLevel ? 1.0f : c.getAlpha());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Image effectImage;
|
|
||||||
ImageEffectFilter* effect;
|
|
||||||
};
|
|
||||||
|
|
||||||
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
|
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
|
||||||
{
|
{
|
||||||
// If sizing a top-level-window and the OS paint message is delivered synchronously
|
// If sizing a top-level-window and the OS paint message is delivered synchronously
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
#include "native/accessibility/juce_Accessibility.cpp"
|
||||||
#include "accessibility/juce_AccessibilityHandler.cpp"
|
#include "accessibility/juce_AccessibilityHandler.cpp"
|
||||||
#include "application/juce_Application.cpp"
|
#include "application/juce_Application.cpp"
|
||||||
#include "buttons/juce_ArrowButton.cpp"
|
#include "buttons/juce_ArrowButton.cpp"
|
||||||
|
|
@ -330,7 +331,6 @@
|
||||||
#include "mouse/juce_MouseInactivityDetector.cpp"
|
#include "mouse/juce_MouseInactivityDetector.cpp"
|
||||||
#include "mouse/juce_MouseInputSource.cpp"
|
#include "mouse/juce_MouseInputSource.cpp"
|
||||||
#include "mouse/juce_MouseListener.cpp"
|
#include "mouse/juce_MouseListener.cpp"
|
||||||
#include "native/accessibility/juce_Accessibility.cpp"
|
|
||||||
#include "native/juce_ScopedDPIAwarenessDisabler.cpp"
|
#include "native/juce_ScopedDPIAwarenessDisabler.cpp"
|
||||||
#include "positioning/juce_MarkerList.cpp"
|
#include "positioning/juce_MarkerList.cpp"
|
||||||
#include "positioning/juce_RelativeCoordinate.cpp"
|
#include "positioning/juce_RelativeCoordinate.cpp"
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,16 @@ namespace juce
|
||||||
#include "layout/juce_StretchableObjectResizer.h"
|
#include "layout/juce_StretchableObjectResizer.h"
|
||||||
#include "layout/juce_TabbedButtonBar.h"
|
#include "layout/juce_TabbedButtonBar.h"
|
||||||
#include "layout/juce_TabbedComponent.h"
|
#include "layout/juce_TabbedComponent.h"
|
||||||
|
#include "accessibility/interfaces/juce_AccessibilityCellInterface.h"
|
||||||
|
#include "accessibility/interfaces/juce_AccessibilityTableInterface.h"
|
||||||
|
#include "accessibility/interfaces/juce_AccessibilityTextInterface.h"
|
||||||
|
#include "accessibility/interfaces/juce_AccessibilityValueInterface.h"
|
||||||
|
#include "accessibility/enums/juce_AccessibilityActions.h"
|
||||||
|
#include "accessibility/enums/juce_AccessibilityEvent.h"
|
||||||
|
#include "accessibility/enums/juce_AccessibilityRole.h"
|
||||||
|
#include "accessibility/juce_AccessibilityState.h"
|
||||||
|
#include "accessibility/juce_AccessibilityHandler.h"
|
||||||
|
#include "drawables/juce_Drawable.h"
|
||||||
#include "layout/juce_Viewport.h"
|
#include "layout/juce_Viewport.h"
|
||||||
#include "menus/juce_PopupMenu.h"
|
#include "menus/juce_PopupMenu.h"
|
||||||
#include "menus/juce_MenuBarModel.h"
|
#include "menus/juce_MenuBarModel.h"
|
||||||
|
|
@ -260,7 +270,6 @@ namespace juce
|
||||||
#include "positioning/juce_RelativeCoordinatePositioner.h"
|
#include "positioning/juce_RelativeCoordinatePositioner.h"
|
||||||
#include "positioning/juce_RelativeParallelogram.h"
|
#include "positioning/juce_RelativeParallelogram.h"
|
||||||
#include "positioning/juce_RelativePointPath.h"
|
#include "positioning/juce_RelativePointPath.h"
|
||||||
#include "drawables/juce_Drawable.h"
|
|
||||||
#include "drawables/juce_DrawableShape.h"
|
#include "drawables/juce_DrawableShape.h"
|
||||||
#include "drawables/juce_DrawableComposite.h"
|
#include "drawables/juce_DrawableComposite.h"
|
||||||
#include "drawables/juce_DrawableImage.h"
|
#include "drawables/juce_DrawableImage.h"
|
||||||
|
|
@ -331,15 +340,6 @@ namespace juce
|
||||||
#include "lookandfeel/juce_LookAndFeel_V3.h"
|
#include "lookandfeel/juce_LookAndFeel_V3.h"
|
||||||
#include "lookandfeel/juce_LookAndFeel_V4.h"
|
#include "lookandfeel/juce_LookAndFeel_V4.h"
|
||||||
#include "mouse/juce_LassoComponent.h"
|
#include "mouse/juce_LassoComponent.h"
|
||||||
#include "accessibility/interfaces/juce_AccessibilityCellInterface.h"
|
|
||||||
#include "accessibility/interfaces/juce_AccessibilityTableInterface.h"
|
|
||||||
#include "accessibility/interfaces/juce_AccessibilityTextInterface.h"
|
|
||||||
#include "accessibility/interfaces/juce_AccessibilityValueInterface.h"
|
|
||||||
#include "accessibility/enums/juce_AccessibilityActions.h"
|
|
||||||
#include "accessibility/enums/juce_AccessibilityEvent.h"
|
|
||||||
#include "accessibility/enums/juce_AccessibilityRole.h"
|
|
||||||
#include "accessibility/juce_AccessibilityState.h"
|
|
||||||
#include "accessibility/juce_AccessibilityHandler.h"
|
|
||||||
|
|
||||||
#if JUCE_LINUX || JUCE_BSD
|
#if JUCE_LINUX || JUCE_BSD
|
||||||
#if JUCE_GUI_BASICS_INCLUDE_XHEADERS
|
#if JUCE_GUI_BASICS_INCLUDE_XHEADERS
|
||||||
|
|
|
||||||
|
|
@ -827,6 +827,139 @@ private:
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewport)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewport)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
struct TreeView::InsertPoint
|
||||||
|
{
|
||||||
|
InsertPoint (TreeView& view, const StringArray& files,
|
||||||
|
const DragAndDropTarget::SourceDetails& dragSourceDetails)
|
||||||
|
: pos (dragSourceDetails.localPosition),
|
||||||
|
item (view.getItemAt (dragSourceDetails.localPosition.y))
|
||||||
|
{
|
||||||
|
if (item != nullptr)
|
||||||
|
{
|
||||||
|
auto itemPos = item->getItemPosition (true);
|
||||||
|
insertIndex = item->getIndexInParent();
|
||||||
|
auto oldY = pos.y;
|
||||||
|
pos.y = itemPos.getY();
|
||||||
|
|
||||||
|
if (item->getNumSubItems() == 0 || ! item->isOpen())
|
||||||
|
{
|
||||||
|
if (files.size() > 0 ? item->isInterestedInFileDrag (files)
|
||||||
|
: item->isInterestedInDragSource (dragSourceDetails))
|
||||||
|
{
|
||||||
|
// Check if we're trying to drag into an empty group item..
|
||||||
|
if (oldY > itemPos.getY() + itemPos.getHeight() / 4
|
||||||
|
&& oldY < itemPos.getBottom() - itemPos.getHeight() / 4)
|
||||||
|
{
|
||||||
|
insertIndex = 0;
|
||||||
|
pos.x = itemPos.getX() + view.getIndentSize();
|
||||||
|
pos.y = itemPos.getBottom();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldY > itemPos.getCentreY())
|
||||||
|
{
|
||||||
|
pos.y += item->getItemHeight();
|
||||||
|
|
||||||
|
while (item->isLastOfSiblings() && item->getParentItem() != nullptr
|
||||||
|
&& item->getParentItem()->getParentItem() != nullptr)
|
||||||
|
{
|
||||||
|
if (pos.x > itemPos.getX())
|
||||||
|
break;
|
||||||
|
|
||||||
|
item = item->getParentItem();
|
||||||
|
itemPos = item->getItemPosition (true);
|
||||||
|
insertIndex = item->getIndexInParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
++insertIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos.x = itemPos.getX();
|
||||||
|
item = item->getParentItem();
|
||||||
|
}
|
||||||
|
else if (auto* root = view.getRootItem())
|
||||||
|
{
|
||||||
|
// If they're dragging beyond the bottom of the list, then insert at the end of the root item..
|
||||||
|
item = root;
|
||||||
|
insertIndex = root->getNumSubItems();
|
||||||
|
pos = root->getItemPosition (true).getBottomLeft();
|
||||||
|
pos.x += view.getIndentSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Point<int> pos;
|
||||||
|
TreeViewItem* item;
|
||||||
|
int insertIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class TreeView::InsertPointHighlight final : public Component
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InsertPointHighlight()
|
||||||
|
{
|
||||||
|
setSize (100, 12);
|
||||||
|
setAlwaysOnTop (true);
|
||||||
|
setInterceptsMouseClicks (false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTargetPosition (const InsertPoint& insertPos, const int width) noexcept
|
||||||
|
{
|
||||||
|
lastItem = insertPos.item;
|
||||||
|
lastIndex = insertPos.insertIndex;
|
||||||
|
auto offset = getHeight() / 2;
|
||||||
|
setBounds (insertPos.pos.x - offset, insertPos.pos.y - offset,
|
||||||
|
width - (insertPos.pos.x - offset), getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint (Graphics& g) override
|
||||||
|
{
|
||||||
|
Path p;
|
||||||
|
auto h = (float) getHeight();
|
||||||
|
p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f);
|
||||||
|
p.startNewSubPath (h - 2.0f, h / 2.0f);
|
||||||
|
p.lineTo ((float) getWidth(), h / 2.0f);
|
||||||
|
|
||||||
|
g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
|
||||||
|
g.strokePath (p, PathStrokeType (2.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeViewItem* lastItem = nullptr;
|
||||||
|
int lastIndex = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (InsertPointHighlight)
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class TreeView::TargetGroupHighlight final : public Component
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TargetGroupHighlight()
|
||||||
|
{
|
||||||
|
setAlwaysOnTop (true);
|
||||||
|
setInterceptsMouseClicks (false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTargetPosition (TreeViewItem* const item) noexcept
|
||||||
|
{
|
||||||
|
setBounds (item->getItemPosition (true)
|
||||||
|
.withHeight (item->getItemHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint (Graphics& g) override
|
||||||
|
{
|
||||||
|
g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
|
||||||
|
g.drawRoundedRectangle (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 3.0f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JUCE_DECLARE_NON_COPYABLE (TargetGroupHighlight)
|
||||||
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
TreeView::TreeView (const String& name) : Component (name)
|
TreeView::TreeView (const String& name) : Component (name)
|
||||||
{
|
{
|
||||||
|
|
@ -1234,139 +1367,6 @@ void TreeView::updateVisibleItems (std::optional<Point<int>> viewportPosition)
|
||||||
viewport->recalculatePositions (TreeViewport::Async::yes, std::move (viewportPosition));
|
viewport->recalculatePositions (TreeViewport::Async::yes, std::move (viewportPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
struct TreeView::InsertPoint
|
|
||||||
{
|
|
||||||
InsertPoint (TreeView& view, const StringArray& files,
|
|
||||||
const DragAndDropTarget::SourceDetails& dragSourceDetails)
|
|
||||||
: pos (dragSourceDetails.localPosition),
|
|
||||||
item (view.getItemAt (dragSourceDetails.localPosition.y))
|
|
||||||
{
|
|
||||||
if (item != nullptr)
|
|
||||||
{
|
|
||||||
auto itemPos = item->getItemPosition (true);
|
|
||||||
insertIndex = item->getIndexInParent();
|
|
||||||
auto oldY = pos.y;
|
|
||||||
pos.y = itemPos.getY();
|
|
||||||
|
|
||||||
if (item->getNumSubItems() == 0 || ! item->isOpen())
|
|
||||||
{
|
|
||||||
if (files.size() > 0 ? item->isInterestedInFileDrag (files)
|
|
||||||
: item->isInterestedInDragSource (dragSourceDetails))
|
|
||||||
{
|
|
||||||
// Check if we're trying to drag into an empty group item..
|
|
||||||
if (oldY > itemPos.getY() + itemPos.getHeight() / 4
|
|
||||||
&& oldY < itemPos.getBottom() - itemPos.getHeight() / 4)
|
|
||||||
{
|
|
||||||
insertIndex = 0;
|
|
||||||
pos.x = itemPos.getX() + view.getIndentSize();
|
|
||||||
pos.y = itemPos.getBottom();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldY > itemPos.getCentreY())
|
|
||||||
{
|
|
||||||
pos.y += item->getItemHeight();
|
|
||||||
|
|
||||||
while (item->isLastOfSiblings() && item->getParentItem() != nullptr
|
|
||||||
&& item->getParentItem()->getParentItem() != nullptr)
|
|
||||||
{
|
|
||||||
if (pos.x > itemPos.getX())
|
|
||||||
break;
|
|
||||||
|
|
||||||
item = item->getParentItem();
|
|
||||||
itemPos = item->getItemPosition (true);
|
|
||||||
insertIndex = item->getIndexInParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
++insertIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos.x = itemPos.getX();
|
|
||||||
item = item->getParentItem();
|
|
||||||
}
|
|
||||||
else if (auto* root = view.getRootItem())
|
|
||||||
{
|
|
||||||
// If they're dragging beyond the bottom of the list, then insert at the end of the root item..
|
|
||||||
item = root;
|
|
||||||
insertIndex = root->getNumSubItems();
|
|
||||||
pos = root->getItemPosition (true).getBottomLeft();
|
|
||||||
pos.x += view.getIndentSize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Point<int> pos;
|
|
||||||
TreeViewItem* item;
|
|
||||||
int insertIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
class TreeView::InsertPointHighlight final : public Component
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
InsertPointHighlight()
|
|
||||||
{
|
|
||||||
setSize (100, 12);
|
|
||||||
setAlwaysOnTop (true);
|
|
||||||
setInterceptsMouseClicks (false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTargetPosition (const InsertPoint& insertPos, const int width) noexcept
|
|
||||||
{
|
|
||||||
lastItem = insertPos.item;
|
|
||||||
lastIndex = insertPos.insertIndex;
|
|
||||||
auto offset = getHeight() / 2;
|
|
||||||
setBounds (insertPos.pos.x - offset, insertPos.pos.y - offset,
|
|
||||||
width - (insertPos.pos.x - offset), getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
void paint (Graphics& g) override
|
|
||||||
{
|
|
||||||
Path p;
|
|
||||||
auto h = (float) getHeight();
|
|
||||||
p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f);
|
|
||||||
p.startNewSubPath (h - 2.0f, h / 2.0f);
|
|
||||||
p.lineTo ((float) getWidth(), h / 2.0f);
|
|
||||||
|
|
||||||
g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
|
|
||||||
g.strokePath (p, PathStrokeType (2.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
TreeViewItem* lastItem = nullptr;
|
|
||||||
int lastIndex = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
JUCE_DECLARE_NON_COPYABLE (InsertPointHighlight)
|
|
||||||
};
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
class TreeView::TargetGroupHighlight final : public Component
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TargetGroupHighlight()
|
|
||||||
{
|
|
||||||
setAlwaysOnTop (true);
|
|
||||||
setInterceptsMouseClicks (false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTargetPosition (TreeViewItem* const item) noexcept
|
|
||||||
{
|
|
||||||
setBounds (item->getItemPosition (true)
|
|
||||||
.withHeight (item->getItemHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void paint (Graphics& g) override
|
|
||||||
{
|
|
||||||
g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
|
|
||||||
g.drawRoundedRectangle (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 3.0f, 2.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
JUCE_DECLARE_NON_COPYABLE (TargetGroupHighlight)
|
|
||||||
};
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void TreeView::showDragHighlight (const InsertPoint& insertPos) noexcept
|
void TreeView::showDragHighlight (const InsertPoint& insertPos) noexcept
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,16 @@
|
||||||
#include "misc/juce_PushNotifications.cpp"
|
#include "misc/juce_PushNotifications.cpp"
|
||||||
#include "misc/juce_RecentlyOpenedFilesList.cpp"
|
#include "misc/juce_RecentlyOpenedFilesList.cpp"
|
||||||
#include "misc/juce_SplashScreen.cpp"
|
#include "misc/juce_SplashScreen.cpp"
|
||||||
|
|
||||||
|
#if JUCE_MAC
|
||||||
|
#include "native/juce_SystemTrayIcon_mac.cpp"
|
||||||
|
#elif JUCE_WINDOWS
|
||||||
|
#include "native/juce_ActiveXComponent_windows.cpp"
|
||||||
|
#include "native/juce_SystemTrayIcon_windows.cpp"
|
||||||
|
#elif JUCE_LINUX
|
||||||
|
#include "native/juce_SystemTrayIcon_linux.cpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "misc/juce_SystemTrayIconComponent.cpp"
|
#include "misc/juce_SystemTrayIconComponent.cpp"
|
||||||
#include "misc/juce_LiveConstantEditor.cpp"
|
#include "misc/juce_LiveConstantEditor.cpp"
|
||||||
#include "misc/juce_AnimatedAppComponent.cpp"
|
#include "misc/juce_AnimatedAppComponent.cpp"
|
||||||
|
|
@ -161,7 +171,6 @@
|
||||||
#include "native/juce_NSViewFrameWatcher_mac.h"
|
#include "native/juce_NSViewFrameWatcher_mac.h"
|
||||||
#include "native/juce_NSViewComponent_mac.mm"
|
#include "native/juce_NSViewComponent_mac.mm"
|
||||||
#include "native/juce_AppleRemote_mac.mm"
|
#include "native/juce_AppleRemote_mac.mm"
|
||||||
#include "native/juce_SystemTrayIcon_mac.cpp"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JUCE_IOS
|
#if JUCE_IOS
|
||||||
|
|
@ -174,12 +183,10 @@
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#elif JUCE_WINDOWS
|
#elif JUCE_WINDOWS
|
||||||
#include "native/juce_ActiveXComponent_windows.cpp"
|
|
||||||
#include "native/juce_HWNDComponent_windows.cpp"
|
#include "native/juce_HWNDComponent_windows.cpp"
|
||||||
#if JUCE_WEB_BROWSER
|
#if JUCE_WEB_BROWSER
|
||||||
#include "native/juce_WebBrowserComponent_windows.cpp"
|
#include "native/juce_WebBrowserComponent_windows.cpp"
|
||||||
#endif
|
#endif
|
||||||
#include "native/juce_SystemTrayIcon_windows.cpp"
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#elif JUCE_LINUX || JUCE_BSD
|
#elif JUCE_LINUX || JUCE_BSD
|
||||||
|
|
@ -198,8 +205,6 @@
|
||||||
|
|
||||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||||
|
|
||||||
#include "native/juce_SystemTrayIcon_linux.cpp"
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#elif JUCE_ANDROID
|
#elif JUCE_ANDROID
|
||||||
#include "native/juce_AndroidViewComponent.cpp"
|
#include "native/juce_AndroidViewComponent.cpp"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue