|
|
|
@@ -26,32 +26,23 @@ |
|
|
|
namespace juce
|
|
|
|
{
|
|
|
|
|
|
|
|
// Win32NativeFileChooser needs to be a reference counted object as there
|
|
|
|
// is no way for the parent to know when the dialog HWND has actually been
|
|
|
|
// created without pumping the message thread (which is forbidden when modal
|
|
|
|
// loops are disabled). However, the HWND pointer is the only way to cancel
|
|
|
|
// the dialog box. This means that the actual native FileChooser HWND may
|
|
|
|
// not have been created yet when the user deletes JUCE's FileChooser class. If this
|
|
|
|
// occurs the Win32NativeFileChooser will still have a reference count of 1 and will
|
|
|
|
// simply delete itself immediately once the HWND will have been created a while later.
|
|
|
|
class Win32NativeFileChooser : public ReferenceCountedObject,
|
|
|
|
class Win32NativeFileChooser : public std::enable_shared_from_this<Win32NativeFileChooser>,
|
|
|
|
private Thread
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using Ptr = ReferenceCountedObjectPtr<Win32NativeFileChooser>;
|
|
|
|
|
|
|
|
enum { charsAvailableForResult = 32768 };
|
|
|
|
|
|
|
|
Win32NativeFileChooser (Component* parent, int flags, FilePreviewComponent* previewComp,
|
|
|
|
const File& startingFile, const String& titleToUse,
|
|
|
|
const String& filtersToUse)
|
|
|
|
: Thread ("Native Win32 FileChooser"),
|
|
|
|
owner (parent), title (titleToUse), filtersString (filtersToUse.replaceCharacter (',', ';')),
|
|
|
|
owner (parent),
|
|
|
|
title (titleToUse),
|
|
|
|
filtersString (filtersToUse.replaceCharacter (',', ';')),
|
|
|
|
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
|
|
|
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
|
|
|
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
|
|
|
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
|
|
|
|
nativeDialogRef (nullptr), shouldCancel (0)
|
|
|
|
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0)
|
|
|
|
{
|
|
|
|
auto parentDirectory = startingFile.getParentDirectory();
|
|
|
|
|
|
|
|
@@ -95,10 +86,7 @@ public: |
|
|
|
{
|
|
|
|
jassert (! isThreadRunning());
|
|
|
|
|
|
|
|
threadHasReference.reset();
|
|
|
|
startThread();
|
|
|
|
|
|
|
|
threadHasReference.wait (-1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
@@ -112,7 +100,7 @@ public: |
|
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
|
|
|
|
customComponent = nullptr;
|
|
|
|
shouldCancel.set (1);
|
|
|
|
shouldCancel = true;
|
|
|
|
|
|
|
|
if (auto hwnd = nativeDialogRef.get())
|
|
|
|
EndDialog (hwnd, 0);
|
|
|
|
@@ -151,12 +139,11 @@ private: |
|
|
|
};
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
Component::SafePointer<Component> owner;
|
|
|
|
const Component::SafePointer<Component> owner;
|
|
|
|
String title, filtersString;
|
|
|
|
std::unique_ptr<CustomComponentHolder> customComponent;
|
|
|
|
String initialPath, returnedString;
|
|
|
|
|
|
|
|
WaitableEvent threadHasReference;
|
|
|
|
CriticalSection deletingDialog;
|
|
|
|
|
|
|
|
bool selectsDirectories, isSave, warnAboutOverwrite, selectMultiple;
|
|
|
|
@@ -164,15 +151,15 @@ private: |
|
|
|
HeapBlock<WCHAR> files;
|
|
|
|
HeapBlock<WCHAR> filters;
|
|
|
|
|
|
|
|
Atomic<HWND> nativeDialogRef;
|
|
|
|
Atomic<int> shouldCancel;
|
|
|
|
Atomic<HWND> nativeDialogRef { nullptr };
|
|
|
|
bool shouldCancel = false;
|
|
|
|
|
|
|
|
struct FreeLPWSTR
|
|
|
|
{
|
|
|
|
void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); }
|
|
|
|
};
|
|
|
|
|
|
|
|
bool showDialog (IFileDialog& dialog, bool async) const
|
|
|
|
bool showDialog (IFileDialog& dialog, bool async)
|
|
|
|
{
|
|
|
|
FILEOPENDIALOGOPTIONS flags = {};
|
|
|
|
|
|
|
|
@@ -235,7 +222,49 @@ private: |
|
|
|
if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return dialog.Show (static_cast<HWND> (async ? nullptr : owner->getWindowHandle())) == S_OK;
|
|
|
|
struct Events : public ComBaseClassHelper<IFileDialogEvents>
|
|
|
|
{
|
|
|
|
explicit Events (Win32NativeFileChooser& o) : owner (o) {}
|
|
|
|
|
|
|
|
JUCE_COMRESULT OnTypeChange (IFileDialog* d) override
|
|
|
|
{
|
|
|
|
HWND hwnd = nullptr;
|
|
|
|
IUnknown_GetWindow (d, &hwnd);
|
|
|
|
|
|
|
|
ScopedLock lock (owner.deletingDialog);
|
|
|
|
|
|
|
|
if (hwnd != nullptr)
|
|
|
|
owner.nativeDialogRef = hwnd;
|
|
|
|
|
|
|
|
return owner.shouldCancel ? S_FALSE : S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
JUCE_COMRESULT OnFolderChanging (IFileDialog*, IShellItem*) override { return S_OK; }
|
|
|
|
JUCE_COMRESULT OnFileOk (IFileDialog*) override { return S_OK; }
|
|
|
|
JUCE_COMRESULT OnFolderChange (IFileDialog*) override { return S_OK; }
|
|
|
|
JUCE_COMRESULT OnSelectionChange (IFileDialog*) override { return S_OK; }
|
|
|
|
JUCE_COMRESULT OnShareViolation (IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) override { return S_OK; }
|
|
|
|
JUCE_COMRESULT OnOverwrite (IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) override { return S_OK; }
|
|
|
|
|
|
|
|
Win32NativeFileChooser& owner;
|
|
|
|
};
|
|
|
|
|
|
|
|
DWORD cookie = 0;
|
|
|
|
dialog.Advise (new Events { *this }, &cookie);
|
|
|
|
|
|
|
|
{
|
|
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
|
|
|
|
if (shouldCancel)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto result = dialog.Show (async ? nullptr : static_cast<HWND> (owner->getWindowHandle())) == S_OK;
|
|
|
|
|
|
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
nativeDialogRef = nullptr;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
@@ -447,33 +476,21 @@ private: |
|
|
|
|
|
|
|
void run() override
|
|
|
|
{
|
|
|
|
// We use a functor rather than a lambda here because
|
|
|
|
// we want to move ownership of the Ptr into the function
|
|
|
|
// object, and C++11 doesn't support general lambda capture
|
|
|
|
struct AsyncCallback
|
|
|
|
{
|
|
|
|
AsyncCallback (Ptr p, Array<URL> r)
|
|
|
|
: ptr (std::move (p)),
|
|
|
|
results (std::move (r)) {}
|
|
|
|
// IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment
|
|
|
|
CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED);
|
|
|
|
|
|
|
|
void operator()()
|
|
|
|
{
|
|
|
|
ptr->results = std::move (results);
|
|
|
|
|
|
|
|
if (ptr->owner != nullptr)
|
|
|
|
ptr->owner->exitModalState (ptr->results.size() > 0 ? 1 : 0);
|
|
|
|
}
|
|
|
|
auto resultsCopy = openDialog (true);
|
|
|
|
auto safeOwner = owner;
|
|
|
|
std::weak_ptr<Win32NativeFileChooser> weakThis = shared_from_this();
|
|
|
|
|
|
|
|
Ptr ptr;
|
|
|
|
Array<URL> results;
|
|
|
|
};
|
|
|
|
|
|
|
|
// as long as the thread is running, don't delete this class
|
|
|
|
Ptr safeThis (this);
|
|
|
|
threadHasReference.signal();
|
|
|
|
MessageManager::callAsync ([resultsCopy, safeOwner, weakThis]
|
|
|
|
{
|
|
|
|
if (auto locked = weakThis.lock())
|
|
|
|
locked->results = resultsCopy;
|
|
|
|
|
|
|
|
auto r = openDialog (true);
|
|
|
|
MessageManager::callAsync (AsyncCallback (std::move (safeThis), std::move (r)));
|
|
|
|
if (safeOwner != nullptr)
|
|
|
|
safeOwner->exitModalState (resultsCopy.size() > 0 ? 1 : 0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
static HashMap<HWND, Win32NativeFileChooser*>& getNativeDialogList()
|
|
|
|
@@ -482,9 +499,9 @@ private: |
|
|
|
return dialogs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Win32NativeFileChooser* getNativePointerForDialog (HWND hWnd)
|
|
|
|
static Win32NativeFileChooser* getNativePointerForDialog (HWND hwnd)
|
|
|
|
{
|
|
|
|
return getNativeDialogList()[hWnd];
|
|
|
|
return getNativeDialogList()[hwnd];
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
@@ -552,7 +569,7 @@ private: |
|
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
getNativeDialogList().set (hdlg, this);
|
|
|
|
|
|
|
|
if (shouldCancel.get() != 0)
|
|
|
|
if (shouldCancel)
|
|
|
|
{
|
|
|
|
EndDialog (hdlg, 0);
|
|
|
|
}
|
|
|
|
@@ -617,7 +634,7 @@ private: |
|
|
|
{
|
|
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
|
|
|
|
if (customComponent != nullptr && shouldCancel.get() == 0)
|
|
|
|
if (customComponent != nullptr && ! shouldCancel)
|
|
|
|
{
|
|
|
|
if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent (0)))
|
|
|
|
{
|
|
|
|
@@ -715,14 +732,15 @@ private: |
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser)
|
|
|
|
};
|
|
|
|
|
|
|
|
class FileChooser::Native : public Component,
|
|
|
|
class FileChooser::Native : public std::enable_shared_from_this<Native>,
|
|
|
|
public Component,
|
|
|
|
public FileChooser::Pimpl
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp)
|
|
|
|
: owner (fileChooser),
|
|
|
|
nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile,
|
|
|
|
fileChooser.title, fileChooser.filters))
|
|
|
|
nativeFileChooser (std::make_shared<Win32NativeFileChooser> (this, flags, previewComp, fileChooser.startingFile,
|
|
|
|
fileChooser.title, fileChooser.filters))
|
|
|
|
{
|
|
|
|
auto mainMon = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
|
|
|
|
|
|
|
@@ -739,19 +757,17 @@ public: |
|
|
|
{
|
|
|
|
exitModalState (0);
|
|
|
|
nativeFileChooser->cancel();
|
|
|
|
nativeFileChooser = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void launch() override
|
|
|
|
{
|
|
|
|
SafePointer<Native> safeThis (this);
|
|
|
|
std::weak_ptr<Native> safeThis = shared_from_this();
|
|
|
|
|
|
|
|
enterModalState (true, ModalCallbackFunction::create (
|
|
|
|
[safeThis] (int)
|
|
|
|
{
|
|
|
|
if (safeThis != nullptr)
|
|
|
|
safeThis->owner.finished (safeThis->nativeFileChooser->results);
|
|
|
|
}));
|
|
|
|
enterModalState (true, ModalCallbackFunction::create ([safeThis] (int)
|
|
|
|
{
|
|
|
|
if (auto locked = safeThis.lock())
|
|
|
|
locked->owner.finished (locked->nativeFileChooser->results);
|
|
|
|
}));
|
|
|
|
|
|
|
|
nativeFileChooser->open (true);
|
|
|
|
}
|
|
|
|
@@ -783,7 +799,7 @@ public: |
|
|
|
|
|
|
|
private:
|
|
|
|
FileChooser& owner;
|
|
|
|
Win32NativeFileChooser::Ptr nativeFileChooser;
|
|
|
|
std::shared_ptr<Win32NativeFileChooser> nativeFileChooser;
|
|
|
|
};
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
@@ -796,10 +812,10 @@ bool FileChooser::isPlatformDialogAvailable() |
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
|
|
|
FilePreviewComponent* preview)
|
|
|
|
std::unique_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
|
|
|
FilePreviewComponent* preview)
|
|
|
|
{
|
|
|
|
return new FileChooser::Native (owner, flags, preview);
|
|
|
|
return std::make_unique<FileChooser::Native> (owner, flags, preview);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace juce
|