1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

Fix C++23 compilation

This commit is contained in:
attila 2024-07-09 18:58:42 +02:00
parent b77249ad52
commit 5ce2fc388e
16 changed files with 817 additions and 760 deletions

View file

@ -36,6 +36,158 @@
#include "jucer_Application.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()
: 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)
{
#if JUCE_MAC || JUCE_LINUX || JUCE_BSD

View file

@ -275,6 +275,89 @@ void SourceCodeEditor::valueTreeRedirected (ValueTree&)
void SourceCodeEditor::codeDocumentTextInserted (const String&, 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,
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()
{
CodeEditorComponent::resized();

View file

@ -58,7 +58,7 @@
#include "juce_audio_devices.h"
#include "audio_io/juce_SampleRateHelpers.cpp"
#include "midi_io/juce_MidiDevices.cpp"
#include "midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp"
//==============================================================================
#if JUCE_MAC || JUCE_IOS
@ -258,6 +258,8 @@ namespace juce
#endif
#include "midi_io/juce_MidiDevices.cpp"
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
namespace juce
{

View file

@ -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

View file

@ -35,74 +35,6 @@
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
{
if (broadcaster != nullptr)

View file

@ -148,247 +148,6 @@ public:
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
{
@ -638,6 +397,248 @@ private:
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)
{
scanFor (format, StringArray());

View file

@ -153,6 +153,14 @@
#include "misc/juce_ConsoleApplication.cpp"
#include "misc/juce_ScopeGuard.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_Socket.cpp"
#include "network/juce_IPAddress.cpp"
@ -197,12 +205,8 @@
#include "native/juce_PlatformTimerListener.h"
//==============================================================================
#if ! JUCE_WINDOWS
#include "native/juce_SharedCode_posix.h"
#include "native/juce_NamedPipe_posix.cpp"
#if ! JUCE_ANDROID || __ANDROID_API__ >= 24
#include "native/juce_IPAddress_posix.h"
#endif
#if ! JUCE_WINDOWS && (! JUCE_ANDROID || __ANDROID_API__ >= 24)
#include "native/juce_IPAddress_posix.h"
#endif
//==============================================================================
@ -218,7 +222,6 @@
//==============================================================================
#elif JUCE_WINDOWS
#include "native/juce_Files_windows.cpp"
#include "native/juce_Network_windows.cpp"
#include "native/juce_Registry_windows.cpp"
#include "native/juce_SystemStats_windows.cpp"

View file

@ -152,7 +152,7 @@ bool File::setAsCurrentWorkingDirectory() const
//==============================================================================
// 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
return 0;

View file

@ -255,8 +255,6 @@ static void handleCrash (int signum)
globalCrashHandler ((void*) (pointer_sized_int) signum);
::kill (getpid(), SIGKILL);
}
int juce_siginterrupt (int sig, int flag);
#endif
void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler)

View file

@ -42,18 +42,6 @@ JUCEApplicationBase* JUCEApplicationBase::appInstance = nullptr;
void* JUCEApplicationBase::iOSCustomDelegate = nullptr;
#endif
JUCEApplicationBase::JUCEApplicationBase()
{
jassert (isStandaloneApp() && appInstance == nullptr);
appInstance = this;
}
JUCEApplicationBase::~JUCEApplicationBase()
{
jassert (appInstance == this);
appInstance = nullptr;
}
void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept
{
appReturnValue = newReturnValue;
@ -154,6 +142,18 @@ bool JUCEApplicationBase::sendCommandLineToPreexistingInstance()
struct JUCEApplicationBase::MultipleInstanceHandler {};
#endif
JUCEApplicationBase::JUCEApplicationBase()
{
jassert (isStandaloneApp() && appInstance == nullptr);
appInstance = this;
}
JUCEApplicationBase::~JUCEApplicationBase()
{
jassert (appInstance == this);
appInstance = nullptr;
}
//==============================================================================
#if JUCE_ANDROID

View file

@ -52,6 +52,7 @@
//==============================================================================
#if JUCE_MAC
#import <QuartzCore/QuartzCore.h>
#include <CoreImage/CIRenderDestination.h>
#include <CoreText/CTFont.h>
#elif JUCE_WINDOWS

View file

@ -172,6 +172,50 @@ private:
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
: componentFlags (0)
@ -1695,50 +1739,6 @@ void Component::paintComponentAndChildren (Graphics& 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)
{
// If sizing a top-level-window and the OS paint message is delivered synchronously

View file

@ -244,6 +244,7 @@
#endif
//==============================================================================
#include "native/accessibility/juce_Accessibility.cpp"
#include "accessibility/juce_AccessibilityHandler.cpp"
#include "application/juce_Application.cpp"
#include "buttons/juce_ArrowButton.cpp"
@ -330,7 +331,6 @@
#include "mouse/juce_MouseInactivityDetector.cpp"
#include "mouse/juce_MouseInputSource.cpp"
#include "mouse/juce_MouseListener.cpp"
#include "native/accessibility/juce_Accessibility.cpp"
#include "native/juce_ScopedDPIAwarenessDisabler.cpp"
#include "positioning/juce_MarkerList.cpp"
#include "positioning/juce_RelativeCoordinate.cpp"

View file

@ -249,6 +249,16 @@ namespace juce
#include "layout/juce_StretchableObjectResizer.h"
#include "layout/juce_TabbedButtonBar.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 "menus/juce_PopupMenu.h"
#include "menus/juce_MenuBarModel.h"
@ -260,7 +270,6 @@ namespace juce
#include "positioning/juce_RelativeCoordinatePositioner.h"
#include "positioning/juce_RelativeParallelogram.h"
#include "positioning/juce_RelativePointPath.h"
#include "drawables/juce_Drawable.h"
#include "drawables/juce_DrawableShape.h"
#include "drawables/juce_DrawableComposite.h"
#include "drawables/juce_DrawableImage.h"
@ -331,15 +340,6 @@ namespace juce
#include "lookandfeel/juce_LookAndFeel_V3.h"
#include "lookandfeel/juce_LookAndFeel_V4.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_GUI_BASICS_INCLUDE_XHEADERS

View file

@ -827,6 +827,139 @@ private:
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)
{
@ -1234,139 +1367,6 @@ void TreeView::updateVisibleItems (std::optional<Point<int>> 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
{

View file

@ -148,6 +148,16 @@
#include "misc/juce_PushNotifications.cpp"
#include "misc/juce_RecentlyOpenedFilesList.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_LiveConstantEditor.cpp"
#include "misc/juce_AnimatedAppComponent.cpp"
@ -161,7 +171,6 @@
#include "native/juce_NSViewFrameWatcher_mac.h"
#include "native/juce_NSViewComponent_mac.mm"
#include "native/juce_AppleRemote_mac.mm"
#include "native/juce_SystemTrayIcon_mac.cpp"
#endif
#if JUCE_IOS
@ -174,12 +183,10 @@
//==============================================================================
#elif JUCE_WINDOWS
#include "native/juce_ActiveXComponent_windows.cpp"
#include "native/juce_HWNDComponent_windows.cpp"
#if JUCE_WEB_BROWSER
#include "native/juce_WebBrowserComponent_windows.cpp"
#endif
#include "native/juce_SystemTrayIcon_windows.cpp"
//==============================================================================
#elif JUCE_LINUX || JUCE_BSD
@ -198,8 +205,6 @@
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#include "native/juce_SystemTrayIcon_linux.cpp"
//==============================================================================
#elif JUCE_ANDROID
#include "native/juce_AndroidViewComponent.cpp"