mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
IFileDialog::Show and CoUninitialize both seem to require the main message loop to be active and running when they are called. If we block the message thread while calling these functions, we may cause a deadlock. The destructor of the Win32NativeFileChooser was blocking the message thread until the background thread exited, but the background thread was unable to make progress while the message thread was blocked. To work around this issue, we now pump the message thread in the destructor of the Win32NativeFileChooser. If a dialog is currently active, this should allow it to exit gracefully. Note that we cannot use MessageManager::runDispatchLoopUntil here: - MessageManager::runDispatchLoopUntil will not process any messages if the quit message has been received, which could lead to deadlocks if the FileChooser is destroyed after the quit message has been posted. - This function isn't defined when JUCE_MODAL_LOOPS_PERMITTED is disabled.
855 lines
28 KiB
C++
855 lines
28 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2020 - Raw Material Software Limited
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
|
|
|
End User License Agreement: www.juce.com/juce-6-licence
|
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
// Implemented in juce_win32_Messageing.cpp
|
|
bool windowsDispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages);
|
|
|
|
class Win32NativeFileChooser : public std::enable_shared_from_this<Win32NativeFileChooser>,
|
|
private Thread
|
|
{
|
|
public:
|
|
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 (',', ';')),
|
|
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
|
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
|
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
|
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0)
|
|
{
|
|
auto parentDirectory = startingFile.getParentDirectory();
|
|
|
|
// Handle nonexistent root directories in the same way as existing ones
|
|
files.calloc (static_cast<size_t> (charsAvailableForResult) + 1);
|
|
|
|
if (startingFile.isDirectory() || startingFile.isRoot())
|
|
{
|
|
initialPath = startingFile.getFullPathName();
|
|
}
|
|
else
|
|
{
|
|
startingFile.getFileName().copyToUTF16 (files,
|
|
static_cast<size_t> (charsAvailableForResult) * sizeof (WCHAR));
|
|
initialPath = parentDirectory.getFullPathName();
|
|
}
|
|
|
|
if (! selectsDirectories)
|
|
{
|
|
if (previewComp != nullptr)
|
|
customComponent.reset (new CustomComponentHolder (previewComp));
|
|
|
|
setupFilters();
|
|
}
|
|
}
|
|
|
|
~Win32NativeFileChooser() override
|
|
{
|
|
signalThreadShouldExit();
|
|
|
|
while (isThreadRunning())
|
|
{
|
|
if (! windowsDispatchNextMessageOnSystemQueue (true))
|
|
Thread::sleep (1);
|
|
}
|
|
}
|
|
|
|
void open (bool async)
|
|
{
|
|
results.clear();
|
|
|
|
// the thread should not be running
|
|
nativeDialogRef.set (nullptr);
|
|
|
|
weakThis = shared_from_this();
|
|
|
|
if (async)
|
|
{
|
|
jassert (! isThreadRunning());
|
|
|
|
startThread();
|
|
}
|
|
else
|
|
{
|
|
results = openDialog (false);
|
|
owner->exitModalState (results.size() > 0 ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
void cancel()
|
|
{
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
customComponent = nullptr;
|
|
shouldCancel = true;
|
|
|
|
if (auto hwnd = nativeDialogRef.get())
|
|
PostMessage (hwnd, WM_CLOSE, 0, 0);
|
|
}
|
|
|
|
Component* getCustomComponent() { return customComponent.get(); }
|
|
|
|
Array<URL> results;
|
|
|
|
private:
|
|
//==============================================================================
|
|
class CustomComponentHolder : public Component
|
|
{
|
|
public:
|
|
CustomComponentHolder (Component* const customComp)
|
|
{
|
|
setVisible (true);
|
|
setOpaque (true);
|
|
addAndMakeVisible (customComp);
|
|
setSize (jlimit (20, 800, customComp->getWidth()), customComp->getHeight());
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (Colours::lightgrey);
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
if (Component* const c = getChildComponent(0))
|
|
c->setBounds (getLocalBounds());
|
|
}
|
|
|
|
private:
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
|
|
};
|
|
|
|
//==============================================================================
|
|
const Component::SafePointer<Component> owner;
|
|
std::weak_ptr<Win32NativeFileChooser> weakThis;
|
|
String title, filtersString;
|
|
std::unique_ptr<CustomComponentHolder> customComponent;
|
|
String initialPath, returnedString;
|
|
|
|
CriticalSection deletingDialog;
|
|
|
|
bool selectsDirectories, isSave, warnAboutOverwrite, selectMultiple;
|
|
|
|
HeapBlock<WCHAR> files;
|
|
HeapBlock<WCHAR> filters;
|
|
|
|
Atomic<HWND> nativeDialogRef { nullptr };
|
|
bool shouldCancel = false;
|
|
|
|
struct FreeLPWSTR
|
|
{
|
|
void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); }
|
|
};
|
|
|
|
bool showDialog (IFileDialog& dialog, bool async)
|
|
{
|
|
FILEOPENDIALOGOPTIONS flags = {};
|
|
|
|
if (FAILED (dialog.GetOptions (&flags)))
|
|
return false;
|
|
|
|
const auto setBit = [] (FILEOPENDIALOGOPTIONS& field, bool value, FILEOPENDIALOGOPTIONS option)
|
|
{
|
|
if (value)
|
|
field |= option;
|
|
else
|
|
field &= ~option;
|
|
};
|
|
|
|
setBit (flags, selectsDirectories, FOS_PICKFOLDERS);
|
|
setBit (flags, warnAboutOverwrite, FOS_OVERWRITEPROMPT);
|
|
setBit (flags, selectMultiple, FOS_ALLOWMULTISELECT);
|
|
setBit (flags, customComponent != nullptr, FOS_FORCEPREVIEWPANEON);
|
|
|
|
if (FAILED (dialog.SetOptions (flags)) || FAILED (dialog.SetTitle (title.toUTF16())))
|
|
return false;
|
|
|
|
PIDLIST_ABSOLUTE pidl = {};
|
|
|
|
if (FAILED (SHParseDisplayName (initialPath.toWideCharPointer(), nullptr, &pidl, SFGAO_FOLDER, nullptr)))
|
|
{
|
|
LPWSTR ptr = nullptr;
|
|
auto result = SHGetKnownFolderPath (FOLDERID_Desktop, 0, nullptr, &ptr);
|
|
std::unique_ptr<WCHAR, FreeLPWSTR> desktopPath (ptr);
|
|
|
|
if (FAILED (result))
|
|
return false;
|
|
|
|
if (FAILED (SHParseDisplayName (desktopPath.get(), nullptr, &pidl, SFGAO_FOLDER, nullptr)))
|
|
return false;
|
|
}
|
|
|
|
const auto item = [&]
|
|
{
|
|
ComSmartPtr<IShellItem> ptr;
|
|
SHCreateShellItem (nullptr, nullptr, pidl, ptr.resetAndGetPointerAddress());
|
|
return ptr;
|
|
}();
|
|
|
|
if (item == nullptr || FAILED (dialog.SetFolder (item)))
|
|
return false;
|
|
|
|
String filename (files.getData());
|
|
|
|
if (FAILED (dialog.SetFileName (filename.toWideCharPointer())))
|
|
return false;
|
|
|
|
auto extension = getDefaultFileExtension (filename);
|
|
|
|
if (extension.isNotEmpty() && FAILED (dialog.SetDefaultExtension (extension.toWideCharPointer())))
|
|
return false;
|
|
|
|
const COMDLG_FILTERSPEC spec[] { { filtersString.toWideCharPointer(), filtersString.toWideCharPointer() } };
|
|
|
|
if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec)))
|
|
return false;
|
|
|
|
struct Events : public ComBaseClassHelper<IFileDialogEvents>
|
|
{
|
|
explicit Events (Win32NativeFileChooser& o) : owner (o) {}
|
|
|
|
JUCE_COMRESULT OnTypeChange (IFileDialog* d) override { return updateHwnd (d); }
|
|
JUCE_COMRESULT OnFolderChanging (IFileDialog* d, IShellItem*) override { return updateHwnd (d); }
|
|
JUCE_COMRESULT OnFileOk (IFileDialog* d) override { return updateHwnd (d); }
|
|
JUCE_COMRESULT OnFolderChange (IFileDialog* d) override { return updateHwnd (d); }
|
|
JUCE_COMRESULT OnSelectionChange (IFileDialog* d) override { return updateHwnd (d); }
|
|
JUCE_COMRESULT OnShareViolation (IFileDialog* d, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) override { return updateHwnd (d); }
|
|
JUCE_COMRESULT OnOverwrite (IFileDialog* d, IShellItem*, FDE_OVERWRITE_RESPONSE*) override { return updateHwnd (d); }
|
|
|
|
JUCE_COMRESULT updateHwnd (IFileDialog* d)
|
|
{
|
|
HWND hwnd = nullptr;
|
|
IUnknown_GetWindow (d, &hwnd);
|
|
|
|
ScopedLock lock (owner.deletingDialog);
|
|
|
|
if (owner.shouldCancel)
|
|
d->Close (S_FALSE);
|
|
else if (hwnd != nullptr)
|
|
owner.nativeDialogRef = hwnd;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
Win32NativeFileChooser& owner;
|
|
};
|
|
|
|
{
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
if (shouldCancel)
|
|
return false;
|
|
}
|
|
|
|
const auto result = [&]
|
|
{
|
|
struct ScopedAdvise
|
|
{
|
|
ScopedAdvise (IFileDialog& d, Events& events) : dialog (d) { dialog.Advise (&events, &cookie); }
|
|
~ScopedAdvise() { dialog.Unadvise (cookie); }
|
|
IFileDialog& dialog;
|
|
DWORD cookie = 0;
|
|
};
|
|
|
|
Events events { *this };
|
|
ScopedAdvise scope { dialog, events };
|
|
return dialog.Show (async ? nullptr : static_cast<HWND> (owner->getWindowHandle())) == S_OK;
|
|
}();
|
|
|
|
ScopedLock lock (deletingDialog);
|
|
nativeDialogRef = nullptr;
|
|
|
|
return result;
|
|
}
|
|
|
|
//==============================================================================
|
|
Array<URL> openDialogVistaAndUp (bool async)
|
|
{
|
|
const auto getUrl = [] (IShellItem& item)
|
|
{
|
|
LPWSTR ptr = nullptr;
|
|
|
|
if (item.GetDisplayName (SIGDN_FILESYSPATH, &ptr) != S_OK)
|
|
return URL();
|
|
|
|
const auto path = std::unique_ptr<WCHAR, FreeLPWSTR> { ptr };
|
|
return URL (File (String (path.get())));
|
|
};
|
|
|
|
if (isSave)
|
|
{
|
|
const auto dialog = [&]
|
|
{
|
|
ComSmartPtr<IFileDialog> ptr;
|
|
ptr.CoCreateInstance (CLSID_FileSaveDialog, CLSCTX_INPROC_SERVER);
|
|
return ptr;
|
|
}();
|
|
|
|
if (dialog == nullptr)
|
|
return {};
|
|
|
|
showDialog (*dialog, async);
|
|
|
|
const auto item = [&]
|
|
{
|
|
ComSmartPtr<IShellItem> ptr;
|
|
dialog->GetResult (ptr.resetAndGetPointerAddress());
|
|
return ptr;
|
|
}();
|
|
|
|
if (item == nullptr)
|
|
return {};
|
|
|
|
const auto url = getUrl (*item);
|
|
|
|
if (url.isEmpty())
|
|
return {};
|
|
|
|
return { url };
|
|
}
|
|
|
|
const auto dialog = [&]
|
|
{
|
|
ComSmartPtr<IFileOpenDialog> ptr;
|
|
ptr.CoCreateInstance (CLSID_FileOpenDialog, CLSCTX_INPROC_SERVER);
|
|
return ptr;
|
|
}();
|
|
|
|
if (dialog == nullptr)
|
|
return {};
|
|
|
|
showDialog (*dialog, async);
|
|
|
|
const auto items = [&]
|
|
{
|
|
ComSmartPtr<IShellItemArray> ptr;
|
|
dialog->GetResults (ptr.resetAndGetPointerAddress());
|
|
return ptr;
|
|
}();
|
|
|
|
if (items == nullptr)
|
|
return {};
|
|
|
|
Array<URL> result;
|
|
|
|
DWORD numItems = 0;
|
|
items->GetCount (&numItems);
|
|
|
|
for (DWORD i = 0; i < numItems; ++i)
|
|
{
|
|
ComSmartPtr<IShellItem> scope;
|
|
items->GetItemAt (i, scope.resetAndGetPointerAddress());
|
|
|
|
if (scope != nullptr)
|
|
{
|
|
const auto url = getUrl (*scope);
|
|
|
|
if (! url.isEmpty())
|
|
result.add (url);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Array<URL> openDialogPreVista (bool async)
|
|
{
|
|
Array<URL> selections;
|
|
|
|
if (selectsDirectories)
|
|
{
|
|
BROWSEINFO bi = {};
|
|
bi.hwndOwner = (HWND) (async ? nullptr : owner->getWindowHandle());
|
|
bi.pszDisplayName = files;
|
|
bi.lpszTitle = title.toWideCharPointer();
|
|
bi.lParam = (LPARAM) this;
|
|
bi.lpfn = browseCallbackProc;
|
|
#ifdef BIF_USENEWUI
|
|
bi.ulFlags = BIF_USENEWUI | BIF_VALIDATE;
|
|
#else
|
|
bi.ulFlags = 0x50;
|
|
#endif
|
|
|
|
LPITEMIDLIST list = SHBrowseForFolder (&bi);
|
|
|
|
if (! SHGetPathFromIDListW (list, files))
|
|
{
|
|
files[0] = 0;
|
|
returnedString.clear();
|
|
}
|
|
|
|
LPMALLOC al;
|
|
|
|
if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
|
|
al->Free (list);
|
|
|
|
if (files[0] != 0)
|
|
{
|
|
File result (String (files.get()));
|
|
|
|
if (returnedString.isNotEmpty())
|
|
result = result.getSiblingFile (returnedString);
|
|
|
|
selections.add (URL (result));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OPENFILENAMEW of = {};
|
|
|
|
#ifdef OPENFILENAME_SIZE_VERSION_400W
|
|
of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
|
|
#else
|
|
of.lStructSize = sizeof (of);
|
|
#endif
|
|
of.hwndOwner = (HWND) (async ? nullptr : owner->getWindowHandle());
|
|
of.lpstrFilter = filters.getData();
|
|
of.nFilterIndex = 1;
|
|
of.lpstrFile = files;
|
|
of.nMaxFile = (DWORD) charsAvailableForResult;
|
|
of.lpstrInitialDir = initialPath.toWideCharPointer();
|
|
of.lpstrTitle = title.toWideCharPointer();
|
|
of.Flags = getOpenFilenameFlags (async);
|
|
of.lCustData = (LPARAM) this;
|
|
of.lpfnHook = &openCallback;
|
|
|
|
if (isSave)
|
|
{
|
|
auto extension = getDefaultFileExtension (files.getData());
|
|
|
|
if (extension.isNotEmpty())
|
|
of.lpstrDefExt = extension.toWideCharPointer();
|
|
|
|
if (! GetSaveFileName (&of))
|
|
return {};
|
|
}
|
|
else
|
|
{
|
|
if (! GetOpenFileName (&of))
|
|
return {};
|
|
}
|
|
|
|
if (selectMultiple && of.nFileOffset > 0 && files[of.nFileOffset - 1] == 0)
|
|
{
|
|
const WCHAR* filename = files + of.nFileOffset;
|
|
|
|
while (*filename != 0)
|
|
{
|
|
selections.add (URL (File (String (files.get())).getChildFile (String (filename))));
|
|
filename += wcslen (filename) + 1;
|
|
}
|
|
}
|
|
else if (files[0] != 0)
|
|
{
|
|
selections.add (URL (File (String (files.get()))));
|
|
}
|
|
}
|
|
|
|
return selections;
|
|
}
|
|
|
|
Array<URL> openDialog (bool async)
|
|
{
|
|
struct Remover
|
|
{
|
|
explicit Remover (Win32NativeFileChooser& chooser) : item (chooser) {}
|
|
~Remover() { getNativeDialogList().removeValue (&item); }
|
|
|
|
Win32NativeFileChooser& item;
|
|
};
|
|
|
|
const Remover remover (*this);
|
|
|
|
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista
|
|
&& customComponent == nullptr)
|
|
{
|
|
return openDialogVistaAndUp (async);
|
|
}
|
|
|
|
return openDialogPreVista (async);
|
|
}
|
|
|
|
void run() override
|
|
{
|
|
auto resultsCopy = [&]
|
|
{
|
|
struct ScopedCoInitialize
|
|
{
|
|
// IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment
|
|
ScopedCoInitialize() { CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); }
|
|
~ScopedCoInitialize() { CoUninitialize(); }
|
|
};
|
|
|
|
ScopedCoInitialize scope;
|
|
|
|
return openDialog (true);
|
|
}();
|
|
|
|
auto safeOwner = owner;
|
|
auto weakThisCopy = weakThis;
|
|
|
|
MessageManager::callAsync ([resultsCopy, safeOwner, weakThisCopy]
|
|
{
|
|
if (auto locked = weakThisCopy.lock())
|
|
locked->results = resultsCopy;
|
|
|
|
if (safeOwner != nullptr)
|
|
safeOwner->exitModalState (resultsCopy.size() > 0 ? 1 : 0);
|
|
});
|
|
}
|
|
|
|
static HashMap<HWND, Win32NativeFileChooser*>& getNativeDialogList()
|
|
{
|
|
static HashMap<HWND, Win32NativeFileChooser*> dialogs;
|
|
return dialogs;
|
|
}
|
|
|
|
static Win32NativeFileChooser* getNativePointerForDialog (HWND hwnd)
|
|
{
|
|
return getNativeDialogList()[hwnd];
|
|
}
|
|
|
|
//==============================================================================
|
|
void setupFilters()
|
|
{
|
|
const size_t filterSpaceNumChars = 2048;
|
|
filters.calloc (filterSpaceNumChars);
|
|
|
|
const size_t bytesWritten = filtersString.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
|
|
filtersString.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
|
|
((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));
|
|
|
|
for (size_t i = 0; i < filterSpaceNumChars; ++i)
|
|
if (filters[i] == '|')
|
|
filters[i] = 0;
|
|
}
|
|
|
|
DWORD getOpenFilenameFlags (bool async)
|
|
{
|
|
DWORD ofFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;
|
|
|
|
if (warnAboutOverwrite)
|
|
ofFlags |= OFN_OVERWRITEPROMPT;
|
|
|
|
if (selectMultiple)
|
|
ofFlags |= OFN_ALLOWMULTISELECT;
|
|
|
|
if (async || customComponent != nullptr)
|
|
ofFlags |= OFN_ENABLEHOOK;
|
|
|
|
return ofFlags;
|
|
}
|
|
|
|
String getDefaultFileExtension (const String& filename) const
|
|
{
|
|
auto extension = filename.fromLastOccurrenceOf (".", false, false);
|
|
|
|
if (extension.isEmpty())
|
|
{
|
|
auto tokens = StringArray::fromTokens (filtersString, ";,", "\"'");
|
|
tokens.trim();
|
|
tokens.removeEmptyStrings();
|
|
|
|
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
|
|
extension = tokens[0].fromFirstOccurrenceOf (".", false, false);
|
|
}
|
|
|
|
return extension;
|
|
}
|
|
|
|
//==============================================================================
|
|
void initialised (HWND hWnd)
|
|
{
|
|
SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) initialPath.toWideCharPointer());
|
|
initDialog (hWnd);
|
|
}
|
|
|
|
void validateFailed (const String& path)
|
|
{
|
|
returnedString = path;
|
|
}
|
|
|
|
void initDialog (HWND hdlg)
|
|
{
|
|
ScopedLock lock (deletingDialog);
|
|
getNativeDialogList().set (hdlg, this);
|
|
|
|
if (shouldCancel)
|
|
{
|
|
EndDialog (hdlg, 0);
|
|
}
|
|
else
|
|
{
|
|
nativeDialogRef.set (hdlg);
|
|
|
|
if (customComponent != nullptr)
|
|
{
|
|
Component::SafePointer<Component> safeCustomComponent (customComponent.get());
|
|
|
|
RECT dialogScreenRect, dialogClientRect;
|
|
GetWindowRect (hdlg, &dialogScreenRect);
|
|
GetClientRect (hdlg, &dialogClientRect);
|
|
|
|
auto screenRectangle = Rectangle<int>::leftTopRightBottom (dialogScreenRect.left, dialogScreenRect.top,
|
|
dialogScreenRect.right, dialogScreenRect.bottom);
|
|
|
|
auto scale = Desktop::getInstance().getDisplays().getDisplayForRect (screenRectangle, true)->scale;
|
|
auto physicalComponentWidth = roundToInt (safeCustomComponent->getWidth() * scale);
|
|
|
|
SetWindowPos (hdlg, nullptr, screenRectangle.getX(), screenRectangle.getY(),
|
|
physicalComponentWidth + jmax (150, screenRectangle.getWidth()),
|
|
jmax (150, screenRectangle.getHeight()),
|
|
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
|
|
|
|
auto appendCustomComponent = [safeCustomComponent, dialogClientRect, scale, hdlg]() mutable
|
|
{
|
|
if (safeCustomComponent != nullptr)
|
|
{
|
|
auto scaledClientRectangle = Rectangle<int>::leftTopRightBottom (dialogClientRect.left, dialogClientRect.top,
|
|
dialogClientRect.right, dialogClientRect.bottom) / scale;
|
|
|
|
safeCustomComponent->setBounds (scaledClientRectangle.getRight(), scaledClientRectangle.getY(),
|
|
safeCustomComponent->getWidth(), scaledClientRectangle.getHeight());
|
|
safeCustomComponent->addToDesktop (0, hdlg);
|
|
}
|
|
};
|
|
|
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
|
appendCustomComponent();
|
|
else
|
|
MessageManager::callAsync (appendCustomComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void destroyDialog (HWND hdlg)
|
|
{
|
|
ScopedLock exiting (deletingDialog);
|
|
|
|
getNativeDialogList().remove (hdlg);
|
|
nativeDialogRef.set (nullptr);
|
|
|
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
|
customComponent = nullptr;
|
|
else
|
|
MessageManager::callAsync ([this] { customComponent = nullptr; });
|
|
}
|
|
|
|
void selectionChanged (HWND hdlg)
|
|
{
|
|
ScopedLock lock (deletingDialog);
|
|
|
|
if (customComponent != nullptr && ! shouldCancel)
|
|
{
|
|
if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent (0)))
|
|
{
|
|
WCHAR path [MAX_PATH * 2] = { 0 };
|
|
CommDlg_OpenSave_GetFilePath (hdlg, (LPARAM) &path, MAX_PATH);
|
|
|
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
|
{
|
|
comp->selectedFileChanged (File (path));
|
|
}
|
|
else
|
|
{
|
|
Component::SafePointer<FilePreviewComponent> safeComp (comp);
|
|
|
|
File selectedFile (path);
|
|
MessageManager::callAsync ([safeComp, selectedFile]() mutable
|
|
{
|
|
safeComp->selectedFileChanged (selectedFile);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
|
|
{
|
|
auto* self = reinterpret_cast<Win32NativeFileChooser*> (lpData);
|
|
|
|
switch (msg)
|
|
{
|
|
case BFFM_INITIALIZED: self->initialised (hWnd); break;
|
|
case BFFM_VALIDATEFAILEDW: self->validateFailed (String ((LPCWSTR) lParam)); break;
|
|
case BFFM_VALIDATEFAILEDA: self->validateFailed (String ((const char*) lParam)); break;
|
|
default: break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static UINT_PTR CALLBACK openCallback (HWND hwnd, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
|
|
{
|
|
auto hdlg = getDialogFromHWND (hwnd);
|
|
|
|
switch (uiMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
if (auto* self = reinterpret_cast<Win32NativeFileChooser*> (((OPENFILENAMEW*) lParam)->lCustData))
|
|
self->initDialog (hdlg);
|
|
|
|
break;
|
|
}
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
if (auto* self = getNativeDialogList()[hdlg])
|
|
self->destroyDialog (hdlg);
|
|
|
|
break;
|
|
}
|
|
|
|
case WM_NOTIFY:
|
|
{
|
|
auto ofn = reinterpret_cast<LPOFNOTIFY> (lParam);
|
|
|
|
if (ofn->hdr.code == CDN_SELCHANGE)
|
|
if (auto* self = reinterpret_cast<Win32NativeFileChooser*> (ofn->lpOFN->lCustData))
|
|
self->selectionChanged (hdlg);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static HWND getDialogFromHWND (HWND hwnd)
|
|
{
|
|
if (hwnd == nullptr)
|
|
return nullptr;
|
|
|
|
HWND dialogH = GetParent (hwnd);
|
|
|
|
if (dialogH == nullptr)
|
|
dialogH = hwnd;
|
|
|
|
return dialogH;
|
|
}
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser)
|
|
};
|
|
|
|
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 (std::make_shared<Win32NativeFileChooser> (this, flags, previewComp, fileChooser.startingFile,
|
|
fileChooser.title, fileChooser.filters))
|
|
{
|
|
auto mainMon = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
|
|
|
setBounds (mainMon.getX() + mainMon.getWidth() / 4,
|
|
mainMon.getY() + mainMon.getHeight() / 4,
|
|
0, 0);
|
|
|
|
setOpaque (true);
|
|
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
|
addToDesktop (0);
|
|
}
|
|
|
|
~Native() override
|
|
{
|
|
exitModalState (0);
|
|
nativeFileChooser->cancel();
|
|
}
|
|
|
|
void launch() override
|
|
{
|
|
std::weak_ptr<Native> safeThis = shared_from_this();
|
|
|
|
enterModalState (true, ModalCallbackFunction::create ([safeThis] (int)
|
|
{
|
|
if (auto locked = safeThis.lock())
|
|
locked->owner.finished (locked->nativeFileChooser->results);
|
|
}));
|
|
|
|
nativeFileChooser->open (true);
|
|
}
|
|
|
|
void runModally() override
|
|
{
|
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
|
enterModalState (true);
|
|
nativeFileChooser->open (false);
|
|
exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0);
|
|
nativeFileChooser->cancel();
|
|
|
|
owner.finished (nativeFileChooser->results);
|
|
#else
|
|
jassertfalse;
|
|
#endif
|
|
}
|
|
|
|
bool canModalEventBeSentToComponent (const Component* targetComponent) override
|
|
{
|
|
if (targetComponent == nullptr)
|
|
return false;
|
|
|
|
if (targetComponent == nativeFileChooser->getCustomComponent())
|
|
return true;
|
|
|
|
return targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr;
|
|
}
|
|
|
|
private:
|
|
FileChooser& owner;
|
|
std::shared_ptr<Win32NativeFileChooser> nativeFileChooser;
|
|
};
|
|
|
|
//==============================================================================
|
|
bool FileChooser::isPlatformDialogAvailable()
|
|
{
|
|
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
|
FilePreviewComponent* preview)
|
|
{
|
|
return std::make_shared<FileChooser::Native> (owner, flags, preview);
|
|
}
|
|
|
|
} // namespace juce
|