migrating to the latest JUCE version
This commit is contained in:
@ -1,280 +1,280 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
//==============================================================================
|
||||
class ContentSharer::PrepareImagesThread : private Thread
|
||||
{
|
||||
public:
|
||||
PrepareImagesThread (ContentSharer& cs, const Array<Image>& imagesToUse,
|
||||
ImageFileFormat* imageFileFormatToUse)
|
||||
: Thread ("ContentSharer::PrepareImagesThread"),
|
||||
owner (cs),
|
||||
images (imagesToUse),
|
||||
imageFileFormat (imageFileFormatToUse == nullptr ? new PNGImageFormat()
|
||||
: imageFileFormatToUse),
|
||||
extension (imageFileFormat->getFormatName().toLowerCase())
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
~PrepareImagesThread() override
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
for (const auto& image : images)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
File tempFile = File::createTempFile (extension);
|
||||
|
||||
if (! tempFile.create().wasOk())
|
||||
break;
|
||||
|
||||
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
|
||||
|
||||
if (outputStream == nullptr)
|
||||
break;
|
||||
|
||||
if (imageFileFormat->writeImageToStream (image, *outputStream))
|
||||
owner.temporaryFiles.add (tempFile);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
void finish()
|
||||
{
|
||||
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
|
||||
}
|
||||
|
||||
ContentSharer& owner;
|
||||
const Array<Image> images;
|
||||
std::unique_ptr<ImageFileFormat> imageFileFormat;
|
||||
String extension;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ContentSharer::PrepareDataThread : private Thread
|
||||
{
|
||||
public:
|
||||
PrepareDataThread (ContentSharer& cs, const MemoryBlock& mb)
|
||||
: Thread ("ContentSharer::PrepareDataThread"),
|
||||
owner (cs),
|
||||
data (mb)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
~PrepareDataThread() override
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
File tempFile = File::createTempFile ("data");
|
||||
|
||||
if (tempFile.create().wasOk())
|
||||
{
|
||||
if (auto outputStream = std::unique_ptr<FileOutputStream> (tempFile.createOutputStream()))
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t totalSize = data.getSize();
|
||||
|
||||
while (pos < totalSize)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
|
||||
|
||||
outputStream->write (data.begin() + pos, numToWrite);
|
||||
|
||||
pos += numToWrite;
|
||||
}
|
||||
|
||||
owner.temporaryFiles.add (tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
void finish()
|
||||
{
|
||||
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
|
||||
}
|
||||
|
||||
ContentSharer& owner;
|
||||
const MemoryBlock data;
|
||||
};
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
JUCE_IMPLEMENT_SINGLETON (ContentSharer)
|
||||
|
||||
ContentSharer::ContentSharer() {}
|
||||
ContentSharer::~ContentSharer() { clearSingletonInstance(); }
|
||||
|
||||
void ContentSharer::shareFiles (const Array<URL>& files,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
pimpl->shareFiles (files);
|
||||
#else
|
||||
ignoreUnused (files);
|
||||
|
||||
// Content sharing is not available on this platform!
|
||||
jassertfalse;
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing is not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
void ContentSharer::startNewShare (std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
// You should not start another sharing operation before the previous one is finished.
|
||||
// Forcibly stopping a previous sharing operation is rarely a good idea!
|
||||
jassert (pimpl == nullptr);
|
||||
pimpl.reset();
|
||||
|
||||
prepareDataThread = nullptr;
|
||||
prepareImagesThread = nullptr;
|
||||
|
||||
deleteTemporaryFiles();
|
||||
|
||||
// You need to pass a valid callback.
|
||||
jassert (callbackToUse);
|
||||
callback = std::move (callbackToUse);
|
||||
|
||||
pimpl.reset (createPimpl());
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContentSharer::shareText (const String& text,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
pimpl->shareText (text);
|
||||
#else
|
||||
ignoreUnused (text);
|
||||
|
||||
// Content sharing is not available on this platform!
|
||||
jassertfalse;
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing is not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ContentSharer::shareImages (const Array<Image>& images,
|
||||
std::function<void (bool, const String&)> callbackToUse,
|
||||
ImageFileFormat* imageFileFormatToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
prepareImagesThread.reset (new PrepareImagesThread (*this, images, imageFileFormatToUse));
|
||||
#else
|
||||
ignoreUnused (images, imageFileFormatToUse);
|
||||
|
||||
// Content sharing is not available on this platform!
|
||||
jassertfalse;
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing is not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
void ContentSharer::filesToSharePrepared()
|
||||
{
|
||||
Array<URL> urls;
|
||||
|
||||
for (const auto& tempFile : temporaryFiles)
|
||||
urls.add (URL (tempFile));
|
||||
|
||||
prepareImagesThread = nullptr;
|
||||
prepareDataThread = nullptr;
|
||||
|
||||
pimpl->shareFiles (urls);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContentSharer::shareData (const MemoryBlock& mb,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
prepareDataThread.reset (new PrepareDataThread (*this, mb));
|
||||
#else
|
||||
ignoreUnused (mb);
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ContentSharer::sharingFinished (bool succeeded, const String& errorDescription)
|
||||
{
|
||||
deleteTemporaryFiles();
|
||||
|
||||
std::function<void (bool, String)> cb;
|
||||
std::swap (cb, callback);
|
||||
|
||||
String error (errorDescription);
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
pimpl.reset();
|
||||
#endif
|
||||
|
||||
if (cb)
|
||||
cb (succeeded, error);
|
||||
}
|
||||
|
||||
void ContentSharer::deleteTemporaryFiles()
|
||||
{
|
||||
for (auto& f : temporaryFiles)
|
||||
f.deleteFile();
|
||||
|
||||
temporaryFiles.clear();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
//==============================================================================
|
||||
class ContentSharer::PrepareImagesThread : private Thread
|
||||
{
|
||||
public:
|
||||
PrepareImagesThread (ContentSharer& cs, const Array<Image>& imagesToUse,
|
||||
ImageFileFormat* imageFileFormatToUse)
|
||||
: Thread ("ContentSharer::PrepareImagesThread"),
|
||||
owner (cs),
|
||||
images (imagesToUse),
|
||||
imageFileFormat (imageFileFormatToUse == nullptr ? new PNGImageFormat()
|
||||
: imageFileFormatToUse),
|
||||
extension (imageFileFormat->getFormatName().toLowerCase())
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
~PrepareImagesThread() override
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
for (const auto& image : images)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
File tempFile = File::createTempFile (extension);
|
||||
|
||||
if (! tempFile.create().wasOk())
|
||||
break;
|
||||
|
||||
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
|
||||
|
||||
if (outputStream == nullptr)
|
||||
break;
|
||||
|
||||
if (imageFileFormat->writeImageToStream (image, *outputStream))
|
||||
owner.temporaryFiles.add (tempFile);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
void finish()
|
||||
{
|
||||
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
|
||||
}
|
||||
|
||||
ContentSharer& owner;
|
||||
const Array<Image> images;
|
||||
std::unique_ptr<ImageFileFormat> imageFileFormat;
|
||||
String extension;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ContentSharer::PrepareDataThread : private Thread
|
||||
{
|
||||
public:
|
||||
PrepareDataThread (ContentSharer& cs, const MemoryBlock& mb)
|
||||
: Thread ("ContentSharer::PrepareDataThread"),
|
||||
owner (cs),
|
||||
data (mb)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
~PrepareDataThread() override
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
File tempFile = File::createTempFile ("data");
|
||||
|
||||
if (tempFile.create().wasOk())
|
||||
{
|
||||
if (auto outputStream = std::unique_ptr<FileOutputStream> (tempFile.createOutputStream()))
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t totalSize = data.getSize();
|
||||
|
||||
while (pos < totalSize)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
|
||||
|
||||
outputStream->write (data.begin() + pos, numToWrite);
|
||||
|
||||
pos += numToWrite;
|
||||
}
|
||||
|
||||
owner.temporaryFiles.add (tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
void finish()
|
||||
{
|
||||
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
|
||||
}
|
||||
|
||||
ContentSharer& owner;
|
||||
const MemoryBlock data;
|
||||
};
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
JUCE_IMPLEMENT_SINGLETON (ContentSharer)
|
||||
|
||||
ContentSharer::ContentSharer() {}
|
||||
ContentSharer::~ContentSharer() { clearSingletonInstance(); }
|
||||
|
||||
void ContentSharer::shareFiles (const Array<URL>& files,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
pimpl->shareFiles (files);
|
||||
#else
|
||||
ignoreUnused (files);
|
||||
|
||||
// Content sharing is not available on this platform!
|
||||
jassertfalse;
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing is not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
void ContentSharer::startNewShare (std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
// You should not start another sharing operation before the previous one is finished.
|
||||
// Forcibly stopping a previous sharing operation is rarely a good idea!
|
||||
jassert (pimpl == nullptr);
|
||||
pimpl.reset();
|
||||
|
||||
prepareDataThread = nullptr;
|
||||
prepareImagesThread = nullptr;
|
||||
|
||||
deleteTemporaryFiles();
|
||||
|
||||
// You need to pass a valid callback.
|
||||
jassert (callbackToUse);
|
||||
callback = std::move (callbackToUse);
|
||||
|
||||
pimpl.reset (createPimpl());
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContentSharer::shareText (const String& text,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
pimpl->shareText (text);
|
||||
#else
|
||||
ignoreUnused (text);
|
||||
|
||||
// Content sharing is not available on this platform!
|
||||
jassertfalse;
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing is not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ContentSharer::shareImages (const Array<Image>& images,
|
||||
std::function<void (bool, const String&)> callbackToUse,
|
||||
ImageFileFormat* imageFileFormatToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
prepareImagesThread.reset (new PrepareImagesThread (*this, images, imageFileFormatToUse));
|
||||
#else
|
||||
ignoreUnused (images, imageFileFormatToUse);
|
||||
|
||||
// Content sharing is not available on this platform!
|
||||
jassertfalse;
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing is not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
void ContentSharer::filesToSharePrepared()
|
||||
{
|
||||
Array<URL> urls;
|
||||
|
||||
for (const auto& tempFile : temporaryFiles)
|
||||
urls.add (URL (tempFile));
|
||||
|
||||
prepareImagesThread = nullptr;
|
||||
prepareDataThread = nullptr;
|
||||
|
||||
pimpl->shareFiles (urls);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContentSharer::shareData (const MemoryBlock& mb,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_CONTENT_SHARING
|
||||
startNewShare (callbackToUse);
|
||||
prepareDataThread.reset (new PrepareDataThread (*this, mb));
|
||||
#else
|
||||
ignoreUnused (mb);
|
||||
|
||||
if (callbackToUse)
|
||||
callbackToUse (false, "Content sharing not available on this platform!");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ContentSharer::sharingFinished (bool succeeded, const String& errorDescription)
|
||||
{
|
||||
deleteTemporaryFiles();
|
||||
|
||||
std::function<void (bool, String)> cb;
|
||||
std::swap (cb, callback);
|
||||
|
||||
String error (errorDescription);
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
pimpl.reset();
|
||||
#endif
|
||||
|
||||
if (cb)
|
||||
cb (succeeded, error);
|
||||
}
|
||||
|
||||
void ContentSharer::deleteTemporaryFiles()
|
||||
{
|
||||
for (auto& f : temporaryFiles)
|
||||
f.deleteFile();
|
||||
|
||||
temporaryFiles.clear();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,143 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** A singleton class responsible for sharing content between apps and devices.
|
||||
|
||||
You can share text, images, files or an arbitrary data block.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ContentSharer : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
JUCE_DECLARE_SINGLETON (ContentSharer, false)
|
||||
|
||||
/** Shares the given files. Each URL should be either a full file path
|
||||
or it should point to a resource within the application bundle. For
|
||||
resources on iOS it should be something like "content/image.png" if you
|
||||
want to specify a file from application bundle located in "content"
|
||||
directory. On Android you should specify only a filename, without an
|
||||
extension.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareFiles (const Array<URL>& files,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback);
|
||||
|
||||
/** Shares the given text.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareText (const String& text,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback);
|
||||
|
||||
/** A convenience function to share an image. This is useful when you have images
|
||||
loaded in memory. The images will be written to temporary files first, so if
|
||||
you have the images in question stored on disk already call shareFiles() instead.
|
||||
By default, images will be saved to PNG files, but you can supply a custom
|
||||
ImageFileFormat to override this. The custom file format will be owned and
|
||||
deleted by the sharer. e.g.
|
||||
|
||||
@code
|
||||
Graphics g (myImage);
|
||||
g.setColour (Colours::green);
|
||||
g.fillEllipse (20, 20, 300, 200);
|
||||
Array<Image> images;
|
||||
images.add (myImage);
|
||||
ContentSharer::getInstance()->shareImages (images, myCallback);
|
||||
@endcode
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareImages (const Array<Image>& images,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback,
|
||||
ImageFileFormat* imageFileFormatToUse = nullptr);
|
||||
|
||||
/** A convenience function to share arbitrary data. The data will be written
|
||||
to a temporary file and then that file will be shared. If you have
|
||||
your data stored on disk already, call shareFiles() instead.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareData (const MemoryBlock& mb,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback);
|
||||
|
||||
private:
|
||||
ContentSharer();
|
||||
~ContentSharer();
|
||||
|
||||
Array<File> temporaryFiles;
|
||||
|
||||
std::function<void (bool, String)> callback;
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() {}
|
||||
virtual void shareFiles (const Array<URL>& files) = 0;
|
||||
virtual void shareText (const String& text) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
Pimpl* createPimpl();
|
||||
|
||||
void startNewShare (std::function<void (bool, const String&)>);
|
||||
|
||||
class ContentSharerNativeImpl;
|
||||
friend class ContentSharerNativeImpl;
|
||||
|
||||
class PrepareImagesThread;
|
||||
friend class PrepareImagesThread;
|
||||
std::unique_ptr<PrepareImagesThread> prepareImagesThread;
|
||||
|
||||
class PrepareDataThread;
|
||||
friend class PrepareDataThread;
|
||||
std::unique_ptr<PrepareDataThread> prepareDataThread;
|
||||
|
||||
void filesToSharePrepared();
|
||||
#endif
|
||||
|
||||
void deleteTemporaryFiles();
|
||||
void sharingFinished (bool, const String&);
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** A singleton class responsible for sharing content between apps and devices.
|
||||
|
||||
You can share text, images, files or an arbitrary data block.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ContentSharer : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
JUCE_DECLARE_SINGLETON (ContentSharer, false)
|
||||
|
||||
/** Shares the given files. Each URL should be either a full file path
|
||||
or it should point to a resource within the application bundle. For
|
||||
resources on iOS it should be something like "content/image.png" if you
|
||||
want to specify a file from application bundle located in "content"
|
||||
directory. On Android you should specify only a filename, without an
|
||||
extension.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareFiles (const Array<URL>& files,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback);
|
||||
|
||||
/** Shares the given text.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareText (const String& text,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback);
|
||||
|
||||
/** A convenience function to share an image. This is useful when you have images
|
||||
loaded in memory. The images will be written to temporary files first, so if
|
||||
you have the images in question stored on disk already call shareFiles() instead.
|
||||
By default, images will be saved to PNG files, but you can supply a custom
|
||||
ImageFileFormat to override this. The custom file format will be owned and
|
||||
deleted by the sharer. e.g.
|
||||
|
||||
@code
|
||||
Graphics g (myImage);
|
||||
g.setColour (Colours::green);
|
||||
g.fillEllipse (20, 20, 300, 200);
|
||||
Array<Image> images;
|
||||
images.add (myImage);
|
||||
ContentSharer::getInstance()->shareImages (images, myCallback);
|
||||
@endcode
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareImages (const Array<Image>& images,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback,
|
||||
ImageFileFormat* imageFileFormatToUse = nullptr);
|
||||
|
||||
/** A convenience function to share arbitrary data. The data will be written
|
||||
to a temporary file and then that file will be shared. If you have
|
||||
your data stored on disk already, call shareFiles() instead.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
*/
|
||||
void shareData (const MemoryBlock& mb,
|
||||
std::function<void (bool /*success*/, const String& /*error*/)> callback);
|
||||
|
||||
private:
|
||||
ContentSharer();
|
||||
~ContentSharer();
|
||||
|
||||
Array<File> temporaryFiles;
|
||||
|
||||
std::function<void (bool, String)> callback;
|
||||
|
||||
#if JUCE_CONTENT_SHARING
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() {}
|
||||
virtual void shareFiles (const Array<URL>& files) = 0;
|
||||
virtual void shareText (const String& text) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
Pimpl* createPimpl();
|
||||
|
||||
void startNewShare (std::function<void (bool, const String&)>);
|
||||
|
||||
class ContentSharerNativeImpl;
|
||||
friend class ContentSharerNativeImpl;
|
||||
|
||||
class PrepareImagesThread;
|
||||
friend class PrepareImagesThread;
|
||||
std::unique_ptr<PrepareImagesThread> prepareImagesThread;
|
||||
|
||||
class PrepareDataThread;
|
||||
friend class PrepareDataThread;
|
||||
std::unique_ptr<PrepareDataThread> prepareDataThread;
|
||||
|
||||
void filesToSharePrepared();
|
||||
#endif
|
||||
|
||||
void deleteTemporaryFiles();
|
||||
void sharingFinished (bool, const String&);
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,70 +1,70 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& l)
|
||||
: directoryContentsList (l)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileBrowserListener::~FileBrowserListener()
|
||||
{
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* l) { listeners.add (l); }
|
||||
void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* l) { listeners.remove (l); }
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendSelectionChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e)
|
||||
{
|
||||
if (directoryContentsList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (file, e); });
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file)
|
||||
{
|
||||
if (directoryContentsList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (file); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& l)
|
||||
: directoryContentsList (l)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileBrowserListener::~FileBrowserListener()
|
||||
{
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* l) { listeners.add (l); }
|
||||
void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* l) { listeners.remove (l); }
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendSelectionChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e)
|
||||
{
|
||||
if (directoryContentsList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (file, e); });
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file)
|
||||
{
|
||||
if (directoryContentsList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (file); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,116 +1,116 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for components that display a list of the files in a directory.
|
||||
|
||||
@see DirectoryContentsList
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DirectoryContentsDisplayComponent for a given list of files. */
|
||||
DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~DirectoryContentsDisplayComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** The list that this component is displaying */
|
||||
DirectoryContentsList& directoryContentsList;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
virtual int getNumSelectedFiles() const = 0;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
virtual File getSelectedFile (int index) const = 0;
|
||||
|
||||
/** Deselects any selected files. */
|
||||
virtual void deselectAllFiles() = 0;
|
||||
|
||||
/** Scrolls this view to the top. */
|
||||
virtual void scrollToTop() = 0;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
virtual void setSelectedFile (const File&) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when files are selected or clicked.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the list.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
highlightColourId = 0x1000540, /**< The colour to use to fill a highlighted row of the list. */
|
||||
textColourId = 0x1000541, /**< The colour for the text. */
|
||||
highlightedTextColourId = 0x1000542 /**< The colour with which to draw the text in highlighted sections. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void sendSelectionChangeMessage();
|
||||
/** @internal */
|
||||
void sendDoubleClickMessage (const File&);
|
||||
/** @internal */
|
||||
void sendMouseClickMessage (const File&, const MouseEvent&);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
ListenerList<FileBrowserListener> listeners;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsDisplayComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for components that display a list of the files in a directory.
|
||||
|
||||
@see DirectoryContentsList
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DirectoryContentsDisplayComponent for a given list of files. */
|
||||
DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~DirectoryContentsDisplayComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** The list that this component is displaying */
|
||||
DirectoryContentsList& directoryContentsList;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
virtual int getNumSelectedFiles() const = 0;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
virtual File getSelectedFile (int index) const = 0;
|
||||
|
||||
/** Deselects any selected files. */
|
||||
virtual void deselectAllFiles() = 0;
|
||||
|
||||
/** Scrolls this view to the top. */
|
||||
virtual void scrollToTop() = 0;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
virtual void setSelectedFile (const File&) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when files are selected or clicked.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the list.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
highlightColourId = 0x1000540, /**< The colour to use to fill a highlighted row of the list. */
|
||||
textColourId = 0x1000541, /**< The colour for the text. */
|
||||
highlightedTextColourId = 0x1000542 /**< The colour with which to draw the text in highlighted sections. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void sendSelectionChangeMessage();
|
||||
/** @internal */
|
||||
void sendDoubleClickMessage (const File&);
|
||||
/** @internal */
|
||||
void sendMouseClickMessage (const File&, const MouseEvent&);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
ListenerList<FileBrowserListener> listeners;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsDisplayComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,275 +1,276 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
DirectoryContentsList::DirectoryContentsList (const FileFilter* f, TimeSliceThread& t)
|
||||
: fileFilter (f), thread (t)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsList::~DirectoryContentsList()
|
||||
{
|
||||
stopSearching();
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setIgnoresHiddenFiles (const bool shouldIgnoreHiddenFiles)
|
||||
{
|
||||
setTypeFlags (shouldIgnoreHiddenFiles ? (fileTypeFlags | File::ignoreHiddenFiles)
|
||||
: (fileTypeFlags & ~File::ignoreHiddenFiles));
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::ignoresHiddenFiles() const
|
||||
{
|
||||
return (fileTypeFlags & File::ignoreHiddenFiles) != 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DirectoryContentsList::setDirectory (const File& directory,
|
||||
const bool includeDirectories,
|
||||
const bool includeFiles)
|
||||
{
|
||||
jassert (includeDirectories || includeFiles); // you have to specify at least one of these!
|
||||
|
||||
if (directory != root)
|
||||
{
|
||||
clear();
|
||||
root = directory;
|
||||
changed();
|
||||
|
||||
// (this forces a refresh when setTypeFlags() is called, rather than triggering two refreshes)
|
||||
fileTypeFlags &= ~(File::findDirectories | File::findFiles);
|
||||
}
|
||||
|
||||
auto newFlags = fileTypeFlags;
|
||||
|
||||
if (includeDirectories) newFlags |= File::findDirectories;
|
||||
else newFlags &= ~File::findDirectories;
|
||||
|
||||
if (includeFiles) newFlags |= File::findFiles;
|
||||
else newFlags &= ~File::findFiles;
|
||||
|
||||
setTypeFlags (newFlags);
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setTypeFlags (const int newFlags)
|
||||
{
|
||||
if (fileTypeFlags != newFlags)
|
||||
{
|
||||
fileTypeFlags = newFlags;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::stopSearching()
|
||||
{
|
||||
shouldStop = true;
|
||||
thread.removeTimeSliceClient (this);
|
||||
fileFindHandle = nullptr;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::clear()
|
||||
{
|
||||
stopSearching();
|
||||
|
||||
if (! files.isEmpty())
|
||||
{
|
||||
files.clear();
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::refresh()
|
||||
{
|
||||
stopSearching();
|
||||
wasEmpty = files.isEmpty();
|
||||
files.clear();
|
||||
|
||||
if (root.isDirectory())
|
||||
{
|
||||
fileFindHandle = std::make_unique<RangedDirectoryIterator> (root, false, "*", fileTypeFlags);
|
||||
shouldStop = false;
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setFileFilter (const FileFilter* newFileFilter)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
fileFilter = newFileFilter;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::getNumFiles() const noexcept
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
return files.size();
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (auto* info = files [index])
|
||||
{
|
||||
result = *info;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
File DirectoryContentsList::getFile (const int index) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (auto* info = files [index])
|
||||
return root.getChildFile (info->filename);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::contains (const File& targetFile) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (root.getChildFile (files.getUnchecked(i)->filename) == targetFile)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::isStillLoading() const
|
||||
{
|
||||
return fileFindHandle != nullptr;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::changed()
|
||||
{
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::useTimeSlice()
|
||||
{
|
||||
auto startTime = Time::getApproximateMillisecondCounter();
|
||||
bool hasChanged = false;
|
||||
|
||||
for (int i = 100; --i >= 0;)
|
||||
{
|
||||
if (! checkNextFile (hasChanged))
|
||||
{
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 500;
|
||||
}
|
||||
|
||||
if (shouldStop || (Time::getApproximateMillisecondCounter() > startTime + 150))
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::checkNextFile (bool& hasChanged)
|
||||
{
|
||||
if (fileFindHandle != nullptr)
|
||||
{
|
||||
if (*fileFindHandle != RangedDirectoryIterator())
|
||||
{
|
||||
const auto entry = *(*fileFindHandle)++;
|
||||
|
||||
if (addFile (entry.getFile(),
|
||||
entry.isDirectory(),
|
||||
entry.getFileSize(),
|
||||
entry.getModificationTime(),
|
||||
entry.getCreationTime(),
|
||||
entry.isReadOnly()))
|
||||
{
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fileFindHandle = nullptr;
|
||||
|
||||
if (! wasEmpty && files.isEmpty())
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool DirectoryContentsList::addFile (const File& file, const bool isDir,
|
||||
const int64 fileSize,
|
||||
Time modTime, Time creationTime,
|
||||
const bool isReadOnly)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (fileFilter == nullptr
|
||||
|| ((! isDir) && fileFilter->isFileSuitable (file))
|
||||
|| (isDir && fileFilter->isDirectorySuitable (file)))
|
||||
{
|
||||
auto info = std::make_unique<FileInfo>();
|
||||
|
||||
info->filename = file.getFileName();
|
||||
info->fileSize = fileSize;
|
||||
info->modificationTime = modTime;
|
||||
info->creationTime = creationTime;
|
||||
info->isDirectory = isDir;
|
||||
info->isReadOnly = isReadOnly;
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (files.getUnchecked(i)->filename == info->filename)
|
||||
return false;
|
||||
|
||||
files.add (std::move (info));
|
||||
|
||||
std::sort (files.begin(), files.end(), [] (const FileInfo* a, const FileInfo* b)
|
||||
{
|
||||
// #if JUCE_WINDOWS
|
||||
if (a->isDirectory != b->isDirectory)
|
||||
return a->isDirectory;
|
||||
// #endif
|
||||
|
||||
return a->filename.compareNatural (b->filename) < 0;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
DirectoryContentsList::DirectoryContentsList (const FileFilter* f, TimeSliceThread& t)
|
||||
: fileFilter (f), thread (t)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsList::~DirectoryContentsList()
|
||||
{
|
||||
stopSearching();
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setIgnoresHiddenFiles (const bool shouldIgnoreHiddenFiles)
|
||||
{
|
||||
setTypeFlags (shouldIgnoreHiddenFiles ? (fileTypeFlags | File::ignoreHiddenFiles)
|
||||
: (fileTypeFlags & ~File::ignoreHiddenFiles));
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::ignoresHiddenFiles() const
|
||||
{
|
||||
return (fileTypeFlags & File::ignoreHiddenFiles) != 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DirectoryContentsList::setDirectory (const File& directory,
|
||||
const bool includeDirectories,
|
||||
const bool includeFiles)
|
||||
{
|
||||
jassert (includeDirectories || includeFiles); // you have to specify at least one of these!
|
||||
|
||||
if (directory != root)
|
||||
{
|
||||
clear();
|
||||
root = directory;
|
||||
changed();
|
||||
|
||||
// (this forces a refresh when setTypeFlags() is called, rather than triggering two refreshes)
|
||||
fileTypeFlags &= ~(File::findDirectories | File::findFiles);
|
||||
}
|
||||
|
||||
auto newFlags = fileTypeFlags;
|
||||
|
||||
if (includeDirectories) newFlags |= File::findDirectories;
|
||||
else newFlags &= ~File::findDirectories;
|
||||
|
||||
if (includeFiles) newFlags |= File::findFiles;
|
||||
else newFlags &= ~File::findFiles;
|
||||
|
||||
setTypeFlags (newFlags);
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setTypeFlags (const int newFlags)
|
||||
{
|
||||
if (fileTypeFlags != newFlags)
|
||||
{
|
||||
fileTypeFlags = newFlags;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::stopSearching()
|
||||
{
|
||||
shouldStop = true;
|
||||
thread.removeTimeSliceClient (this);
|
||||
isSearching = false;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::clear()
|
||||
{
|
||||
stopSearching();
|
||||
|
||||
if (! files.isEmpty())
|
||||
{
|
||||
files.clear();
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::refresh()
|
||||
{
|
||||
stopSearching();
|
||||
wasEmpty = files.isEmpty();
|
||||
files.clear();
|
||||
|
||||
if (root.isDirectory())
|
||||
{
|
||||
fileFindHandle = std::make_unique<RangedDirectoryIterator> (root, false, "*", fileTypeFlags);
|
||||
shouldStop = false;
|
||||
isSearching = true;
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setFileFilter (const FileFilter* newFileFilter)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
fileFilter = newFileFilter;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::getNumFiles() const noexcept
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
return files.size();
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (auto* info = files [index])
|
||||
{
|
||||
result = *info;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
File DirectoryContentsList::getFile (const int index) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (auto* info = files [index])
|
||||
return root.getChildFile (info->filename);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::contains (const File& targetFile) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (root.getChildFile (files.getUnchecked(i)->filename) == targetFile)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::isStillLoading() const
|
||||
{
|
||||
return isSearching;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::changed()
|
||||
{
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::useTimeSlice()
|
||||
{
|
||||
auto startTime = Time::getApproximateMillisecondCounter();
|
||||
bool hasChanged = false;
|
||||
|
||||
for (int i = 100; --i >= 0;)
|
||||
{
|
||||
if (! checkNextFile (hasChanged))
|
||||
{
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 500;
|
||||
}
|
||||
|
||||
if (shouldStop || (Time::getApproximateMillisecondCounter() > startTime + 150))
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::checkNextFile (bool& hasChanged)
|
||||
{
|
||||
if (fileFindHandle != nullptr)
|
||||
{
|
||||
if (*fileFindHandle != RangedDirectoryIterator())
|
||||
{
|
||||
const auto entry = *(*fileFindHandle)++;
|
||||
|
||||
if (addFile (entry.getFile(),
|
||||
entry.isDirectory(),
|
||||
entry.getFileSize(),
|
||||
entry.getModificationTime(),
|
||||
entry.getCreationTime(),
|
||||
entry.isReadOnly()))
|
||||
{
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fileFindHandle = nullptr;
|
||||
isSearching = false;
|
||||
|
||||
if (! wasEmpty && files.isEmpty())
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::addFile (const File& file, const bool isDir,
|
||||
const int64 fileSize,
|
||||
Time modTime, Time creationTime,
|
||||
const bool isReadOnly)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (fileFilter == nullptr
|
||||
|| ((! isDir) && fileFilter->isFileSuitable (file))
|
||||
|| (isDir && fileFilter->isDirectorySuitable (file)))
|
||||
{
|
||||
auto info = std::make_unique<FileInfo>();
|
||||
|
||||
info->filename = file.getFileName();
|
||||
info->fileSize = fileSize;
|
||||
info->modificationTime = modTime;
|
||||
info->creationTime = creationTime;
|
||||
info->isDirectory = isDir;
|
||||
info->isReadOnly = isReadOnly;
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (files.getUnchecked(i)->filename == info->filename)
|
||||
return false;
|
||||
|
||||
files.add (std::move (info));
|
||||
|
||||
std::sort (files.begin(), files.end(), [] (const FileInfo* a, const FileInfo* b)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
if (a->isDirectory != b->isDirectory)
|
||||
return a->isDirectory;
|
||||
#endif
|
||||
|
||||
return a->filename.compareNatural (b->filename) < 0;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,226 +1,226 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class to asynchronously scan for details about the files in a directory.
|
||||
|
||||
This keeps a list of files and some information about them, using a background
|
||||
thread to scan for more files. As files are found, it broadcasts change messages
|
||||
to tell any listeners.
|
||||
|
||||
@see FileListComponent, FileBrowserComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DirectoryContentsList : public ChangeBroadcaster,
|
||||
private TimeSliceClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a directory list.
|
||||
|
||||
To set the directory it should point to, use setDirectory(), which will
|
||||
also start it scanning for files on the background thread.
|
||||
|
||||
When the background thread finds and adds new files to this list, the
|
||||
ChangeBroadcaster class will send a change message, so you can register
|
||||
listeners and update them when the list changes.
|
||||
|
||||
@param fileFilter an optional filter to select which files are
|
||||
included in the list. If this is nullptr, then all files
|
||||
and directories are included. Make sure that the filter
|
||||
doesn't get deleted during the lifetime of this object
|
||||
@param threadToUse a thread object that this list can use
|
||||
to scan for files as a background task. Make sure
|
||||
that the thread you give it has been started, or you
|
||||
won't get any files!
|
||||
*/
|
||||
DirectoryContentsList (const FileFilter* fileFilter,
|
||||
TimeSliceThread& threadToUse);
|
||||
|
||||
/** Destructor. */
|
||||
~DirectoryContentsList() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory that's currently being used. */
|
||||
const File& getDirectory() const noexcept { return root; }
|
||||
|
||||
/** Sets the directory to look in for files.
|
||||
|
||||
If the directory that's passed in is different to the current one, this will
|
||||
also start the background thread scanning it for files.
|
||||
*/
|
||||
void setDirectory (const File& directory,
|
||||
bool includeDirectories,
|
||||
bool includeFiles);
|
||||
|
||||
/** Returns true if this list contains directories.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingDirectories() const noexcept { return (fileTypeFlags & File::findDirectories) != 0; }
|
||||
|
||||
/** Returns true if this list contains files.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingFiles() const noexcept { return (fileTypeFlags & File::findFiles) != 0; }
|
||||
|
||||
/** Clears the list, and stops the thread scanning for files. */
|
||||
void clear();
|
||||
|
||||
/** Clears the list and restarts scanning the directory for files. */
|
||||
void refresh();
|
||||
|
||||
/** True if the background thread hasn't yet finished scanning for files. */
|
||||
bool isStillLoading() const;
|
||||
|
||||
/** Tells the list whether or not to ignore hidden files.
|
||||
By default these are ignored.
|
||||
*/
|
||||
void setIgnoresHiddenFiles (bool shouldIgnoreHiddenFiles);
|
||||
|
||||
/** Returns true if hidden files are ignored.
|
||||
@see setIgnoresHiddenFiles
|
||||
*/
|
||||
bool ignoresHiddenFiles() const;
|
||||
|
||||
/** Replaces the current FileFilter.
|
||||
This can be nullptr to have no filter. The DirectoryContentList does not take
|
||||
ownership of this object - it just keeps a pointer to it, so you must manage its
|
||||
lifetime.
|
||||
Note that this only replaces the filter, it doesn't refresh the list - you'll
|
||||
probably want to call refresh() after calling this.
|
||||
*/
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
//==============================================================================
|
||||
/** Contains cached information about one of the files in a DirectoryContentsList.
|
||||
*/
|
||||
struct FileInfo
|
||||
{
|
||||
//==============================================================================
|
||||
/** The filename.
|
||||
|
||||
This isn't a full pathname, it's just the last part of the path, same as you'd
|
||||
get from File::getFileName().
|
||||
|
||||
To get the full pathname, use DirectoryContentsList::getDirectory().getChildFile (filename).
|
||||
*/
|
||||
String filename;
|
||||
|
||||
/** File size in bytes. */
|
||||
int64 fileSize;
|
||||
|
||||
/** File modification time.
|
||||
As supplied by File::getLastModificationTime().
|
||||
*/
|
||||
Time modificationTime;
|
||||
|
||||
/** File creation time.
|
||||
As supplied by File::getCreationTime().
|
||||
*/
|
||||
Time creationTime;
|
||||
|
||||
/** True if the file is a directory. */
|
||||
bool isDirectory;
|
||||
|
||||
/** True if the file is read-only. */
|
||||
bool isReadOnly;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files currently available in the list.
|
||||
|
||||
The info about one of these files can be retrieved with getFileInfo() or getFile().
|
||||
|
||||
Obviously as the background thread runs and scans the directory for files, this
|
||||
number will change.
|
||||
|
||||
@see getFileInfo, getFile
|
||||
*/
|
||||
int getNumFiles() const noexcept;
|
||||
|
||||
/** Returns the cached information about one of the files in the list.
|
||||
|
||||
If the index is in-range, this will return true and will copy the file's details
|
||||
to the structure that is passed-in.
|
||||
|
||||
If it returns false, then the index wasn't in range, and the structure won't
|
||||
be affected.
|
||||
|
||||
@see getNumFiles, getFile
|
||||
*/
|
||||
bool getFileInfo (int index, FileInfo& resultInfo) const;
|
||||
|
||||
/** Returns one of the files in the list.
|
||||
|
||||
@param index should be less than getNumFiles(). If this is out-of-range, the
|
||||
return value will be a default File() object
|
||||
@see getNumFiles, getFileInfo
|
||||
*/
|
||||
File getFile (int index) const;
|
||||
|
||||
/** Returns the file filter being used.
|
||||
The filter is specified in the constructor.
|
||||
*/
|
||||
const FileFilter* getFilter() const noexcept { return fileFilter; }
|
||||
|
||||
/** Returns true if the list contains the specified file. */
|
||||
bool contains (const File&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
TimeSliceThread& getTimeSliceThread() const noexcept { return thread; }
|
||||
|
||||
private:
|
||||
File root;
|
||||
const FileFilter* fileFilter = nullptr;
|
||||
TimeSliceThread& thread;
|
||||
int fileTypeFlags = File::ignoreHiddenFiles | File::findFiles;
|
||||
|
||||
CriticalSection fileListLock;
|
||||
OwnedArray<FileInfo> files;
|
||||
|
||||
std::unique_ptr<RangedDirectoryIterator> fileFindHandle;
|
||||
std::atomic<bool> shouldStop { true };
|
||||
|
||||
bool wasEmpty = true;
|
||||
|
||||
int useTimeSlice() override;
|
||||
void stopSearching();
|
||||
void changed();
|
||||
bool checkNextFile (bool& hasChanged);
|
||||
bool addFile (const File&, bool isDir, int64 fileSize, Time modTime,
|
||||
Time creationTime, bool isReadOnly);
|
||||
void setTypeFlags (int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsList)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class to asynchronously scan for details about the files in a directory.
|
||||
|
||||
This keeps a list of files and some information about them, using a background
|
||||
thread to scan for more files. As files are found, it broadcasts change messages
|
||||
to tell any listeners.
|
||||
|
||||
@see FileListComponent, FileBrowserComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DirectoryContentsList : public ChangeBroadcaster,
|
||||
private TimeSliceClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a directory list.
|
||||
|
||||
To set the directory it should point to, use setDirectory(), which will
|
||||
also start it scanning for files on the background thread.
|
||||
|
||||
When the background thread finds and adds new files to this list, the
|
||||
ChangeBroadcaster class will send a change message, so you can register
|
||||
listeners and update them when the list changes.
|
||||
|
||||
@param fileFilter an optional filter to select which files are
|
||||
included in the list. If this is nullptr, then all files
|
||||
and directories are included. Make sure that the filter
|
||||
doesn't get deleted during the lifetime of this object
|
||||
@param threadToUse a thread object that this list can use
|
||||
to scan for files as a background task. Make sure
|
||||
that the thread you give it has been started, or you
|
||||
won't get any files!
|
||||
*/
|
||||
DirectoryContentsList (const FileFilter* fileFilter,
|
||||
TimeSliceThread& threadToUse);
|
||||
|
||||
/** Destructor. */
|
||||
~DirectoryContentsList() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory that's currently being used. */
|
||||
const File& getDirectory() const noexcept { return root; }
|
||||
|
||||
/** Sets the directory to look in for files.
|
||||
|
||||
If the directory that's passed in is different to the current one, this will
|
||||
also start the background thread scanning it for files.
|
||||
*/
|
||||
void setDirectory (const File& directory,
|
||||
bool includeDirectories,
|
||||
bool includeFiles);
|
||||
|
||||
/** Returns true if this list contains directories.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingDirectories() const noexcept { return (fileTypeFlags & File::findDirectories) != 0; }
|
||||
|
||||
/** Returns true if this list contains files.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingFiles() const noexcept { return (fileTypeFlags & File::findFiles) != 0; }
|
||||
|
||||
/** Clears the list, and stops the thread scanning for files. */
|
||||
void clear();
|
||||
|
||||
/** Clears the list and restarts scanning the directory for files. */
|
||||
void refresh();
|
||||
|
||||
/** True if the background thread hasn't yet finished scanning for files. */
|
||||
bool isStillLoading() const;
|
||||
|
||||
/** Tells the list whether or not to ignore hidden files.
|
||||
By default these are ignored.
|
||||
*/
|
||||
void setIgnoresHiddenFiles (bool shouldIgnoreHiddenFiles);
|
||||
|
||||
/** Returns true if hidden files are ignored.
|
||||
@see setIgnoresHiddenFiles
|
||||
*/
|
||||
bool ignoresHiddenFiles() const;
|
||||
|
||||
/** Replaces the current FileFilter.
|
||||
This can be nullptr to have no filter. The DirectoryContentList does not take
|
||||
ownership of this object - it just keeps a pointer to it, so you must manage its
|
||||
lifetime.
|
||||
Note that this only replaces the filter, it doesn't refresh the list - you'll
|
||||
probably want to call refresh() after calling this.
|
||||
*/
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
//==============================================================================
|
||||
/** Contains cached information about one of the files in a DirectoryContentsList.
|
||||
*/
|
||||
struct FileInfo
|
||||
{
|
||||
//==============================================================================
|
||||
/** The filename.
|
||||
|
||||
This isn't a full pathname, it's just the last part of the path, same as you'd
|
||||
get from File::getFileName().
|
||||
|
||||
To get the full pathname, use DirectoryContentsList::getDirectory().getChildFile (filename).
|
||||
*/
|
||||
String filename;
|
||||
|
||||
/** File size in bytes. */
|
||||
int64 fileSize;
|
||||
|
||||
/** File modification time.
|
||||
As supplied by File::getLastModificationTime().
|
||||
*/
|
||||
Time modificationTime;
|
||||
|
||||
/** File creation time.
|
||||
As supplied by File::getCreationTime().
|
||||
*/
|
||||
Time creationTime;
|
||||
|
||||
/** True if the file is a directory. */
|
||||
bool isDirectory;
|
||||
|
||||
/** True if the file is read-only. */
|
||||
bool isReadOnly;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files currently available in the list.
|
||||
|
||||
The info about one of these files can be retrieved with getFileInfo() or getFile().
|
||||
|
||||
Obviously as the background thread runs and scans the directory for files, this
|
||||
number will change.
|
||||
|
||||
@see getFileInfo, getFile
|
||||
*/
|
||||
int getNumFiles() const noexcept;
|
||||
|
||||
/** Returns the cached information about one of the files in the list.
|
||||
|
||||
If the index is in-range, this will return true and will copy the file's details
|
||||
to the structure that is passed-in.
|
||||
|
||||
If it returns false, then the index wasn't in range, and the structure won't
|
||||
be affected.
|
||||
|
||||
@see getNumFiles, getFile
|
||||
*/
|
||||
bool getFileInfo (int index, FileInfo& resultInfo) const;
|
||||
|
||||
/** Returns one of the files in the list.
|
||||
|
||||
@param index should be less than getNumFiles(). If this is out-of-range, the
|
||||
return value will be a default File() object
|
||||
@see getNumFiles, getFileInfo
|
||||
*/
|
||||
File getFile (int index) const;
|
||||
|
||||
/** Returns the file filter being used.
|
||||
The filter is specified in the constructor.
|
||||
*/
|
||||
const FileFilter* getFilter() const noexcept { return fileFilter; }
|
||||
|
||||
/** Returns true if the list contains the specified file. */
|
||||
bool contains (const File&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
TimeSliceThread& getTimeSliceThread() const noexcept { return thread; }
|
||||
|
||||
private:
|
||||
File root;
|
||||
const FileFilter* fileFilter = nullptr;
|
||||
TimeSliceThread& thread;
|
||||
int fileTypeFlags = File::ignoreHiddenFiles | File::findFiles;
|
||||
|
||||
CriticalSection fileListLock;
|
||||
OwnedArray<FileInfo> files;
|
||||
|
||||
std::unique_ptr<RangedDirectoryIterator> fileFindHandle;
|
||||
std::atomic<bool> shouldStop { true }, isSearching { false };
|
||||
|
||||
bool wasEmpty = true;
|
||||
|
||||
int useTimeSlice() override;
|
||||
void stopSearching();
|
||||
void changed();
|
||||
bool checkNextFile (bool& hasChanged);
|
||||
bool addFile (const File&, bool isDir, int64 fileSize, Time modTime,
|
||||
Time creationTime, bool isReadOnly);
|
||||
void setTypeFlags (int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsList)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,296 +1,296 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component for browsing and selecting a file or directory to open or save.
|
||||
|
||||
This contains a FileListComponent and adds various boxes and controls for
|
||||
navigating and selecting a file. It can work in different modes so that it can
|
||||
be used for loading or saving a file, or for choosing a directory.
|
||||
|
||||
@see FileChooserDialogBox, FileChooser, FileListComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBrowserComponent : public Component,
|
||||
private FileBrowserListener,
|
||||
private FileFilter,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Various options for the browser.
|
||||
|
||||
A combination of these is passed into the FileBrowserComponent constructor.
|
||||
*/
|
||||
enum FileChooserFlags
|
||||
{
|
||||
openMode = 1, /**< specifies that the component should allow the user to
|
||||
choose an existing file with the intention of opening it. */
|
||||
saveMode = 2, /**< specifies that the component should allow the user to specify
|
||||
the name of a file that will be used to save something. */
|
||||
canSelectFiles = 4, /**< specifies that the user can select files (can be used in
|
||||
conjunction with canSelectDirectories). */
|
||||
canSelectDirectories = 8, /**< specifies that the user can select directories (can be used in
|
||||
conjunction with canSelectFiles). */
|
||||
canSelectMultipleItems = 16, /**< specifies that the user can select multiple items. */
|
||||
useTreeView = 32, /**< specifies that a tree-view should be shown instead of a file list. */
|
||||
filenameBoxIsReadOnly = 64, /**< specifies that the user can't type directly into the filename box. */
|
||||
warnAboutOverwriting = 128, /**< specifies that the dialog should warn about overwriting existing files (if possible). */
|
||||
doNotClearFileNameOnRootChange = 256 /**< specifies that the file name should not be cleared upon root change. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a FileBrowserComponent.
|
||||
|
||||
@param flags A combination of flags from the FileChooserFlags enumeration, used to
|
||||
specify the component's behaviour. The flags must contain either openMode
|
||||
or saveMode, and canSelectFiles and/or canSelectDirectories.
|
||||
@param initialFileOrDirectory The file or directory that should be selected when the component begins.
|
||||
If this is File(), a default directory will be chosen.
|
||||
@param fileFilter an optional filter to use to determine which files are shown.
|
||||
If this is nullptr then all files are displayed. Note that a pointer
|
||||
is kept internally to this object, so make sure that it is not deleted
|
||||
before the FileBrowserComponent object is deleted.
|
||||
@param previewComp an optional preview component that will be used to show previews of
|
||||
files that the user selects
|
||||
*/
|
||||
FileBrowserComponent (int flags,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter,
|
||||
FilePreviewComponent* previewComp);
|
||||
|
||||
/** Destructor. */
|
||||
~FileBrowserComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files that the user has got selected.
|
||||
If multiple select isn't active, this will only be 0 or 1. To get the complete
|
||||
list of files they've chosen, pass an index to getCurrentFile().
|
||||
*/
|
||||
int getNumSelectedFiles() const noexcept;
|
||||
|
||||
/** Returns one of the files that the user has chosen.
|
||||
If the box has multi-select enabled, the index lets you specify which of the files
|
||||
to get - see getNumSelectedFiles() to find out how many files were chosen.
|
||||
@see getHighlightedFile
|
||||
*/
|
||||
File getSelectedFile (int index) const noexcept;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles();
|
||||
|
||||
/** Returns true if the currently selected file(s) are usable.
|
||||
|
||||
This can be used to decide whether the user can press "ok" for the
|
||||
current file. What it does depends on the mode, so for example in an "open"
|
||||
mode, this only returns true if a file has been selected and if it exists.
|
||||
In a "save" mode, a non-existent file would also be valid.
|
||||
*/
|
||||
bool currentFileIsValid() const;
|
||||
|
||||
/** This returns the last item in the view that the user has highlighted.
|
||||
This may be different from getCurrentFile(), which returns the value
|
||||
that is shown in the filename box, and if there are multiple selections,
|
||||
this will only return one of them.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
File getHighlightedFile() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory whose contents are currently being shown in the listbox. */
|
||||
const File& getRoot() const;
|
||||
|
||||
/** Changes the directory that's being shown in the listbox. */
|
||||
void setRoot (const File& newRootDirectory);
|
||||
|
||||
/** Changes the name that is currently shown in the filename box. */
|
||||
void setFileName (const String& newName);
|
||||
|
||||
/** Equivalent to pressing the "up" button to browse the parent directory. */
|
||||
void goUp();
|
||||
|
||||
/** Refreshes the directory that's currently being listed. */
|
||||
void refresh();
|
||||
|
||||
/** Changes the filter that's being used to sift the files. */
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
/** Returns a verb to describe what should happen when the file is accepted.
|
||||
|
||||
E.g. if browsing in "load file" mode, this will be "Open", if in "save file"
|
||||
mode, it'll be "Save", etc.
|
||||
*/
|
||||
virtual String getActionVerb() const;
|
||||
|
||||
/** Returns true if the saveMode flag was set when this component was created. */
|
||||
bool isSaveMode() const noexcept;
|
||||
|
||||
/** Sets the label that will be displayed next to the filename entry box.
|
||||
By default this is just "file", but you might want to change it to something more
|
||||
appropriate for your app.
|
||||
*/
|
||||
void setFilenameBoxLabel (const String& name);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when the user selects and clicks on files.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
/** Returns a platform-specific list of names and paths for some suggested places the user
|
||||
might want to use as root folders.
|
||||
The list returned contains empty strings to indicate section breaks.
|
||||
@see getRoots()
|
||||
*/
|
||||
static void getDefaultRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
various file-browser layout and drawing methods.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
// These return a pointer to an internally cached drawable - make sure you don't keep
|
||||
// a copy of this pointer anywhere, as it may become invalid in the future.
|
||||
virtual const Drawable* getDefaultFolderImage() = 0;
|
||||
virtual const Drawable* getDefaultDocumentFileImage() = 0;
|
||||
|
||||
virtual AttributedString createFileChooserHeaderText (const String& title,
|
||||
const String& instructions) = 0;
|
||||
|
||||
virtual void drawFileBrowserRow (Graphics&, int width, int height,
|
||||
const File& file,
|
||||
const String& filename,
|
||||
Image* optionalIcon,
|
||||
const String& fileSizeDescription,
|
||||
const String& fileTimeDescription,
|
||||
bool isDirectory,
|
||||
bool isItemSelected,
|
||||
int itemIndex,
|
||||
DirectoryContentsDisplayComponent&) = 0;
|
||||
|
||||
virtual Button* createFileBrowserGoUpButton() = 0;
|
||||
|
||||
virtual void layoutFileBrowserComponent (FileBrowserComponent& browserComp,
|
||||
DirectoryContentsDisplayComponent* fileListComponent,
|
||||
FilePreviewComponent* previewComp,
|
||||
ComboBox* currentPathBox,
|
||||
TextEditor* filenameBox,
|
||||
Button* goUpButton) = 0;
|
||||
};
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the FileBrowserComponent.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
currentPathBoxBackgroundColourId = 0x1000640, /**< The colour to use to fill the background of the current path ComboBox. */
|
||||
currentPathBoxTextColourId = 0x1000641, /**< The colour to use for the text of the current path ComboBox. */
|
||||
currentPathBoxArrowColourId = 0x1000642, /**< The colour to use to draw the arrow of the current path ComboBox. */
|
||||
filenameBoxBackgroundColourId = 0x1000643, /**< The colour to use to fill the background of the filename TextEditor. */
|
||||
filenameBoxTextColourId = 0x1000644 /**< The colour to use for the text of the filename TextEditor. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void selectionChanged() override;
|
||||
/** @internal */
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void fileDoubleClicked (const File&) override;
|
||||
/** @internal */
|
||||
void browserRootChanged (const File&) override;
|
||||
/** @internal */
|
||||
bool isFileSuitable (const File&) const override;
|
||||
/** @internal */
|
||||
bool isDirectorySuitable (const File&) const override;
|
||||
/** @internal */
|
||||
FilePreviewComponent* getPreviewComponent() const noexcept;
|
||||
/** @internal */
|
||||
DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept;
|
||||
|
||||
protected:
|
||||
/** Returns a list of names and paths for the default places the user might want to look.
|
||||
|
||||
By default this just calls getDefaultRoots(), but you may want to override it to
|
||||
return a custom list.
|
||||
*/
|
||||
virtual void getRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
/** Updates the items in the dropdown list of recent paths with the values from getRoots(). */
|
||||
void resetRecentPaths();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<DirectoryContentsList> fileList;
|
||||
const FileFilter* fileFilter;
|
||||
|
||||
int flags;
|
||||
File currentRoot;
|
||||
Array<File> chosenFiles;
|
||||
ListenerList<FileBrowserListener> listeners;
|
||||
|
||||
std::unique_ptr<DirectoryContentsDisplayComponent> fileListComponent;
|
||||
FilePreviewComponent* previewComp;
|
||||
ComboBox currentPathBox;
|
||||
TextEditor filenameBox;
|
||||
Label fileLabel;
|
||||
std::unique_ptr<Button> goUpButton;
|
||||
TimeSliceThread thread;
|
||||
bool wasProcessActive;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void timerCallback() override;
|
||||
void sendListenerChangeMessage();
|
||||
bool isFileOrDirSuitable (const File&) const;
|
||||
void updateSelectedPath();
|
||||
void changeFilename();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBrowserComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component for browsing and selecting a file or directory to open or save.
|
||||
|
||||
This contains a FileListComponent and adds various boxes and controls for
|
||||
navigating and selecting a file. It can work in different modes so that it can
|
||||
be used for loading or saving a file, or for choosing a directory.
|
||||
|
||||
@see FileChooserDialogBox, FileChooser, FileListComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBrowserComponent : public Component,
|
||||
private FileBrowserListener,
|
||||
private FileFilter,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Various options for the browser.
|
||||
|
||||
A combination of these is passed into the FileBrowserComponent constructor.
|
||||
*/
|
||||
enum FileChooserFlags
|
||||
{
|
||||
openMode = 1, /**< specifies that the component should allow the user to
|
||||
choose an existing file with the intention of opening it. */
|
||||
saveMode = 2, /**< specifies that the component should allow the user to specify
|
||||
the name of a file that will be used to save something. */
|
||||
canSelectFiles = 4, /**< specifies that the user can select files (can be used in
|
||||
conjunction with canSelectDirectories). */
|
||||
canSelectDirectories = 8, /**< specifies that the user can select directories (can be used in
|
||||
conjunction with canSelectFiles). */
|
||||
canSelectMultipleItems = 16, /**< specifies that the user can select multiple items. */
|
||||
useTreeView = 32, /**< specifies that a tree-view should be shown instead of a file list. */
|
||||
filenameBoxIsReadOnly = 64, /**< specifies that the user can't type directly into the filename box. */
|
||||
warnAboutOverwriting = 128, /**< specifies that the dialog should warn about overwriting existing files (if possible). */
|
||||
doNotClearFileNameOnRootChange = 256 /**< specifies that the file name should not be cleared upon root change. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a FileBrowserComponent.
|
||||
|
||||
@param flags A combination of flags from the FileChooserFlags enumeration, used to
|
||||
specify the component's behaviour. The flags must contain either openMode
|
||||
or saveMode, and canSelectFiles and/or canSelectDirectories.
|
||||
@param initialFileOrDirectory The file or directory that should be selected when the component begins.
|
||||
If this is File(), a default directory will be chosen.
|
||||
@param fileFilter an optional filter to use to determine which files are shown.
|
||||
If this is nullptr then all files are displayed. Note that a pointer
|
||||
is kept internally to this object, so make sure that it is not deleted
|
||||
before the FileBrowserComponent object is deleted.
|
||||
@param previewComp an optional preview component that will be used to show previews of
|
||||
files that the user selects
|
||||
*/
|
||||
FileBrowserComponent (int flags,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter,
|
||||
FilePreviewComponent* previewComp);
|
||||
|
||||
/** Destructor. */
|
||||
~FileBrowserComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files that the user has got selected.
|
||||
If multiple select isn't active, this will only be 0 or 1. To get the complete
|
||||
list of files they've chosen, pass an index to getCurrentFile().
|
||||
*/
|
||||
int getNumSelectedFiles() const noexcept;
|
||||
|
||||
/** Returns one of the files that the user has chosen.
|
||||
If the box has multi-select enabled, the index lets you specify which of the files
|
||||
to get - see getNumSelectedFiles() to find out how many files were chosen.
|
||||
@see getHighlightedFile
|
||||
*/
|
||||
File getSelectedFile (int index) const noexcept;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles();
|
||||
|
||||
/** Returns true if the currently selected file(s) are usable.
|
||||
|
||||
This can be used to decide whether the user can press "ok" for the
|
||||
current file. What it does depends on the mode, so for example in an "open"
|
||||
mode, this only returns true if a file has been selected and if it exists.
|
||||
In a "save" mode, a non-existent file would also be valid.
|
||||
*/
|
||||
bool currentFileIsValid() const;
|
||||
|
||||
/** This returns the last item in the view that the user has highlighted.
|
||||
This may be different from getCurrentFile(), which returns the value
|
||||
that is shown in the filename box, and if there are multiple selections,
|
||||
this will only return one of them.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
File getHighlightedFile() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory whose contents are currently being shown in the listbox. */
|
||||
const File& getRoot() const;
|
||||
|
||||
/** Changes the directory that's being shown in the listbox. */
|
||||
void setRoot (const File& newRootDirectory);
|
||||
|
||||
/** Changes the name that is currently shown in the filename box. */
|
||||
void setFileName (const String& newName);
|
||||
|
||||
/** Equivalent to pressing the "up" button to browse the parent directory. */
|
||||
void goUp();
|
||||
|
||||
/** Refreshes the directory that's currently being listed. */
|
||||
void refresh();
|
||||
|
||||
/** Changes the filter that's being used to sift the files. */
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
/** Returns a verb to describe what should happen when the file is accepted.
|
||||
|
||||
E.g. if browsing in "load file" mode, this will be "Open", if in "save file"
|
||||
mode, it'll be "Save", etc.
|
||||
*/
|
||||
virtual String getActionVerb() const;
|
||||
|
||||
/** Returns true if the saveMode flag was set when this component was created. */
|
||||
bool isSaveMode() const noexcept;
|
||||
|
||||
/** Sets the label that will be displayed next to the filename entry box.
|
||||
By default this is just "file", but you might want to change it to something more
|
||||
appropriate for your app.
|
||||
*/
|
||||
void setFilenameBoxLabel (const String& name);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when the user selects and clicks on files.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
/** Returns a platform-specific list of names and paths for some suggested places the user
|
||||
might want to use as root folders.
|
||||
The list returned contains empty strings to indicate section breaks.
|
||||
@see getRoots()
|
||||
*/
|
||||
static void getDefaultRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
various file-browser layout and drawing methods.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
// These return a pointer to an internally cached drawable - make sure you don't keep
|
||||
// a copy of this pointer anywhere, as it may become invalid in the future.
|
||||
virtual const Drawable* getDefaultFolderImage() = 0;
|
||||
virtual const Drawable* getDefaultDocumentFileImage() = 0;
|
||||
|
||||
virtual AttributedString createFileChooserHeaderText (const String& title,
|
||||
const String& instructions) = 0;
|
||||
|
||||
virtual void drawFileBrowserRow (Graphics&, int width, int height,
|
||||
const File& file,
|
||||
const String& filename,
|
||||
Image* optionalIcon,
|
||||
const String& fileSizeDescription,
|
||||
const String& fileTimeDescription,
|
||||
bool isDirectory,
|
||||
bool isItemSelected,
|
||||
int itemIndex,
|
||||
DirectoryContentsDisplayComponent&) = 0;
|
||||
|
||||
virtual Button* createFileBrowserGoUpButton() = 0;
|
||||
|
||||
virtual void layoutFileBrowserComponent (FileBrowserComponent& browserComp,
|
||||
DirectoryContentsDisplayComponent* fileListComponent,
|
||||
FilePreviewComponent* previewComp,
|
||||
ComboBox* currentPathBox,
|
||||
TextEditor* filenameBox,
|
||||
Button* goUpButton) = 0;
|
||||
};
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the FileBrowserComponent.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
currentPathBoxBackgroundColourId = 0x1000640, /**< The colour to use to fill the background of the current path ComboBox. */
|
||||
currentPathBoxTextColourId = 0x1000641, /**< The colour to use for the text of the current path ComboBox. */
|
||||
currentPathBoxArrowColourId = 0x1000642, /**< The colour to use to draw the arrow of the current path ComboBox. */
|
||||
filenameBoxBackgroundColourId = 0x1000643, /**< The colour to use to fill the background of the filename TextEditor. */
|
||||
filenameBoxTextColourId = 0x1000644 /**< The colour to use for the text of the filename TextEditor. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void selectionChanged() override;
|
||||
/** @internal */
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void fileDoubleClicked (const File&) override;
|
||||
/** @internal */
|
||||
void browserRootChanged (const File&) override;
|
||||
/** @internal */
|
||||
bool isFileSuitable (const File&) const override;
|
||||
/** @internal */
|
||||
bool isDirectorySuitable (const File&) const override;
|
||||
/** @internal */
|
||||
FilePreviewComponent* getPreviewComponent() const noexcept;
|
||||
/** @internal */
|
||||
DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept;
|
||||
|
||||
protected:
|
||||
/** Returns a list of names and paths for the default places the user might want to look.
|
||||
|
||||
By default this just calls getDefaultRoots(), but you may want to override it to
|
||||
return a custom list.
|
||||
*/
|
||||
virtual void getRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
/** Updates the items in the dropdown list of recent paths with the values from getRoots(). */
|
||||
void resetRecentPaths();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<DirectoryContentsList> fileList;
|
||||
const FileFilter* fileFilter;
|
||||
|
||||
int flags;
|
||||
File currentRoot;
|
||||
Array<File> chosenFiles;
|
||||
ListenerList<FileBrowserListener> listeners;
|
||||
|
||||
std::unique_ptr<DirectoryContentsDisplayComponent> fileListComponent;
|
||||
FilePreviewComponent* previewComp;
|
||||
ComboBox currentPathBox;
|
||||
TextEditor filenameBox;
|
||||
Label fileLabel;
|
||||
std::unique_ptr<Button> goUpButton;
|
||||
TimeSliceThread thread;
|
||||
bool wasProcessActive;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void timerCallback() override;
|
||||
void sendListenerChangeMessage();
|
||||
bool isFileOrDirSuitable (const File&) const;
|
||||
void updateSelectedPath();
|
||||
void changeFilename();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBrowserComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,58 +1,58 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A listener for user selection events in a file browser.
|
||||
|
||||
This is used by a FileBrowserComponent or FileListComponent.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~FileBrowserListener();
|
||||
|
||||
//==============================================================================
|
||||
/** Callback when the user selects a different file in the browser. */
|
||||
virtual void selectionChanged() = 0;
|
||||
|
||||
/** Callback when the user clicks on a file in the browser. */
|
||||
virtual void fileClicked (const File& file, const MouseEvent& e) = 0;
|
||||
|
||||
/** Callback when the user double-clicks on a file in the browser. */
|
||||
virtual void fileDoubleClicked (const File& file) = 0;
|
||||
|
||||
/** Callback when the browser's root folder changes. */
|
||||
virtual void browserRootChanged (const File& newRoot) = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A listener for user selection events in a file browser.
|
||||
|
||||
This is used by a FileBrowserComponent or FileListComponent.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~FileBrowserListener();
|
||||
|
||||
//==============================================================================
|
||||
/** Callback when the user selects a different file in the browser. */
|
||||
virtual void selectionChanged() = 0;
|
||||
|
||||
/** Callback when the user clicks on a file in the browser. */
|
||||
virtual void fileClicked (const File& file, const MouseEvent& e) = 0;
|
||||
|
||||
/** Callback when the user double-clicks on a file in the browser. */
|
||||
virtual void fileDoubleClicked (const File& file) = 0;
|
||||
|
||||
/** Callback when the browser's root folder changes. */
|
||||
virtual void browserRootChanged (const File& newRoot) = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,268 +1,277 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class FileChooser::NonNative : public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
NonNative (FileChooser& fileChooser, int flags, FilePreviewComponent* preview)
|
||||
: owner (fileChooser),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
|
||||
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
||||
|
||||
filter (selectsFiles ? owner.filters : String(), selectsDirectories ? "*" : String(), {}),
|
||||
browserComponent (flags, owner.startingFile, &filter, preview),
|
||||
dialogBox (owner.title, {}, browserComponent, warnAboutOverwrite,
|
||||
browserComponent.findColour (AlertWindow::backgroundColourId), owner.parent)
|
||||
{}
|
||||
|
||||
~NonNative() override
|
||||
{
|
||||
dialogBox.exitModalState (0);
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
dialogBox.centreWithDefaultSize (nullptr);
|
||||
dialogBox.enterModalState (true, ModalCallbackFunction::create ([this] (int r) { modalStateFinished (r); }), true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
modalStateFinished (dialogBox.show() ? 1 : 0);
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void modalStateFinished (int returnValue)
|
||||
{
|
||||
Array<URL> result;
|
||||
|
||||
if (returnValue != 0)
|
||||
{
|
||||
for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
|
||||
result.add (URL (browserComponent.getSelectedFile (i)));
|
||||
}
|
||||
|
||||
owner.finished (result);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
bool selectsDirectories, selectsFiles, warnAboutOverwrite;
|
||||
|
||||
WildcardFileFilter filter;
|
||||
FileBrowserComponent browserComponent;
|
||||
FileChooserDialogBox dialogBox;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonNative)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooser::FileChooser (const String& chooserBoxTitle,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& fileFilters,
|
||||
const bool useNativeBox,
|
||||
const bool treatFilePackagesAsDirectories,
|
||||
Component* parentComponentToUse)
|
||||
: title (chooserBoxTitle),
|
||||
filters (fileFilters),
|
||||
startingFile (currentFileOrDirectory),
|
||||
parent (parentComponentToUse),
|
||||
useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
|
||||
treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
|
||||
{
|
||||
#ifndef JUCE_MAC
|
||||
ignoreUnused (treatFilePackagesAsDirs);
|
||||
#endif
|
||||
|
||||
if (! fileFilters.containsNonWhitespaceChars())
|
||||
filters = "*";
|
||||
}
|
||||
|
||||
FileChooser::~FileChooser()
|
||||
{
|
||||
asyncCallback = nullptr;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectDirectories
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::saveMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForDirectory()
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectDirectories,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
|
||||
{
|
||||
FocusRestorer focusRestorer;
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->runModally();
|
||||
|
||||
// ensure that the finished function was invoked
|
||||
jassert (pimpl == nullptr);
|
||||
|
||||
return (results.size() > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
|
||||
FilePreviewComponent* previewComp)
|
||||
{
|
||||
// You must specify a callback when using launchAsync
|
||||
jassert (callback);
|
||||
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassert (asyncCallback == nullptr);
|
||||
|
||||
asyncCallback = std::move (callback);
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->launch();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp)
|
||||
{
|
||||
results.clear();
|
||||
|
||||
// the preview component needs to be the right size before you pass it in here..
|
||||
jassert (previewComp == nullptr || (previewComp->getWidth() > 10
|
||||
&& previewComp->getHeight() > 10));
|
||||
|
||||
if (pimpl != nullptr)
|
||||
{
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassertfalse;
|
||||
pimpl.reset();
|
||||
}
|
||||
|
||||
// You've set the flags for both saveMode and openMode!
|
||||
jassert (! (((flags & FileBrowserComponent::saveMode) != 0)
|
||||
&& ((flags & FileBrowserComponent::openMode) != 0)));
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
const bool selectsFiles = (flags & FileBrowserComponent::canSelectFiles) != 0;
|
||||
const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
|
||||
|
||||
if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
|
||||
#else
|
||||
if (useNativeDialogBox)
|
||||
#endif
|
||||
{
|
||||
return showPlatformDialog (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
return std::make_unique<NonNative> (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
Array<File> FileChooser::getResults() const noexcept
|
||||
{
|
||||
Array<File> files;
|
||||
|
||||
for (auto url : getURLResults())
|
||||
if (url.isLocalFile())
|
||||
files.add (url.getLocalFile());
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
File FileChooser::getResult() const
|
||||
{
|
||||
auto fileResults = getResults();
|
||||
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (fileResults.size() <= 1);
|
||||
|
||||
return fileResults.getFirst();
|
||||
}
|
||||
|
||||
URL FileChooser::getURLResult() const
|
||||
{
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (results.size() <= 1);
|
||||
|
||||
return results.getFirst();
|
||||
}
|
||||
|
||||
void FileChooser::finished (const Array<URL>& asyncResults)
|
||||
{
|
||||
std::function<void (const FileChooser&)> callback;
|
||||
std::swap (callback, asyncCallback);
|
||||
|
||||
results = asyncResults;
|
||||
|
||||
pimpl.reset();
|
||||
|
||||
if (callback)
|
||||
callback (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FilePreviewComponent::FilePreviewComponent() {}
|
||||
FilePreviewComponent::~FilePreviewComponent() {}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class FileChooser::NonNative : public std::enable_shared_from_this<NonNative>,
|
||||
public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
NonNative (FileChooser& fileChooser, int flags, FilePreviewComponent* preview)
|
||||
: owner (fileChooser),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
|
||||
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
||||
|
||||
filter (selectsFiles ? owner.filters : String(), selectsDirectories ? "*" : String(), {}),
|
||||
browserComponent (flags, owner.startingFile, &filter, preview),
|
||||
dialogBox (owner.title, {}, browserComponent, warnAboutOverwrite,
|
||||
browserComponent.findColour (AlertWindow::backgroundColourId), owner.parent)
|
||||
{}
|
||||
|
||||
~NonNative() override
|
||||
{
|
||||
dialogBox.exitModalState (0);
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
dialogBox.centreWithDefaultSize (nullptr);
|
||||
|
||||
const std::weak_ptr<NonNative> ref (shared_from_this());
|
||||
auto* callback = ModalCallbackFunction::create ([ref] (int r)
|
||||
{
|
||||
if (auto locked = ref.lock())
|
||||
locked->modalStateFinished (r);
|
||||
});
|
||||
|
||||
dialogBox.enterModalState (true, callback, true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
modalStateFinished (dialogBox.show() ? 1 : 0);
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void modalStateFinished (int returnValue)
|
||||
{
|
||||
Array<URL> result;
|
||||
|
||||
if (returnValue != 0)
|
||||
{
|
||||
for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
|
||||
result.add (URL (browserComponent.getSelectedFile (i)));
|
||||
}
|
||||
|
||||
owner.finished (result);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
bool selectsDirectories, selectsFiles, warnAboutOverwrite;
|
||||
|
||||
WildcardFileFilter filter;
|
||||
FileBrowserComponent browserComponent;
|
||||
FileChooserDialogBox dialogBox;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonNative)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooser::FileChooser (const String& chooserBoxTitle,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& fileFilters,
|
||||
const bool useNativeBox,
|
||||
const bool treatFilePackagesAsDirectories,
|
||||
Component* parentComponentToUse)
|
||||
: title (chooserBoxTitle),
|
||||
filters (fileFilters),
|
||||
startingFile (currentFileOrDirectory),
|
||||
parent (parentComponentToUse),
|
||||
useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
|
||||
treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
|
||||
{
|
||||
#ifndef JUCE_MAC
|
||||
ignoreUnused (treatFilePackagesAsDirs);
|
||||
#endif
|
||||
|
||||
if (! fileFilters.containsNonWhitespaceChars())
|
||||
filters = "*";
|
||||
}
|
||||
|
||||
FileChooser::~FileChooser()
|
||||
{
|
||||
asyncCallback = nullptr;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectDirectories
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::saveMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForDirectory()
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectDirectories,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
|
||||
{
|
||||
FocusRestorer focusRestorer;
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->runModally();
|
||||
|
||||
// ensure that the finished function was invoked
|
||||
jassert (pimpl == nullptr);
|
||||
|
||||
return (results.size() > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
|
||||
FilePreviewComponent* previewComp)
|
||||
{
|
||||
// You must specify a callback when using launchAsync
|
||||
jassert (callback);
|
||||
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassert (asyncCallback == nullptr);
|
||||
|
||||
asyncCallback = std::move (callback);
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->launch();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp)
|
||||
{
|
||||
results.clear();
|
||||
|
||||
// the preview component needs to be the right size before you pass it in here..
|
||||
jassert (previewComp == nullptr || (previewComp->getWidth() > 10
|
||||
&& previewComp->getHeight() > 10));
|
||||
|
||||
if (pimpl != nullptr)
|
||||
{
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassertfalse;
|
||||
pimpl.reset();
|
||||
}
|
||||
|
||||
// You've set the flags for both saveMode and openMode!
|
||||
jassert (! (((flags & FileBrowserComponent::saveMode) != 0)
|
||||
&& ((flags & FileBrowserComponent::openMode) != 0)));
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
const bool selectsFiles = (flags & FileBrowserComponent::canSelectFiles) != 0;
|
||||
const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
|
||||
|
||||
if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
|
||||
#else
|
||||
if (useNativeDialogBox)
|
||||
#endif
|
||||
{
|
||||
return showPlatformDialog (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
return std::make_unique<NonNative> (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
Array<File> FileChooser::getResults() const noexcept
|
||||
{
|
||||
Array<File> files;
|
||||
|
||||
for (auto url : getURLResults())
|
||||
if (url.isLocalFile())
|
||||
files.add (url.getLocalFile());
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
File FileChooser::getResult() const
|
||||
{
|
||||
auto fileResults = getResults();
|
||||
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (fileResults.size() <= 1);
|
||||
|
||||
return fileResults.getFirst();
|
||||
}
|
||||
|
||||
URL FileChooser::getURLResult() const
|
||||
{
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (results.size() <= 1);
|
||||
|
||||
return results.getFirst();
|
||||
}
|
||||
|
||||
void FileChooser::finished (const Array<URL>& asyncResults)
|
||||
{
|
||||
std::function<void (const FileChooser&)> callback;
|
||||
std::swap (callback, asyncCallback);
|
||||
|
||||
results = asyncResults;
|
||||
|
||||
pimpl.reset();
|
||||
|
||||
if (callback)
|
||||
callback (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FilePreviewComponent::FilePreviewComponent() {}
|
||||
FilePreviewComponent::~FilePreviewComponent() {}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,336 +1,336 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a dialog box to choose a file or directory to load or save.
|
||||
|
||||
@code
|
||||
std::unique_ptr<FileChooser> myChooser;
|
||||
|
||||
void loadMooseFile()
|
||||
{
|
||||
myChooser = std::make_unique<FileChooser> ("Please select the moose you want to load...",
|
||||
File::getSpecialLocation (File::userHomeDirectory),
|
||||
"*.moose");
|
||||
|
||||
auto folderChooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser)
|
||||
{
|
||||
File mooseFile (chooser.getResult());
|
||||
|
||||
loadMoose (mooseFile);
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileChooser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FileChooser.
|
||||
|
||||
After creating one of these, use one of the browseFor... methods to display it.
|
||||
|
||||
@param dialogBoxTitle a text string to display in the dialog box to
|
||||
tell the user what's going on
|
||||
@param initialFileOrDirectory the file or directory that should be selected
|
||||
when the dialog box opens. If this parameter is
|
||||
set to File(), a sensible default directory will
|
||||
be used instead. When using native dialogs, not
|
||||
all platforms will actually select the file. For
|
||||
example, on macOS, when initialFileOrDirectory is
|
||||
a file, only the parent directory of
|
||||
initialFileOrDirectory will be used as the initial
|
||||
directory of the native file chooser.
|
||||
|
||||
Note: On iOS when saving a file, a user will not
|
||||
be able to change a file name, so it may be a good
|
||||
idea to include at least a valid file name in
|
||||
initialFileOrDirectory. When no filename is found,
|
||||
"Untitled" will be used.
|
||||
|
||||
Also, if you pass an already existing file on iOS,
|
||||
that file will be automatically copied to the
|
||||
destination chosen by user and if it can be previewed,
|
||||
its preview will be presented in the dialog too. You
|
||||
will still be able to write into this file copy, since
|
||||
its URL will be returned by getURLResult(). This can be
|
||||
useful when you want to save e.g. an image, so that
|
||||
you can pass a (temporary) file with low quality
|
||||
preview and after the user picks the destination,
|
||||
you can write a high quality image into the copied
|
||||
file. If you create such a temporary file, you need
|
||||
to delete it yourself, once it is not needed anymore.
|
||||
|
||||
@param filePatternsAllowed a set of file patterns to specify which files can be
|
||||
selected - each pattern should be separated by a comma or
|
||||
semi-colon, e.g. "*" or "*.jpg;*.gif". The native MacOS
|
||||
file browser only supports wildcard that specify
|
||||
extensions, so "*.jpg" is OK but "myfilename*" will not
|
||||
work. An empty string means that all files are allowed
|
||||
@param useOSNativeDialogBox if true, then a native dialog box will be used
|
||||
if possible; if false, then a Juce-based
|
||||
browser dialog box will always be used
|
||||
@param treatFilePackagesAsDirectories if true, then the file chooser will allow the
|
||||
selection of files inside packages when
|
||||
invoked on OS X and when using native dialog
|
||||
boxes.
|
||||
@param parentComponent An optional component which should be the parent
|
||||
for the file chooser. If this is a nullptr then the
|
||||
FileChooser will be a top-level window. AUv3s on iOS
|
||||
must specify this parameter as opening a top-level window
|
||||
in an AUv3 is forbidden due to sandbox restrictions.
|
||||
|
||||
@see browseForFileToOpen, browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
FileChooser (const String& dialogBoxTitle,
|
||||
const File& initialFileOrDirectory = File(),
|
||||
const String& filePatternsAllowed = String(),
|
||||
bool useOSNativeDialogBox = true,
|
||||
bool treatFilePackagesAsDirectories = false,
|
||||
Component* parentComponent = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooser();
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Shows a dialog box to choose a file to open.
|
||||
|
||||
This will display the dialog box modally, using an "open file" mode, so that
|
||||
it won't allow non-existent files or directories to be chosen.
|
||||
|
||||
@param previewComponent an optional component to display inside the dialog
|
||||
box to show special info about the files that the user
|
||||
is browsing. The component will not be deleted by this
|
||||
object, so the caller must take care of it.
|
||||
@returns true if the user selected a file, in which case, use the getResult()
|
||||
method to find out what it was. Returns false if they cancelled instead.
|
||||
@see browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Shows a dialog box to choose a file to save.
|
||||
|
||||
This will display the dialog box modally, using an "save file" mode, so it
|
||||
will allow non-existent files to be chosen, but not directories.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true, the dialog box will ask
|
||||
the user if they're sure they want to overwrite a file that already
|
||||
exists
|
||||
@returns true if the user chose a file and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what the file was. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToSave (bool warnAboutOverwritingExistingFiles);
|
||||
|
||||
/** Shows a dialog box to choose a directory.
|
||||
|
||||
This will display the dialog box modally, using an "open directory" mode, so it
|
||||
will only allow directories to be returned, not files.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForFileToSave
|
||||
*/
|
||||
bool browseForDirectory();
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files and directories.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Runs a dialog box for the given set of option flags.
|
||||
The flag values used are those in FileBrowserComponent::FileChooserFlags.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see FileBrowserComponent::FileChooserFlags
|
||||
*/
|
||||
bool showDialog (int flags, FilePreviewComponent* previewComponent);
|
||||
#endif
|
||||
|
||||
/** Use this method to launch the file browser window asynchronously.
|
||||
|
||||
This will create a file browser dialog based on the settings in this
|
||||
structure and will launch it modally, returning immediately.
|
||||
|
||||
You must specify a callback which is called when the file browser is
|
||||
cancelled or a file is selected. To abort the file selection, simply
|
||||
delete the FileChooser object.
|
||||
|
||||
You must ensure that the lifetime of the callback object is longer than
|
||||
the lifetime of the file-chooser.
|
||||
*/
|
||||
void launchAsync (int flags,
|
||||
std::function<void (const FileChooser&)>,
|
||||
FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last file that was chosen by one of the browseFor methods.
|
||||
|
||||
After calling the appropriate browseFor... method, this method lets you
|
||||
find out what file or directory they chose.
|
||||
|
||||
Note that the file returned is only valid if the browse method returned true (i.e.
|
||||
if the user pressed 'ok' rather than cancelling).
|
||||
|
||||
On mobile platforms, the file browser may return a URL instead of a local file.
|
||||
Therefore, on mobile platforms, you should call getURLResult() instead.
|
||||
|
||||
If you're using a multiple-file select, then use the getResults() method instead,
|
||||
to obtain the list of all files chosen.
|
||||
|
||||
@see getURLResult, getResults
|
||||
*/
|
||||
File getResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
On mobile platforms, the file browser may return a URL instead of a local file.
|
||||
Therefore, on mobile platforms, you should call getURLResults() instead.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
@see getURLResults, getResult
|
||||
*/
|
||||
Array<File> getResults() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last document that was chosen by one of the browseFor methods.
|
||||
|
||||
Use this method if you are using the FileChooser on a mobile platform which
|
||||
may return a URL to a remote document. If a local file is chosen then you can
|
||||
convert this file to a JUCE File class via the URL::getLocalFile method.
|
||||
|
||||
Note: On iOS you must use the returned URL object directly (you are also
|
||||
allowed to copy- or move-construct another URL from the returned URL), rather
|
||||
than just storing the path as a String and then creating a new URL from that
|
||||
String. This is because the returned URL contains internally a security
|
||||
bookmark that is required to access the files pointed by it. Then, once you stop
|
||||
dealing with the file pointed by the URL, you should dispose that URL object,
|
||||
so that the security bookmark can be released by the system (only a limited
|
||||
number of such URLs is allowed).
|
||||
|
||||
@see getResult, URL::getLocalFile
|
||||
*/
|
||||
URL getURLResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
Use this method if you are using the FileChooser on a mobile platform which
|
||||
may return a URL to a remote document. If a local file is chosen then you can
|
||||
convert this file to a JUCE File class via the URL::getLocalFile method.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
Note: On iOS you must use the returned URL object directly (you are also
|
||||
allowed to copy- or move-construct another URL from the returned URL), rather
|
||||
than just storing the path as a String and then creating a new URL from that
|
||||
String. This is because the returned URL contains internally a security
|
||||
bookmark that is required to access the files pointed by it. Then, once you stop
|
||||
dealing with the file pointed by the URL, you should dispose that URL object,
|
||||
so that the security bookmark can be released by the system (only a limited
|
||||
number of such URLs is allowed).
|
||||
|
||||
@see getResults, URL::getLocalFile
|
||||
*/
|
||||
const Array<URL>& getURLResults() const noexcept { return results; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns if a native filechooser is currently available on this platform.
|
||||
|
||||
Note: On iOS this will only return true if you have iCloud permissions
|
||||
and code-signing enabled in the Projucer and have added iCloud containers
|
||||
to your app in Apple's online developer portal. Additionally, the user must
|
||||
have installed the iCloud app on their device and used the app at least once.
|
||||
*/
|
||||
static bool isPlatformDialogAvailable();
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
class Native;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String title, filters;
|
||||
File startingFile;
|
||||
Component* parent;
|
||||
Array<URL> results;
|
||||
const bool useNativeDialogBox;
|
||||
const bool treatFilePackagesAsDirs;
|
||||
std::function<void (const FileChooser&)> asyncCallback;
|
||||
|
||||
//==============================================================================
|
||||
void finished (const Array<URL>&);
|
||||
|
||||
//==============================================================================
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() = default;
|
||||
|
||||
virtual void launch() = 0;
|
||||
virtual void runModally() = 0;
|
||||
};
|
||||
|
||||
std::shared_ptr<Pimpl> pimpl;
|
||||
|
||||
//==============================================================================
|
||||
std::shared_ptr<Pimpl> createPimpl (int, FilePreviewComponent*);
|
||||
static std::shared_ptr<Pimpl> showPlatformDialog (FileChooser&, int, FilePreviewComponent*);
|
||||
|
||||
class NonNative;
|
||||
friend class NonNative;
|
||||
friend class Native;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a dialog box to choose a file or directory to load or save.
|
||||
|
||||
@code
|
||||
std::unique_ptr<FileChooser> myChooser;
|
||||
|
||||
void loadMooseFile()
|
||||
{
|
||||
myChooser = std::make_unique<FileChooser> ("Please select the moose you want to load...",
|
||||
File::getSpecialLocation (File::userHomeDirectory),
|
||||
"*.moose");
|
||||
|
||||
auto folderChooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser)
|
||||
{
|
||||
File mooseFile (chooser.getResult());
|
||||
|
||||
loadMoose (mooseFile);
|
||||
});
|
||||
}
|
||||
@endcode
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileChooser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FileChooser.
|
||||
|
||||
After creating one of these, use one of the browseFor... methods to display it.
|
||||
|
||||
@param dialogBoxTitle a text string to display in the dialog box to
|
||||
tell the user what's going on
|
||||
@param initialFileOrDirectory the file or directory that should be selected
|
||||
when the dialog box opens. If this parameter is
|
||||
set to File(), a sensible default directory will
|
||||
be used instead. When using native dialogs, not
|
||||
all platforms will actually select the file. For
|
||||
example, on macOS, when initialFileOrDirectory is
|
||||
a file, only the parent directory of
|
||||
initialFileOrDirectory will be used as the initial
|
||||
directory of the native file chooser.
|
||||
|
||||
Note: On iOS when saving a file, a user will not
|
||||
be able to change a file name, so it may be a good
|
||||
idea to include at least a valid file name in
|
||||
initialFileOrDirectory. When no filename is found,
|
||||
"Untitled" will be used.
|
||||
|
||||
Also, if you pass an already existing file on iOS,
|
||||
that file will be automatically copied to the
|
||||
destination chosen by user and if it can be previewed,
|
||||
its preview will be presented in the dialog too. You
|
||||
will still be able to write into this file copy, since
|
||||
its URL will be returned by getURLResult(). This can be
|
||||
useful when you want to save e.g. an image, so that
|
||||
you can pass a (temporary) file with low quality
|
||||
preview and after the user picks the destination,
|
||||
you can write a high quality image into the copied
|
||||
file. If you create such a temporary file, you need
|
||||
to delete it yourself, once it is not needed anymore.
|
||||
|
||||
@param filePatternsAllowed a set of file patterns to specify which files can be
|
||||
selected - each pattern should be separated by a comma or
|
||||
semi-colon, e.g. "*" or "*.jpg;*.gif". The native MacOS
|
||||
file browser only supports wildcard that specify
|
||||
extensions, so "*.jpg" is OK but "myfilename*" will not
|
||||
work. An empty string means that all files are allowed
|
||||
@param useOSNativeDialogBox if true, then a native dialog box will be used
|
||||
if possible; if false, then a Juce-based
|
||||
browser dialog box will always be used
|
||||
@param treatFilePackagesAsDirectories if true, then the file chooser will allow the
|
||||
selection of files inside packages when
|
||||
invoked on OS X and when using native dialog
|
||||
boxes.
|
||||
@param parentComponent An optional component which should be the parent
|
||||
for the file chooser. If this is a nullptr then the
|
||||
FileChooser will be a top-level window. AUv3s on iOS
|
||||
must specify this parameter as opening a top-level window
|
||||
in an AUv3 is forbidden due to sandbox restrictions.
|
||||
|
||||
@see browseForFileToOpen, browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
FileChooser (const String& dialogBoxTitle,
|
||||
const File& initialFileOrDirectory = File(),
|
||||
const String& filePatternsAllowed = String(),
|
||||
bool useOSNativeDialogBox = true,
|
||||
bool treatFilePackagesAsDirectories = false,
|
||||
Component* parentComponent = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooser();
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Shows a dialog box to choose a file to open.
|
||||
|
||||
This will display the dialog box modally, using an "open file" mode, so that
|
||||
it won't allow non-existent files or directories to be chosen.
|
||||
|
||||
@param previewComponent an optional component to display inside the dialog
|
||||
box to show special info about the files that the user
|
||||
is browsing. The component will not be deleted by this
|
||||
object, so the caller must take care of it.
|
||||
@returns true if the user selected a file, in which case, use the getResult()
|
||||
method to find out what it was. Returns false if they cancelled instead.
|
||||
@see browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Shows a dialog box to choose a file to save.
|
||||
|
||||
This will display the dialog box modally, using an "save file" mode, so it
|
||||
will allow non-existent files to be chosen, but not directories.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true, the dialog box will ask
|
||||
the user if they're sure they want to overwrite a file that already
|
||||
exists
|
||||
@returns true if the user chose a file and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what the file was. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToSave (bool warnAboutOverwritingExistingFiles);
|
||||
|
||||
/** Shows a dialog box to choose a directory.
|
||||
|
||||
This will display the dialog box modally, using an "open directory" mode, so it
|
||||
will only allow directories to be returned, not files.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForFileToSave
|
||||
*/
|
||||
bool browseForDirectory();
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files and directories.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Runs a dialog box for the given set of option flags.
|
||||
The flag values used are those in FileBrowserComponent::FileChooserFlags.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see FileBrowserComponent::FileChooserFlags
|
||||
*/
|
||||
bool showDialog (int flags, FilePreviewComponent* previewComponent);
|
||||
#endif
|
||||
|
||||
/** Use this method to launch the file browser window asynchronously.
|
||||
|
||||
This will create a file browser dialog based on the settings in this
|
||||
structure and will launch it modally, returning immediately.
|
||||
|
||||
You must specify a callback which is called when the file browser is
|
||||
cancelled or a file is selected. To abort the file selection, simply
|
||||
delete the FileChooser object.
|
||||
|
||||
You must ensure that the lifetime of the callback object is longer than
|
||||
the lifetime of the file-chooser.
|
||||
*/
|
||||
void launchAsync (int flags,
|
||||
std::function<void (const FileChooser&)>,
|
||||
FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last file that was chosen by one of the browseFor methods.
|
||||
|
||||
After calling the appropriate browseFor... method, this method lets you
|
||||
find out what file or directory they chose.
|
||||
|
||||
Note that the file returned is only valid if the browse method returned true (i.e.
|
||||
if the user pressed 'ok' rather than cancelling).
|
||||
|
||||
On mobile platforms, the file browser may return a URL instead of a local file.
|
||||
Therefore, on mobile platforms, you should call getURLResult() instead.
|
||||
|
||||
If you're using a multiple-file select, then use the getResults() method instead,
|
||||
to obtain the list of all files chosen.
|
||||
|
||||
@see getURLResult, getResults
|
||||
*/
|
||||
File getResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
On mobile platforms, the file browser may return a URL instead of a local file.
|
||||
Therefore, on mobile platforms, you should call getURLResults() instead.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
@see getURLResults, getResult
|
||||
*/
|
||||
Array<File> getResults() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last document that was chosen by one of the browseFor methods.
|
||||
|
||||
Use this method if you are using the FileChooser on a mobile platform which
|
||||
may return a URL to a remote document. If a local file is chosen then you can
|
||||
convert this file to a JUCE File class via the URL::getLocalFile method.
|
||||
|
||||
Note: On iOS you must use the returned URL object directly (you are also
|
||||
allowed to copy- or move-construct another URL from the returned URL), rather
|
||||
than just storing the path as a String and then creating a new URL from that
|
||||
String. This is because the returned URL contains internally a security
|
||||
bookmark that is required to access the files pointed by it. Then, once you stop
|
||||
dealing with the file pointed by the URL, you should dispose that URL object,
|
||||
so that the security bookmark can be released by the system (only a limited
|
||||
number of such URLs is allowed).
|
||||
|
||||
@see getResult, URL::getLocalFile
|
||||
*/
|
||||
URL getURLResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
Use this method if you are using the FileChooser on a mobile platform which
|
||||
may return a URL to a remote document. If a local file is chosen then you can
|
||||
convert this file to a JUCE File class via the URL::getLocalFile method.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
Note: On iOS you must use the returned URL object directly (you are also
|
||||
allowed to copy- or move-construct another URL from the returned URL), rather
|
||||
than just storing the path as a String and then creating a new URL from that
|
||||
String. This is because the returned URL contains internally a security
|
||||
bookmark that is required to access the files pointed by it. Then, once you stop
|
||||
dealing with the file pointed by the URL, you should dispose that URL object,
|
||||
so that the security bookmark can be released by the system (only a limited
|
||||
number of such URLs is allowed).
|
||||
|
||||
@see getResults, URL::getLocalFile
|
||||
*/
|
||||
const Array<URL>& getURLResults() const noexcept { return results; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns if a native filechooser is currently available on this platform.
|
||||
|
||||
Note: On iOS this will only return true if you have iCloud permissions
|
||||
and code-signing enabled in the Projucer and have added iCloud containers
|
||||
to your app in Apple's online developer portal. Additionally, the user must
|
||||
have installed the iCloud app on their device and used the app at least once.
|
||||
*/
|
||||
static bool isPlatformDialogAvailable();
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
class Native;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String title, filters;
|
||||
File startingFile;
|
||||
Component* parent;
|
||||
Array<URL> results;
|
||||
const bool useNativeDialogBox;
|
||||
const bool treatFilePackagesAsDirs;
|
||||
std::function<void (const FileChooser&)> asyncCallback;
|
||||
|
||||
//==============================================================================
|
||||
void finished (const Array<URL>&);
|
||||
|
||||
//==============================================================================
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() = default;
|
||||
|
||||
virtual void launch() = 0;
|
||||
virtual void runModally() = 0;
|
||||
};
|
||||
|
||||
std::shared_ptr<Pimpl> pimpl;
|
||||
|
||||
//==============================================================================
|
||||
std::shared_ptr<Pimpl> createPimpl (int, FilePreviewComponent*);
|
||||
static std::shared_ptr<Pimpl> showPlatformDialog (FileChooser&, int, FilePreviewComponent*);
|
||||
|
||||
class NonNative;
|
||||
friend class NonNative;
|
||||
friend class Native;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,262 +1,262 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class FileChooserDialogBox::ContentComponent : public Component
|
||||
{
|
||||
public:
|
||||
ContentComponent (const String& name, const String& desc, FileBrowserComponent& chooser)
|
||||
: Component (name),
|
||||
chooserComponent (chooser),
|
||||
okButton (chooser.getActionVerb()),
|
||||
cancelButton (TRANS ("Cancel")),
|
||||
newFolderButton (TRANS ("New Folder")),
|
||||
instructions (desc)
|
||||
{
|
||||
addAndMakeVisible (chooserComponent);
|
||||
|
||||
addAndMakeVisible (okButton);
|
||||
okButton.addShortcut (KeyPress (KeyPress::returnKey));
|
||||
|
||||
addAndMakeVisible (cancelButton);
|
||||
cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
|
||||
|
||||
addChildComponent (newFolderButton);
|
||||
|
||||
setInterceptsMouseClicks (false, true);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
text.draw (g, getLocalBounds().reduced (6)
|
||||
.removeFromTop ((int) text.getHeight()).toFloat());
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
const int buttonHeight = 26;
|
||||
|
||||
auto area = getLocalBounds();
|
||||
|
||||
text.createLayout (getLookAndFeel().createFileChooserHeaderText (getName(), instructions),
|
||||
(float) getWidth() - 12.0f);
|
||||
|
||||
area.removeFromTop (roundToInt (text.getHeight()) + 10);
|
||||
|
||||
chooserComponent.setBounds (area.removeFromTop (area.getHeight() - buttonHeight - 20));
|
||||
auto buttonArea = area.reduced (16, 10);
|
||||
|
||||
okButton.changeWidthToFitText (buttonHeight);
|
||||
okButton.setBounds (buttonArea.removeFromRight (okButton.getWidth() + 16));
|
||||
|
||||
buttonArea.removeFromRight (16);
|
||||
|
||||
cancelButton.changeWidthToFitText (buttonHeight);
|
||||
cancelButton.setBounds (buttonArea.removeFromRight (cancelButton.getWidth()));
|
||||
|
||||
newFolderButton.changeWidthToFitText (buttonHeight);
|
||||
newFolderButton.setBounds (buttonArea.removeFromLeft (newFolderButton.getWidth()));
|
||||
}
|
||||
|
||||
FileBrowserComponent& chooserComponent;
|
||||
TextButton okButton, cancelButton, newFolderButton;
|
||||
String instructions;
|
||||
TextLayout text;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooserDialogBox::FileChooserDialogBox (const String& name,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& chooserComponent,
|
||||
bool shouldWarn,
|
||||
Colour backgroundColour,
|
||||
Component* parentComp)
|
||||
: ResizableWindow (name, backgroundColour, parentComp == nullptr),
|
||||
warnAboutOverwritingExistingFiles (shouldWarn)
|
||||
{
|
||||
content = new ContentComponent (name, instructions, chooserComponent);
|
||||
setContentOwned (content, false);
|
||||
|
||||
setResizable (true, true);
|
||||
setResizeLimits (300, 300, 1200, 1000);
|
||||
|
||||
content->okButton.onClick = [this] { okButtonPressed(); };
|
||||
content->cancelButton.onClick = [this] { closeButtonPressed(); };
|
||||
content->newFolderButton.onClick = [this] { createNewFolder(); };
|
||||
|
||||
content->chooserComponent.addListener (this);
|
||||
|
||||
FileChooserDialogBox::selectionChanged();
|
||||
|
||||
if (parentComp != nullptr)
|
||||
parentComp->addAndMakeVisible (this);
|
||||
else
|
||||
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
}
|
||||
|
||||
FileChooserDialogBox::~FileChooserDialogBox()
|
||||
{
|
||||
content->chooserComponent.removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooserDialogBox::show (int w, int h)
|
||||
{
|
||||
return showAt (-1, -1, w, h);
|
||||
}
|
||||
|
||||
bool FileChooserDialogBox::showAt (int x, int y, int w, int h)
|
||||
{
|
||||
if (w <= 0) w = getDefaultWidth();
|
||||
if (h <= 0) h = 500;
|
||||
|
||||
if (x < 0 || y < 0)
|
||||
centreWithSize (w, h);
|
||||
else
|
||||
setBounds (x, y, w, h);
|
||||
|
||||
const bool ok = (runModalLoop() != 0);
|
||||
setVisible (false);
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooserDialogBox::centreWithDefaultSize (Component* componentToCentreAround)
|
||||
{
|
||||
centreAroundComponent (componentToCentreAround, getDefaultWidth(), 500);
|
||||
}
|
||||
|
||||
int FileChooserDialogBox::getDefaultWidth() const
|
||||
{
|
||||
if (auto* previewComp = content->chooserComponent.getPreviewComponent())
|
||||
return 400 + previewComp->getWidth();
|
||||
|
||||
return 600;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileChooserDialogBox::closeButtonPressed()
|
||||
{
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::selectionChanged()
|
||||
{
|
||||
content->okButton.setEnabled (content->chooserComponent.currentFileIsValid());
|
||||
|
||||
content->newFolderButton.setVisible (content->chooserComponent.isSaveMode()
|
||||
&& content->chooserComponent.getRoot().isDirectory());
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::fileDoubleClicked (const File&)
|
||||
{
|
||||
selectionChanged();
|
||||
content->okButton.triggerClick();
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::fileClicked (const File&, const MouseEvent&) {}
|
||||
void FileChooserDialogBox::browserRootChanged (const File&) {}
|
||||
|
||||
void FileChooserDialogBox::okToOverwriteFileCallback (int result, FileChooserDialogBox* box)
|
||||
{
|
||||
if (result != 0 && box != nullptr)
|
||||
box->exitModalState (1);
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::okButtonPressed()
|
||||
{
|
||||
if (warnAboutOverwritingExistingFiles
|
||||
&& content->chooserComponent.isSaveMode()
|
||||
&& content->chooserComponent.getSelectedFile(0).exists())
|
||||
{
|
||||
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
|
||||
TRANS("File already exists"),
|
||||
TRANS("There's already a file called: FLNM")
|
||||
.replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName())
|
||||
+ "\n\n"
|
||||
+ TRANS("Are you sure you want to overwrite it?"),
|
||||
TRANS("Overwrite"),
|
||||
TRANS("Cancel"),
|
||||
this,
|
||||
ModalCallbackFunction::forComponent (okToOverwriteFileCallback, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
exitModalState (1);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolderCallback (int result, FileChooserDialogBox* box,
|
||||
Component::SafePointer<AlertWindow> alert)
|
||||
{
|
||||
if (result != 0 && alert != nullptr && box != nullptr)
|
||||
{
|
||||
alert->setVisible (false);
|
||||
box->createNewFolderConfirmed (alert->getTextEditorContents ("Folder Name"));
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolder()
|
||||
{
|
||||
auto parent = content->chooserComponent.getRoot();
|
||||
|
||||
if (parent.isDirectory())
|
||||
{
|
||||
auto* aw = new AlertWindow (TRANS("New Folder"),
|
||||
TRANS("Please enter the name for the folder"),
|
||||
MessageBoxIconType::NoIcon, this);
|
||||
|
||||
aw->addTextEditor ("Folder Name", String(), String(), false);
|
||||
aw->addButton (TRANS("Create Folder"), 1, KeyPress (KeyPress::returnKey));
|
||||
aw->addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey));
|
||||
|
||||
aw->enterModalState (true,
|
||||
ModalCallbackFunction::forComponent (createNewFolderCallback, this,
|
||||
Component::SafePointer<AlertWindow> (aw)),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolderConfirmed (const String& nameFromDialog)
|
||||
{
|
||||
auto name = File::createLegalFileName (nameFromDialog);
|
||||
|
||||
if (! name.isEmpty())
|
||||
{
|
||||
auto parent = content->chooserComponent.getRoot();
|
||||
|
||||
if (! parent.getChildFile (name).createDirectory())
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
TRANS ("New Folder"),
|
||||
TRANS ("Couldn't create the folder!"));
|
||||
|
||||
content->chooserComponent.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class FileChooserDialogBox::ContentComponent : public Component
|
||||
{
|
||||
public:
|
||||
ContentComponent (const String& name, const String& desc, FileBrowserComponent& chooser)
|
||||
: Component (name),
|
||||
chooserComponent (chooser),
|
||||
okButton (chooser.getActionVerb()),
|
||||
cancelButton (TRANS ("Cancel")),
|
||||
newFolderButton (TRANS ("New Folder")),
|
||||
instructions (desc)
|
||||
{
|
||||
addAndMakeVisible (chooserComponent);
|
||||
|
||||
addAndMakeVisible (okButton);
|
||||
okButton.addShortcut (KeyPress (KeyPress::returnKey));
|
||||
|
||||
addAndMakeVisible (cancelButton);
|
||||
cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
|
||||
|
||||
addChildComponent (newFolderButton);
|
||||
|
||||
setInterceptsMouseClicks (false, true);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
text.draw (g, getLocalBounds().reduced (6)
|
||||
.removeFromTop ((int) text.getHeight()).toFloat());
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
const int buttonHeight = 26;
|
||||
|
||||
auto area = getLocalBounds();
|
||||
|
||||
text.createLayout (getLookAndFeel().createFileChooserHeaderText (getName(), instructions),
|
||||
(float) getWidth() - 12.0f);
|
||||
|
||||
area.removeFromTop (roundToInt (text.getHeight()) + 10);
|
||||
|
||||
chooserComponent.setBounds (area.removeFromTop (area.getHeight() - buttonHeight - 20));
|
||||
auto buttonArea = area.reduced (16, 10);
|
||||
|
||||
okButton.changeWidthToFitText (buttonHeight);
|
||||
okButton.setBounds (buttonArea.removeFromRight (okButton.getWidth() + 16));
|
||||
|
||||
buttonArea.removeFromRight (16);
|
||||
|
||||
cancelButton.changeWidthToFitText (buttonHeight);
|
||||
cancelButton.setBounds (buttonArea.removeFromRight (cancelButton.getWidth()));
|
||||
|
||||
newFolderButton.changeWidthToFitText (buttonHeight);
|
||||
newFolderButton.setBounds (buttonArea.removeFromLeft (newFolderButton.getWidth()));
|
||||
}
|
||||
|
||||
FileBrowserComponent& chooserComponent;
|
||||
TextButton okButton, cancelButton, newFolderButton;
|
||||
String instructions;
|
||||
TextLayout text;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooserDialogBox::FileChooserDialogBox (const String& name,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& chooserComponent,
|
||||
bool shouldWarn,
|
||||
Colour backgroundColour,
|
||||
Component* parentComp)
|
||||
: ResizableWindow (name, backgroundColour, parentComp == nullptr),
|
||||
warnAboutOverwritingExistingFiles (shouldWarn)
|
||||
{
|
||||
content = new ContentComponent (name, instructions, chooserComponent);
|
||||
setContentOwned (content, false);
|
||||
|
||||
setResizable (true, true);
|
||||
setResizeLimits (300, 300, 1200, 1000);
|
||||
|
||||
content->okButton.onClick = [this] { okButtonPressed(); };
|
||||
content->cancelButton.onClick = [this] { closeButtonPressed(); };
|
||||
content->newFolderButton.onClick = [this] { createNewFolder(); };
|
||||
|
||||
content->chooserComponent.addListener (this);
|
||||
|
||||
FileChooserDialogBox::selectionChanged();
|
||||
|
||||
if (parentComp != nullptr)
|
||||
parentComp->addAndMakeVisible (this);
|
||||
else
|
||||
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
}
|
||||
|
||||
FileChooserDialogBox::~FileChooserDialogBox()
|
||||
{
|
||||
content->chooserComponent.removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooserDialogBox::show (int w, int h)
|
||||
{
|
||||
return showAt (-1, -1, w, h);
|
||||
}
|
||||
|
||||
bool FileChooserDialogBox::showAt (int x, int y, int w, int h)
|
||||
{
|
||||
if (w <= 0) w = getDefaultWidth();
|
||||
if (h <= 0) h = 500;
|
||||
|
||||
if (x < 0 || y < 0)
|
||||
centreWithSize (w, h);
|
||||
else
|
||||
setBounds (x, y, w, h);
|
||||
|
||||
const bool ok = (runModalLoop() != 0);
|
||||
setVisible (false);
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooserDialogBox::centreWithDefaultSize (Component* componentToCentreAround)
|
||||
{
|
||||
centreAroundComponent (componentToCentreAround, getDefaultWidth(), 500);
|
||||
}
|
||||
|
||||
int FileChooserDialogBox::getDefaultWidth() const
|
||||
{
|
||||
if (auto* previewComp = content->chooserComponent.getPreviewComponent())
|
||||
return 400 + previewComp->getWidth();
|
||||
|
||||
return 600;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileChooserDialogBox::closeButtonPressed()
|
||||
{
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::selectionChanged()
|
||||
{
|
||||
content->okButton.setEnabled (content->chooserComponent.currentFileIsValid());
|
||||
|
||||
content->newFolderButton.setVisible (content->chooserComponent.isSaveMode()
|
||||
&& content->chooserComponent.getRoot().isDirectory());
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::fileDoubleClicked (const File&)
|
||||
{
|
||||
selectionChanged();
|
||||
content->okButton.triggerClick();
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::fileClicked (const File&, const MouseEvent&) {}
|
||||
void FileChooserDialogBox::browserRootChanged (const File&) {}
|
||||
|
||||
void FileChooserDialogBox::okToOverwriteFileCallback (int result, FileChooserDialogBox* box)
|
||||
{
|
||||
if (result != 0 && box != nullptr)
|
||||
box->exitModalState (1);
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::okButtonPressed()
|
||||
{
|
||||
if (warnAboutOverwritingExistingFiles
|
||||
&& content->chooserComponent.isSaveMode()
|
||||
&& content->chooserComponent.getSelectedFile(0).exists())
|
||||
{
|
||||
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
|
||||
TRANS("File already exists"),
|
||||
TRANS("There's already a file called: FLNM")
|
||||
.replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName())
|
||||
+ "\n\n"
|
||||
+ TRANS("Are you sure you want to overwrite it?"),
|
||||
TRANS("Overwrite"),
|
||||
TRANS("Cancel"),
|
||||
this,
|
||||
ModalCallbackFunction::forComponent (okToOverwriteFileCallback, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
exitModalState (1);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolderCallback (int result, FileChooserDialogBox* box,
|
||||
Component::SafePointer<AlertWindow> alert)
|
||||
{
|
||||
if (result != 0 && alert != nullptr && box != nullptr)
|
||||
{
|
||||
alert->setVisible (false);
|
||||
box->createNewFolderConfirmed (alert->getTextEditorContents ("Folder Name"));
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolder()
|
||||
{
|
||||
auto parent = content->chooserComponent.getRoot();
|
||||
|
||||
if (parent.isDirectory())
|
||||
{
|
||||
auto* aw = new AlertWindow (TRANS("New Folder"),
|
||||
TRANS("Please enter the name for the folder"),
|
||||
MessageBoxIconType::NoIcon, this);
|
||||
|
||||
aw->addTextEditor ("Folder Name", String(), String(), false);
|
||||
aw->addButton (TRANS("Create Folder"), 1, KeyPress (KeyPress::returnKey));
|
||||
aw->addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey));
|
||||
|
||||
aw->enterModalState (true,
|
||||
ModalCallbackFunction::forComponent (createNewFolderCallback, this,
|
||||
Component::SafePointer<AlertWindow> (aw)),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolderConfirmed (const String& nameFromDialog)
|
||||
{
|
||||
auto name = File::createLegalFileName (nameFromDialog);
|
||||
|
||||
if (! name.isEmpty())
|
||||
{
|
||||
auto parent = content->chooserComponent.getRoot();
|
||||
|
||||
if (! parent.getChildFile (name).createDirectory())
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
TRANS ("New Folder"),
|
||||
TRANS ("Couldn't create the folder!"));
|
||||
|
||||
content->chooserComponent.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,167 +1,167 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A file open/save dialog box.
|
||||
|
||||
This is a Juce-based file dialog box; to use a native file chooser, see the
|
||||
FileChooser class.
|
||||
|
||||
@code
|
||||
{
|
||||
wildcardFilter = std::make_unique<WildcardFileFilter> ("*.foo", String(), "Foo files");
|
||||
|
||||
browser = std::make_unique<FileBrowserComponent> (FileBrowserComponent::canSelectFiles,
|
||||
File(),
|
||||
wildcardFilter.get(),
|
||||
nullptr);
|
||||
|
||||
dialogBox = std::make_unique<FileChooserDialogBox> ("Open some kind of file",
|
||||
"Please choose some kind of file that you want to open...",
|
||||
*browser,
|
||||
false,
|
||||
Colours::lightgrey);
|
||||
|
||||
auto onFileSelected = [this] (int r)
|
||||
{
|
||||
modalStateFinished (r);
|
||||
|
||||
auto selectedFile = browser->getSelectedFile (0);
|
||||
|
||||
...etc...
|
||||
};
|
||||
|
||||
dialogBox->centreWithDefaultSize (nullptr);
|
||||
dialogBox->enterModalState (true,
|
||||
ModalCallbackFunction::create (onFileSelected),
|
||||
true);
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see FileChooser
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileChooserDialogBox : public ResizableWindow,
|
||||
private FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a file chooser box.
|
||||
|
||||
@param title the main title to show at the top of the box
|
||||
@param instructions an optional longer piece of text to show below the title in
|
||||
a smaller font, describing in more detail what's required.
|
||||
@param browserComponent a FileBrowserComponent that will be shown inside this dialog
|
||||
box. Make sure you delete this after (but not before!) the
|
||||
dialog box has been deleted.
|
||||
@param warnAboutOverwritingExistingFiles if true, then the user will be asked to confirm
|
||||
if they try to select a file that already exists. (This
|
||||
flag is only used when saving files)
|
||||
@param backgroundColour the background colour for the top level window
|
||||
@param parentComponent an optional component which should be the parent
|
||||
for the file chooser. If this is a nullptr then the
|
||||
dialog box will be a top-level window. AUv3s on iOS
|
||||
must specify this parameter as opening a top-level window
|
||||
in an AUv3 is forbidden due to sandbox restrictions.
|
||||
|
||||
@see FileBrowserComponent, FilePreviewComponent
|
||||
*/
|
||||
FileChooserDialogBox (const String& title,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& browserComponent,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
Colour backgroundColour,
|
||||
Component* parentComponent = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooserDialogBox() override;
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Displays and runs the dialog box modally.
|
||||
|
||||
This will show the box with the specified size, returning true if the user
|
||||
pressed 'ok', or false if they cancelled.
|
||||
|
||||
Leave the width or height as 0 to use the default size
|
||||
*/
|
||||
bool show (int width = 0, int height = 0);
|
||||
|
||||
/** Displays and runs the dialog box modally.
|
||||
|
||||
This will show the box with the specified size at the specified location,
|
||||
returning true if the user pressed 'ok', or false if they cancelled.
|
||||
|
||||
Leave the width or height as 0 to use the default size.
|
||||
*/
|
||||
bool showAt (int x, int y, int width, int height);
|
||||
#endif
|
||||
|
||||
/** Sets the size of this dialog box to its default and positions it either in the
|
||||
centre of the screen, or centred around a component that is provided.
|
||||
*/
|
||||
void centreWithDefaultSize (Component* componentToCentreAround = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the box.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
titleTextColourId = 0x1000850, /**< The colour to use to draw the box's title. */
|
||||
};
|
||||
|
||||
private:
|
||||
class ContentComponent;
|
||||
ContentComponent* content;
|
||||
const bool warnAboutOverwritingExistingFiles;
|
||||
|
||||
void closeButtonPressed();
|
||||
void selectionChanged() override;
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
void fileDoubleClicked (const File&) override;
|
||||
void browserRootChanged (const File&) override;
|
||||
int getDefaultWidth() const;
|
||||
|
||||
void okButtonPressed();
|
||||
void createNewFolder();
|
||||
void createNewFolderConfirmed (const String& name);
|
||||
|
||||
static void okToOverwriteFileCallback (int result, FileChooserDialogBox*);
|
||||
static void createNewFolderCallback (int result, FileChooserDialogBox*, Component::SafePointer<AlertWindow>);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooserDialogBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A file open/save dialog box.
|
||||
|
||||
This is a Juce-based file dialog box; to use a native file chooser, see the
|
||||
FileChooser class.
|
||||
|
||||
@code
|
||||
{
|
||||
wildcardFilter = std::make_unique<WildcardFileFilter> ("*.foo", String(), "Foo files");
|
||||
|
||||
browser = std::make_unique<FileBrowserComponent> (FileBrowserComponent::canSelectFiles,
|
||||
File(),
|
||||
wildcardFilter.get(),
|
||||
nullptr);
|
||||
|
||||
dialogBox = std::make_unique<FileChooserDialogBox> ("Open some kind of file",
|
||||
"Please choose some kind of file that you want to open...",
|
||||
*browser,
|
||||
false,
|
||||
Colours::lightgrey);
|
||||
|
||||
auto onFileSelected = [this] (int r)
|
||||
{
|
||||
modalStateFinished (r);
|
||||
|
||||
auto selectedFile = browser->getSelectedFile (0);
|
||||
|
||||
...etc...
|
||||
};
|
||||
|
||||
dialogBox->centreWithDefaultSize (nullptr);
|
||||
dialogBox->enterModalState (true,
|
||||
ModalCallbackFunction::create (onFileSelected),
|
||||
true);
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see FileChooser
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileChooserDialogBox : public ResizableWindow,
|
||||
private FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a file chooser box.
|
||||
|
||||
@param title the main title to show at the top of the box
|
||||
@param instructions an optional longer piece of text to show below the title in
|
||||
a smaller font, describing in more detail what's required.
|
||||
@param browserComponent a FileBrowserComponent that will be shown inside this dialog
|
||||
box. Make sure you delete this after (but not before!) the
|
||||
dialog box has been deleted.
|
||||
@param warnAboutOverwritingExistingFiles if true, then the user will be asked to confirm
|
||||
if they try to select a file that already exists. (This
|
||||
flag is only used when saving files)
|
||||
@param backgroundColour the background colour for the top level window
|
||||
@param parentComponent an optional component which should be the parent
|
||||
for the file chooser. If this is a nullptr then the
|
||||
dialog box will be a top-level window. AUv3s on iOS
|
||||
must specify this parameter as opening a top-level window
|
||||
in an AUv3 is forbidden due to sandbox restrictions.
|
||||
|
||||
@see FileBrowserComponent, FilePreviewComponent
|
||||
*/
|
||||
FileChooserDialogBox (const String& title,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& browserComponent,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
Colour backgroundColour,
|
||||
Component* parentComponent = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooserDialogBox() override;
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Displays and runs the dialog box modally.
|
||||
|
||||
This will show the box with the specified size, returning true if the user
|
||||
pressed 'ok', or false if they cancelled.
|
||||
|
||||
Leave the width or height as 0 to use the default size
|
||||
*/
|
||||
bool show (int width = 0, int height = 0);
|
||||
|
||||
/** Displays and runs the dialog box modally.
|
||||
|
||||
This will show the box with the specified size at the specified location,
|
||||
returning true if the user pressed 'ok', or false if they cancelled.
|
||||
|
||||
Leave the width or height as 0 to use the default size.
|
||||
*/
|
||||
bool showAt (int x, int y, int width, int height);
|
||||
#endif
|
||||
|
||||
/** Sets the size of this dialog box to its default and positions it either in the
|
||||
centre of the screen, or centred around a component that is provided.
|
||||
*/
|
||||
void centreWithDefaultSize (Component* componentToCentreAround = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the box.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
titleTextColourId = 0x1000850, /**< The colour to use to draw the box's title. */
|
||||
};
|
||||
|
||||
private:
|
||||
class ContentComponent;
|
||||
ContentComponent* content;
|
||||
const bool warnAboutOverwritingExistingFiles;
|
||||
|
||||
void closeButtonPressed();
|
||||
void selectionChanged() override;
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
void fileDoubleClicked (const File&) override;
|
||||
void browserRootChanged (const File&) override;
|
||||
int getDefaultWidth() const;
|
||||
|
||||
void okButtonPressed();
|
||||
void createNewFolder();
|
||||
void createNewFolderConfirmed (const String& name);
|
||||
|
||||
static void okToOverwriteFileCallback (int result, FileChooserDialogBox*);
|
||||
static void createNewFolderCallback (int result, FileChooserDialogBox*, Component::SafePointer<AlertWindow>);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooserDialogBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,320 +1,280 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
Image juce_createIconForFile (const File& file);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
FileListComponent::FileListComponent (DirectoryContentsList& listToShow)
|
||||
: ListBox ({}, nullptr),
|
||||
DirectoryContentsDisplayComponent (listToShow),
|
||||
lastDirectory (listToShow.getDirectory())
|
||||
{
|
||||
setTitle ("Files");
|
||||
setModel (this);
|
||||
directoryContentsList.addChangeListener (this);
|
||||
}
|
||||
|
||||
FileListComponent::~FileListComponent()
|
||||
{
|
||||
directoryContentsList.removeChangeListener (this);
|
||||
}
|
||||
|
||||
int FileListComponent::getNumSelectedFiles() const
|
||||
{
|
||||
return getNumSelectedRows();
|
||||
}
|
||||
|
||||
File FileListComponent::getSelectedFile (int index) const
|
||||
{
|
||||
return directoryContentsList.getFile (getSelectedRow (index));
|
||||
}
|
||||
|
||||
void FileListComponent::deselectAllFiles()
|
||||
{
|
||||
deselectAllRows();
|
||||
}
|
||||
|
||||
void FileListComponent::scrollToTop()
|
||||
{
|
||||
getVerticalScrollBar().setCurrentRangeStart (0);
|
||||
}
|
||||
|
||||
void FileListComponent::setSelectedFile (const File& f)
|
||||
{
|
||||
for (int i = directoryContentsList.getNumFiles(); --i >= 0;)
|
||||
{
|
||||
if (directoryContentsList.getFile (i) == f)
|
||||
{
|
||||
fileWaitingToBeSelected = File();
|
||||
|
||||
selectRow (i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllRows();
|
||||
fileWaitingToBeSelected = f;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileListComponent::changeListenerCallback (ChangeBroadcaster*)
|
||||
{
|
||||
updateContent();
|
||||
|
||||
if (lastDirectory != directoryContentsList.getDirectory())
|
||||
{
|
||||
fileWaitingToBeSelected = File();
|
||||
lastDirectory = directoryContentsList.getDirectory();
|
||||
deselectAllRows();
|
||||
}
|
||||
|
||||
if (fileWaitingToBeSelected != File())
|
||||
setSelectedFile (fileWaitingToBeSelected);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class FileListComponent::ItemComponent : public Component,
|
||||
private TimeSliceClient,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
ItemComponent (FileListComponent& fc, TimeSliceThread& t)
|
||||
: owner (fc), thread (t)
|
||||
{
|
||||
}
|
||||
|
||||
~ItemComponent() override
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
getLookAndFeel().drawFileBrowserRow (g, getWidth(), getHeight(),
|
||||
file, file.getFileName(),
|
||||
&icon, fileSize, modTime,
|
||||
isDirectory, highlighted,
|
||||
index, owner);
|
||||
}
|
||||
|
||||
bool isInDragToScrollViewport() const noexcept
|
||||
{
|
||||
if (auto* vp = owner.getViewport())
|
||||
return vp->isScrollOnDragEnabled() && (vp->canScrollVertically() || vp->canScrollHorizontally());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
selectRowOnMouseUp = false;
|
||||
isDraggingToScroll = false;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
if (owner.getRowSelectedOnMouseDown() && ! (owner.isRowSelected(index) || isInDragToScrollViewport())) {
|
||||
//performSelection (e, false);
|
||||
owner.selectRowsBasedOnModifierKeys (index, e.mods, true);
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
|
||||
}
|
||||
else
|
||||
selectRowOnMouseUp = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (! isDraggingToScroll)
|
||||
if (auto* vp = owner.getViewport())
|
||||
isDraggingToScroll = vp->isCurrentlyScrollingOnDrag();
|
||||
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent& e) override
|
||||
{
|
||||
if (isEnabled() && selectRowOnMouseUp && ! (/*isDragging || */ isDraggingToScroll)) {
|
||||
owner.selectRowsBasedOnModifierKeys (index, e.mods, true);
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
//performSelection (e, true);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent&) override
|
||||
{
|
||||
owner.sendDoubleClickMessage (file);
|
||||
}
|
||||
|
||||
void update (const File& root, const DirectoryContentsList::FileInfo* fileInfo,
|
||||
int newIndex, bool nowHighlighted)
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
|
||||
if (nowHighlighted != highlighted || newIndex != index)
|
||||
{
|
||||
index = newIndex;
|
||||
highlighted = nowHighlighted;
|
||||
repaint();
|
||||
}
|
||||
|
||||
File newFile;
|
||||
String newFileSize, newModTime;
|
||||
|
||||
if (fileInfo != nullptr)
|
||||
{
|
||||
newFile = root.getChildFile (fileInfo->filename);
|
||||
newFileSize = File::descriptionOfSizeInBytes (fileInfo->fileSize);
|
||||
newModTime = fileInfo->modificationTime.formatted ("%d %b '%y %H:%M");
|
||||
}
|
||||
|
||||
if (newFile != file
|
||||
|| fileSize != newFileSize
|
||||
|| modTime != newModTime)
|
||||
{
|
||||
file = newFile;
|
||||
fileSize = newFileSize;
|
||||
modTime = newModTime;
|
||||
icon = Image();
|
||||
isDirectory = fileInfo != nullptr && fileInfo->isDirectory;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
if (file != File() && icon.isNull() && ! isDirectory)
|
||||
{
|
||||
updateIcon (true);
|
||||
|
||||
if (! icon.isValid())
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
updateIcon (false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileListComponent& owner;
|
||||
TimeSliceThread& thread;
|
||||
File file;
|
||||
String fileSize, modTime;
|
||||
Image icon;
|
||||
int index = 0;
|
||||
bool highlighted = false, isDirectory = false;
|
||||
bool selectRowOnMouseUp = false;
|
||||
bool isDraggingToScroll = false;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return createIgnoredAccessibilityHandler (*this);
|
||||
}
|
||||
|
||||
void updateIcon (const bool onlyUpdateIfCached)
|
||||
{
|
||||
if (icon.isNull())
|
||||
{
|
||||
auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
|
||||
auto im = ImageCache::getFromHashCode (hashCode);
|
||||
|
||||
if (im.isNull() && ! onlyUpdateIfCached)
|
||||
{
|
||||
im = juce_createIconForFile (file);
|
||||
|
||||
if (im.isValid())
|
||||
ImageCache::addImageToCache (im, hashCode);
|
||||
}
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
icon = im;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
int FileListComponent::getNumRows()
|
||||
{
|
||||
return directoryContentsList.getNumFiles();
|
||||
}
|
||||
|
||||
String FileListComponent::getNameForRow (int rowNumber)
|
||||
{
|
||||
return directoryContentsList.getFile (rowNumber).getFileName();
|
||||
}
|
||||
|
||||
void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool)
|
||||
{
|
||||
}
|
||||
|
||||
Component* FileListComponent::refreshComponentForRow (int row, bool isSelected, Component* existingComponentToUpdate)
|
||||
{
|
||||
jassert (existingComponentToUpdate == nullptr || dynamic_cast<ItemComponent*> (existingComponentToUpdate) != nullptr);
|
||||
|
||||
auto comp = static_cast<ItemComponent*> (existingComponentToUpdate);
|
||||
|
||||
if (comp == nullptr)
|
||||
comp = new ItemComponent (*this, directoryContentsList.getTimeSliceThread());
|
||||
|
||||
DirectoryContentsList::FileInfo fileInfo;
|
||||
comp->update (directoryContentsList.getDirectory(),
|
||||
directoryContentsList.getFileInfo (row, fileInfo) ? &fileInfo : nullptr,
|
||||
row, isSelected);
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
void FileListComponent::selectedRowsChanged (int /*lastRowSelected*/)
|
||||
{
|
||||
sendSelectionChangeMessage();
|
||||
}
|
||||
|
||||
void FileListComponent::deleteKeyPressed (int /*currentSelectedRow*/)
|
||||
{
|
||||
}
|
||||
|
||||
void FileListComponent::returnKeyPressed (int currentSelectedRow)
|
||||
{
|
||||
sendDoubleClickMessage (directoryContentsList.getFile (currentSelectedRow));
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
Image juce_createIconForFile (const File& file);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
FileListComponent::FileListComponent (DirectoryContentsList& listToShow)
|
||||
: ListBox ({}, nullptr),
|
||||
DirectoryContentsDisplayComponent (listToShow),
|
||||
lastDirectory (listToShow.getDirectory())
|
||||
{
|
||||
setTitle ("Files");
|
||||
setModel (this);
|
||||
directoryContentsList.addChangeListener (this);
|
||||
}
|
||||
|
||||
FileListComponent::~FileListComponent()
|
||||
{
|
||||
directoryContentsList.removeChangeListener (this);
|
||||
}
|
||||
|
||||
int FileListComponent::getNumSelectedFiles() const
|
||||
{
|
||||
return getNumSelectedRows();
|
||||
}
|
||||
|
||||
File FileListComponent::getSelectedFile (int index) const
|
||||
{
|
||||
return directoryContentsList.getFile (getSelectedRow (index));
|
||||
}
|
||||
|
||||
void FileListComponent::deselectAllFiles()
|
||||
{
|
||||
deselectAllRows();
|
||||
}
|
||||
|
||||
void FileListComponent::scrollToTop()
|
||||
{
|
||||
getVerticalScrollBar().setCurrentRangeStart (0);
|
||||
}
|
||||
|
||||
void FileListComponent::setSelectedFile (const File& f)
|
||||
{
|
||||
for (int i = directoryContentsList.getNumFiles(); --i >= 0;)
|
||||
{
|
||||
if (directoryContentsList.getFile (i) == f)
|
||||
{
|
||||
fileWaitingToBeSelected = File();
|
||||
|
||||
selectRow (i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllRows();
|
||||
fileWaitingToBeSelected = f;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileListComponent::changeListenerCallback (ChangeBroadcaster*)
|
||||
{
|
||||
updateContent();
|
||||
|
||||
if (lastDirectory != directoryContentsList.getDirectory())
|
||||
{
|
||||
fileWaitingToBeSelected = File();
|
||||
lastDirectory = directoryContentsList.getDirectory();
|
||||
deselectAllRows();
|
||||
}
|
||||
|
||||
if (fileWaitingToBeSelected != File())
|
||||
setSelectedFile (fileWaitingToBeSelected);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class FileListComponent::ItemComponent : public Component,
|
||||
private TimeSliceClient,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
ItemComponent (FileListComponent& fc, TimeSliceThread& t)
|
||||
: owner (fc), thread (t)
|
||||
{
|
||||
}
|
||||
|
||||
~ItemComponent() override
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
getLookAndFeel().drawFileBrowserRow (g, getWidth(), getHeight(),
|
||||
file, file.getFileName(),
|
||||
&icon, fileSize, modTime,
|
||||
isDirectory, highlighted,
|
||||
index, owner);
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (index, e.mods, true);
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent&) override
|
||||
{
|
||||
owner.sendDoubleClickMessage (file);
|
||||
}
|
||||
|
||||
void update (const File& root, const DirectoryContentsList::FileInfo* fileInfo,
|
||||
int newIndex, bool nowHighlighted)
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
|
||||
if (nowHighlighted != highlighted || newIndex != index)
|
||||
{
|
||||
index = newIndex;
|
||||
highlighted = nowHighlighted;
|
||||
repaint();
|
||||
}
|
||||
|
||||
File newFile;
|
||||
String newFileSize, newModTime;
|
||||
|
||||
if (fileInfo != nullptr)
|
||||
{
|
||||
newFile = root.getChildFile (fileInfo->filename);
|
||||
newFileSize = File::descriptionOfSizeInBytes (fileInfo->fileSize);
|
||||
newModTime = fileInfo->modificationTime.formatted ("%d %b '%y %H:%M");
|
||||
}
|
||||
|
||||
if (newFile != file
|
||||
|| fileSize != newFileSize
|
||||
|| modTime != newModTime)
|
||||
{
|
||||
file = newFile;
|
||||
fileSize = newFileSize;
|
||||
modTime = newModTime;
|
||||
icon = Image();
|
||||
isDirectory = fileInfo != nullptr && fileInfo->isDirectory;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
if (file != File() && icon.isNull() && ! isDirectory)
|
||||
{
|
||||
updateIcon (true);
|
||||
|
||||
if (! icon.isValid())
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
updateIcon (false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileListComponent& owner;
|
||||
TimeSliceThread& thread;
|
||||
File file;
|
||||
String fileSize, modTime;
|
||||
Image icon;
|
||||
int index = 0;
|
||||
bool highlighted = false, isDirectory = false;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return createIgnoredAccessibilityHandler (*this);
|
||||
}
|
||||
|
||||
void updateIcon (const bool onlyUpdateIfCached)
|
||||
{
|
||||
if (icon.isNull())
|
||||
{
|
||||
auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
|
||||
auto im = ImageCache::getFromHashCode (hashCode);
|
||||
|
||||
if (im.isNull() && ! onlyUpdateIfCached)
|
||||
{
|
||||
im = juce_createIconForFile (file);
|
||||
|
||||
if (im.isValid())
|
||||
ImageCache::addImageToCache (im, hashCode);
|
||||
}
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
icon = im;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
int FileListComponent::getNumRows()
|
||||
{
|
||||
return directoryContentsList.getNumFiles();
|
||||
}
|
||||
|
||||
String FileListComponent::getNameForRow (int rowNumber)
|
||||
{
|
||||
return directoryContentsList.getFile (rowNumber).getFileName();
|
||||
}
|
||||
|
||||
void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool)
|
||||
{
|
||||
}
|
||||
|
||||
Component* FileListComponent::refreshComponentForRow (int row, bool isSelected, Component* existingComponentToUpdate)
|
||||
{
|
||||
jassert (existingComponentToUpdate == nullptr || dynamic_cast<ItemComponent*> (existingComponentToUpdate) != nullptr);
|
||||
|
||||
auto comp = static_cast<ItemComponent*> (existingComponentToUpdate);
|
||||
|
||||
if (comp == nullptr)
|
||||
comp = new ItemComponent (*this, directoryContentsList.getTimeSliceThread());
|
||||
|
||||
DirectoryContentsList::FileInfo fileInfo;
|
||||
comp->update (directoryContentsList.getDirectory(),
|
||||
directoryContentsList.getFileInfo (row, fileInfo) ? &fileInfo : nullptr,
|
||||
row, isSelected);
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
void FileListComponent::selectedRowsChanged (int /*lastRowSelected*/)
|
||||
{
|
||||
sendSelectionChangeMessage();
|
||||
}
|
||||
|
||||
void FileListComponent::deleteKeyPressed (int /*currentSelectedRow*/)
|
||||
{
|
||||
}
|
||||
|
||||
void FileListComponent::returnKeyPressed (int currentSelectedRow)
|
||||
{
|
||||
sendDoubleClickMessage (directoryContentsList.getFile (currentSelectedRow));
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,95 +1,95 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays the files in a directory as a listbox.
|
||||
|
||||
This implements the DirectoryContentsDisplayComponent base class so that
|
||||
it can be used in a FileBrowserComponent.
|
||||
|
||||
To attach a listener to it, use its DirectoryContentsDisplayComponent base
|
||||
class and the FileBrowserListener class.
|
||||
|
||||
@see DirectoryContentsList, FileTreeComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileListComponent : public ListBox,
|
||||
public DirectoryContentsDisplayComponent,
|
||||
private ListBoxModel,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a listbox to show the contents of a specified directory. */
|
||||
FileListComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
~FileListComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
int getNumSelectedFiles() const override;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
File getSelectedFile (int index = 0) const override;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles() override;
|
||||
|
||||
/** Scrolls to the top of the list. */
|
||||
void scrollToTop() override;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
void setSelectedFile (const File&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
File lastDirectory, fileWaitingToBeSelected;
|
||||
class ItemComponent;
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
int getNumRows() override;
|
||||
String getNameForRow (int rowNumber) override;
|
||||
void paintListBoxItem (int, Graphics&, int, int, bool) override;
|
||||
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component*) override;
|
||||
void selectedRowsChanged (int row) override;
|
||||
void deleteKeyPressed (int currentSelectedRow) override;
|
||||
void returnKeyPressed (int currentSelectedRow) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays the files in a directory as a listbox.
|
||||
|
||||
This implements the DirectoryContentsDisplayComponent base class so that
|
||||
it can be used in a FileBrowserComponent.
|
||||
|
||||
To attach a listener to it, use its DirectoryContentsDisplayComponent base
|
||||
class and the FileBrowserListener class.
|
||||
|
||||
@see DirectoryContentsList, FileTreeComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileListComponent : public ListBox,
|
||||
public DirectoryContentsDisplayComponent,
|
||||
private ListBoxModel,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a listbox to show the contents of a specified directory. */
|
||||
FileListComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
~FileListComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
int getNumSelectedFiles() const override;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
File getSelectedFile (int index = 0) const override;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles() override;
|
||||
|
||||
/** Scrolls to the top of the list. */
|
||||
void scrollToTop() override;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
void setSelectedFile (const File&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
File lastDirectory, fileWaitingToBeSelected;
|
||||
class ItemComponent;
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
int getNumRows() override;
|
||||
String getNameForRow (int rowNumber) override;
|
||||
void paintListBoxItem (int, Graphics&, int, int, bool) override;
|
||||
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component*) override;
|
||||
void selectedRowsChanged (int row) override;
|
||||
void deleteKeyPressed (int currentSelectedRow) override;
|
||||
void returnKeyPressed (int currentSelectedRow) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,66 +1,66 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Base class for components that live inside a file chooser dialog box and
|
||||
show previews of the files that get selected.
|
||||
|
||||
One of these allows special extra information to be displayed for files
|
||||
in a dialog box as the user selects them. Each time the current file or
|
||||
directory is changed, the selectedFileChanged() method will be called
|
||||
to allow it to update itself appropriately.
|
||||
|
||||
@see FileChooser, ImagePreviewComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FilePreviewComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FilePreviewComponent. */
|
||||
FilePreviewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~FilePreviewComponent() override;
|
||||
|
||||
/** Called to indicate that the user's currently selected file has changed.
|
||||
|
||||
@param newSelectedFile the newly selected file or directory, which may be
|
||||
a default File() object if none is selected.
|
||||
*/
|
||||
virtual void selectedFileChanged (const File& newSelectedFile) = 0;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePreviewComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Base class for components that live inside a file chooser dialog box and
|
||||
show previews of the files that get selected.
|
||||
|
||||
One of these allows special extra information to be displayed for files
|
||||
in a dialog box as the user selects them. Each time the current file or
|
||||
directory is changed, the selectedFileChanged() method will be called
|
||||
to allow it to update itself appropriately.
|
||||
|
||||
@see FileChooser, ImagePreviewComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FilePreviewComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FilePreviewComponent. */
|
||||
FilePreviewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~FilePreviewComponent() override;
|
||||
|
||||
/** Called to indicate that the user's currently selected file has changed.
|
||||
|
||||
@param newSelectedFile the newly selected file or directory, which may be
|
||||
a default File() object if none is selected.
|
||||
*/
|
||||
virtual void selectedFileChanged (const File& newSelectedFile) = 0;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePreviewComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,275 +1,271 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
FileSearchPathListComponent::FileSearchPathListComponent()
|
||||
: addButton ("+"),
|
||||
removeButton ("-"),
|
||||
changeButton (TRANS ("change...")),
|
||||
upButton ({}, DrawableButton::ImageOnButtonBackground),
|
||||
downButton ({}, DrawableButton::ImageOnButtonBackground)
|
||||
{
|
||||
listBox.setModel (this);
|
||||
addAndMakeVisible (listBox);
|
||||
listBox.setColour (ListBox::backgroundColourId, Colours::black.withAlpha (0.02f));
|
||||
listBox.setColour (ListBox::outlineColourId, Colours::black.withAlpha (0.1f));
|
||||
listBox.setOutlineThickness (1);
|
||||
|
||||
addAndMakeVisible (addButton);
|
||||
addButton.onClick = [this] { addPath(); };
|
||||
addButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
|
||||
|
||||
addAndMakeVisible (removeButton);
|
||||
removeButton.onClick = [this] { deleteSelected(); };
|
||||
removeButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
|
||||
|
||||
addAndMakeVisible (changeButton);
|
||||
changeButton.onClick = [this] { editSelected(); };
|
||||
|
||||
addAndMakeVisible (upButton);
|
||||
upButton.onClick = [this] { moveSelection (-1); };
|
||||
|
||||
auto arrowColour = findColour (ListBox::textColourId);
|
||||
|
||||
{
|
||||
Path arrowPath;
|
||||
arrowPath.addArrow ({ 50.0f, 100.0f, 50.0f, 0.0f }, 40.0f, 100.0f, 50.0f);
|
||||
DrawablePath arrowImage;
|
||||
arrowImage.setFill (arrowColour);
|
||||
arrowImage.setPath (arrowPath);
|
||||
|
||||
upButton.setImages (&arrowImage);
|
||||
}
|
||||
|
||||
addAndMakeVisible (downButton);
|
||||
downButton.onClick = [this] { moveSelection (1); };
|
||||
|
||||
{
|
||||
Path arrowPath;
|
||||
arrowPath.addArrow ({ 50.0f, 0.0f, 50.0f, 100.0f }, 40.0f, 100.0f, 50.0f);
|
||||
DrawablePath arrowImage;
|
||||
arrowImage.setFill (arrowColour);
|
||||
arrowImage.setPath (arrowPath);
|
||||
|
||||
downButton.setImages (&arrowImage);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
FileSearchPathListComponent::~FileSearchPathListComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::updateButtons()
|
||||
{
|
||||
const bool anythingSelected = listBox.getNumSelectedRows() > 0;
|
||||
|
||||
removeButton.setEnabled (anythingSelected);
|
||||
changeButton.setEnabled (anythingSelected);
|
||||
upButton.setEnabled (anythingSelected);
|
||||
downButton.setEnabled (anythingSelected);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::changed()
|
||||
{
|
||||
listBox.updateContent();
|
||||
listBox.repaint();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileSearchPathListComponent::setPath (const FileSearchPath& newPath)
|
||||
{
|
||||
if (newPath.toString() != path.toString())
|
||||
{
|
||||
path = newPath;
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
|
||||
{
|
||||
defaultBrowseTarget = newDefaultDirectory;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int FileSearchPathListComponent::getNumRows()
|
||||
{
|
||||
return path.getNumPaths();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
|
||||
{
|
||||
if (rowIsSelected)
|
||||
g.fillAll (findColour (TextEditor::highlightColourId));
|
||||
|
||||
g.setColour (findColour (ListBox::textColourId));
|
||||
Font f ((float) height * 0.7f);
|
||||
f.setHorizontalScale (0.9f);
|
||||
g.setFont (f);
|
||||
|
||||
g.drawText (path[rowNumber].getFullPathName(),
|
||||
4, 0, width - 6, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::deleteKeyPressed (int row)
|
||||
{
|
||||
if (isPositiveAndBelow (row, path.getNumPaths()))
|
||||
{
|
||||
path.remove (row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::returnKeyPressed (int row)
|
||||
{
|
||||
chooser = std::make_unique<FileChooser> (TRANS("Change folder..."), path[row], "*");
|
||||
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc)
|
||||
{
|
||||
if (fc.getResult() == File{})
|
||||
return;
|
||||
|
||||
path.remove (row);
|
||||
path.add (fc.getResult(), row);
|
||||
changed();
|
||||
});
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&)
|
||||
{
|
||||
returnKeyPressed (row);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::selectedRowsChanged (int)
|
||||
{
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::resized()
|
||||
{
|
||||
const int buttonH = 22;
|
||||
const int buttonY = getHeight() - buttonH - 4;
|
||||
listBox.setBounds (2, 2, getWidth() - 4, buttonY - 5);
|
||||
|
||||
addButton.setBounds (2, buttonY, buttonH, buttonH);
|
||||
removeButton.setBounds (addButton.getRight(), buttonY, buttonH, buttonH);
|
||||
|
||||
changeButton.changeWidthToFitText (buttonH);
|
||||
downButton.setSize (buttonH * 2, buttonH);
|
||||
upButton.setSize (buttonH * 2, buttonH);
|
||||
|
||||
downButton.setTopRightPosition (getWidth() - 2, buttonY);
|
||||
upButton.setTopRightPosition (downButton.getX() - 4, buttonY);
|
||||
changeButton.setTopRightPosition (upButton.getX() - 8, buttonY);
|
||||
}
|
||||
|
||||
bool FileSearchPathListComponent::isInterestedInFileDrag (const StringArray&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::filesDropped (const StringArray& filenames, int, int mouseY)
|
||||
{
|
||||
for (int i = filenames.size(); --i >= 0;)
|
||||
{
|
||||
const File f (filenames[i]);
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
auto row = listBox.getRowContainingPosition (0, mouseY - listBox.getY());
|
||||
path.add (f, row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::addPath()
|
||||
{
|
||||
auto start = defaultBrowseTarget;
|
||||
|
||||
if (start == File())
|
||||
start = path[0];
|
||||
|
||||
if (start == File())
|
||||
start = File::getCurrentWorkingDirectory();
|
||||
|
||||
chooser = std::make_unique<FileChooser> (TRANS("Add a folder..."), start, "*");
|
||||
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||
{
|
||||
if (fc.getResult() == File{})
|
||||
return;
|
||||
|
||||
path.add (fc.getResult(), listBox.getSelectedRow());
|
||||
changed();
|
||||
});
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::deleteSelected()
|
||||
{
|
||||
deleteKeyPressed (listBox.getSelectedRow());
|
||||
changed();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::editSelected()
|
||||
{
|
||||
returnKeyPressed (listBox.getSelectedRow());
|
||||
changed();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::moveSelection (int delta)
|
||||
{
|
||||
jassert (delta == -1 || delta == 1);
|
||||
auto currentRow = listBox.getSelectedRow();
|
||||
|
||||
if (isPositiveAndBelow (currentRow, path.getNumPaths()))
|
||||
{
|
||||
auto newRow = jlimit (0, path.getNumPaths() - 1, currentRow + delta);
|
||||
|
||||
if (currentRow != newRow)
|
||||
{
|
||||
auto f = path[currentRow];
|
||||
path.remove (currentRow);
|
||||
path.add (f, newRow);
|
||||
listBox.selectRow (newRow);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
FileSearchPathListComponent::FileSearchPathListComponent()
|
||||
: addButton ("+"),
|
||||
removeButton ("-"),
|
||||
changeButton (TRANS ("change...")),
|
||||
upButton ({}, DrawableButton::ImageOnButtonBackground),
|
||||
downButton ({}, DrawableButton::ImageOnButtonBackground)
|
||||
{
|
||||
listBox.setModel (this);
|
||||
addAndMakeVisible (listBox);
|
||||
listBox.setColour (ListBox::backgroundColourId, Colours::black.withAlpha (0.02f));
|
||||
listBox.setColour (ListBox::outlineColourId, Colours::black.withAlpha (0.1f));
|
||||
listBox.setOutlineThickness (1);
|
||||
|
||||
addAndMakeVisible (addButton);
|
||||
addButton.onClick = [this] { addPath(); };
|
||||
addButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
|
||||
|
||||
addAndMakeVisible (removeButton);
|
||||
removeButton.onClick = [this] { deleteSelected(); };
|
||||
removeButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
|
||||
|
||||
addAndMakeVisible (changeButton);
|
||||
changeButton.onClick = [this] { editSelected(); };
|
||||
|
||||
addAndMakeVisible (upButton);
|
||||
upButton.onClick = [this] { moveSelection (-1); };
|
||||
|
||||
auto arrowColour = findColour (ListBox::textColourId);
|
||||
|
||||
{
|
||||
Path arrowPath;
|
||||
arrowPath.addArrow ({ 50.0f, 100.0f, 50.0f, 0.0f }, 40.0f, 100.0f, 50.0f);
|
||||
DrawablePath arrowImage;
|
||||
arrowImage.setFill (arrowColour);
|
||||
arrowImage.setPath (arrowPath);
|
||||
|
||||
upButton.setImages (&arrowImage);
|
||||
}
|
||||
|
||||
addAndMakeVisible (downButton);
|
||||
downButton.onClick = [this] { moveSelection (1); };
|
||||
|
||||
{
|
||||
Path arrowPath;
|
||||
arrowPath.addArrow ({ 50.0f, 0.0f, 50.0f, 100.0f }, 40.0f, 100.0f, 50.0f);
|
||||
DrawablePath arrowImage;
|
||||
arrowImage.setFill (arrowColour);
|
||||
arrowImage.setPath (arrowPath);
|
||||
|
||||
downButton.setImages (&arrowImage);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::updateButtons()
|
||||
{
|
||||
const bool anythingSelected = listBox.getNumSelectedRows() > 0;
|
||||
|
||||
removeButton.setEnabled (anythingSelected);
|
||||
changeButton.setEnabled (anythingSelected);
|
||||
upButton.setEnabled (anythingSelected);
|
||||
downButton.setEnabled (anythingSelected);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::changed()
|
||||
{
|
||||
listBox.updateContent();
|
||||
listBox.repaint();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileSearchPathListComponent::setPath (const FileSearchPath& newPath)
|
||||
{
|
||||
if (newPath.toString() != path.toString())
|
||||
{
|
||||
path = newPath;
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
|
||||
{
|
||||
defaultBrowseTarget = newDefaultDirectory;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int FileSearchPathListComponent::getNumRows()
|
||||
{
|
||||
return path.getNumPaths();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
|
||||
{
|
||||
if (rowIsSelected)
|
||||
g.fillAll (findColour (TextEditor::highlightColourId));
|
||||
|
||||
g.setColour (findColour (ListBox::textColourId));
|
||||
Font f ((float) height * 0.7f);
|
||||
f.setHorizontalScale (0.9f);
|
||||
g.setFont (f);
|
||||
|
||||
g.drawText (path[rowNumber].getFullPathName(),
|
||||
4, 0, width - 6, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::deleteKeyPressed (int row)
|
||||
{
|
||||
if (isPositiveAndBelow (row, path.getNumPaths()))
|
||||
{
|
||||
path.remove (row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::returnKeyPressed (int row)
|
||||
{
|
||||
chooser = std::make_unique<FileChooser> (TRANS("Change folder..."), path[row], "*");
|
||||
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc)
|
||||
{
|
||||
if (fc.getResult() == File{})
|
||||
return;
|
||||
|
||||
path.remove (row);
|
||||
path.add (fc.getResult(), row);
|
||||
changed();
|
||||
});
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&)
|
||||
{
|
||||
returnKeyPressed (row);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::selectedRowsChanged (int)
|
||||
{
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::resized()
|
||||
{
|
||||
const int buttonH = 22;
|
||||
const int buttonY = getHeight() - buttonH - 4;
|
||||
listBox.setBounds (2, 2, getWidth() - 4, buttonY - 5);
|
||||
|
||||
addButton.setBounds (2, buttonY, buttonH, buttonH);
|
||||
removeButton.setBounds (addButton.getRight(), buttonY, buttonH, buttonH);
|
||||
|
||||
changeButton.changeWidthToFitText (buttonH);
|
||||
downButton.setSize (buttonH * 2, buttonH);
|
||||
upButton.setSize (buttonH * 2, buttonH);
|
||||
|
||||
downButton.setTopRightPosition (getWidth() - 2, buttonY);
|
||||
upButton.setTopRightPosition (downButton.getX() - 4, buttonY);
|
||||
changeButton.setTopRightPosition (upButton.getX() - 8, buttonY);
|
||||
}
|
||||
|
||||
bool FileSearchPathListComponent::isInterestedInFileDrag (const StringArray&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::filesDropped (const StringArray& filenames, int, int mouseY)
|
||||
{
|
||||
for (int i = filenames.size(); --i >= 0;)
|
||||
{
|
||||
const File f (filenames[i]);
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
auto row = listBox.getRowContainingPosition (0, mouseY - listBox.getY());
|
||||
path.add (f, row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::addPath()
|
||||
{
|
||||
auto start = defaultBrowseTarget;
|
||||
|
||||
if (start == File())
|
||||
start = path[0];
|
||||
|
||||
if (start == File())
|
||||
start = File::getCurrentWorkingDirectory();
|
||||
|
||||
chooser = std::make_unique<FileChooser> (TRANS("Add a folder..."), start, "*");
|
||||
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||
{
|
||||
if (fc.getResult() == File{})
|
||||
return;
|
||||
|
||||
path.add (fc.getResult(), listBox.getSelectedRow());
|
||||
changed();
|
||||
});
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::deleteSelected()
|
||||
{
|
||||
deleteKeyPressed (listBox.getSelectedRow());
|
||||
changed();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::editSelected()
|
||||
{
|
||||
returnKeyPressed (listBox.getSelectedRow());
|
||||
changed();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::moveSelection (int delta)
|
||||
{
|
||||
jassert (delta == -1 || delta == 1);
|
||||
auto currentRow = listBox.getSelectedRow();
|
||||
|
||||
if (isPositiveAndBelow (currentRow, path.getNumPaths()))
|
||||
{
|
||||
auto newRow = jlimit (0, path.getNumPaths() - 1, currentRow + delta);
|
||||
|
||||
if (currentRow != newRow)
|
||||
{
|
||||
auto f = path[currentRow];
|
||||
path.remove (currentRow);
|
||||
path.add (f, newRow);
|
||||
listBox.selectRow (newRow);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,120 +1,117 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Shows a set of file paths in a list, allowing them to be added, removed or
|
||||
re-ordered.
|
||||
|
||||
@see FileSearchPath
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileSearchPathListComponent : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
private ListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty FileSearchPathListComponent. */
|
||||
FileSearchPathListComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~FileSearchPathListComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the path as it is currently shown. */
|
||||
const FileSearchPath& getPath() const noexcept { return path; }
|
||||
|
||||
/** Changes the current path. */
|
||||
void setPath (const FileSearchPath& newPath);
|
||||
|
||||
/** Sets a file or directory to be the default starting point for the browser to show.
|
||||
|
||||
This is only used if the current file hasn't been set.
|
||||
*/
|
||||
void setDefaultBrowseTarget (const File& newDefaultDirectory);
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1004100, /**< The background colour to fill the component with.
|
||||
Make this transparent if you don't want the background to be filled. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
int getNumRows() override;
|
||||
/** @internal */
|
||||
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override;
|
||||
/** @internal */
|
||||
void deleteKeyPressed (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void returnKeyPressed (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void listBoxItemDoubleClicked (int row, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void selectedRowsChanged (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray& files, int, int) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileSearchPath path;
|
||||
File defaultBrowseTarget;
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
ListBox listBox;
|
||||
TextButton addButton, removeButton, changeButton;
|
||||
DrawableButton upButton, downButton;
|
||||
|
||||
void changed();
|
||||
void updateButtons();
|
||||
|
||||
void addPath();
|
||||
void deleteSelected();
|
||||
void editSelected();
|
||||
void moveSelection (int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileSearchPathListComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Shows a set of file paths in a list, allowing them to be added, removed or
|
||||
re-ordered.
|
||||
|
||||
@see FileSearchPath
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileSearchPathListComponent : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
private ListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty FileSearchPathListComponent. */
|
||||
FileSearchPathListComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the path as it is currently shown. */
|
||||
const FileSearchPath& getPath() const noexcept { return path; }
|
||||
|
||||
/** Changes the current path. */
|
||||
void setPath (const FileSearchPath& newPath);
|
||||
|
||||
/** Sets a file or directory to be the default starting point for the browser to show.
|
||||
|
||||
This is only used if the current file hasn't been set.
|
||||
*/
|
||||
void setDefaultBrowseTarget (const File& newDefaultDirectory);
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1004100, /**< The background colour to fill the component with.
|
||||
Make this transparent if you don't want the background to be filled. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
int getNumRows() override;
|
||||
/** @internal */
|
||||
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override;
|
||||
/** @internal */
|
||||
void deleteKeyPressed (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void returnKeyPressed (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void listBoxItemDoubleClicked (int row, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void selectedRowsChanged (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray& files, int, int) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileSearchPath path;
|
||||
File defaultBrowseTarget;
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
ListBox listBox;
|
||||
TextButton addButton, removeButton, changeButton;
|
||||
DrawableButton upButton, downButton;
|
||||
|
||||
void changed();
|
||||
void updateButtons();
|
||||
|
||||
void addPath();
|
||||
void deleteSelected();
|
||||
void editSelected();
|
||||
void moveSelection (int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileSearchPathListComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,334 +1,334 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class FileListTreeItem : public TreeViewItem,
|
||||
private TimeSliceClient,
|
||||
private AsyncUpdater,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
FileListTreeItem (FileTreeComponent& treeComp,
|
||||
DirectoryContentsList* parentContents,
|
||||
int indexInContents,
|
||||
const File& f,
|
||||
TimeSliceThread& t)
|
||||
: file (f),
|
||||
owner (treeComp),
|
||||
parentContentsList (parentContents),
|
||||
indexInContentsList (indexInContents),
|
||||
subContentsList (nullptr, false),
|
||||
thread (t)
|
||||
{
|
||||
DirectoryContentsList::FileInfo fileInfo;
|
||||
|
||||
if (parentContents != nullptr
|
||||
&& parentContents->getFileInfo (indexInContents, fileInfo))
|
||||
{
|
||||
fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
|
||||
modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
|
||||
isDirectory = fileInfo.isDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
isDirectory = true;
|
||||
}
|
||||
}
|
||||
|
||||
~FileListTreeItem() override
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
clearSubItems();
|
||||
removeSubContentsList();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool mightContainSubItems() override { return isDirectory; }
|
||||
String getUniqueName() const override { return file.getFullPathName(); }
|
||||
int getItemHeight() const override { return owner.getItemHeight(); }
|
||||
|
||||
var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen)
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
isDirectory = file.isDirectory();
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
if (subContentsList == nullptr && parentContentsList != nullptr)
|
||||
{
|
||||
auto l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
|
||||
|
||||
l->setDirectory (file,
|
||||
parentContentsList->isFindingDirectories(),
|
||||
parentContentsList->isFindingFiles());
|
||||
|
||||
setSubContentsList (l, true);
|
||||
}
|
||||
|
||||
changeListenerCallback (nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeSubContentsList()
|
||||
{
|
||||
if (subContentsList != nullptr)
|
||||
{
|
||||
subContentsList->removeChangeListener (this);
|
||||
subContentsList.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
|
||||
{
|
||||
removeSubContentsList();
|
||||
|
||||
subContentsList = OptionalScopedPointer<DirectoryContentsList> (newList, canDeleteList);
|
||||
newList->addChangeListener (this);
|
||||
}
|
||||
|
||||
bool selectFile (const File& target)
|
||||
{
|
||||
if (file == target)
|
||||
{
|
||||
setSelected (true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (target.isAChildOf (file))
|
||||
{
|
||||
setOpen (true);
|
||||
|
||||
for (int maxRetries = 500; --maxRetries > 0;)
|
||||
{
|
||||
for (int i = 0; i < getNumSubItems(); ++i)
|
||||
if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
|
||||
if (f->selectFile (target))
|
||||
return true;
|
||||
|
||||
// if we've just opened and the contents are still loading, wait for it..
|
||||
if (subContentsList != nullptr && subContentsList->isStillLoading())
|
||||
{
|
||||
Thread::sleep (10);
|
||||
rebuildItemsFromContentList();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override
|
||||
{
|
||||
rebuildItemsFromContentList();
|
||||
}
|
||||
|
||||
void rebuildItemsFromContentList()
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
if (isOpen() && subContentsList != nullptr)
|
||||
{
|
||||
for (int i = 0; i < subContentsList->getNumFiles(); ++i)
|
||||
addSubItem (new FileListTreeItem (owner, subContentsList, i,
|
||||
subContentsList->getFile(i), thread));
|
||||
}
|
||||
}
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
ScopedLock lock (iconUpdate);
|
||||
|
||||
if (file != File())
|
||||
{
|
||||
updateIcon (true);
|
||||
|
||||
if (icon.isNull())
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
|
||||
owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
|
||||
file, file.getFileName(),
|
||||
&icon, fileSize, modTime,
|
||||
isDirectory, isSelected(),
|
||||
indexInContentsList, owner);
|
||||
}
|
||||
|
||||
String getAccessibilityName() override
|
||||
{
|
||||
return file.getFileName();
|
||||
}
|
||||
|
||||
void itemClicked (const MouseEvent& e) override
|
||||
{
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
}
|
||||
|
||||
void itemDoubleClicked (const MouseEvent& e) override
|
||||
{
|
||||
TreeViewItem::itemDoubleClicked (e);
|
||||
|
||||
owner.sendDoubleClickMessage (file);
|
||||
}
|
||||
|
||||
void itemSelectionChanged (bool) override
|
||||
{
|
||||
owner.sendSelectionChangeMessage();
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
updateIcon (false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
owner.repaint();
|
||||
}
|
||||
|
||||
const File file;
|
||||
|
||||
private:
|
||||
FileTreeComponent& owner;
|
||||
DirectoryContentsList* parentContentsList;
|
||||
int indexInContentsList;
|
||||
OptionalScopedPointer<DirectoryContentsList> subContentsList;
|
||||
bool isDirectory;
|
||||
TimeSliceThread& thread;
|
||||
CriticalSection iconUpdate;
|
||||
Image icon;
|
||||
String fileSize, modTime;
|
||||
|
||||
void updateIcon (const bool onlyUpdateIfCached)
|
||||
{
|
||||
if (icon.isNull())
|
||||
{
|
||||
auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
|
||||
auto im = ImageCache::getFromHashCode (hashCode);
|
||||
|
||||
if (im.isNull() && ! onlyUpdateIfCached)
|
||||
{
|
||||
im = juce_createIconForFile (file);
|
||||
|
||||
if (im.isValid())
|
||||
ImageCache::addImageToCache (im, hashCode);
|
||||
}
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
{
|
||||
ScopedLock lock (iconUpdate);
|
||||
icon = im;
|
||||
}
|
||||
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
|
||||
: DirectoryContentsDisplayComponent (listToShow),
|
||||
itemHeight (22)
|
||||
{
|
||||
setRootItemVisible (false);
|
||||
refresh();
|
||||
}
|
||||
|
||||
FileTreeComponent::~FileTreeComponent()
|
||||
{
|
||||
deleteRootItem();
|
||||
}
|
||||
|
||||
void FileTreeComponent::refresh()
|
||||
{
|
||||
deleteRootItem();
|
||||
|
||||
auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
|
||||
directoryContentsList.getTimeSliceThread());
|
||||
|
||||
root->setSubContentsList (&directoryContentsList, false);
|
||||
setRootItem (root);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
File FileTreeComponent::getSelectedFile (const int index) const
|
||||
{
|
||||
if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
|
||||
return item->file;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void FileTreeComponent::deselectAllFiles()
|
||||
{
|
||||
clearSelectedItems();
|
||||
}
|
||||
|
||||
void FileTreeComponent::scrollToTop()
|
||||
{
|
||||
getViewport()->getVerticalScrollBar().setCurrentRangeStart (0);
|
||||
}
|
||||
|
||||
void FileTreeComponent::setDragAndDropDescription (const String& description)
|
||||
{
|
||||
dragAndDropDescription = description;
|
||||
}
|
||||
|
||||
void FileTreeComponent::setSelectedFile (const File& target)
|
||||
{
|
||||
if (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
|
||||
if (! t->selectFile (target))
|
||||
clearSelectedItems();
|
||||
}
|
||||
|
||||
void FileTreeComponent::setItemHeight (int newHeight)
|
||||
{
|
||||
if (itemHeight != newHeight)
|
||||
{
|
||||
itemHeight = newHeight;
|
||||
|
||||
if (auto* root = getRootItem())
|
||||
root->treeHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class FileListTreeItem : public TreeViewItem,
|
||||
private TimeSliceClient,
|
||||
private AsyncUpdater,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
FileListTreeItem (FileTreeComponent& treeComp,
|
||||
DirectoryContentsList* parentContents,
|
||||
int indexInContents,
|
||||
const File& f,
|
||||
TimeSliceThread& t)
|
||||
: file (f),
|
||||
owner (treeComp),
|
||||
parentContentsList (parentContents),
|
||||
indexInContentsList (indexInContents),
|
||||
subContentsList (nullptr, false),
|
||||
thread (t)
|
||||
{
|
||||
DirectoryContentsList::FileInfo fileInfo;
|
||||
|
||||
if (parentContents != nullptr
|
||||
&& parentContents->getFileInfo (indexInContents, fileInfo))
|
||||
{
|
||||
fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
|
||||
modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
|
||||
isDirectory = fileInfo.isDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
isDirectory = true;
|
||||
}
|
||||
}
|
||||
|
||||
~FileListTreeItem() override
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
clearSubItems();
|
||||
removeSubContentsList();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool mightContainSubItems() override { return isDirectory; }
|
||||
String getUniqueName() const override { return file.getFullPathName(); }
|
||||
int getItemHeight() const override { return owner.getItemHeight(); }
|
||||
|
||||
var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen)
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
isDirectory = file.isDirectory();
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
if (subContentsList == nullptr && parentContentsList != nullptr)
|
||||
{
|
||||
auto l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
|
||||
|
||||
l->setDirectory (file,
|
||||
parentContentsList->isFindingDirectories(),
|
||||
parentContentsList->isFindingFiles());
|
||||
|
||||
setSubContentsList (l, true);
|
||||
}
|
||||
|
||||
changeListenerCallback (nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeSubContentsList()
|
||||
{
|
||||
if (subContentsList != nullptr)
|
||||
{
|
||||
subContentsList->removeChangeListener (this);
|
||||
subContentsList.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
|
||||
{
|
||||
removeSubContentsList();
|
||||
|
||||
subContentsList = OptionalScopedPointer<DirectoryContentsList> (newList, canDeleteList);
|
||||
newList->addChangeListener (this);
|
||||
}
|
||||
|
||||
bool selectFile (const File& target)
|
||||
{
|
||||
if (file == target)
|
||||
{
|
||||
setSelected (true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (target.isAChildOf (file))
|
||||
{
|
||||
setOpen (true);
|
||||
|
||||
for (int maxRetries = 500; --maxRetries > 0;)
|
||||
{
|
||||
for (int i = 0; i < getNumSubItems(); ++i)
|
||||
if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
|
||||
if (f->selectFile (target))
|
||||
return true;
|
||||
|
||||
// if we've just opened and the contents are still loading, wait for it..
|
||||
if (subContentsList != nullptr && subContentsList->isStillLoading())
|
||||
{
|
||||
Thread::sleep (10);
|
||||
rebuildItemsFromContentList();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override
|
||||
{
|
||||
rebuildItemsFromContentList();
|
||||
}
|
||||
|
||||
void rebuildItemsFromContentList()
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
if (isOpen() && subContentsList != nullptr)
|
||||
{
|
||||
for (int i = 0; i < subContentsList->getNumFiles(); ++i)
|
||||
addSubItem (new FileListTreeItem (owner, subContentsList, i,
|
||||
subContentsList->getFile(i), thread));
|
||||
}
|
||||
}
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
ScopedLock lock (iconUpdate);
|
||||
|
||||
if (file != File())
|
||||
{
|
||||
updateIcon (true);
|
||||
|
||||
if (icon.isNull())
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
|
||||
owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
|
||||
file, file.getFileName(),
|
||||
&icon, fileSize, modTime,
|
||||
isDirectory, isSelected(),
|
||||
indexInContentsList, owner);
|
||||
}
|
||||
|
||||
String getAccessibilityName() override
|
||||
{
|
||||
return file.getFileName();
|
||||
}
|
||||
|
||||
void itemClicked (const MouseEvent& e) override
|
||||
{
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
}
|
||||
|
||||
void itemDoubleClicked (const MouseEvent& e) override
|
||||
{
|
||||
TreeViewItem::itemDoubleClicked (e);
|
||||
|
||||
owner.sendDoubleClickMessage (file);
|
||||
}
|
||||
|
||||
void itemSelectionChanged (bool) override
|
||||
{
|
||||
owner.sendSelectionChangeMessage();
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
updateIcon (false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
owner.repaint();
|
||||
}
|
||||
|
||||
const File file;
|
||||
|
||||
private:
|
||||
FileTreeComponent& owner;
|
||||
DirectoryContentsList* parentContentsList;
|
||||
int indexInContentsList;
|
||||
OptionalScopedPointer<DirectoryContentsList> subContentsList;
|
||||
bool isDirectory;
|
||||
TimeSliceThread& thread;
|
||||
CriticalSection iconUpdate;
|
||||
Image icon;
|
||||
String fileSize, modTime;
|
||||
|
||||
void updateIcon (const bool onlyUpdateIfCached)
|
||||
{
|
||||
if (icon.isNull())
|
||||
{
|
||||
auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
|
||||
auto im = ImageCache::getFromHashCode (hashCode);
|
||||
|
||||
if (im.isNull() && ! onlyUpdateIfCached)
|
||||
{
|
||||
im = juce_createIconForFile (file);
|
||||
|
||||
if (im.isValid())
|
||||
ImageCache::addImageToCache (im, hashCode);
|
||||
}
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
{
|
||||
ScopedLock lock (iconUpdate);
|
||||
icon = im;
|
||||
}
|
||||
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
|
||||
: DirectoryContentsDisplayComponent (listToShow),
|
||||
itemHeight (22)
|
||||
{
|
||||
setRootItemVisible (false);
|
||||
refresh();
|
||||
}
|
||||
|
||||
FileTreeComponent::~FileTreeComponent()
|
||||
{
|
||||
deleteRootItem();
|
||||
}
|
||||
|
||||
void FileTreeComponent::refresh()
|
||||
{
|
||||
deleteRootItem();
|
||||
|
||||
auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
|
||||
directoryContentsList.getTimeSliceThread());
|
||||
|
||||
root->setSubContentsList (&directoryContentsList, false);
|
||||
setRootItem (root);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
File FileTreeComponent::getSelectedFile (const int index) const
|
||||
{
|
||||
if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
|
||||
return item->file;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void FileTreeComponent::deselectAllFiles()
|
||||
{
|
||||
clearSelectedItems();
|
||||
}
|
||||
|
||||
void FileTreeComponent::scrollToTop()
|
||||
{
|
||||
getViewport()->getVerticalScrollBar().setCurrentRangeStart (0);
|
||||
}
|
||||
|
||||
void FileTreeComponent::setDragAndDropDescription (const String& description)
|
||||
{
|
||||
dragAndDropDescription = description;
|
||||
}
|
||||
|
||||
void FileTreeComponent::setSelectedFile (const File& target)
|
||||
{
|
||||
if (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
|
||||
if (! t->selectFile (target))
|
||||
clearSelectedItems();
|
||||
}
|
||||
|
||||
void FileTreeComponent::setItemHeight (int newHeight)
|
||||
{
|
||||
if (itemHeight != newHeight)
|
||||
{
|
||||
itemHeight = newHeight;
|
||||
|
||||
if (auto* root = getRootItem())
|
||||
root->treeHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,105 +1,105 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays the files in a directory as a treeview.
|
||||
|
||||
This implements the DirectoryContentsDisplayComponent base class so that
|
||||
it can be used in a FileBrowserComponent.
|
||||
|
||||
To attach a listener to it, use its DirectoryContentsDisplayComponent base
|
||||
class and the FileBrowserListener class.
|
||||
|
||||
@see DirectoryContentsList, FileListComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileTreeComponent : public TreeView,
|
||||
public DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a listbox to show the contents of a specified directory.
|
||||
*/
|
||||
FileTreeComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
~FileTreeComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
int getNumSelectedFiles() const override { return TreeView::getNumSelectedItems(); }
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
File getSelectedFile (int index = 0) const override;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles() override;
|
||||
|
||||
/** Scrolls the list to the top. */
|
||||
void scrollToTop() override;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
void setSelectedFile (const File&) override;
|
||||
|
||||
/** Updates the files in the list. */
|
||||
void refresh();
|
||||
|
||||
/** Setting a name for this allows tree items to be dragged.
|
||||
|
||||
The string that you pass in here will be returned by the getDragSourceDescription()
|
||||
of the items in the tree. For more info, see TreeViewItem::getDragSourceDescription().
|
||||
*/
|
||||
void setDragAndDropDescription (const String& description);
|
||||
|
||||
/** Returns the last value that was set by setDragAndDropDescription().
|
||||
*/
|
||||
const String& getDragAndDropDescription() const noexcept { return dragAndDropDescription; }
|
||||
|
||||
/** Changes the height of the treeview items. */
|
||||
void setItemHeight (int newHeight);
|
||||
|
||||
/** Returns the height of the treeview items. */
|
||||
int getItemHeight() const noexcept { return itemHeight; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String dragAndDropDescription;
|
||||
int itemHeight;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileTreeComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays the files in a directory as a treeview.
|
||||
|
||||
This implements the DirectoryContentsDisplayComponent base class so that
|
||||
it can be used in a FileBrowserComponent.
|
||||
|
||||
To attach a listener to it, use its DirectoryContentsDisplayComponent base
|
||||
class and the FileBrowserListener class.
|
||||
|
||||
@see DirectoryContentsList, FileListComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileTreeComponent : public TreeView,
|
||||
public DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a listbox to show the contents of a specified directory.
|
||||
*/
|
||||
FileTreeComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
~FileTreeComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
int getNumSelectedFiles() const override { return TreeView::getNumSelectedItems(); }
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
File getSelectedFile (int index = 0) const override;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles() override;
|
||||
|
||||
/** Scrolls the list to the top. */
|
||||
void scrollToTop() override;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
void setSelectedFile (const File&) override;
|
||||
|
||||
/** Updates the files in the list. */
|
||||
void refresh();
|
||||
|
||||
/** Setting a name for this allows tree items to be dragged.
|
||||
|
||||
The string that you pass in here will be returned by the getDragSourceDescription()
|
||||
of the items in the tree. For more info, see TreeViewItem::getDragSourceDescription().
|
||||
*/
|
||||
void setDragAndDropDescription (const String& description);
|
||||
|
||||
/** Returns the last value that was set by setDragAndDropDescription().
|
||||
*/
|
||||
const String& getDragAndDropDescription() const noexcept { return dragAndDropDescription; }
|
||||
|
||||
/** Changes the height of the treeview items. */
|
||||
void setItemHeight (int newHeight);
|
||||
|
||||
/** Returns the height of the treeview items. */
|
||||
int getItemHeight() const noexcept { return itemHeight; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String dragAndDropDescription;
|
||||
int itemHeight;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileTreeComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,268 +1,268 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
FilenameComponent::FilenameComponent (const String& name,
|
||||
const File& currentFile,
|
||||
bool canEditFilename,
|
||||
bool isDirectory,
|
||||
bool isForSaving,
|
||||
const String& fileBrowserWildcard,
|
||||
const String& suffix,
|
||||
const String& textWhenNothingSelected)
|
||||
: Component (name),
|
||||
isDir (isDirectory),
|
||||
isSaving (isForSaving),
|
||||
wildcard (fileBrowserWildcard),
|
||||
enforcedSuffix (suffix)
|
||||
{
|
||||
addAndMakeVisible (filenameBox);
|
||||
filenameBox.setEditableText (canEditFilename);
|
||||
filenameBox.setTextWhenNothingSelected (textWhenNothingSelected);
|
||||
filenameBox.setTextWhenNoChoicesAvailable (TRANS ("(no recently selected files)"));
|
||||
filenameBox.onChange = [this] { setCurrentFile (getCurrentFile(), true); };
|
||||
|
||||
setBrowseButtonText ("...");
|
||||
|
||||
setCurrentFile (currentFile, true, dontSendNotification);
|
||||
}
|
||||
|
||||
FilenameComponent::~FilenameComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FilenameComponent::paintOverChildren (Graphics& g)
|
||||
{
|
||||
if (isFileDragOver)
|
||||
{
|
||||
g.setColour (Colours::red.withAlpha (0.2f));
|
||||
g.drawRect (getLocalBounds(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::resized()
|
||||
{
|
||||
getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<ComponentTraverser> FilenameComponent::createKeyboardFocusTraverser()
|
||||
{
|
||||
// This prevents the sub-components from grabbing focus if the
|
||||
// FilenameComponent has been set to refuse focus.
|
||||
return getWantsKeyboardFocus() ? Component::createKeyboardFocusTraverser() : nullptr;
|
||||
}
|
||||
|
||||
void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)
|
||||
{
|
||||
browseButtonText = newBrowseButtonText;
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void FilenameComponent::lookAndFeelChanged()
|
||||
{
|
||||
browseButton.reset();
|
||||
browseButton.reset (getLookAndFeel().createFilenameComponentBrowseButton (browseButtonText));
|
||||
addAndMakeVisible (browseButton.get());
|
||||
browseButton->setConnectedEdges (Button::ConnectedOnLeft);
|
||||
browseButton->onClick = [this] { showChooser(); };
|
||||
resized();
|
||||
}
|
||||
|
||||
void FilenameComponent::setTooltip (const String& newTooltip)
|
||||
{
|
||||
SettableTooltipClient::setTooltip (newTooltip);
|
||||
filenameBox.setTooltip (newTooltip);
|
||||
}
|
||||
|
||||
void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
|
||||
{
|
||||
defaultBrowseFile = newDefaultDirectory;
|
||||
}
|
||||
|
||||
File FilenameComponent::getLocationToBrowse()
|
||||
{
|
||||
if (lastFilename.isEmpty() && defaultBrowseFile != File())
|
||||
return defaultBrowseFile;
|
||||
|
||||
return getCurrentFile();
|
||||
}
|
||||
|
||||
void FilenameComponent::showChooser()
|
||||
{
|
||||
chooser = std::make_unique<FileChooser> (isDir ? TRANS ("Choose a new directory")
|
||||
: TRANS ("Choose a new file"),
|
||||
getLocationToBrowse(),
|
||||
wildcard);
|
||||
|
||||
auto chooserFlags = isDir ? FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories
|
||||
: FileBrowserComponent::canSelectFiles | (isSaving ? FileBrowserComponent::saveMode
|
||||
: FileBrowserComponent::openMode);
|
||||
|
||||
chooser->launchAsync (chooserFlags, [this] (const FileChooser&)
|
||||
{
|
||||
if (chooser->getResult() == File{})
|
||||
return;
|
||||
|
||||
setCurrentFile (chooser->getResult(), true);
|
||||
});
|
||||
}
|
||||
|
||||
bool FilenameComponent::isInterestedInFileDrag (const StringArray&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilenameComponent::filesDropped (const StringArray& filenames, int, int)
|
||||
{
|
||||
isFileDragOver = false;
|
||||
repaint();
|
||||
|
||||
const File f (filenames[0]);
|
||||
|
||||
if (f.exists() && (f.isDirectory() == isDir))
|
||||
setCurrentFile (f, true);
|
||||
}
|
||||
|
||||
void FilenameComponent::fileDragEnter (const StringArray&, int, int)
|
||||
{
|
||||
isFileDragOver = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void FilenameComponent::fileDragExit (const StringArray&)
|
||||
{
|
||||
isFileDragOver = false;
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String FilenameComponent::getCurrentFileText() const
|
||||
{
|
||||
return filenameBox.getText();
|
||||
}
|
||||
|
||||
File FilenameComponent::getCurrentFile() const
|
||||
{
|
||||
auto f = File::getCurrentWorkingDirectory().getChildFile (getCurrentFileText());
|
||||
|
||||
if (enforcedSuffix.isNotEmpty())
|
||||
f = f.withFileExtension (enforcedSuffix);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void FilenameComponent::setCurrentFile (File newFile,
|
||||
const bool addToRecentlyUsedList,
|
||||
NotificationType notification)
|
||||
{
|
||||
if (enforcedSuffix.isNotEmpty())
|
||||
newFile = newFile.withFileExtension (enforcedSuffix);
|
||||
|
||||
if (newFile.getFullPathName() != lastFilename)
|
||||
{
|
||||
lastFilename = newFile.getFullPathName();
|
||||
|
||||
if (addToRecentlyUsedList)
|
||||
addRecentlyUsedFile (newFile);
|
||||
|
||||
filenameBox.setText (lastFilename, dontSendNotification);
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
handleUpdateNowIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::setFilenameIsEditable (const bool shouldBeEditable)
|
||||
{
|
||||
filenameBox.setEditableText (shouldBeEditable);
|
||||
}
|
||||
|
||||
StringArray FilenameComponent::getRecentlyUsedFilenames() const
|
||||
{
|
||||
StringArray names;
|
||||
|
||||
for (int i = 0; i < filenameBox.getNumItems(); ++i)
|
||||
names.add (filenameBox.getItemText (i));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void FilenameComponent::setRecentlyUsedFilenames (const StringArray& filenames)
|
||||
{
|
||||
if (filenames != getRecentlyUsedFilenames())
|
||||
{
|
||||
filenameBox.clear();
|
||||
|
||||
for (int i = 0; i < jmin (filenames.size(), maxRecentFiles); ++i)
|
||||
filenameBox.addItem (filenames[i], i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::setMaxNumberOfRecentFiles (const int newMaximum)
|
||||
{
|
||||
maxRecentFiles = jmax (1, newMaximum);
|
||||
|
||||
setRecentlyUsedFilenames (getRecentlyUsedFilenames());
|
||||
}
|
||||
|
||||
void FilenameComponent::addRecentlyUsedFile (const File& file)
|
||||
{
|
||||
auto files = getRecentlyUsedFilenames();
|
||||
|
||||
if (file.getFullPathName().isNotEmpty())
|
||||
{
|
||||
files.removeString (file.getFullPathName(), true);
|
||||
files.insert (0, file.getFullPathName());
|
||||
|
||||
setRecentlyUsedFilenames (files);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FilenameComponent::addListener (FilenameComponentListener* const listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void FilenameComponent::removeListener (FilenameComponentListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void FilenameComponent::handleAsyncUpdate()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [this] (FilenameComponentListener& l) { l.filenameComponentChanged (this); });
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
FilenameComponent::FilenameComponent (const String& name,
|
||||
const File& currentFile,
|
||||
bool canEditFilename,
|
||||
bool isDirectory,
|
||||
bool isForSaving,
|
||||
const String& fileBrowserWildcard,
|
||||
const String& suffix,
|
||||
const String& textWhenNothingSelected)
|
||||
: Component (name),
|
||||
isDir (isDirectory),
|
||||
isSaving (isForSaving),
|
||||
wildcard (fileBrowserWildcard),
|
||||
enforcedSuffix (suffix)
|
||||
{
|
||||
addAndMakeVisible (filenameBox);
|
||||
filenameBox.setEditableText (canEditFilename);
|
||||
filenameBox.setTextWhenNothingSelected (textWhenNothingSelected);
|
||||
filenameBox.setTextWhenNoChoicesAvailable (TRANS ("(no recently selected files)"));
|
||||
filenameBox.onChange = [this] { setCurrentFile (getCurrentFile(), true); };
|
||||
|
||||
setBrowseButtonText ("...");
|
||||
|
||||
setCurrentFile (currentFile, true, dontSendNotification);
|
||||
}
|
||||
|
||||
FilenameComponent::~FilenameComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FilenameComponent::paintOverChildren (Graphics& g)
|
||||
{
|
||||
if (isFileDragOver)
|
||||
{
|
||||
g.setColour (Colours::red.withAlpha (0.2f));
|
||||
g.drawRect (getLocalBounds(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::resized()
|
||||
{
|
||||
getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<ComponentTraverser> FilenameComponent::createKeyboardFocusTraverser()
|
||||
{
|
||||
// This prevents the sub-components from grabbing focus if the
|
||||
// FilenameComponent has been set to refuse focus.
|
||||
return getWantsKeyboardFocus() ? Component::createKeyboardFocusTraverser() : nullptr;
|
||||
}
|
||||
|
||||
void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)
|
||||
{
|
||||
browseButtonText = newBrowseButtonText;
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void FilenameComponent::lookAndFeelChanged()
|
||||
{
|
||||
browseButton.reset();
|
||||
browseButton.reset (getLookAndFeel().createFilenameComponentBrowseButton (browseButtonText));
|
||||
addAndMakeVisible (browseButton.get());
|
||||
browseButton->setConnectedEdges (Button::ConnectedOnLeft);
|
||||
browseButton->onClick = [this] { showChooser(); };
|
||||
resized();
|
||||
}
|
||||
|
||||
void FilenameComponent::setTooltip (const String& newTooltip)
|
||||
{
|
||||
SettableTooltipClient::setTooltip (newTooltip);
|
||||
filenameBox.setTooltip (newTooltip);
|
||||
}
|
||||
|
||||
void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
|
||||
{
|
||||
defaultBrowseFile = newDefaultDirectory;
|
||||
}
|
||||
|
||||
File FilenameComponent::getLocationToBrowse()
|
||||
{
|
||||
if (lastFilename.isEmpty() && defaultBrowseFile != File())
|
||||
return defaultBrowseFile;
|
||||
|
||||
return getCurrentFile();
|
||||
}
|
||||
|
||||
void FilenameComponent::showChooser()
|
||||
{
|
||||
chooser = std::make_unique<FileChooser> (isDir ? TRANS ("Choose a new directory")
|
||||
: TRANS ("Choose a new file"),
|
||||
getLocationToBrowse(),
|
||||
wildcard);
|
||||
|
||||
auto chooserFlags = isDir ? FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories
|
||||
: FileBrowserComponent::canSelectFiles | (isSaving ? FileBrowserComponent::saveMode
|
||||
: FileBrowserComponent::openMode);
|
||||
|
||||
chooser->launchAsync (chooserFlags, [this] (const FileChooser&)
|
||||
{
|
||||
if (chooser->getResult() == File{})
|
||||
return;
|
||||
|
||||
setCurrentFile (chooser->getResult(), true);
|
||||
});
|
||||
}
|
||||
|
||||
bool FilenameComponent::isInterestedInFileDrag (const StringArray&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilenameComponent::filesDropped (const StringArray& filenames, int, int)
|
||||
{
|
||||
isFileDragOver = false;
|
||||
repaint();
|
||||
|
||||
const File f (filenames[0]);
|
||||
|
||||
if (f.exists() && (f.isDirectory() == isDir))
|
||||
setCurrentFile (f, true);
|
||||
}
|
||||
|
||||
void FilenameComponent::fileDragEnter (const StringArray&, int, int)
|
||||
{
|
||||
isFileDragOver = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void FilenameComponent::fileDragExit (const StringArray&)
|
||||
{
|
||||
isFileDragOver = false;
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String FilenameComponent::getCurrentFileText() const
|
||||
{
|
||||
return filenameBox.getText();
|
||||
}
|
||||
|
||||
File FilenameComponent::getCurrentFile() const
|
||||
{
|
||||
auto f = File::getCurrentWorkingDirectory().getChildFile (getCurrentFileText());
|
||||
|
||||
if (enforcedSuffix.isNotEmpty())
|
||||
f = f.withFileExtension (enforcedSuffix);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void FilenameComponent::setCurrentFile (File newFile,
|
||||
const bool addToRecentlyUsedList,
|
||||
NotificationType notification)
|
||||
{
|
||||
if (enforcedSuffix.isNotEmpty())
|
||||
newFile = newFile.withFileExtension (enforcedSuffix);
|
||||
|
||||
if (newFile.getFullPathName() != lastFilename)
|
||||
{
|
||||
lastFilename = newFile.getFullPathName();
|
||||
|
||||
if (addToRecentlyUsedList)
|
||||
addRecentlyUsedFile (newFile);
|
||||
|
||||
filenameBox.setText (lastFilename, dontSendNotification);
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
handleUpdateNowIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::setFilenameIsEditable (const bool shouldBeEditable)
|
||||
{
|
||||
filenameBox.setEditableText (shouldBeEditable);
|
||||
}
|
||||
|
||||
StringArray FilenameComponent::getRecentlyUsedFilenames() const
|
||||
{
|
||||
StringArray names;
|
||||
|
||||
for (int i = 0; i < filenameBox.getNumItems(); ++i)
|
||||
names.add (filenameBox.getItemText (i));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void FilenameComponent::setRecentlyUsedFilenames (const StringArray& filenames)
|
||||
{
|
||||
if (filenames != getRecentlyUsedFilenames())
|
||||
{
|
||||
filenameBox.clear();
|
||||
|
||||
for (int i = 0; i < jmin (filenames.size(), maxRecentFiles); ++i)
|
||||
filenameBox.addItem (filenames[i], i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::setMaxNumberOfRecentFiles (const int newMaximum)
|
||||
{
|
||||
maxRecentFiles = jmax (1, newMaximum);
|
||||
|
||||
setRecentlyUsedFilenames (getRecentlyUsedFilenames());
|
||||
}
|
||||
|
||||
void FilenameComponent::addRecentlyUsedFile (const File& file)
|
||||
{
|
||||
auto files = getRecentlyUsedFilenames();
|
||||
|
||||
if (file.getFullPathName().isNotEmpty())
|
||||
{
|
||||
files.removeString (file.getFullPathName(), true);
|
||||
files.insert (0, file.getFullPathName());
|
||||
|
||||
setRecentlyUsedFilenames (files);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FilenameComponent::addListener (FilenameComponentListener* const listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void FilenameComponent::removeListener (FilenameComponentListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void FilenameComponent::handleAsyncUpdate()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [this] (FilenameComponentListener& l) { l.filenameComponentChanged (this); });
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,236 +1,236 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Listens for events happening to a FilenameComponent.
|
||||
|
||||
Use FilenameComponent::addListener() and FilenameComponent::removeListener() to
|
||||
register one of these objects for event callbacks when the filename is changed.
|
||||
|
||||
@see FilenameComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FilenameComponentListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~FilenameComponentListener() = default;
|
||||
|
||||
/** This method is called after the FilenameComponent's file has been changed. */
|
||||
virtual void filenameComponentChanged (FilenameComponent* fileComponentThatHasChanged) = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Shows a filename as an editable text box, with a 'browse' button and a
|
||||
drop-down list for recently selected files.
|
||||
|
||||
A handy component for dialogue boxes where you want the user to be able to
|
||||
select a file or directory.
|
||||
|
||||
Attach an FilenameComponentListener using the addListener() method, and it will
|
||||
get called each time the user changes the filename, either by browsing for a file
|
||||
and clicking 'ok', or by typing a new filename into the box and pressing return.
|
||||
|
||||
@see FileChooser, ComboBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FilenameComponent : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FilenameComponent.
|
||||
|
||||
@param name the name for this component.
|
||||
@param currentFile the file to initially show in the box
|
||||
@param canEditFilename if true, the user can manually edit the filename; if false,
|
||||
they can only change it by browsing for a new file
|
||||
@param isDirectory if true, the file will be treated as a directory, and
|
||||
an appropriate directory browser used
|
||||
@param isForSaving if true, the file browser will allow non-existent files to
|
||||
be picked, as the file is assumed to be used for saving rather
|
||||
than loading
|
||||
@param fileBrowserWildcard a wildcard pattern to use in the file browser - e.g. "*.txt;*.foo".
|
||||
If an empty string is passed in, then the pattern is assumed to be "*"
|
||||
@param enforcedSuffix if this is non-empty, it is treated as a suffix that will be added
|
||||
to any filenames that are entered or chosen
|
||||
@param textWhenNothingSelected the message to display in the box before any filename is entered. (This
|
||||
will only appear if the initial file isn't valid)
|
||||
*/
|
||||
FilenameComponent (const String& name,
|
||||
const File& currentFile,
|
||||
bool canEditFilename,
|
||||
bool isDirectory,
|
||||
bool isForSaving,
|
||||
const String& fileBrowserWildcard,
|
||||
const String& enforcedSuffix,
|
||||
const String& textWhenNothingSelected);
|
||||
|
||||
/** Destructor. */
|
||||
~FilenameComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the currently displayed filename. */
|
||||
File getCurrentFile() const;
|
||||
|
||||
/** Returns the raw text that the user has entered. */
|
||||
String getCurrentFileText() const;
|
||||
|
||||
/** Changes the current filename.
|
||||
|
||||
@param newFile the new filename to use
|
||||
@param addToRecentlyUsedList if true, the filename will also be added to the
|
||||
drop-down list of recent files.
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the filename has changed.
|
||||
*/
|
||||
void setCurrentFile (File newFile,
|
||||
bool addToRecentlyUsedList,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes whether the use can type into the filename box.
|
||||
*/
|
||||
void setFilenameIsEditable (bool shouldBeEditable);
|
||||
|
||||
/** Sets a file or directory to be the default starting point for the browser to show.
|
||||
|
||||
This is only used if the current file hasn't been set.
|
||||
*/
|
||||
void setDefaultBrowseTarget (const File& newDefaultDirectory);
|
||||
|
||||
/** This can be overridden to return a custom location that you want the dialog box
|
||||
to show when the browse button is pushed.
|
||||
The default implementation of this method will return either the current file
|
||||
(if one has been chosen) or the location that was set by setDefaultBrowseTarget().
|
||||
*/
|
||||
virtual File getLocationToBrowse();
|
||||
|
||||
/** Returns all the entries on the recent files list.
|
||||
|
||||
This can be used in conjunction with setRecentlyUsedFilenames() for saving the
|
||||
state of this list.
|
||||
|
||||
@see setRecentlyUsedFilenames
|
||||
*/
|
||||
StringArray getRecentlyUsedFilenames() const;
|
||||
|
||||
/** Sets all the entries on the recent files list.
|
||||
|
||||
This can be used in conjunction with getRecentlyUsedFilenames() for saving the
|
||||
state of this list.
|
||||
|
||||
@see getRecentlyUsedFilenames, addRecentlyUsedFile
|
||||
*/
|
||||
void setRecentlyUsedFilenames (const StringArray& filenames);
|
||||
|
||||
/** Adds an entry to the recently-used files dropdown list.
|
||||
|
||||
If the file is already in the list, it will be moved to the top. A limit
|
||||
is also placed on the number of items that are kept in the list.
|
||||
|
||||
@see getRecentlyUsedFilenames, setRecentlyUsedFilenames, setMaxNumberOfRecentFiles
|
||||
*/
|
||||
void addRecentlyUsedFile (const File& file);
|
||||
|
||||
/** Changes the limit for the number of files that will be stored in the recent-file list.
|
||||
*/
|
||||
void setMaxNumberOfRecentFiles (int newMaximum);
|
||||
|
||||
/** Changes the text shown on the 'browse' button.
|
||||
|
||||
By default this button just says "..." but you can change it. The button itself
|
||||
can be changed using the look-and-feel classes, so it might not actually have any
|
||||
text on it.
|
||||
*/
|
||||
void setBrowseButtonText (const String& browseButtonText);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener that will be called when the selected file is changed. */
|
||||
void addListener (FilenameComponentListener* listener);
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (FilenameComponentListener* listener);
|
||||
|
||||
/** Gives the component a tooltip. */
|
||||
void setTooltip (const String& newTooltip) override;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual Button* createFilenameComponentBrowseButton (const String& text) = 0;
|
||||
virtual void layoutFilenameComponent (FilenameComponent&, ComboBox* filenameBox, Button* browseButton) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintOverChildren (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragEnter (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragExit (const StringArray&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
void showChooser();
|
||||
|
||||
ComboBox filenameBox;
|
||||
String lastFilename;
|
||||
std::unique_ptr<Button> browseButton;
|
||||
int maxRecentFiles = 30;
|
||||
bool isDir, isSaving, isFileDragOver = false;
|
||||
String wildcard, enforcedSuffix, browseButtonText;
|
||||
ListenerList <FilenameComponentListener> listeners;
|
||||
File defaultBrowseFile;
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Listens for events happening to a FilenameComponent.
|
||||
|
||||
Use FilenameComponent::addListener() and FilenameComponent::removeListener() to
|
||||
register one of these objects for event callbacks when the filename is changed.
|
||||
|
||||
@see FilenameComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FilenameComponentListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~FilenameComponentListener() = default;
|
||||
|
||||
/** This method is called after the FilenameComponent's file has been changed. */
|
||||
virtual void filenameComponentChanged (FilenameComponent* fileComponentThatHasChanged) = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Shows a filename as an editable text box, with a 'browse' button and a
|
||||
drop-down list for recently selected files.
|
||||
|
||||
A handy component for dialogue boxes where you want the user to be able to
|
||||
select a file or directory.
|
||||
|
||||
Attach an FilenameComponentListener using the addListener() method, and it will
|
||||
get called each time the user changes the filename, either by browsing for a file
|
||||
and clicking 'ok', or by typing a new filename into the box and pressing return.
|
||||
|
||||
@see FileChooser, ComboBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FilenameComponent : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FilenameComponent.
|
||||
|
||||
@param name the name for this component.
|
||||
@param currentFile the file to initially show in the box
|
||||
@param canEditFilename if true, the user can manually edit the filename; if false,
|
||||
they can only change it by browsing for a new file
|
||||
@param isDirectory if true, the file will be treated as a directory, and
|
||||
an appropriate directory browser used
|
||||
@param isForSaving if true, the file browser will allow non-existent files to
|
||||
be picked, as the file is assumed to be used for saving rather
|
||||
than loading
|
||||
@param fileBrowserWildcard a wildcard pattern to use in the file browser - e.g. "*.txt;*.foo".
|
||||
If an empty string is passed in, then the pattern is assumed to be "*"
|
||||
@param enforcedSuffix if this is non-empty, it is treated as a suffix that will be added
|
||||
to any filenames that are entered or chosen
|
||||
@param textWhenNothingSelected the message to display in the box before any filename is entered. (This
|
||||
will only appear if the initial file isn't valid)
|
||||
*/
|
||||
FilenameComponent (const String& name,
|
||||
const File& currentFile,
|
||||
bool canEditFilename,
|
||||
bool isDirectory,
|
||||
bool isForSaving,
|
||||
const String& fileBrowserWildcard,
|
||||
const String& enforcedSuffix,
|
||||
const String& textWhenNothingSelected);
|
||||
|
||||
/** Destructor. */
|
||||
~FilenameComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the currently displayed filename. */
|
||||
File getCurrentFile() const;
|
||||
|
||||
/** Returns the raw text that the user has entered. */
|
||||
String getCurrentFileText() const;
|
||||
|
||||
/** Changes the current filename.
|
||||
|
||||
@param newFile the new filename to use
|
||||
@param addToRecentlyUsedList if true, the filename will also be added to the
|
||||
drop-down list of recent files.
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the filename has changed.
|
||||
*/
|
||||
void setCurrentFile (File newFile,
|
||||
bool addToRecentlyUsedList,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes whether the use can type into the filename box.
|
||||
*/
|
||||
void setFilenameIsEditable (bool shouldBeEditable);
|
||||
|
||||
/** Sets a file or directory to be the default starting point for the browser to show.
|
||||
|
||||
This is only used if the current file hasn't been set.
|
||||
*/
|
||||
void setDefaultBrowseTarget (const File& newDefaultDirectory);
|
||||
|
||||
/** This can be overridden to return a custom location that you want the dialog box
|
||||
to show when the browse button is pushed.
|
||||
The default implementation of this method will return either the current file
|
||||
(if one has been chosen) or the location that was set by setDefaultBrowseTarget().
|
||||
*/
|
||||
virtual File getLocationToBrowse();
|
||||
|
||||
/** Returns all the entries on the recent files list.
|
||||
|
||||
This can be used in conjunction with setRecentlyUsedFilenames() for saving the
|
||||
state of this list.
|
||||
|
||||
@see setRecentlyUsedFilenames
|
||||
*/
|
||||
StringArray getRecentlyUsedFilenames() const;
|
||||
|
||||
/** Sets all the entries on the recent files list.
|
||||
|
||||
This can be used in conjunction with getRecentlyUsedFilenames() for saving the
|
||||
state of this list.
|
||||
|
||||
@see getRecentlyUsedFilenames, addRecentlyUsedFile
|
||||
*/
|
||||
void setRecentlyUsedFilenames (const StringArray& filenames);
|
||||
|
||||
/** Adds an entry to the recently-used files dropdown list.
|
||||
|
||||
If the file is already in the list, it will be moved to the top. A limit
|
||||
is also placed on the number of items that are kept in the list.
|
||||
|
||||
@see getRecentlyUsedFilenames, setRecentlyUsedFilenames, setMaxNumberOfRecentFiles
|
||||
*/
|
||||
void addRecentlyUsedFile (const File& file);
|
||||
|
||||
/** Changes the limit for the number of files that will be stored in the recent-file list.
|
||||
*/
|
||||
void setMaxNumberOfRecentFiles (int newMaximum);
|
||||
|
||||
/** Changes the text shown on the 'browse' button.
|
||||
|
||||
By default this button just says "..." but you can change it. The button itself
|
||||
can be changed using the look-and-feel classes, so it might not actually have any
|
||||
text on it.
|
||||
*/
|
||||
void setBrowseButtonText (const String& browseButtonText);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener that will be called when the selected file is changed. */
|
||||
void addListener (FilenameComponentListener* listener);
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (FilenameComponentListener* listener);
|
||||
|
||||
/** Gives the component a tooltip. */
|
||||
void setTooltip (const String& newTooltip) override;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual Button* createFilenameComponentBrowseButton (const String& text) = 0;
|
||||
virtual void layoutFilenameComponent (FilenameComponent&, ComboBox* filenameBox, Button* browseButton) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintOverChildren (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragEnter (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragExit (const StringArray&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
void showChooser();
|
||||
|
||||
ComboBox filenameBox;
|
||||
String lastFilename;
|
||||
std::unique_ptr<Button> browseButton;
|
||||
int maxRecentFiles = 30;
|
||||
bool isDir, isSaving, isFileDragOver = false;
|
||||
String wildcard, enforcedSuffix, browseButtonText;
|
||||
ListenerList <FilenameComponentListener> listeners;
|
||||
File defaultBrowseFile;
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,126 +1,126 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ImagePreviewComponent::ImagePreviewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
ImagePreviewComponent::~ImagePreviewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ImagePreviewComponent::getThumbSize (int& w, int& h) const
|
||||
{
|
||||
auto availableW = proportionOfWidth (0.97f);
|
||||
auto availableH = getHeight() - 13 * 4;
|
||||
|
||||
auto scale = jmin (1.0,
|
||||
availableW / (double) w,
|
||||
availableH / (double) h);
|
||||
|
||||
w = roundToInt (scale * w);
|
||||
h = roundToInt (scale * h);
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::selectedFileChanged (const File& file)
|
||||
{
|
||||
if (fileToLoad != file)
|
||||
{
|
||||
fileToLoad = file;
|
||||
startTimer (100);
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::timerCallback()
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
currentThumbnail = Image();
|
||||
currentDetails.clear();
|
||||
repaint();
|
||||
|
||||
FileInputStream in (fileToLoad);
|
||||
|
||||
if (in.openedOk() && fileToLoad.existsAsFile())
|
||||
{
|
||||
if (auto format = ImageFileFormat::findImageFormatForStream (in))
|
||||
{
|
||||
currentThumbnail = format->decodeImage (in);
|
||||
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
auto w = currentThumbnail.getWidth();
|
||||
auto h = currentThumbnail.getHeight();
|
||||
|
||||
currentDetails
|
||||
<< fileToLoad.getFileName() << "\n"
|
||||
<< format->getFormatName() << "\n"
|
||||
<< w << " x " << h << " pixels\n"
|
||||
<< File::descriptionOfSizeInBytes (fileToLoad.getSize());
|
||||
|
||||
getThumbSize (w, h);
|
||||
|
||||
currentThumbnail = currentThumbnail.rescaled (w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::paint (Graphics& g)
|
||||
{
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
g.setFont (13.0f);
|
||||
|
||||
auto w = currentThumbnail.getWidth();
|
||||
auto h = currentThumbnail.getHeight();
|
||||
getThumbSize (w, h);
|
||||
|
||||
const int numLines = 4;
|
||||
auto totalH = 13 * numLines + h + 4;
|
||||
auto y = (getHeight() - totalH) / 2;
|
||||
|
||||
g.drawImageWithin (currentThumbnail,
|
||||
(getWidth() - w) / 2, y, w, h,
|
||||
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize,
|
||||
false);
|
||||
|
||||
g.drawFittedText (currentDetails,
|
||||
0, y + h + 4, getWidth(), 100,
|
||||
Justification::centredTop, numLines);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ImagePreviewComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ImagePreviewComponent::ImagePreviewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
ImagePreviewComponent::~ImagePreviewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ImagePreviewComponent::getThumbSize (int& w, int& h) const
|
||||
{
|
||||
auto availableW = proportionOfWidth (0.97f);
|
||||
auto availableH = getHeight() - 13 * 4;
|
||||
|
||||
auto scale = jmin (1.0,
|
||||
availableW / (double) w,
|
||||
availableH / (double) h);
|
||||
|
||||
w = roundToInt (scale * w);
|
||||
h = roundToInt (scale * h);
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::selectedFileChanged (const File& file)
|
||||
{
|
||||
if (fileToLoad != file)
|
||||
{
|
||||
fileToLoad = file;
|
||||
startTimer (100);
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::timerCallback()
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
currentThumbnail = Image();
|
||||
currentDetails.clear();
|
||||
repaint();
|
||||
|
||||
FileInputStream in (fileToLoad);
|
||||
|
||||
if (in.openedOk() && fileToLoad.existsAsFile())
|
||||
{
|
||||
if (auto format = ImageFileFormat::findImageFormatForStream (in))
|
||||
{
|
||||
currentThumbnail = format->decodeImage (in);
|
||||
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
auto w = currentThumbnail.getWidth();
|
||||
auto h = currentThumbnail.getHeight();
|
||||
|
||||
currentDetails
|
||||
<< fileToLoad.getFileName() << "\n"
|
||||
<< format->getFormatName() << "\n"
|
||||
<< w << " x " << h << " pixels\n"
|
||||
<< File::descriptionOfSizeInBytes (fileToLoad.getSize());
|
||||
|
||||
getThumbSize (w, h);
|
||||
|
||||
currentThumbnail = currentThumbnail.rescaled (w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::paint (Graphics& g)
|
||||
{
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
g.setFont (13.0f);
|
||||
|
||||
auto w = currentThumbnail.getWidth();
|
||||
auto h = currentThumbnail.getHeight();
|
||||
getThumbSize (w, h);
|
||||
|
||||
const int numLines = 4;
|
||||
auto totalH = 13 * numLines + h + 4;
|
||||
auto y = (getHeight() - totalH) / 2;
|
||||
|
||||
g.drawImageWithin (currentThumbnail,
|
||||
(getWidth() - w) / 2, y, w, h,
|
||||
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize,
|
||||
false);
|
||||
|
||||
g.drawFittedText (currentDetails,
|
||||
0, y + h + 4, getWidth(), 100,
|
||||
Justification::centredTop, numLines);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ImagePreviewComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,67 +1,67 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple preview component that shows thumbnails of image files.
|
||||
|
||||
@see FileChooserDialogBox, FilePreviewComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ImagePreviewComponent : public FilePreviewComponent,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImagePreviewComponent. */
|
||||
ImagePreviewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~ImagePreviewComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void selectedFileChanged (const File& newSelectedFile) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
File fileToLoad;
|
||||
Image currentThumbnail;
|
||||
String currentDetails;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void getThumbSize (int& w, int& h) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple preview component that shows thumbnails of image files.
|
||||
|
||||
@see FileChooserDialogBox, FilePreviewComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ImagePreviewComponent : public FilePreviewComponent,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImagePreviewComponent. */
|
||||
ImagePreviewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~ImagePreviewComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void selectedFileChanged (const File& newSelectedFile) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
File fileToLoad;
|
||||
Image currentThumbnail;
|
||||
String currentDetails;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void getThumbSize (int& w, int& h) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
Reference in New Issue
Block a user