1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/modules/juce_gui_basics/native/juce_FileChooser_windows.cpp
reuk 1f9ba0682c
ComSmartPtr: Make ownership more explicit
COM objects now always start with a refcount of 1, as is convention.
2024-04-16 21:07:45 +01:00

911 lines
30 KiB
C++

/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce
{
// Implemented in juce_Messaging_windows.cpp
namespace detail
{
bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages);
} // namespace detail
class Win32NativeFileChooser final : 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),
// When dealing with directories, it is not possible to 'Save' them. However, one can 'Open' a directory in order to save into it.
// If the 'saveMode' and 'canSelectDirectories' flags are both present, create an FileOpenDialog instead of an FileSaveDialog.
isSave ((flags & FileBrowserComponent::saveMode) != 0 && ! selectsDirectories),
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 (! detail::dispatchNextMessageOnSystemQueue (true))
Thread::sleep (1);
}
}
void open (bool async)
{
results.clear();
// the thread should not be running
nativeDialogRef.set (nullptr);
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 final : 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;
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)
{
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)
{
dialog.SetDefaultFolder (item);
if (! initialPath.isEmpty())
dialog.SetFolder (item);
}
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 final : 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 { focusWorkaround(); 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); }
/* Workaround for a bug in Vista+, OpenFileDialog will truncate the initialFile text.
Moving the caret along the full length of the text and back will reveal the full string.
*/
void focusWorkaround()
{
if (! defaultFileNameTextCaretMoved)
{
auto makeKeyInput = [] (WORD vk, bool pressed)
{
INPUT i;
ZeroMemory (&i, sizeof (INPUT));
i.type = INPUT_KEYBOARD;
i.ki.wVk = vk;
i.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
return i;
};
INPUT inputs[] = {
makeKeyInput (VK_HOME, true),
makeKeyInput (VK_HOME, false),
makeKeyInput (VK_END, true),
makeKeyInput (VK_END, false),
};
SendInput ((UINT) std::size (inputs), inputs, sizeof (INPUT));
defaultFileNameTextCaretMoved = true;
}
}
JUCE_COMRESULT updateHwnd (IFileDialog* d)
{
HWND hwnd = nullptr;
if (auto window = addComSmartPtrOwner (d).getInterface<IOleWindow>())
window->GetWindow (&hwnd);
ScopedLock lock (owner.deletingDialog);
if (owner.shouldCancel)
d->Close (S_FALSE);
else if (hwnd != nullptr)
owner.nativeDialogRef = hwnd;
return S_OK;
}
bool defaultFileNameTextCaretMoved = false;
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 (GetActiveWindow()) == S_OK;
}();
ScopedLock lock (deletingDialog);
nativeDialogRef = nullptr;
return result;
}
//==============================================================================
Array<URL> openDialogVistaAndUp()
{
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);
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);
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 = GetActiveWindow();
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
if (files[0] != 0)
{
auto startingFile = File (initialPath).getChildFile (String (files.get()));
startingFile.getFullPathName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR));
}
of.hwndOwner = GetActiveWindow();
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();
}
return openDialogPreVista (async);
}
void run() override
{
results = [&]
{
struct ScopedCoInitialize
{
// IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment
ScopedCoInitialize() { [[maybe_unused]] const auto result = CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); }
~ScopedCoInitialize() { CoUninitialize(); }
};
ScopedCoInitialize scope;
return openDialog (true);
}();
auto safeOwner = owner;
auto resultCode = results.size() > 0 ? 1 : 0;
MessageManager::callAsync ([resultCode, safeOwner]
{
if (safeOwner != nullptr)
safeOwner->exitModalState (resultCode);
});
}
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
{
const auto extension = filename.contains (".") ? filename.fromLastOccurrenceOf (".", false, false)
: String();
if (! extension.isEmpty())
return extension;
auto tokens = StringArray::fromTokens (filtersString, ";,", "\"'");
tokens.trim();
tokens.removeEmptyStrings();
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
return tokens[0].fromFirstOccurrenceOf (".", false, false);
return {};
}
//==============================================================================
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
{
MessageManager::callAsync ([safeComp = Component::SafePointer<FilePreviewComponent> { comp },
selectedFile = File { path }]() mutable
{
if (safeComp != nullptr)
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 final : public std::enable_shared_from_this<Native>,
public Component,
public FileChooser::Pimpl
{
public:
Native (FileChooser& fileChooser, int flagsIn, FilePreviewComponent* previewComp)
: owner (fileChooser),
nativeFileChooser (std::make_unique<Win32NativeFileChooser> (this, flagsIn, 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 (WindowUtils::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;
}
void inputAttemptWhenModal() override {}
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