fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
283
modules/juce_gui_basics/filebrowser/juce_ContentSharer.cpp
Normal file
283
modules/juce_gui_basics/filebrowser/juce_ContentSharer.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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_IOS || JUCE_ANDROID
|
||||
//==============================================================================
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
File tempFile = File::createTempFile ("data");
|
||||
|
||||
if (tempFile.create().wasOk())
|
||||
{
|
||||
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
|
||||
|
||||
if (outputStream != nullptr)
|
||||
{
|
||||
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_IOS || JUCE_ANDROID
|
||||
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_IOS || JUCE_ANDROID
|
||||
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 = static_cast<std::function<void (bool, const String&)>&&> (callbackToUse);
|
||||
|
||||
pimpl.reset (createPimpl());
|
||||
}
|
||||
#endif
|
||||
|
||||
void ContentSharer::shareText (const String& text,
|
||||
std::function<void (bool, const String&)> callbackToUse)
|
||||
{
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
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_IOS || JUCE_ANDROID
|
||||
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_IOS || JUCE_ANDROID
|
||||
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_IOS || JUCE_ANDROID
|
||||
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_IOS || JUCE_ANDROID
|
||||
pimpl.reset();
|
||||
#endif
|
||||
|
||||
if (cb)
|
||||
cb (succeeded, error);
|
||||
}
|
||||
|
||||
void ContentSharer::deleteTemporaryFiles()
|
||||
{
|
||||
for (auto& f : temporaryFiles)
|
||||
f.deleteFile();
|
||||
|
||||
temporaryFiles.clear();
|
||||
}
|
||||
|
||||
} // namespace juce
|
151
modules/juce_gui_basics/filebrowser/juce_ContentSharer.h
Normal file
151
modules/juce_gui_basics/filebrowser/juce_ContentSharer.h
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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:
|
||||
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_IOS || JUCE_ANDROID
|
||||
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&);
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend void* juce_contentSharerOpenFile (void*, void*, void*);
|
||||
friend void* juce_contentSharerQuery (void*, void*, void*, void*, void*, void*);
|
||||
friend void* juce_contentSharerGetStreamTypes (void*, void*);
|
||||
friend void juce_contentSharingCompleted (int);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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
|
@ -0,0 +1,268 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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),
|
||||
fileTypeFlags (File::ignoreHiddenFiles | File::findFiles),
|
||||
shouldStop (true)
|
||||
{
|
||||
}
|
||||
|
||||
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 speciify 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);
|
||||
}
|
||||
|
||||
int 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.reset();
|
||||
}
|
||||
|
||||
void DirectoryContentsList::clear()
|
||||
{
|
||||
stopSearching();
|
||||
|
||||
if (files.size() > 0)
|
||||
{
|
||||
files.clear();
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::refresh()
|
||||
{
|
||||
clear();
|
||||
|
||||
if (root.isDirectory())
|
||||
{
|
||||
fileFindHandle.reset (new DirectoryIterator (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()
|
||||
{
|
||||
const uint32 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)
|
||||
{
|
||||
bool fileFoundIsDir, isHidden, isReadOnly;
|
||||
int64 fileSize;
|
||||
Time modTime, creationTime;
|
||||
|
||||
if (fileFindHandle->next (&fileFoundIsDir, &isHidden, &fileSize,
|
||||
&modTime, &creationTime, &isReadOnly))
|
||||
{
|
||||
if (addFile (fileFindHandle->getFile(), fileFoundIsDir,
|
||||
fileSize, modTime, creationTime, isReadOnly))
|
||||
{
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fileFindHandle.reset();
|
||||
}
|
||||
|
||||
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)))
|
||||
{
|
||||
std::unique_ptr<FileInfo> info (new 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 (info.release());
|
||||
|
||||
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
|
225
modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h
Normal file
225
modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
TimeSliceThread& thread;
|
||||
int fileTypeFlags;
|
||||
|
||||
CriticalSection fileListLock;
|
||||
OwnedArray<FileInfo> files;
|
||||
|
||||
std::unique_ptr<DirectoryIterator> fileFindHandle;
|
||||
bool volatile shouldStop;
|
||||
|
||||
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
|
@ -0,0 +1,613 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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
|
||||
{
|
||||
|
||||
FileBrowserComponent::FileBrowserComponent (int flags_,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter_,
|
||||
FilePreviewComponent* previewComp_)
|
||||
: FileFilter ({}),
|
||||
fileFilter (fileFilter_),
|
||||
flags (flags_),
|
||||
previewComp (previewComp_),
|
||||
currentPathBox ("path"),
|
||||
fileLabel ("f", TRANS ("file:")),
|
||||
thread ("JUCE FileBrowser"),
|
||||
wasProcessActive (true)
|
||||
{
|
||||
// You need to specify one or other of the open/save flags..
|
||||
jassert ((flags & (saveMode | openMode)) != 0);
|
||||
jassert ((flags & (saveMode | openMode)) != (saveMode | openMode));
|
||||
|
||||
// You need to specify at least one of these flags..
|
||||
jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0);
|
||||
|
||||
String filename;
|
||||
|
||||
if (initialFileOrDirectory == File())
|
||||
{
|
||||
currentRoot = File::getCurrentWorkingDirectory();
|
||||
}
|
||||
else if (initialFileOrDirectory.isDirectory())
|
||||
{
|
||||
currentRoot = initialFileOrDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
chosenFiles.add (initialFileOrDirectory);
|
||||
currentRoot = initialFileOrDirectory.getParentDirectory();
|
||||
filename = initialFileOrDirectory.getFileName();
|
||||
}
|
||||
|
||||
fileList.reset (new DirectoryContentsList (this, thread));
|
||||
fileList->setDirectory (currentRoot, true, true);
|
||||
|
||||
if ((flags & useTreeView) != 0)
|
||||
{
|
||||
auto tree = new FileTreeComponent (*fileList);
|
||||
fileListComponent.reset (tree);
|
||||
|
||||
if ((flags & canSelectMultipleItems) != 0)
|
||||
tree->setMultiSelectEnabled (true);
|
||||
|
||||
addAndMakeVisible (tree);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto list = new FileListComponent (*fileList);
|
||||
fileListComponent.reset (list);
|
||||
list->setOutlineThickness (1);
|
||||
|
||||
if ((flags & canSelectMultipleItems) != 0)
|
||||
list->setMultipleSelectionEnabled (true);
|
||||
|
||||
addAndMakeVisible (list);
|
||||
}
|
||||
|
||||
fileListComponent->addListener (this);
|
||||
|
||||
addAndMakeVisible (currentPathBox);
|
||||
currentPathBox.setEditableText (true);
|
||||
resetRecentPaths();
|
||||
currentPathBox.onChange = [this] { updateSelectedPath(); };
|
||||
|
||||
addAndMakeVisible (filenameBox);
|
||||
filenameBox.setMultiLine (false);
|
||||
filenameBox.setSelectAllWhenFocused (true);
|
||||
filenameBox.setText (filename, false);
|
||||
filenameBox.onTextChange = [this] { sendListenerChangeMessage(); };
|
||||
filenameBox.onReturnKey = [this] { changeFilename(); };
|
||||
filenameBox.onFocusLost = [this]
|
||||
{
|
||||
if (! isSaveMode())
|
||||
selectionChanged();
|
||||
};
|
||||
|
||||
filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0);
|
||||
|
||||
addAndMakeVisible (fileLabel);
|
||||
fileLabel.attachToComponent (&filenameBox, true);
|
||||
|
||||
goUpButton.reset (getLookAndFeel().createFileBrowserGoUpButton());
|
||||
addAndMakeVisible (goUpButton.get());
|
||||
goUpButton->onClick = [this] { goUp(); };
|
||||
goUpButton->setTooltip (TRANS ("Go up to parent directory"));
|
||||
|
||||
if (previewComp != nullptr)
|
||||
addAndMakeVisible (previewComp);
|
||||
|
||||
lookAndFeelChanged();
|
||||
|
||||
setRoot (currentRoot);
|
||||
|
||||
if (filename.isNotEmpty())
|
||||
setFileName (filename);
|
||||
|
||||
thread.startThread (4);
|
||||
|
||||
startTimer (2000);
|
||||
}
|
||||
|
||||
FileBrowserComponent::~FileBrowserComponent()
|
||||
{
|
||||
fileListComponent.reset();
|
||||
fileList.reset();
|
||||
thread.stopThread (10000);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::addListener (FileBrowserListener* const newListener)
|
||||
{
|
||||
listeners.add (newListener);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::removeListener (FileBrowserListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileBrowserComponent::isSaveMode() const noexcept
|
||||
{
|
||||
return (flags & saveMode) != 0;
|
||||
}
|
||||
|
||||
int FileBrowserComponent::getNumSelectedFiles() const noexcept
|
||||
{
|
||||
if (chosenFiles.isEmpty() && currentFileIsValid())
|
||||
return 1;
|
||||
|
||||
return chosenFiles.size();
|
||||
}
|
||||
|
||||
File FileBrowserComponent::getSelectedFile (int index) const noexcept
|
||||
{
|
||||
if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty())
|
||||
return currentRoot;
|
||||
|
||||
if (! filenameBox.isReadOnly())
|
||||
return currentRoot.getChildFile (filenameBox.getText());
|
||||
|
||||
return chosenFiles[index];
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::currentFileIsValid() const
|
||||
{
|
||||
auto f = getSelectedFile (0);
|
||||
|
||||
if (isSaveMode())
|
||||
return (flags & canSelectDirectories) != 0 || ! f.isDirectory();
|
||||
|
||||
return f.exists();
|
||||
}
|
||||
|
||||
File FileBrowserComponent::getHighlightedFile() const noexcept
|
||||
{
|
||||
return fileListComponent->getSelectedFile (0);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::deselectAllFiles()
|
||||
{
|
||||
fileListComponent->deselectAllFiles();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileBrowserComponent::isFileSuitable (const File& file) const
|
||||
{
|
||||
return (flags & canSelectFiles) != 0
|
||||
&& (fileFilter == nullptr || fileFilter->isFileSuitable (file));
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::isDirectorySuitable (const File&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const
|
||||
{
|
||||
if (f.isDirectory())
|
||||
return (flags & canSelectDirectories) != 0
|
||||
&& (fileFilter == nullptr || fileFilter->isDirectorySuitable (f));
|
||||
|
||||
return (flags & canSelectFiles) != 0 && f.exists()
|
||||
&& (fileFilter == nullptr || fileFilter->isFileSuitable (f));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const File& FileBrowserComponent::getRoot() const
|
||||
{
|
||||
return currentRoot;
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setRoot (const File& newRootDirectory)
|
||||
{
|
||||
bool callListeners = false;
|
||||
|
||||
if (currentRoot != newRootDirectory)
|
||||
{
|
||||
callListeners = true;
|
||||
fileListComponent->scrollToTop();
|
||||
|
||||
String path (newRootDirectory.getFullPathName());
|
||||
|
||||
if (path.isEmpty())
|
||||
path = File::getSeparatorString();
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
if (! rootPaths.contains (path, true))
|
||||
{
|
||||
bool alreadyListed = false;
|
||||
|
||||
for (int i = currentPathBox.getNumItems(); --i >= 0;)
|
||||
{
|
||||
if (currentPathBox.getItemText (i).equalsIgnoreCase (path))
|
||||
{
|
||||
alreadyListed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! alreadyListed)
|
||||
currentPathBox.addItem (path, currentPathBox.getNumItems() + 2);
|
||||
}
|
||||
}
|
||||
|
||||
currentRoot = newRootDirectory;
|
||||
fileList->setDirectory (currentRoot, true, true);
|
||||
|
||||
if (auto* tree = dynamic_cast<FileTreeComponent*> (fileListComponent.get()))
|
||||
tree->refresh();
|
||||
|
||||
auto currentRootName = currentRoot.getFullPathName();
|
||||
|
||||
if (currentRootName.isEmpty())
|
||||
currentRootName = File::getSeparatorString();
|
||||
|
||||
currentPathBox.setText (currentRootName, dontSendNotification);
|
||||
|
||||
goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
|
||||
&& currentRoot.getParentDirectory() != currentRoot);
|
||||
|
||||
if (callListeners)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.browserRootChanged (currentRoot); });
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFileName (const String& newName)
|
||||
{
|
||||
filenameBox.setText (newName, true);
|
||||
|
||||
fileListComponent->setSelectedFile (currentRoot.getChildFile (newName));
|
||||
}
|
||||
|
||||
void FileBrowserComponent::resetRecentPaths()
|
||||
{
|
||||
currentPathBox.clear();
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
for (int i = 0; i < rootNames.size(); ++i)
|
||||
{
|
||||
if (rootNames[i].isEmpty())
|
||||
currentPathBox.addSeparator();
|
||||
else
|
||||
currentPathBox.addItem (rootNames[i], i + 1);
|
||||
}
|
||||
|
||||
currentPathBox.addSeparator();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::goUp()
|
||||
{
|
||||
setRoot (getRoot().getParentDirectory());
|
||||
}
|
||||
|
||||
void FileBrowserComponent::refresh()
|
||||
{
|
||||
fileList->refresh();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFileFilter (const FileFilter* const newFileFilter)
|
||||
{
|
||||
if (fileFilter != newFileFilter)
|
||||
{
|
||||
fileFilter = newFileFilter;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
String FileBrowserComponent::getActionVerb() const
|
||||
{
|
||||
return isSaveMode() ? ((flags & canSelectDirectories) != 0 ? TRANS("Choose")
|
||||
: TRANS("Save"))
|
||||
: TRANS("Open");
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFilenameBoxLabel (const String& name)
|
||||
{
|
||||
fileLabel.setText (name, dontSendNotification);
|
||||
}
|
||||
|
||||
FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept
|
||||
{
|
||||
return previewComp;
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent* FileBrowserComponent::getDisplayComponent() const noexcept
|
||||
{
|
||||
return fileListComponent.get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::resized()
|
||||
{
|
||||
getLookAndFeel()
|
||||
.layoutFileBrowserComponent (*this, fileListComponent.get(), previewComp,
|
||||
¤tPathBox, &filenameBox, goUpButton.get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::lookAndFeelChanged()
|
||||
{
|
||||
currentPathBox.setColour (ComboBox::backgroundColourId, findColour (currentPathBoxBackgroundColourId));
|
||||
currentPathBox.setColour (ComboBox::textColourId, findColour (currentPathBoxTextColourId));
|
||||
currentPathBox.setColour (ComboBox::arrowColourId, findColour (currentPathBoxArrowColourId));
|
||||
|
||||
filenameBox.setColour (TextEditor::backgroundColourId, findColour (filenameBoxBackgroundColourId));
|
||||
filenameBox.setColour (TextEditor::textColourId, findColour (filenameBoxTextColourId));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::sendListenerChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
if (previewComp != nullptr)
|
||||
previewComp->selectedFileChanged (getSelectedFile (0));
|
||||
|
||||
// You shouldn't delete the browser when the file gets changed!
|
||||
jassert (! checker.shouldBailOut());
|
||||
|
||||
listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
|
||||
}
|
||||
|
||||
void FileBrowserComponent::selectionChanged()
|
||||
{
|
||||
StringArray newFilenames;
|
||||
bool resetChosenFiles = true;
|
||||
|
||||
for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i)
|
||||
{
|
||||
const File f (fileListComponent->getSelectedFile (i));
|
||||
|
||||
if (isFileOrDirSuitable (f))
|
||||
{
|
||||
if (resetChosenFiles)
|
||||
{
|
||||
chosenFiles.clear();
|
||||
resetChosenFiles = false;
|
||||
}
|
||||
|
||||
chosenFiles.add (f);
|
||||
newFilenames.add (f.getRelativePathFrom (getRoot()));
|
||||
}
|
||||
}
|
||||
|
||||
if (newFilenames.size() > 0)
|
||||
filenameBox.setText (newFilenames.joinIntoString (", "), false);
|
||||
|
||||
sendListenerChangeMessage();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (f, e); });
|
||||
}
|
||||
|
||||
void FileBrowserComponent::fileDoubleClicked (const File& f)
|
||||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
|
||||
if ((flags & canSelectDirectories) != 0 && (flags & doNotClearFileNameOnRootChange) == 0)
|
||||
filenameBox.setText ({});
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (f); });
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::browserRootChanged (const File&) {}
|
||||
|
||||
bool FileBrowserComponent::keyPressed (const KeyPress& key)
|
||||
{
|
||||
#if JUCE_LINUX || JUCE_WINDOWS
|
||||
if (key.getModifiers().isCommandDown()
|
||||
&& (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
|
||||
{
|
||||
fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
|
||||
fileList->refresh();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
ignoreUnused (key);
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::changeFilename()
|
||||
{
|
||||
if (filenameBox.getText().containsChar (File::getSeparatorChar()))
|
||||
{
|
||||
auto f = currentRoot.getChildFile (filenameBox.getText());
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
chosenFiles.clear();
|
||||
|
||||
if ((flags & doNotClearFileNameOnRootChange) == 0)
|
||||
filenameBox.setText ({});
|
||||
}
|
||||
else
|
||||
{
|
||||
setRoot (f.getParentDirectory());
|
||||
chosenFiles.clear();
|
||||
chosenFiles.add (f);
|
||||
filenameBox.setText (f.getFileName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fileDoubleClicked (getSelectedFile (0));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::updateSelectedPath()
|
||||
{
|
||||
auto newText = currentPathBox.getText().trim().unquoted();
|
||||
|
||||
if (newText.isNotEmpty())
|
||||
{
|
||||
auto index = currentPathBox.getSelectedId() - 1;
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
if (rootPaths[index].isNotEmpty())
|
||||
{
|
||||
setRoot (File (rootPaths[index]));
|
||||
}
|
||||
else
|
||||
{
|
||||
File f (newText);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (f.getParentDirectory() == f)
|
||||
break;
|
||||
|
||||
f = f.getParentDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootPaths)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
Array<File> roots;
|
||||
File::findFileSystemRoots (roots);
|
||||
rootPaths.clear();
|
||||
|
||||
for (int i = 0; i < roots.size(); ++i)
|
||||
{
|
||||
const File& drive = roots.getReference(i);
|
||||
|
||||
String name (drive.getFullPathName());
|
||||
rootPaths.add (name);
|
||||
|
||||
if (drive.isOnHardDisk())
|
||||
{
|
||||
String volume (drive.getVolumeLabel());
|
||||
|
||||
if (volume.isEmpty())
|
||||
volume = TRANS("Hard Drive");
|
||||
|
||||
name << " [" << volume << ']';
|
||||
}
|
||||
else if (drive.isOnCDRomDrive())
|
||||
{
|
||||
name << " [" << TRANS("CD/DVD drive") << ']';
|
||||
}
|
||||
|
||||
rootNames.add (name);
|
||||
}
|
||||
|
||||
rootPaths.add ({});
|
||||
rootNames.add ({});
|
||||
|
||||
rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Documents"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Music"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Pictures"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Desktop"));
|
||||
|
||||
#elif JUCE_MAC
|
||||
rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Home folder"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Documents"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Music"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Pictures"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Desktop"));
|
||||
|
||||
rootPaths.add ({});
|
||||
rootNames.add ({});
|
||||
|
||||
for (auto& volume : File ("/Volumes").findChildFiles (File::findDirectories, false))
|
||||
{
|
||||
if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.'))
|
||||
{
|
||||
rootPaths.add (volume.getFullPathName());
|
||||
rootNames.add (volume.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
rootPaths.add ("/");
|
||||
rootNames.add ("/");
|
||||
rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Home folder"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Desktop"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths)
|
||||
{
|
||||
getDefaultRoots (rootNames, rootPaths);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::timerCallback()
|
||||
{
|
||||
const bool isProcessActive = Process::isForegroundProcess();
|
||||
|
||||
if (wasProcessActive != isProcessActive)
|
||||
{
|
||||
wasProcessActive = isProcessActive;
|
||||
|
||||
if (isProcessActive && fileList != nullptr)
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
296
modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h
Normal file
296
modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
//==============================================================================
|
||||
/** 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() {}
|
||||
|
||||
// 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;
|
||||
|
||||
void timerCallback() override;
|
||||
void sendListenerChangeMessage();
|
||||
bool isFileOrDirSuitable (const File&) const;
|
||||
void updateSelectedPath();
|
||||
void changeFilename();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBrowserComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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
|
269
modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp
Normal file
269
modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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))
|
||||
{}
|
||||
|
||||
~NonNative()
|
||||
{
|
||||
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)
|
||||
: title (chooserBoxTitle),
|
||||
filters (fileFilters),
|
||||
startingFile (currentFileOrDirectory),
|
||||
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.reset (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 = static_cast<std::function<void (const FileChooser&)>&&> (callback);
|
||||
|
||||
pimpl.reset (createPimpl (flags, previewComp));
|
||||
pimpl->launch();
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new 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
|
334
modules/juce_gui_basics/filebrowser/juce_FileChooser.h
Normal file
334
modules/juce_gui_basics/filebrowser/juce_FileChooser.h
Normal file
@ -0,0 +1,334 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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.
|
||||
|
||||
To use a FileChooser:
|
||||
- create one (as a local stack variable is the neatest way)
|
||||
- call one of its browseFor.. methods
|
||||
- if this returns true, the user has selected a file, so you can retrieve it
|
||||
with the getResult() method.
|
||||
|
||||
e.g. @code
|
||||
void loadMooseFile()
|
||||
{
|
||||
FileChooser myChooser ("Please select the moose you want to load...",
|
||||
File::getSpecialLocation (File::userHomeDirectory),
|
||||
"*.moose");
|
||||
|
||||
if (myChooser.browseForFileToOpen())
|
||||
{
|
||||
File mooseFile (myChooser.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.
|
||||
|
||||
@see browseForFileToOpen, browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
FileChooser (const String& dialogBoxTitle,
|
||||
const File& initialFileOrDirectory = File(),
|
||||
const String& filePatternsAllowed = String(),
|
||||
bool useOSNativeDialogBox = true,
|
||||
bool treatFilePackagesAsDirectories = false);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooser();
|
||||
|
||||
//==============================================================================
|
||||
/** 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);
|
||||
|
||||
/** 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
|
||||
canceled or a file is selected. To abort the file selection, simply
|
||||
delete the FileChooser object.
|
||||
|
||||
You can use the ModalCallbackFunction::create method to wrap a lambda
|
||||
into a modal Callback 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, om 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, om 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 leat once.
|
||||
*/
|
||||
static bool isPlatformDialogAvailable();
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
class Native;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String title, filters;
|
||||
File startingFile;
|
||||
Array<URL> results;
|
||||
const bool useNativeDialogBox;
|
||||
const bool treatFilePackagesAsDirs;
|
||||
std::function<void (const FileChooser&)> asyncCallback;
|
||||
|
||||
//==============================================================================
|
||||
void finished (const Array<URL>&);
|
||||
|
||||
//==============================================================================
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() {}
|
||||
|
||||
virtual void launch() = 0;
|
||||
virtual void runModally() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
//==============================================================================
|
||||
Pimpl* createPimpl (int, FilePreviewComponent*);
|
||||
static Pimpl* showPlatformDialog (FileChooser&, int,
|
||||
FilePreviewComponent*);
|
||||
|
||||
class NonNative;
|
||||
friend class NonNative;
|
||||
friend class Native;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,257 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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),
|
||||
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)
|
||||
: ResizableWindow (name, backgroundColour, true),
|
||||
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();
|
||||
}
|
||||
|
||||
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 (AlertWindow::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"),
|
||||
AlertWindow::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 (AlertWindow::WarningIcon,
|
||||
TRANS ("New Folder"),
|
||||
TRANS ("Couldn't create the folder!"));
|
||||
|
||||
content->chooserComponent.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
157
modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h
Normal file
157
modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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.
|
||||
|
||||
To use one of these, create it and call its show() method. e.g.
|
||||
|
||||
@code
|
||||
{
|
||||
WildcardFileFilter wildcardFilter ("*.foo", String(), "Foo files");
|
||||
|
||||
FileBrowserComponent browser (FileBrowserComponent::canSelectFiles,
|
||||
File(),
|
||||
&wildcardFilter,
|
||||
nullptr);
|
||||
|
||||
FileChooserDialogBox dialogBox ("Open some kind of file",
|
||||
"Please choose some kind of file that you want to open...",
|
||||
browser,
|
||||
false,
|
||||
Colours::lightgrey);
|
||||
|
||||
if (dialogBox.show())
|
||||
{
|
||||
File selectedFile = browser.getSelectedFile (0);
|
||||
|
||||
...etc..
|
||||
}
|
||||
}
|
||||
@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
|
||||
|
||||
@see FileBrowserComponent, FilePreviewComponent
|
||||
*/
|
||||
FileChooserDialogBox (const String& title,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& browserComponent,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
Colour backgroundColour);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooserDialogBox();
|
||||
|
||||
//==============================================================================
|
||||
#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
|
270
modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp
Normal file
270
modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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())
|
||||
{
|
||||
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()
|
||||
{
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
95
modules/juce_gui_basics/filebrowser/juce_FileListComponent.h
Normal file
95
modules/juce_gui_basics/filebrowser/juce_FileListComponent.h
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
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
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
/** Called to indicate that the user's currently selected file has changed.
|
||||
|
||||
@param newSelectedFile the newly selected file or directory, which may be
|
||||
a defualt File() object if none is selected.
|
||||
*/
|
||||
virtual void selectedFileChanged (const File& newSelectedFile) = 0;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePreviewComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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 (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)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser chooser (TRANS("Change folder..."), path[row], "*");
|
||||
|
||||
if (chooser.browseForDirectory())
|
||||
{
|
||||
path.remove (row);
|
||||
path.add (chooser.getResult(), row);
|
||||
changed();
|
||||
}
|
||||
#else
|
||||
ignoreUnused (row);
|
||||
#endif
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser chooser (TRANS("Add a folder..."), start, "*");
|
||||
|
||||
if (chooser.browseForDirectory())
|
||||
path.add (chooser.getResult(), listBox.getSelectedRow());
|
||||
|
||||
changed();
|
||||
#else
|
||||
jassertfalse; // needs rewriting to deal with non-modal environments
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
|
||||
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
|
335
modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp
Normal file
335
modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp
Normal file
@ -0,0 +1,335 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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&);
|
||||
|
||||
//==============================================================================
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
jassert (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();
|
||||
|
||||
OptionalScopedPointer<DirectoryContentsList> newPointer (newList, canDeleteList);
|
||||
subContentsList = newPointer;
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
106
modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h
Normal file
106
modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
267
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp
Normal file
267
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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());
|
||||
}
|
||||
|
||||
KeyboardFocusTraverser* FilenameComponent::createFocusTraverser()
|
||||
{
|
||||
// This prevents the sub-components from grabbing focus if the
|
||||
// FilenameComponent has been set to refuse focus.
|
||||
return getWantsKeyboardFocus() ? Component::createFocusTraverser() : 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()
|
||||
{
|
||||
return getCurrentFile() == File() ? defaultBrowseFile
|
||||
: getCurrentFile();
|
||||
}
|
||||
|
||||
void FilenameComponent::showChooser()
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser fc (isDir ? TRANS ("Choose a new directory")
|
||||
: TRANS ("Choose a new file"),
|
||||
getLocationToBrowse(),
|
||||
wildcard);
|
||||
|
||||
if (isDir ? fc.browseForDirectory()
|
||||
: (isSaving ? fc.browseForFileToSave (false)
|
||||
: fc.browseForFileToOpen()))
|
||||
{
|
||||
setCurrentFile (fc.getResult(), true);
|
||||
}
|
||||
#else
|
||||
ignoreUnused (isSaving);
|
||||
jassertfalse; // needs rewriting to deal with non-modal environments
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|
235
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h
Normal file
235
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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() {}
|
||||
|
||||
/** 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();
|
||||
|
||||
//==============================================================================
|
||||
/** 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() {}
|
||||
|
||||
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 */
|
||||
KeyboardFocusTraverser* createFocusTraverser() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
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;
|
||||
|
||||
void showChooser();
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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
|
||||
{
|
||||
const int availableW = proportionOfWidth (0.97f);
|
||||
const int availableH = getHeight() - 13 * 4;
|
||||
|
||||
const double 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();
|
||||
|
||||
std::unique_ptr<FileInputStream> in (fileToLoad.createInputStream());
|
||||
|
||||
if (in != nullptr && in->getFile().existsAsFile())
|
||||
{
|
||||
if (ImageFileFormat* const format = ImageFileFormat::findImageFormatForStream (*in))
|
||||
{
|
||||
currentThumbnail = format->decodeImage (*in);
|
||||
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
int w = currentThumbnail.getWidth();
|
||||
int 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);
|
||||
|
||||
int w = currentThumbnail.getWidth();
|
||||
int h = currentThumbnail.getHeight();
|
||||
getThumbSize (w, h);
|
||||
|
||||
const int numLines = 4;
|
||||
const int totalH = 13 * numLines + h + 4;
|
||||
const int 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);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-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();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void selectedFileChanged (const File& newSelectedFile) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
File fileToLoad;
|
||||
Image currentThumbnail;
|
||||
String currentDetails;
|
||||
|
||||
void getThumbSize (int& w, int& h) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user