mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added iOS/Android native file chooser support and support for asynchronous invocation of file choosers
This commit is contained in:
parent
7e1ec31df9
commit
7e23bf28ae
15 changed files with 2426 additions and 675 deletions
|
|
@ -266,81 +266,94 @@ private:
|
|||
}
|
||||
else if (type >= loadChooser && type <= saveChooser)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
const bool useNativeVersion = nativeButton.getToggleState();
|
||||
|
||||
if (type == loadChooser)
|
||||
{
|
||||
FileChooser fc ("Choose a file to open...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*",
|
||||
useNativeVersion);
|
||||
fc = new FileChooser ("Choose a file to open...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*",
|
||||
useNativeVersion);
|
||||
|
||||
if (fc.browseForMultipleFilesToOpen())
|
||||
{
|
||||
String chosen;
|
||||
for (int i = 0; i < fc.getResults().size(); ++i)
|
||||
chosen << fc.getResults().getReference(i).getFullPathName() << "\n";
|
||||
fc->launchAsync (FileBrowserComponent::canSelectMultipleItems | FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles,
|
||||
[] (const FileChooser& chooser)
|
||||
{
|
||||
String chosen;
|
||||
auto results = chooser.getURLResults();
|
||||
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + chosen);
|
||||
}
|
||||
for (auto result : results)
|
||||
chosen << (result.isLocalFile() ? result.getLocalFile().getFullPathName() : result.toString (false)) << "\n";
|
||||
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + chosen);
|
||||
});
|
||||
}
|
||||
else if (type == loadWithPreviewChooser)
|
||||
{
|
||||
ImagePreviewComponent imagePreview;
|
||||
imagePreview.setSize (200, 200);
|
||||
|
||||
FileChooser fc ("Choose an image to open...",
|
||||
File::getSpecialLocation (File::userPicturesDirectory),
|
||||
"*.jpg;*.jpeg;*.png;*.gif",
|
||||
useNativeVersion);
|
||||
fc = new FileChooser ("Choose an image to open...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*.jpg;*.jpeg;*.png;*.gif",
|
||||
useNativeVersion);
|
||||
|
||||
if (fc.browseForMultipleFilesToOpen (&imagePreview))
|
||||
{
|
||||
String chosen;
|
||||
for (int i = 0; i < fc.getResults().size(); ++i)
|
||||
chosen << fc.getResults().getReference (i).getFullPathName() << "\n";
|
||||
fc->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
[] (const FileChooser& chooser)
|
||||
{
|
||||
String chosen;
|
||||
auto results = chooser.getURLResults();
|
||||
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + chosen);
|
||||
}
|
||||
for (auto result : results)
|
||||
chosen << (result.isLocalFile() ? result.getLocalFile().getFullPathName() : result.toString (false)) << "\n";
|
||||
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + chosen);
|
||||
},
|
||||
&imagePreview);
|
||||
}
|
||||
else if (type == saveChooser)
|
||||
{
|
||||
FileChooser fc ("Choose a file to save...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*",
|
||||
useNativeVersion);
|
||||
fc = new FileChooser ("Choose a file to save...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*",
|
||||
useNativeVersion);
|
||||
|
||||
if (fc.browseForFileToSave (true))
|
||||
{
|
||||
File chosenFile = fc.getResult();
|
||||
fc->launchAsync (FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles,
|
||||
[] (const FileChooser& chooser)
|
||||
{
|
||||
auto result = chooser.getURLResult();
|
||||
auto name = result.isEmpty() ? String()
|
||||
: (result.isLocalFile() ? result.getLocalFile().getFullPathName()
|
||||
: result.toString (true));
|
||||
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + chosenFile.getFullPathName());
|
||||
}
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + name);
|
||||
});
|
||||
}
|
||||
else if (type == directoryChooser)
|
||||
{
|
||||
FileChooser fc ("Choose a directory...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*",
|
||||
useNativeVersion);
|
||||
fc = new FileChooser ("Choose a directory...",
|
||||
File::getCurrentWorkingDirectory(),
|
||||
"*",
|
||||
useNativeVersion);
|
||||
|
||||
if (fc.browseForDirectory())
|
||||
{
|
||||
File chosenDirectory = fc.getResult();
|
||||
fc->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories,
|
||||
[] (const FileChooser& chooser)
|
||||
{
|
||||
auto result = chooser.getURLResult();
|
||||
auto name = result.isLocalFile() ? result.getLocalFile().getFullPathName()
|
||||
: result.toString (true);
|
||||
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + chosenDirectory.getFullPathName());
|
||||
}
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
|
||||
"File Chooser...",
|
||||
"You picked: " + name);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -358,6 +371,9 @@ private:
|
|||
return showWindow (*button, static_cast<DialogType> (i));
|
||||
}
|
||||
|
||||
ImagePreviewComponent imagePreview;
|
||||
ScopedPointer<FileChooser> fc;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogsDemo)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ public:
|
|||
bool isPushNotificationsEnabled() const { return settings [Ids::iosPushNotifications]; }
|
||||
Value getAppGroupsEnabledValue() { return getSetting (Ids::iosAppGroups); }
|
||||
bool isAppGroupsEnabled() const { return settings [Ids::iosAppGroups]; }
|
||||
Value getiCloudPermissionsEnabled() { return getSetting (Ids::iCloudPermissions); }
|
||||
bool isiCloudPermissionsEnabled() const { return settings [Ids::iCloudPermissions]; }
|
||||
|
||||
Value getIosDevelopmentTeamIDValue() { return getSetting (Ids::iosDevelopmentTeamID); }
|
||||
String getIosDevelopmentTeamIDString() const { return settings [Ids::iosDevelopmentTeamID]; }
|
||||
|
|
@ -252,6 +254,9 @@ public:
|
|||
|
||||
props.add (new BooleanPropertyComponent (getAppGroupsEnabledValue(), "App groups capability", "Enabled"),
|
||||
"Enable this to grant your app the capability to share resources between apps using the same app group ID.");
|
||||
|
||||
props.add (new BooleanPropertyComponent (getiCloudPermissionsEnabled(), "iCloud Permissions", "Enabled"),
|
||||
"Enable this to grant your app the capability to use native file load/save browser windows on iOS.");
|
||||
}
|
||||
|
||||
props.add (new TextPropertyComponent (getPListToMergeValue(), "Custom PList", 8192, true),
|
||||
|
|
@ -845,7 +850,10 @@ public:
|
|||
|
||||
auto developmentTeamID = owner.getIosDevelopmentTeamIDString();
|
||||
if (developmentTeamID.isNotEmpty())
|
||||
{
|
||||
attributes << "DevelopmentTeam = " << developmentTeamID << "; ";
|
||||
attributes << "ProvisioningStyle = Automatic; ";
|
||||
}
|
||||
|
||||
auto appGroupsEnabled = (owner.iOS && owner.isAppGroupsEnabled() ? 1 : 0);
|
||||
auto inAppPurchasesEnabled = owner.isInAppPurchasesEnabled() ? 1 : 0;
|
||||
|
|
@ -862,6 +870,10 @@ public:
|
|||
attributes << "com.apple.InterAppAudio = { enabled = " << interAppAudioEnabled << "; }; ";
|
||||
attributes << "com.apple.Push = { enabled = " << pushNotificationsEnabled << "; }; ";
|
||||
attributes << "com.apple.Sandbox = { enabled = " << sandboxEnabled << "; }; ";
|
||||
|
||||
if (owner.iOS && owner.isiCloudPermissionsEnabled())
|
||||
attributes << "com.apple.iCloud = { enabled = 1; }; ";
|
||||
|
||||
attributes << "}; };";
|
||||
|
||||
return attributes;
|
||||
|
|
@ -901,7 +913,7 @@ public:
|
|||
//==============================================================================
|
||||
bool shouldAddEntitlements() const
|
||||
{
|
||||
if (owner.isPushNotificationsEnabled() || owner.isAppGroupsEnabled())
|
||||
if (owner.isPushNotificationsEnabled() || owner.isAppGroupsEnabled() || (owner.isiOS() && owner.isiCloudPermissionsEnabled()))
|
||||
return true;
|
||||
|
||||
if (owner.project.getProjectType().isAudioPlugin()
|
||||
|
|
@ -1063,8 +1075,15 @@ public:
|
|||
s.set ("GCC_VERSION", gccVersion);
|
||||
s.set ("CLANG_LINK_OBJC_RUNTIME", "NO");
|
||||
|
||||
if (! config.codeSignIdentity.isUsingDefault())
|
||||
s.set ("CODE_SIGN_IDENTITY", config.codeSignIdentity.get().quoted());
|
||||
if (isUsingCodeSigning (config))
|
||||
{
|
||||
s.set (owner.iOS ? "\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"" : "CODE_SIGN_IDENTITY",
|
||||
getCodeSignIdentity (config).quoted());
|
||||
s.set ("PROVISIONING_PROFILE_SPECIFIER", "\"\"");
|
||||
}
|
||||
|
||||
if (owner.getIosDevelopmentTeamIDString().isNotEmpty())
|
||||
s.set ("DEVELOPMENT_TEAM", owner.getIosDevelopmentTeamIDString());
|
||||
|
||||
if (shouldAddEntitlements())
|
||||
s.set ("CODE_SIGN_ENTITLEMENTS", owner.getEntitlementsFileName().quoted());
|
||||
|
|
@ -1628,6 +1647,20 @@ public:
|
|||
return deploymentTarget;
|
||||
}
|
||||
|
||||
String getCodeSignIdentity (const XcodeBuildConfiguration& config) const
|
||||
{
|
||||
if (config.codeSignIdentity.isUsingDefault())
|
||||
return owner.iOS ? "iPhone Developer" : "Mac Developer";
|
||||
|
||||
return config.codeSignIdentity.get();
|
||||
}
|
||||
|
||||
bool isUsingCodeSigning (const XcodeBuildConfiguration& config) const
|
||||
{
|
||||
return (! config.codeSignIdentity.isUsingDefault())
|
||||
|| owner.getIosDevelopmentTeamIDString().isNotEmpty();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const XcodeProjectExporter& owner;
|
||||
|
||||
|
|
@ -2544,6 +2577,24 @@ private:
|
|||
entitlements.set ("com.apple.security.application-groups", groups);
|
||||
}
|
||||
|
||||
if (isiOS() && isiCloudPermissionsEnabled())
|
||||
{
|
||||
entitlements.set ("com.apple.developer.icloud-container-identifiers",
|
||||
"<array>\n"
|
||||
" <string>iCloud.$(CFBundleIdentifier)</string>\n"
|
||||
" </array>");
|
||||
|
||||
entitlements.set ("com.apple.developer.icloud-services",
|
||||
"<array>\n"
|
||||
" <string>CloudDocuments</string>\n"
|
||||
" </array>");
|
||||
|
||||
entitlements.set ("com.apple.developer.ubiquity-container-identifiers",
|
||||
"<array>\n"
|
||||
" <string>iCloud.$(CFBundleIdentifier)</string>\n"
|
||||
" </array>");
|
||||
}
|
||||
|
||||
return entitlements;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ namespace Ids
|
|||
DECLARE_ID (iosBackgroundBle);
|
||||
DECLARE_ID (iosPushNotifications);
|
||||
DECLARE_ID (iosAppGroups);
|
||||
DECLARE_ID (iCloudPermissions);
|
||||
DECLARE_ID (iosDevelopmentTeamID);
|
||||
DECLARE_ID (iosAppGroupsId);
|
||||
DECLARE_ID (iosAppExtensionDuplicateResourcesFolder);
|
||||
|
|
|
|||
|
|
@ -324,6 +324,7 @@ extern AndroidSystem android;
|
|||
METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \
|
||||
METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \
|
||||
METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \
|
||||
METHOD (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)V") \
|
||||
METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \
|
||||
|
||||
DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH);
|
||||
|
|
@ -355,6 +356,9 @@ DECLARE_JNI_CLASS (AndroidBitmapConfig, "android/graphics/Bitmap$Config");
|
|||
METHOD (getIntExtra, "getIntExtra", "(Ljava/lang/String;I)I") \
|
||||
METHOD (getStringExtra, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;") \
|
||||
METHOD (putExtras, "putExtras", "(Landroid/os/Bundle;)Landroid/content/Intent;") \
|
||||
METHOD (putExtraString, "putExtra", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;") \
|
||||
METHOD (putExtraStrings, "putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;") \
|
||||
METHOD (putExtraParcelable, "putExtra", "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;") \
|
||||
METHOD (setAction, "setAction", "(Ljava/lang/String;)Landroid/content/Intent;") \
|
||||
METHOD (setPackage, "setPackage", "(Ljava/lang/String;)Landroid/content/Intent;") \
|
||||
METHOD (setType, "setType", "(Ljava/lang/String;)Landroid/content/Intent;") \
|
||||
|
|
|
|||
|
|
@ -27,6 +27,67 @@
|
|||
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,
|
||||
|
|
@ -38,11 +99,18 @@ FileChooser::FileChooser (const String& chooserBoxTitle,
|
|||
useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
|
||||
treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
|
||||
{
|
||||
#ifndef JUCE_MAC
|
||||
ignoreUnused (treatFilePackagesAsDirs);
|
||||
#endif
|
||||
|
||||
if (! fileFilters.containsNonWhitespaceChars())
|
||||
filters = "*";
|
||||
}
|
||||
|
||||
FileChooser::~FileChooser() {}
|
||||
FileChooser::~FileChooser()
|
||||
{
|
||||
asyncCallback = nullptr;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
|
||||
|
|
@ -88,60 +156,91 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ
|
|||
{
|
||||
FocusRestorer focusRestorer;
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->runModally();
|
||||
|
||||
// ensure that the finished function was invoked
|
||||
jassert (pimpl == nullptr);
|
||||
|
||||
return (results.size() > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
|
||||
FilePreviewComponent* previewComp)
|
||||
{
|
||||
// You must specify a callback when using launchAsync
|
||||
jassert (callback);
|
||||
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassert (asyncCallback == nullptr);
|
||||
|
||||
asyncCallback = static_cast<std::function<void (const FileChooser&)>&&> (callback);
|
||||
|
||||
pimpl = 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));
|
||||
|
||||
const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
|
||||
const bool selectsFiles = (flags & FileBrowserComponent::canSelectFiles) != 0;
|
||||
const bool isSave = (flags & FileBrowserComponent::saveMode) != 0;
|
||||
const bool warnAboutOverwrite = (flags & FileBrowserComponent::warnAboutOverwriting) != 0;
|
||||
const bool selectMultiple = (flags & FileBrowserComponent::canSelectMultipleItems) != 0;
|
||||
if (pimpl != nullptr)
|
||||
{
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassertfalse;
|
||||
|
||||
pimpl = nullptr;
|
||||
}
|
||||
// You've set the flags for both saveMode and openMode!
|
||||
jassert (! (isSave && (flags & FileBrowserComponent::openMode) != 0));
|
||||
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))
|
||||
#elif JUCE_MAC || JUCE_LINUX
|
||||
if (useNativeDialogBox)
|
||||
#else
|
||||
if (false)
|
||||
if (useNativeDialogBox)
|
||||
#endif
|
||||
{
|
||||
showPlatformDialog (results, title, startingFile, filters,
|
||||
selectsDirectories, selectsFiles, isSave,
|
||||
warnAboutOverwrite, selectMultiple, treatFilePackagesAsDirs,
|
||||
previewComp);
|
||||
return showPlatformDialog (*this, flags, previewComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
ignoreUnused (selectMultiple);
|
||||
|
||||
WildcardFileFilter wildcard (selectsFiles ? filters : String(),
|
||||
selectsDirectories ? "*" : String(),
|
||||
String());
|
||||
|
||||
FileBrowserComponent browserComponent (flags, startingFile, &wildcard, previewComp);
|
||||
|
||||
FileChooserDialogBox box (title, String(),
|
||||
browserComponent, warnAboutOverwrite,
|
||||
browserComponent.findColour (AlertWindow::backgroundColourId));
|
||||
|
||||
if (box.show())
|
||||
{
|
||||
for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
|
||||
results.add (browserComponent.getSelectedFile (i));
|
||||
}
|
||||
return new NonNative (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
return results.size() > 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
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.
|
||||
|
|
@ -150,6 +249,19 @@ File FileChooser::getResult() const
|
|||
return results.getFirst();
|
||||
}
|
||||
|
||||
void FileChooser::finished (const Array<URL>& asyncResults)
|
||||
{
|
||||
std::function<void (const FileChooser&)> callback;
|
||||
std::swap (callback, asyncCallback);
|
||||
|
||||
results = asyncResults;
|
||||
|
||||
pimpl = nullptr;
|
||||
|
||||
if (callback)
|
||||
callback (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FilePreviewComponent::FilePreviewComponent() {}
|
||||
FilePreviewComponent::~FilePreviewComponent() {}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,25 @@ public:
|
|||
*/
|
||||
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.
|
||||
|
||||
|
|
@ -168,36 +187,100 @@ public:
|
|||
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 getResults
|
||||
@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 getResult
|
||||
@see getURLResults, getResult
|
||||
*/
|
||||
const Array<File>& getResults() const noexcept { return results; }
|
||||
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.
|
||||
|
||||
@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.
|
||||
|
||||
@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;
|
||||
const File startingFile;
|
||||
Array<File> results;
|
||||
Array<URL> results;
|
||||
const bool useNativeDialogBox;
|
||||
const bool treatFilePackagesAsDirs;
|
||||
std::function<void (const FileChooser&)> asyncCallback;
|
||||
|
||||
static void showPlatformDialog (Array<File>& results, const String& title, const File& file,
|
||||
const String& filters, bool selectsDirectories, bool selectsFiles,
|
||||
bool isSave, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles,
|
||||
bool treatFilePackagesAsDirs, FilePreviewComponent* previewComponent);
|
||||
static bool isPlatformDialogAvailable();
|
||||
//==============================================================================
|
||||
void finished (const Array<URL>&);
|
||||
|
||||
//==============================================================================
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() {}
|
||||
|
||||
virtual void launch() = 0;
|
||||
virtual void runModally() = 0;
|
||||
};
|
||||
|
||||
ScopedPointer<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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -289,10 +289,12 @@ namespace juce
|
|||
#if JUCE_IOS
|
||||
#include "native/juce_ios_UIViewComponentPeer.mm"
|
||||
#include "native/juce_ios_Windowing.mm"
|
||||
#include "native/juce_ios_FileChooser.mm"
|
||||
#else
|
||||
#include "native/juce_mac_NSViewComponentPeer.mm"
|
||||
#include "native/juce_mac_Windowing.mm"
|
||||
#include "native/juce_mac_MainMenu.mm"
|
||||
#include "native/juce_mac_FileChooser.mm"
|
||||
#endif
|
||||
|
||||
#if JUCE_CLANG
|
||||
|
|
@ -300,7 +302,6 @@ namespace juce
|
|||
#endif
|
||||
|
||||
#include "native/juce_mac_MouseCursor.mm"
|
||||
#include "native/juce_mac_FileChooser.mm"
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#include "native/juce_win32_Windowing.cpp"
|
||||
|
|
@ -315,6 +316,7 @@ namespace juce
|
|||
|
||||
#elif JUCE_ANDROID
|
||||
#include "native/juce_android_Windowing.cpp"
|
||||
#include "native/juce_common_MimeTypes.cpp"
|
||||
#include "native/juce_android_FileChooser.cpp"
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
dependencies: juce_events juce_graphics juce_data_structures
|
||||
OSXFrameworks: Cocoa Carbon QuartzCore
|
||||
iOSFrameworks: UIKit
|
||||
iOSFrameworks: UIKit MobileCoreServices
|
||||
linuxPackages: x11 xinerama xext
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
|
|
|||
|
|
@ -27,26 +27,198 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>& /*results*/,
|
||||
const String& /*title*/,
|
||||
const File& /*currentFileOrDirectory*/,
|
||||
const String& /*filter*/,
|
||||
bool /*selectsDirectory*/,
|
||||
bool /*selectsFiles*/,
|
||||
bool /*isSaveDialogue*/,
|
||||
bool /*warnAboutOverwritingExistingFiles*/,
|
||||
bool /*selectMultipleFiles*/,
|
||||
bool /*treatFilePackagesAsDirs*/,
|
||||
FilePreviewComponent* /*extraInfoComponent*/)
|
||||
class FileChooser::Native : public FileChooser::Pimpl
|
||||
{
|
||||
// TODO
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags) : owner (fileChooser)
|
||||
{
|
||||
if (currentFileChooser == nullptr)
|
||||
{
|
||||
currentFileChooser = this;
|
||||
auto* env = getEnv();
|
||||
|
||||
auto sdkVersion = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion);
|
||||
auto saveMode = ((flags & FileBrowserComponent::saveMode) != 0);
|
||||
auto selectsDirectories = ((flags & FileBrowserComponent::canSelectDirectories) != 0);
|
||||
|
||||
// You cannot have try to save a direcrtory
|
||||
jassert (! (saveMode && selectsDirectories));
|
||||
|
||||
if (sdkVersion < 19)
|
||||
{
|
||||
// native save dialogs are only supported in Android versions > 19
|
||||
jassert (! saveMode);
|
||||
saveMode = false;
|
||||
}
|
||||
|
||||
if (sdkVersion < 21)
|
||||
{
|
||||
// native directory chooser dialogs are only supported in Android versions > 21
|
||||
jassert (! selectsDirectories);
|
||||
selectsDirectories = false;
|
||||
}
|
||||
|
||||
const char* action = (selectsDirectories ? "android.intent.action.OPEN_DOCUMENT_TREE"
|
||||
: (saveMode ? "android.intent.action.CREATE_DOCUMENT"
|
||||
: (sdkVersion >= 19 ? "android.intent.action.OPEN_DOCUMENT"
|
||||
: "android.intent.action.GET_CONTENT")));
|
||||
|
||||
|
||||
intent = GlobalRef (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, javaString (action).get()));
|
||||
|
||||
if (owner.startingFile != File())
|
||||
{
|
||||
if (saveMode && (! owner.startingFile.isDirectory()))
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.putExtraString,
|
||||
javaString ("android.intent.extra.TITLE").get(),
|
||||
javaString (owner.startingFile.getFileName()).get());
|
||||
|
||||
|
||||
URL url (owner.startingFile);
|
||||
LocalRef<jobject> uri (env->CallStaticObjectMethod (Uri, Uri.parse, javaString (url.toString (true)).get()));
|
||||
|
||||
if (uri)
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.putExtraParcelable,
|
||||
javaString ("android.provider.extra.INITIAL_URI").get(),
|
||||
uri.get());
|
||||
}
|
||||
|
||||
|
||||
if (! selectsDirectories)
|
||||
{
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.addCategory, javaString ("android.intent.category.OPENABLE").get());
|
||||
|
||||
auto mimeTypes = convertFiltersToMimeTypes (owner.filters);
|
||||
|
||||
if (mimeTypes.size() == 1)
|
||||
{
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeTypes[0]).get());
|
||||
}
|
||||
else
|
||||
{
|
||||
String mimeGroup = "*";
|
||||
|
||||
if (mimeTypes.size() > 0)
|
||||
{
|
||||
mimeGroup = mimeTypes[0].upToFirstOccurrenceOf ("/", false, false);
|
||||
auto allMimeTypesHaveSameGroup = true;
|
||||
|
||||
LocalRef<jobjectArray> jMimeTypes (env->NewObjectArray (mimeTypes.size(), JavaString, javaString("").get()));
|
||||
|
||||
for (int i = 0; i < mimeTypes.size(); ++i)
|
||||
{
|
||||
env->SetObjectArrayElement (jMimeTypes.get(), i, javaString (mimeTypes[i]).get());
|
||||
|
||||
if (mimeGroup != mimeTypes[0].upToFirstOccurrenceOf ("/", false, false))
|
||||
allMimeTypesHaveSameGroup = false;
|
||||
}
|
||||
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.putExtraStrings,
|
||||
javaString ("android.intent.extra.MIME_TYPES").get(),
|
||||
jMimeTypes.get());
|
||||
|
||||
if (! allMimeTypesHaveSameGroup)
|
||||
mimeGroup = "*";
|
||||
}
|
||||
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeGroup + "/*").get());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
jassertfalse; // there can only be a single file chooser
|
||||
}
|
||||
|
||||
~Native()
|
||||
{
|
||||
currentFileChooser = nullptr;
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
// Android does not support modal file choosers
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
if (currentFileChooser != nullptr)
|
||||
android.activity.callVoidMethod (JuceAppActivity.startActivityForResult, intent.get(), /*READ_REQUEST_CODE*/ 42);
|
||||
else
|
||||
jassertfalse; // There is already a file chooser running
|
||||
}
|
||||
|
||||
void completed (int resultCode, jobject intentData)
|
||||
{
|
||||
currentFileChooser = nullptr;
|
||||
auto* env = getEnv();
|
||||
|
||||
Array<URL> chosenURLs;
|
||||
|
||||
if (resultCode == /*Activity.RESULT_OK*/ -1 && intentData != nullptr)
|
||||
{
|
||||
LocalRef<jobject> uri (env->CallObjectMethod (intentData, AndroidIntent.getData));
|
||||
|
||||
if (uri != nullptr)
|
||||
{
|
||||
auto jStr = (jstring) env->CallObjectMethod (uri, JavaObject.toString);
|
||||
|
||||
if (jStr != nullptr)
|
||||
chosenURLs.add (URL (juceString (env, jStr)));
|
||||
}
|
||||
}
|
||||
|
||||
owner.finished (chosenURLs);
|
||||
}
|
||||
|
||||
static Native* currentFileChooser;
|
||||
|
||||
static StringArray convertFiltersToMimeTypes (const String& fileFilters)
|
||||
{
|
||||
StringArray result;
|
||||
auto wildcards = StringArray::fromTokens (fileFilters, ";", "");
|
||||
|
||||
for (auto wildcard : wildcards)
|
||||
{
|
||||
if (wildcard.upToLastOccurrenceOf (".", false, false) == "*")
|
||||
{
|
||||
auto extension = wildcard.fromLastOccurrenceOf (".", false, false);
|
||||
|
||||
result.addArray (getMimeTypesForFileExtension (extension));
|
||||
}
|
||||
}
|
||||
|
||||
result.removeDuplicates (false);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
FileChooser& owner;
|
||||
GlobalRef intent;
|
||||
};
|
||||
|
||||
FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr;
|
||||
|
||||
void juce_fileChooserCompleted (int resultCode, void* intentData)
|
||||
{
|
||||
if (FileChooser::Native::currentFileChooser != nullptr)
|
||||
FileChooser::Native::currentFileChooser->completed (resultCode, (jobject) intentData);
|
||||
}
|
||||
|
||||
|
||||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent*)
|
||||
{
|
||||
return new FileChooser::Native (owner, flags);
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ namespace juce
|
|||
extern void juce_inAppPurchaseCompleted (void*);
|
||||
#endif
|
||||
|
||||
#if ! JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
extern void juce_fileChooserCompleted (int, void*);
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity,
|
||||
jstring appFile, jstring appDataDir))
|
||||
|
|
@ -96,16 +100,20 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env,
|
|||
android.shutdown (env);
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appActivityResult, void, (JNIEnv* env, jobject, jint requestCode, jint /*resultCode*/, jobject intentData))
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appActivityResult, void, (JNIEnv* env, jobject, jint requestCode, jint resultCode, jobject intentData))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES && JUCE_MODULE_AVAILABLE_juce_product_unlocking
|
||||
if (requestCode == 1001)
|
||||
juce_inAppPurchaseCompleted (intentData);
|
||||
#else
|
||||
ignoreUnused (intentData, requestCode);
|
||||
#endif
|
||||
|
||||
#if ! JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
if (requestCode == /*READ_REQUEST_CODE*/42)
|
||||
juce_fileChooserCompleted (resultCode, intentData);
|
||||
#endif
|
||||
ignoreUnused (intentData, requestCode);
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appNewIntent, void, (JNIEnv* env, jobject, jobject intentData))
|
||||
|
|
|
|||
693
modules/juce_gui_basics/native/juce_common_MimeTypes.cpp
Normal file
693
modules/juce_gui_basics/native/juce_common_MimeTypes.cpp
Normal file
|
|
@ -0,0 +1,693 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
struct MimeTypeTableEntry
|
||||
{
|
||||
const char* fileExtension, *mimeType;
|
||||
|
||||
static MimeTypeTableEntry table[640];
|
||||
};
|
||||
|
||||
static StringArray getMimeTypesForFileExtension (const String& fileExtension)
|
||||
{
|
||||
StringArray result;
|
||||
|
||||
for (auto type : MimeTypeTableEntry::table)
|
||||
if (fileExtension == type.fileExtension)
|
||||
result.add (type.mimeType);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MimeTypeTableEntry MimeTypeTableEntry::table[640] =
|
||||
{
|
||||
{"3dm", "x-world/x-3dmf"},
|
||||
{"3dmf", "x-world/x-3dmf"},
|
||||
{"a", "application/octet-stream"},
|
||||
{"aab", "application/x-authorware-bin"},
|
||||
{"aam", "application/x-authorware-map"},
|
||||
{"aas", "application/x-authorware-seg"},
|
||||
{"abc", "text/vnd.abc"},
|
||||
{"acgi", "text/html"},
|
||||
{"afl", "video/animaflex"},
|
||||
{"ai", "application/postscript"},
|
||||
{"aif", "audio/aiff"},
|
||||
{"aif", "audio/x-aiff"},
|
||||
{"aifc", "audio/aiff"},
|
||||
{"aifc", "audio/x-aiff"},
|
||||
{"aiff", "audio/aiff"},
|
||||
{"aiff", "audio/x-aiff"},
|
||||
{"aim", "application/x-aim"},
|
||||
{"aip", "text/x-audiosoft-intra"},
|
||||
{"ani", "application/x-navi-animation"},
|
||||
{"aos", "application/x-nokia-9000-communicator-add-on-software"},
|
||||
{"aps", "application/mime"},
|
||||
{"arc", "application/octet-stream"},
|
||||
{"arj", "application/arj"},
|
||||
{"arj", "application/octet-stream"},
|
||||
{"art", "image/x-jg"},
|
||||
{"asf", "video/x-ms-asf"},
|
||||
{"asm", "text/x-asm"},
|
||||
{"asp", "text/asp"},
|
||||
{"asx", "application/x-mplayer2"},
|
||||
{"asx", "video/x-ms-asf"},
|
||||
{"asx", "video/x-ms-asf-plugin"},
|
||||
{"au", "audio/basic"},
|
||||
{"au", "audio/x-au"},
|
||||
{"avi", "application/x-troff-msvideo"},
|
||||
{"avi", "video/avi"},
|
||||
{"avi", "video/msvideo"},
|
||||
{"avi", "video/x-msvideo"},
|
||||
{"avs", "video/avs-video"},
|
||||
{"bcpio", "application/x-bcpio"},
|
||||
{"bin", "application/mac-binary"},
|
||||
{"bin", "application/macbinary"},
|
||||
{"bin", "application/octet-stream"},
|
||||
{"bin", "application/x-binary"},
|
||||
{"bin", "application/x-macbinary"},
|
||||
{"bm", "image/bmp"},
|
||||
{"bmp", "image/bmp"},
|
||||
{"bmp", "image/x-windows-bmp"},
|
||||
{"boo", "application/book"},
|
||||
{"book", "application/book"},
|
||||
{"boz", "application/x-bzip2"},
|
||||
{"bsh", "application/x-bsh"},
|
||||
{"bz", "application/x-bzip"},
|
||||
{"bz2", "application/x-bzip2"},
|
||||
{"c", "text/plain"},
|
||||
{"c", "text/x-c"},
|
||||
{"c++", "text/plain"},
|
||||
{"cat", "application/vnd.ms-pki.seccat"},
|
||||
{"cc", "text/plain"},
|
||||
{"cc", "text/x-c"},
|
||||
{"ccad", "application/clariscad"},
|
||||
{"cco", "application/x-cocoa"},
|
||||
{"cdf", "application/cdf"},
|
||||
{"cdf", "application/x-cdf"},
|
||||
{"cdf", "application/x-netcdf"},
|
||||
{"cer", "application/pkix-cert"},
|
||||
{"cer", "application/x-x509-ca-cert"},
|
||||
{"cha", "application/x-chat"},
|
||||
{"chat", "application/x-chat"},
|
||||
{"class", "application/java"},
|
||||
{"class", "application/java-byte-code"},
|
||||
{"class", "application/x-java-class"},
|
||||
{"com", "application/octet-stream"},
|
||||
{"com", "text/plain"},
|
||||
{"conf", "text/plain"},
|
||||
{"cpio", "application/x-cpio"},
|
||||
{"cpp", "text/x-c"},
|
||||
{"cpt", "application/mac-compactpro"},
|
||||
{"cpt", "application/x-compactpro"},
|
||||
{"cpt", "application/x-cpt"},
|
||||
{"crl", "application/pkcs-crl"},
|
||||
{"crl", "application/pkix-crl"},
|
||||
{"crt", "application/pkix-cert"},
|
||||
{"crt", "application/x-x509-ca-cert"},
|
||||
{"crt", "application/x-x509-user-cert"},
|
||||
{"csh", "application/x-csh"},
|
||||
{"csh", "text/x-script.csh"},
|
||||
{"css", "application/x-pointplus"},
|
||||
{"css", "text/css"},
|
||||
{"cxx", "text/plain"},
|
||||
{"dcr", "application/x-director"},
|
||||
{"deepv", "application/x-deepv"},
|
||||
{"def", "text/plain"},
|
||||
{"der", "application/x-x509-ca-cert"},
|
||||
{"dif", "video/x-dv"},
|
||||
{"dir", "application/x-director"},
|
||||
{"dl", "video/dl"},
|
||||
{"dl", "video/x-dl"},
|
||||
{"doc", "application/msword"},
|
||||
{"dot", "application/msword"},
|
||||
{"dp", "application/commonground"},
|
||||
{"drw", "application/drafting"},
|
||||
{"dump", "application/octet-stream"},
|
||||
{"dv", "video/x-dv"},
|
||||
{"dvi", "application/x-dvi"},
|
||||
{"dwf", "drawing/x-dwf"},
|
||||
{"dwf", "model/vnd.dwf"},
|
||||
{"dwg", "application/acad"},
|
||||
{"dwg", "image/vnd.dwg"},
|
||||
{"dwg", "image/x-dwg"},
|
||||
{"dxf", "application/dxf"},
|
||||
{"dxf", "image/vnd.dwg"},
|
||||
{"dxf", "image/x-dwg"},
|
||||
{"dxr", "application/x-director"},
|
||||
{"el", "text/x-script.elisp"},
|
||||
{"elc", "application/x-bytecode.elisp"},
|
||||
{"elc", "application/x-elc"},
|
||||
{"env", "application/x-envoy"},
|
||||
{"eps", "application/postscript"},
|
||||
{"es", "application/x-esrehber"},
|
||||
{"etx", "text/x-setext"},
|
||||
{"evy", "application/envoy"},
|
||||
{"evy", "application/x-envoy"},
|
||||
{"exe", "application/octet-stream"},
|
||||
{"f", "text/plain"},
|
||||
{"f", "text/x-fortran"},
|
||||
{"f77", "text/x-fortran"},
|
||||
{"f90", "text/plain"},
|
||||
{"f90", "text/x-fortran"},
|
||||
{"fdf", "application/vnd.fdf"},
|
||||
{"fif", "application/fractals"},
|
||||
{"fif", "image/fif"},
|
||||
{"fli", "video/fli"},
|
||||
{"fli", "video/x-fli"},
|
||||
{"flo", "image/florian"},
|
||||
{"flx", "text/vnd.fmi.flexstor"},
|
||||
{"fmf", "video/x-atomic3d-feature"},
|
||||
{"for", "text/plain"},
|
||||
{"for", "text/x-fortran"},
|
||||
{"fpx", "image/vnd.fpx"},
|
||||
{"fpx", "image/vnd.net-fpx"},
|
||||
{"frl", "application/freeloader"},
|
||||
{"funk", "audio/make"},
|
||||
{"g", "text/plain"},
|
||||
{"g3", "image/g3fax"},
|
||||
{"gif", "image/gif"},
|
||||
{"gl", "video/gl"},
|
||||
{"gl", "video/x-gl"},
|
||||
{"gsd", "audio/x-gsm"},
|
||||
{"gsm", "audio/x-gsm"},
|
||||
{"gsp", "application/x-gsp"},
|
||||
{"gss", "application/x-gss"},
|
||||
{"gtar", "application/x-gtar"},
|
||||
{"gz", "application/x-compressed"},
|
||||
{"gz", "application/x-gzip"},
|
||||
{"gzip", "application/x-gzip"},
|
||||
{"gzip", "multipart/x-gzip"},
|
||||
{"h", "text/plain"},
|
||||
{"h", "text/x-h"},
|
||||
{"hdf", "application/x-hdf"},
|
||||
{"help", "application/x-helpfile"},
|
||||
{"hgl", "application/vnd.hp-hpgl"},
|
||||
{"hh", "text/plain"},
|
||||
{"hh", "text/x-h"},
|
||||
{"hlb", "text/x-script"},
|
||||
{"hlp", "application/hlp"},
|
||||
{"hlp", "application/x-helpfile"},
|
||||
{"hlp", "application/x-winhelp"},
|
||||
{"hpg", "application/vnd.hp-hpgl"},
|
||||
{"hpgl", "application/vnd.hp-hpgl"},
|
||||
{"hqx", "application/binhex"},
|
||||
{"hqx", "application/binhex4"},
|
||||
{"hqx", "application/mac-binhex"},
|
||||
{"hqx", "application/mac-binhex40"},
|
||||
{"hqx", "application/x-binhex40"},
|
||||
{"hqx", "application/x-mac-binhex40"},
|
||||
{"hta", "application/hta"},
|
||||
{"htc", "text/x-component"},
|
||||
{"htm", "text/html"},
|
||||
{"html", "text/html"},
|
||||
{"htmls", "text/html"},
|
||||
{"htt", "text/webviewhtml"},
|
||||
{"htx", "text/html"},
|
||||
{"ice", "x-conference/x-cooltalk"},
|
||||
{"ico", "image/x-icon"},
|
||||
{"idc", "text/plain"},
|
||||
{"ief", "image/ief"},
|
||||
{"iefs", "image/ief"},
|
||||
{"iges", "application/iges"},
|
||||
{"iges", "model/iges"},
|
||||
{"igs", "application/iges"},
|
||||
{"igs", "model/iges"},
|
||||
{"ima", "application/x-ima"},
|
||||
{"imap", "application/x-httpd-imap"},
|
||||
{"inf", "application/inf"},
|
||||
{"ins", "application/x-internett-signup"},
|
||||
{"ip", "application/x-ip2"},
|
||||
{"isu", "video/x-isvideo"},
|
||||
{"it", "audio/it"},
|
||||
{"iv", "application/x-inventor"},
|
||||
{"ivr", "i-world/i-vrml"},
|
||||
{"ivy", "application/x-livescreen"},
|
||||
{"jam", "audio/x-jam"},
|
||||
{"jav", "text/plain"},
|
||||
{"jav", "text/x-java-source"},
|
||||
{"java", "text/plain"},
|
||||
{"java", "text/x-java-source"},
|
||||
{"jcm", "application/x-java-commerce"},
|
||||
{"jfif", "image/jpeg"},
|
||||
{"jfif", "image/pjpeg"},
|
||||
{"jpe", "image/jpeg"},
|
||||
{"jpe", "image/pjpeg"},
|
||||
{"jpeg", "image/jpeg"},
|
||||
{"jpeg", "image/pjpeg"},
|
||||
{"jpg", "image/jpeg"},
|
||||
{"jpg", "image/pjpeg"},
|
||||
{"jps", "image/x-jps"},
|
||||
{"js", "application/x-javascript"},
|
||||
{"jut", "image/jutvision"},
|
||||
{"kar", "audio/midi"},
|
||||
{"kar", "music/x-karaoke"},
|
||||
{"ksh", "application/x-ksh"},
|
||||
{"ksh", "text/x-script.ksh"},
|
||||
{"la", "audio/nspaudio"},
|
||||
{"la", "audio/x-nspaudio"},
|
||||
{"lam", "audio/x-liveaudio"},
|
||||
{"latex", "application/x-latex"},
|
||||
{"lha", "application/lha"},
|
||||
{"lha", "application/octet-stream"},
|
||||
{"lha", "application/x-lha"},
|
||||
{"lhx", "application/octet-stream"},
|
||||
{"list", "text/plain"},
|
||||
{"lma", "audio/nspaudio"},
|
||||
{"lma", "audio/x-nspaudio"},
|
||||
{"log", "text/plain"},
|
||||
{"lsp", "application/x-lisp"},
|
||||
{"lsp", "text/x-script.lisp"},
|
||||
{"lst", "text/plain"},
|
||||
{"lsx", "text/x-la-asf"},
|
||||
{"ltx", "application/x-latex"},
|
||||
{"lzh", "application/octet-stream"},
|
||||
{"lzh", "application/x-lzh"},
|
||||
{"lzx", "application/lzx"},
|
||||
{"lzx", "application/octet-stream"},
|
||||
{"lzx", "application/x-lzx"},
|
||||
{"m", "text/plain"},
|
||||
{"m", "text/x-m"},
|
||||
{"m1v", "video/mpeg"},
|
||||
{"m2a", "audio/mpeg"},
|
||||
{"m2v", "video/mpeg"},
|
||||
{"m3u", "audio/x-mpequrl"},
|
||||
{"man", "application/x-troff-man"},
|
||||
{"map", "application/x-navimap"},
|
||||
{"mar", "text/plain"},
|
||||
{"mbd", "application/mbedlet"},
|
||||
{"mc$", "application/x-magic-cap-package-1.0"},
|
||||
{"mcd", "application/mcad"},
|
||||
{"mcd", "application/x-mathcad"},
|
||||
{"mcf", "image/vasa"},
|
||||
{"mcf", "text/mcf"},
|
||||
{"mcp", "application/netmc"},
|
||||
{"me", "application/x-troff-me"},
|
||||
{"mht", "message/rfc822"},
|
||||
{"mhtml", "message/rfc822"},
|
||||
{"mid", "application/x-midi"},
|
||||
{"mid", "audio/midi"},
|
||||
{"mid", "audio/x-mid"},
|
||||
{"mid", "audio/x-midi"},
|
||||
{"mid", "music/crescendo"},
|
||||
{"mid", "x-music/x-midi"},
|
||||
{"midi", "application/x-midi"},
|
||||
{"midi", "audio/midi"},
|
||||
{"midi", "audio/x-mid"},
|
||||
{"midi", "audio/x-midi"},
|
||||
{"midi", "music/crescendo"},
|
||||
{"midi", "x-music/x-midi"},
|
||||
{"mif", "application/x-frame"},
|
||||
{"mif", "application/x-mif"},
|
||||
{"mime", "message/rfc822"},
|
||||
{"mime", "www/mime"},
|
||||
{"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"},
|
||||
{"mjpg", "video/x-motion-jpeg"},
|
||||
{"mm", "application/base64"},
|
||||
{"mm", "application/x-meme"},
|
||||
{"mme", "application/base64"},
|
||||
{"mod", "audio/mod"},
|
||||
{"mod", "audio/x-mod"},
|
||||
{"moov", "video/quicktime"},
|
||||
{"mov", "video/quicktime"},
|
||||
{"movie", "video/x-sgi-movie"},
|
||||
{"mp2", "audio/mpeg"},
|
||||
{"mp2", "audio/x-mpeg"},
|
||||
{"mp2", "video/mpeg"},
|
||||
{"mp2", "video/x-mpeg"},
|
||||
{"mp2", "video/x-mpeq2a"},
|
||||
{"mp3", "audio/mpeg3"},
|
||||
{"mp3", "audio/x-mpeg-3"},
|
||||
{"mp3", "video/mpeg"},
|
||||
{"mp3", "video/x-mpeg"},
|
||||
{"mpa", "audio/mpeg"},
|
||||
{"mpa", "video/mpeg"},
|
||||
{"mpc", "application/x-project"},
|
||||
{"mpe", "video/mpeg"},
|
||||
{"mpeg", "video/mpeg"},
|
||||
{"mpg", "audio/mpeg"},
|
||||
{"mpg", "video/mpeg"},
|
||||
{"mpga", "audio/mpeg"},
|
||||
{"mpp", "application/vnd.ms-project"},
|
||||
{"mpt", "application/x-project"},
|
||||
{"mpv", "application/x-project"},
|
||||
{"mpx", "application/x-project"},
|
||||
{"mrc", "application/marc"},
|
||||
{"ms", "application/x-troff-ms"},
|
||||
{"mv", "video/x-sgi-movie"},
|
||||
{"my", "audio/make"},
|
||||
{"mzz", "application/x-vnd.audioexplosion.mzz"},
|
||||
{"nap", "image/naplps"},
|
||||
{"naplps", "image/naplps"},
|
||||
{"nc", "application/x-netcdf"},
|
||||
{"ncm", "application/vnd.nokia.configuration-message"},
|
||||
{"nif", "image/x-niff"},
|
||||
{"niff", "image/x-niff"},
|
||||
{"nix", "application/x-mix-transfer"},
|
||||
{"nsc", "application/x-conference"},
|
||||
{"nvd", "application/x-navidoc"},
|
||||
{"o", "application/octet-stream"},
|
||||
{"oda", "application/oda"},
|
||||
{"omc", "application/x-omc"},
|
||||
{"omcd", "application/x-omcdatamaker"},
|
||||
{"omcr", "application/x-omcregerator"},
|
||||
{"p", "text/x-pascal"},
|
||||
{"p10", "application/pkcs10"},
|
||||
{"p10", "application/x-pkcs10"},
|
||||
{"p12", "application/pkcs-12"},
|
||||
{"p12", "application/x-pkcs12"},
|
||||
{"p7a", "application/x-pkcs7-signature"},
|
||||
{"p7c", "application/pkcs7-mime"},
|
||||
{"p7c", "application/x-pkcs7-mime"},
|
||||
{"p7m", "application/pkcs7-mime"},
|
||||
{"p7m", "application/x-pkcs7-mime"},
|
||||
{"p7r", "application/x-pkcs7-certreqresp"},
|
||||
{"p7s", "application/pkcs7-signature"},
|
||||
{"part", "application/pro_eng"},
|
||||
{"pas", "text/pascal"},
|
||||
{"pbm", "image/x-portable-bitmap"},
|
||||
{"pcl", "application/vnd.hp-pcl"},
|
||||
{"pcl", "application/x-pcl"},
|
||||
{"pct", "image/x-pict"},
|
||||
{"pcx", "image/x-pcx"},
|
||||
{"pdb", "chemical/x-pdb"},
|
||||
{"pdf", "application/pdf"},
|
||||
{"pfunk", "audio/make"},
|
||||
{"pfunk", "audio/make.my.funk"},
|
||||
{"pgm", "image/x-portable-graymap"},
|
||||
{"pgm", "image/x-portable-greymap"},
|
||||
{"pic", "image/pict"},
|
||||
{"pict", "image/pict"},
|
||||
{"pkg", "application/x-newton-compatible-pkg"},
|
||||
{"pko", "application/vnd.ms-pki.pko"},
|
||||
{"pl", "text/plain"},
|
||||
{"pl", "text/x-script.perl"},
|
||||
{"plx", "application/x-pixclscript"},
|
||||
{"pm", "image/x-xpixmap"},
|
||||
{"pm", "text/x-script.perl-module"},
|
||||
{"pm4", "application/x-pagemaker"},
|
||||
{"pm5", "application/x-pagemaker"},
|
||||
{"png", "image/png"},
|
||||
{"pnm", "application/x-portable-anymap"},
|
||||
{"pnm", "image/x-portable-anymap"},
|
||||
{"pot", "application/mspowerpoint"},
|
||||
{"pot", "application/vnd.ms-powerpoint"},
|
||||
{"pov", "model/x-pov"},
|
||||
{"ppa", "application/vnd.ms-powerpoint"},
|
||||
{"ppm", "image/x-portable-pixmap"},
|
||||
{"pps", "application/mspowerpoint"},
|
||||
{"pps", "application/vnd.ms-powerpoint"},
|
||||
{"ppt", "application/mspowerpoint"},
|
||||
{"ppt", "application/powerpoint"},
|
||||
{"ppt", "application/vnd.ms-powerpoint"},
|
||||
{"ppt", "application/x-mspowerpoint"},
|
||||
{"ppz", "application/mspowerpoint"},
|
||||
{"pre", "application/x-freelance"},
|
||||
{"prt", "application/pro_eng"},
|
||||
{"ps", "application/postscript"},
|
||||
{"psd", "application/octet-stream"},
|
||||
{"pvu", "paleovu/x-pv"},
|
||||
{"pwz", "application/vnd.ms-powerpoint"},
|
||||
{"py", "text/x-script.phyton"},
|
||||
{"pyc", "applicaiton/x-bytecode.python"},
|
||||
{"qcp", "audio/vnd.qcelp"},
|
||||
{"qd3", "x-world/x-3dmf"},
|
||||
{"qd3d", "x-world/x-3dmf"},
|
||||
{"qif", "image/x-quicktime"},
|
||||
{"qt", "video/quicktime"},
|
||||
{"qtc", "video/x-qtc"},
|
||||
{"qti", "image/x-quicktime"},
|
||||
{"qtif", "image/x-quicktime"},
|
||||
{"ra", "audio/x-pn-realaudio"},
|
||||
{"ra", "audio/x-pn-realaudio-plugin"},
|
||||
{"ra", "audio/x-realaudio"},
|
||||
{"ram", "audio/x-pn-realaudio"},
|
||||
{"ras", "application/x-cmu-raster"},
|
||||
{"ras", "image/cmu-raster"},
|
||||
{"ras", "image/x-cmu-raster"},
|
||||
{"rast", "image/cmu-raster"},
|
||||
{"rexx", "text/x-script.rexx"},
|
||||
{"rf", "image/vnd.rn-realflash"},
|
||||
{"rgb", "image/x-rgb"},
|
||||
{"rm", "application/vnd.rn-realmedia"},
|
||||
{"rm", "audio/x-pn-realaudio"},
|
||||
{"rmi", "audio/mid"},
|
||||
{"rmm", "audio/x-pn-realaudio"},
|
||||
{"rmp", "audio/x-pn-realaudio"},
|
||||
{"rmp", "audio/x-pn-realaudio-plugin"},
|
||||
{"rng", "application/ringing-tones"},
|
||||
{"rng", "application/vnd.nokia.ringing-tone"},
|
||||
{"rnx", "application/vnd.rn-realplayer"},
|
||||
{"roff", "application/x-troff"},
|
||||
{"rp", "image/vnd.rn-realpix"},
|
||||
{"rpm", "audio/x-pn-realaudio-plugin"},
|
||||
{"rt", "text/richtext"},
|
||||
{"rt", "text/vnd.rn-realtext"},
|
||||
{"rtf", "application/rtf"},
|
||||
{"rtf", "application/x-rtf"},
|
||||
{"rtf", "text/richtext"},
|
||||
{"rtx", "application/rtf"},
|
||||
{"rtx", "text/richtext"},
|
||||
{"rv", "video/vnd.rn-realvideo"},
|
||||
{"s", "text/x-asm"},
|
||||
{"s3m", "audio/s3m"},
|
||||
{"saveme", "application/octet-stream"},
|
||||
{"sbk", "application/x-tbook"},
|
||||
{"scm", "application/x-lotusscreencam"},
|
||||
{"scm", "text/x-script.guile"},
|
||||
{"scm", "text/x-script.scheme"},
|
||||
{"scm", "video/x-scm"},
|
||||
{"sdml", "text/plain"},
|
||||
{"sdp", "application/sdp"},
|
||||
{"sdp", "application/x-sdp"},
|
||||
{"sdr", "application/sounder"},
|
||||
{"sea", "application/sea"},
|
||||
{"sea", "application/x-sea"},
|
||||
{"set", "application/set"},
|
||||
{"sgm", "text/sgml"},
|
||||
{"sgm", "text/x-sgml"},
|
||||
{"sgml", "text/sgml"},
|
||||
{"sgml", "text/x-sgml"},
|
||||
{"sh", "application/x-bsh"},
|
||||
{"sh", "application/x-sh"},
|
||||
{"sh", "application/x-shar"},
|
||||
{"sh", "text/x-script.sh"},
|
||||
{"shar", "application/x-bsh"},
|
||||
{"shar", "application/x-shar"},
|
||||
{"shtml", "text/html"},
|
||||
{"shtml", "text/x-server-parsed-html"},
|
||||
{"sid", "audio/x-psid"},
|
||||
{"sit", "application/x-sit"},
|
||||
{"sit", "application/x-stuffit"},
|
||||
{"skd", "application/x-koan"},
|
||||
{"skm", "application/x-koan"},
|
||||
{"skp", "application/x-koan"},
|
||||
{"skt", "application/x-koan"},
|
||||
{"sl", "application/x-seelogo"},
|
||||
{"smi", "application/smil"},
|
||||
{"smil", "application/smil"},
|
||||
{"snd", "audio/basic"},
|
||||
{"snd", "audio/x-adpcm"},
|
||||
{"sol", "application/solids"},
|
||||
{"spc", "application/x-pkcs7-certificates"},
|
||||
{"spc", "text/x-speech"},
|
||||
{"spl", "application/futuresplash"},
|
||||
{"spr", "application/x-sprite"},
|
||||
{"sprite", "application/x-sprite"},
|
||||
{"src", "application/x-wais-source"},
|
||||
{"ssi", "text/x-server-parsed-html"},
|
||||
{"ssm", "application/streamingmedia"},
|
||||
{"sst", "application/vnd.ms-pki.certstore"},
|
||||
{"step", "application/step"},
|
||||
{"stl", "application/sla"},
|
||||
{"stl", "application/vnd.ms-pki.stl"},
|
||||
{"stl", "application/x-navistyle"},
|
||||
{"stp", "application/step"},
|
||||
{"sv4cpio,", "application/x-sv4cpio"},
|
||||
{"sv4crc", "application/x-sv4crc"},
|
||||
{"svf", "image/vnd.dwg"},
|
||||
{"svf", "image/x-dwg"},
|
||||
{"svr", "application/x-world"},
|
||||
{"svr", "x-world/x-svr"},
|
||||
{"swf", "application/x-shockwave-flash"},
|
||||
{"t", "application/x-troff"},
|
||||
{"talk", "text/x-speech"},
|
||||
{"tar", "application/x-tar"},
|
||||
{"tbk", "application/toolbook"},
|
||||
{"tbk", "application/x-tbook"},
|
||||
{"tcl", "application/x-tcl"},
|
||||
{"tcl", "text/x-script.tcl"},
|
||||
{"tcsh", "text/x-script.tcsh"},
|
||||
{"tex", "application/x-tex"},
|
||||
{"texi", "application/x-texinfo"},
|
||||
{"texinfo,", "application/x-texinfo"},
|
||||
{"text", "application/plain"},
|
||||
{"text", "text/plain"},
|
||||
{"tgz", "application/gnutar"},
|
||||
{"tgz", "application/x-compressed"},
|
||||
{"tif", "image/tiff"},
|
||||
{"tif", "image/x-tiff"},
|
||||
{"tiff", "image/tiff"},
|
||||
{"tiff", "image/x-tiff"},
|
||||
{"tr", "application/x-troff"},
|
||||
{"tsi", "audio/tsp-audio"},
|
||||
{"tsp", "application/dsptype"},
|
||||
{"tsp", "audio/tsplayer"},
|
||||
{"tsv", "text/tab-separated-values"},
|
||||
{"turbot", "image/florian"},
|
||||
{"txt", "text/plain"},
|
||||
{"uil", "text/x-uil"},
|
||||
{"uni", "text/uri-list"},
|
||||
{"unis", "text/uri-list"},
|
||||
{"unv", "application/i-deas"},
|
||||
{"uri", "text/uri-list"},
|
||||
{"uris", "text/uri-list"},
|
||||
{"ustar", "application/x-ustar"},
|
||||
{"ustar", "multipart/x-ustar"},
|
||||
{"uu", "application/octet-stream"},
|
||||
{"uu", "text/x-uuencode"},
|
||||
{"uue", "text/x-uuencode"},
|
||||
{"vcd", "application/x-cdlink"},
|
||||
{"vcs", "text/x-vcalendar"},
|
||||
{"vda", "application/vda"},
|
||||
{"vdo", "video/vdo"},
|
||||
{"vew", "application/groupwise"},
|
||||
{"viv", "video/vivo"},
|
||||
{"viv", "video/vnd.vivo"},
|
||||
{"vivo", "video/vivo"},
|
||||
{"vivo", "video/vnd.vivo"},
|
||||
{"vmd", "application/vocaltec-media-desc"},
|
||||
{"vmf", "application/vocaltec-media-file"},
|
||||
{"voc", "audio/voc"},
|
||||
{"voc", "audio/x-voc"},
|
||||
{"vos", "video/vosaic"},
|
||||
{"vox", "audio/voxware"},
|
||||
{"vqe", "audio/x-twinvq-plugin"},
|
||||
{"vqf", "audio/x-twinvq"},
|
||||
{"vql", "audio/x-twinvq-plugin"},
|
||||
{"vrml", "application/x-vrml"},
|
||||
{"vrml", "model/vrml"},
|
||||
{"vrml", "x-world/x-vrml"},
|
||||
{"vrt", "x-world/x-vrt"},
|
||||
{"vsd", "application/x-visio"},
|
||||
{"vst", "application/x-visio"},
|
||||
{"vsw", "application/x-visio"},
|
||||
{"w60", "application/wordperfect6.0"},
|
||||
{"w61", "application/wordperfect6.1"},
|
||||
{"w6w", "application/msword"},
|
||||
{"wav", "audio/wav"},
|
||||
{"wav", "audio/x-wav"},
|
||||
{"wb1", "application/x-qpro"},
|
||||
{"wbmp", "image/vnd.wap.wbmp"},
|
||||
{"web", "application/vnd.xara"},
|
||||
{"wiz", "application/msword"},
|
||||
{"wk1", "application/x-123"},
|
||||
{"wmf", "windows/metafile"},
|
||||
{"wml", "text/vnd.wap.wml"},
|
||||
{"wmlc", "application/vnd.wap.wmlc"},
|
||||
{"wmls", "text/vnd.wap.wmlscript"},
|
||||
{"wmlsc", "application/vnd.wap.wmlscriptc"},
|
||||
{"word", "application/msword"},
|
||||
{"wp", "application/wordperfect"},
|
||||
{"wp5", "application/wordperfect"},
|
||||
{"wp5", "application/wordperfect6.0"},
|
||||
{"wp6", "application/wordperfect"},
|
||||
{"wpd", "application/wordperfect"},
|
||||
{"wpd", "application/x-wpwin"},
|
||||
{"wq1", "application/x-lotus"},
|
||||
{"wri", "application/mswrite"},
|
||||
{"wri", "application/x-wri"},
|
||||
{"wrl", "application/x-world"},
|
||||
{"wrl", "model/vrml"},
|
||||
{"wrl", "x-world/x-vrml"},
|
||||
{"wrz", "model/vrml"},
|
||||
{"wrz", "x-world/x-vrml"},
|
||||
{"wsc", "text/scriplet"},
|
||||
{"wsrc", "application/x-wais-source"},
|
||||
{"wtk", "application/x-wintalk"},
|
||||
{"xbm", "image/x-xbitmap"},
|
||||
{"xbm", "image/x-xbm"},
|
||||
{"xbm", "image/xbm"},
|
||||
{"xdr", "video/x-amt-demorun"},
|
||||
{"xgz", "xgl/drawing"},
|
||||
{"xif", "image/vnd.xiff"},
|
||||
{"xl", "application/excel"},
|
||||
{"xla", "application/excel"},
|
||||
{"xla", "application/x-excel"},
|
||||
{"xla", "application/x-msexcel"},
|
||||
{"xlb", "application/excel"},
|
||||
{"xlb", "application/vnd.ms-excel"},
|
||||
{"xlb", "application/x-excel"},
|
||||
{"xlc", "application/excel"},
|
||||
{"xlc", "application/vnd.ms-excel"},
|
||||
{"xlc", "application/x-excel"},
|
||||
{"xld", "application/excel"},
|
||||
{"xld", "application/x-excel"},
|
||||
{"xlk", "application/excel"},
|
||||
{"xlk", "application/x-excel"},
|
||||
{"xll", "application/excel"},
|
||||
{"xll", "application/vnd.ms-excel"},
|
||||
{"xll", "application/x-excel"},
|
||||
{"xlm", "application/excel"},
|
||||
{"xlm", "application/vnd.ms-excel"},
|
||||
{"xlm", "application/x-excel"},
|
||||
{"xls", "application/excel"},
|
||||
{"xls", "application/vnd.ms-excel"},
|
||||
{"xls", "application/x-excel"},
|
||||
{"xls", "application/x-msexcel"},
|
||||
{"xlt", "application/excel"},
|
||||
{"xlt", "application/x-excel"},
|
||||
{"xlv", "application/excel"},
|
||||
{"xlv", "application/x-excel"},
|
||||
{"xlw", "application/excel"},
|
||||
{"xlw", "application/vnd.ms-excel"},
|
||||
{"xlw", "application/x-excel"},
|
||||
{"xlw", "application/x-msexcel"},
|
||||
{"xm", "audio/xm"},
|
||||
{"xml", "application/xml"},
|
||||
{"xml", "text/xml"},
|
||||
{"xmz", "xgl/movie"},
|
||||
{"xpix", "application/x-vnd.ls-xpix"},
|
||||
{"xpm", "image/x-xpixmap"},
|
||||
{"xpm", "image/xpm"},
|
||||
{"x-png", "image/png"},
|
||||
{"xsr", "video/x-amt-showrun"},
|
||||
{"xwd", "image/x-xwd"},
|
||||
{"xwd", "image/x-xwindowdump"},
|
||||
{"xyz", "chemical/x-pdb"},
|
||||
{"z", "application/x-compress"},
|
||||
{"z", "application/x-compressed"},
|
||||
{"zip", "application/x-compressed"},
|
||||
{"zip", "application/x-zip-compressed"},
|
||||
{"zip", "application/zip"},
|
||||
{"zip", "multipart/x-zip"},
|
||||
{"zoo", "application/octet-stream"}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
237
modules/juce_gui_basics/native/juce_ios_FileChooser.mm
Normal file
237
modules/juce_gui_basics/native/juce_ios_FileChooser.mm
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <> struct ContainerDeletePolicy<UIDocumentPickerViewController> { static void destroy (NSObject* o) { [o release]; } };
|
||||
template <> struct ContainerDeletePolicy<NSObject<UIDocumentPickerDelegate>> { static void destroy (NSObject* o) { [o release]; } };
|
||||
|
||||
class FileChooser::Native : private Component, public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags)
|
||||
: owner (fileChooser)
|
||||
{
|
||||
static FileChooserDelegateClass cls;
|
||||
delegate = [cls.createInstance() init];
|
||||
FileChooserDelegateClass::setOwner (delegate, this);
|
||||
|
||||
auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters));
|
||||
|
||||
if ((flags & FileBrowserComponent::saveMode) != 0)
|
||||
{
|
||||
auto currentFileOrDirectory = owner.startingFile;
|
||||
|
||||
if (! currentFileOrDirectory.existsAsFile())
|
||||
{
|
||||
auto filename = (currentFileOrDirectory.isDirectory() ? "Untitled" : currentFileOrDirectory.getFileName());
|
||||
|
||||
auto tmpDirectory = File::createTempFile ("iosDummyFiles");
|
||||
|
||||
if (tmpDirectory.createDirectory().wasOk())
|
||||
{
|
||||
currentFileOrDirectory = tmpDirectory.getChildFile (filename);
|
||||
currentFileOrDirectory.replaceWithText ("");
|
||||
}
|
||||
}
|
||||
|
||||
auto url = [[NSURL alloc] initFileURLWithPath:juceStringToNS (currentFileOrDirectory.getFullPathName())];
|
||||
|
||||
controller = [[UIDocumentPickerViewController alloc] initWithURL:url
|
||||
inMode:UIDocumentPickerModeMoveToService];
|
||||
[url release];
|
||||
}
|
||||
else
|
||||
{
|
||||
controller = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:utTypeArray
|
||||
inMode:UIDocumentPickerModeOpen];
|
||||
}
|
||||
|
||||
[controller setDelegate:delegate];
|
||||
[controller setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
|
||||
|
||||
setOpaque (false);
|
||||
|
||||
auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
|
||||
setBounds (chooserBounds);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
addToDesktop (0);
|
||||
}
|
||||
|
||||
~Native()
|
||||
{
|
||||
exitModalState (0);
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
enterModalState (true, nullptr, true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
runModalLoop();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void parentHierarchyChanged() override
|
||||
{
|
||||
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
|
||||
|
||||
if (peer != newPeer)
|
||||
{
|
||||
peer = newPeer;
|
||||
|
||||
if (auto* parentController = peer->controller)
|
||||
[parentController showViewController:controller sender:parentController];
|
||||
|
||||
if (peer->view.window != nil)
|
||||
peer->view.window.autoresizesSubviews = YES;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static StringArray getUTTypesForWildcards (const String& filterWildcards)
|
||||
{
|
||||
auto filters = StringArray::fromTokens (filterWildcards, ";", "");
|
||||
StringArray result;
|
||||
|
||||
if (! filters.contains ("*") && filters.size() > 0)
|
||||
{
|
||||
for (auto filter : filters)
|
||||
{
|
||||
// iOS only supports file extension wild cards
|
||||
jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
|
||||
|
||||
auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
|
||||
auto fileExtensionCF = fileExtension.toCFString();
|
||||
|
||||
auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
|
||||
|
||||
if (tag != nullptr)
|
||||
{
|
||||
result.add (String::fromCFString (tag));
|
||||
CFRelease (tag);
|
||||
}
|
||||
|
||||
CFRelease (fileExtensionCF);
|
||||
}
|
||||
}
|
||||
else
|
||||
result.add ("public.data");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void didPickDocumentAtURL (NSURL* url)
|
||||
{
|
||||
Array<URL> chooserResults;
|
||||
chooserResults.add (URL (nsStringToJuce ([url absoluteString])));
|
||||
|
||||
owner.finished (chooserResults);
|
||||
exitModalState (1);
|
||||
}
|
||||
|
||||
void pickerWasCancelled()
|
||||
{
|
||||
Array<URL> chooserResults;
|
||||
|
||||
owner.finished (chooserResults);
|
||||
exitModalState (0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
|
||||
{
|
||||
FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
|
||||
{
|
||||
addIvar<Native*> ("owner");
|
||||
|
||||
addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
|
||||
addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
|
||||
|
||||
addProtocol (@protocol (UIDocumentPickerDelegate));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
|
||||
|
||||
//==============================================================================
|
||||
static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
|
||||
{
|
||||
auto picker = getOwner (self);
|
||||
|
||||
if (picker != nullptr)
|
||||
picker->didPickDocumentAtURL (url);
|
||||
}
|
||||
|
||||
static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
|
||||
{
|
||||
auto picker = getOwner (self);
|
||||
|
||||
if (picker != nullptr)
|
||||
picker->pickerWasCancelled();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
ScopedPointer<NSObject<UIDocumentPickerDelegate>> delegate;
|
||||
ScopedPointer<UIDocumentPickerViewController> controller;
|
||||
UIViewComponentPeer* peer = nullptr;
|
||||
|
||||
static FileChooserDelegateClass fileChooserDelegateClass;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return [[NSFileManager defaultManager] ubiquityIdentityToken] != nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent*)
|
||||
{
|
||||
return new FileChooser::Native (owner, flags);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -29,14 +29,215 @@ namespace juce
|
|||
|
||||
static bool exeIsAvailable (const char* const executable)
|
||||
{
|
||||
ChildProcess child;
|
||||
const bool ok = child.start ("which " + String (executable))
|
||||
&& child.readAllProcessOutput().trim().isNotEmpty();
|
||||
ChildProcess child;
|
||||
const bool ok = child.start ("which " + String (executable))
|
||||
&& child.readAllProcessOutput().trim().isNotEmpty();
|
||||
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
return ok;
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
class FileChooser::Native : public FileChooser::Pimpl,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags)
|
||||
: owner (fileChooser),
|
||||
isDirectory ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
||||
selectMultipleFiles ((flags & FileBrowserComponent::canSelectMultipleItems) != 0)
|
||||
{
|
||||
const File previousWorkingDirectory (File::getCurrentWorkingDirectory());
|
||||
|
||||
// use kdialog for KDE sessions or if zenity is missing
|
||||
if (exeIsAvailable ("kdialog") && (isKdeFullSession() || ! exeIsAvailable ("zenity")))
|
||||
addKDialogArgs();
|
||||
else
|
||||
addZenityArgs();
|
||||
}
|
||||
|
||||
~Native()
|
||||
{
|
||||
finish (true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
child.start (args, ChildProcess::wantStdOut);
|
||||
|
||||
while (child.isRunning())
|
||||
if (! MessageManager::getInstance()->runDispatchLoopUntil(20))
|
||||
break;
|
||||
|
||||
finish (false);
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
child.start (args, ChildProcess::wantStdOut);
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
private:
|
||||
FileChooser& owner;
|
||||
bool isDirectory, isSave, selectMultipleFiles;
|
||||
|
||||
ChildProcess child;
|
||||
StringArray args;
|
||||
String separator;
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (! child.isRunning())
|
||||
{
|
||||
stopTimer();
|
||||
finish (false);
|
||||
}
|
||||
}
|
||||
|
||||
void finish (bool shouldKill)
|
||||
{
|
||||
String result;
|
||||
Array<URL> selection;
|
||||
|
||||
if (shouldKill)
|
||||
child.kill();
|
||||
else
|
||||
result = child.readAllProcessOutput().trim();
|
||||
|
||||
if (result.isNotEmpty())
|
||||
{
|
||||
StringArray tokens;
|
||||
|
||||
if (selectMultipleFiles)
|
||||
tokens.addTokens (result, separator, "\"");
|
||||
else
|
||||
tokens.add (result);
|
||||
|
||||
for (auto& token : tokens)
|
||||
selection.add (URL (File::getCurrentWorkingDirectory().getChildFile (token)));
|
||||
}
|
||||
|
||||
if (! shouldKill)
|
||||
{
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
owner.finished (selection);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64 getTopWindowID() noexcept
|
||||
{
|
||||
if (TopLevelWindow* top = TopLevelWindow::getActiveTopLevelWindow())
|
||||
return (uint64) (pointer_sized_uint) top->getWindowHandle();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool isKdeFullSession()
|
||||
{
|
||||
return SystemStats::getEnvironmentVariable ("KDE_FULL_SESSION", String())
|
||||
.equalsIgnoreCase ("true");
|
||||
}
|
||||
|
||||
void addKDialogArgs()
|
||||
{
|
||||
args.add ("kdialog");
|
||||
|
||||
if (owner.title.isNotEmpty())
|
||||
args.add ("--title=" + owner.title);
|
||||
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
{
|
||||
args.add ("--attach");
|
||||
args.add (String (topWindowID));
|
||||
}
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = "\n";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separate-output");
|
||||
args.add ("--getopenfilename");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isSave) args.add ("--getsavefilename");
|
||||
else if (isDirectory) args.add ("--getexistingdirectory");
|
||||
else args.add ("--getopenfilename");
|
||||
}
|
||||
|
||||
File startPath;
|
||||
|
||||
if (owner.startingFile.exists())
|
||||
{
|
||||
startPath = owner.startingFile;
|
||||
}
|
||||
else if (owner.startingFile.getParentDirectory().exists())
|
||||
{
|
||||
startPath = owner.startingFile.getParentDirectory();
|
||||
}
|
||||
else
|
||||
{
|
||||
startPath = File::getSpecialLocation (File::userHomeDirectory);
|
||||
|
||||
if (isSave)
|
||||
startPath = startPath.getChildFile (owner.startingFile.getFileName());
|
||||
}
|
||||
|
||||
args.add (startPath.getFullPathName());
|
||||
args.add (owner.filters.replaceCharacter (';', ' '));
|
||||
}
|
||||
|
||||
void addZenityArgs()
|
||||
{
|
||||
args.add ("zenity");
|
||||
args.add ("--file-selection");
|
||||
|
||||
if (owner.title.isNotEmpty())
|
||||
args.add ("--title=" + owner.title);
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = ":";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separator=" + separator);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isDirectory) args.add ("--directory");
|
||||
if (isSave) args.add ("--save");
|
||||
}
|
||||
|
||||
if (owner.filters.isNotEmpty() && owner.filters != "*" && owner.filters != "*.*")
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (owner.filters, ";,|", "\"");
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
args.add ("--file-filter=" + tokens[i]);
|
||||
}
|
||||
|
||||
if (owner.startingFile.isDirectory())
|
||||
owner.startingFile.setAsCurrentWorkingDirectory();
|
||||
else if (owner.startingFile.getParentDirectory().exists())
|
||||
owner.startingFile.getParentDirectory().setAsCurrentWorkingDirectory();
|
||||
else
|
||||
File::getSpecialLocation (File::userHomeDirectory).setAsCurrentWorkingDirectory();
|
||||
|
||||
auto filename = owner.startingFile.getFileName();
|
||||
|
||||
if (! filename.isEmpty())
|
||||
args.add ("--filename=" + filename);
|
||||
|
||||
// supplying the window ID of the topmost window makes sure that Zenity pops up..
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
setenv ("WINDOWID", String (topWindowID).toRawUTF8(), true);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
|
||||
};
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
|
|
@ -47,167 +248,9 @@ bool FileChooser::isPlatformDialogAvailable()
|
|||
#endif
|
||||
}
|
||||
|
||||
static uint64 getTopWindowID() noexcept
|
||||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*)
|
||||
{
|
||||
if (TopLevelWindow* top = TopLevelWindow::getActiveTopLevelWindow())
|
||||
return (uint64) (pointer_sized_uint) top->getWindowHandle();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool isKdeFullSession()
|
||||
{
|
||||
return SystemStats::getEnvironmentVariable ("KDE_FULL_SESSION", String())
|
||||
.equalsIgnoreCase ("true");
|
||||
}
|
||||
|
||||
static void addKDialogArgs (StringArray& args, String& separator,
|
||||
const String& title, const File& file, const String& filters,
|
||||
bool isDirectory, bool isSave, bool selectMultipleFiles)
|
||||
{
|
||||
args.add ("kdialog");
|
||||
|
||||
if (title.isNotEmpty())
|
||||
args.add ("--title=" + title);
|
||||
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
{
|
||||
args.add ("--attach");
|
||||
args.add (String (topWindowID));
|
||||
}
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = "\n";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separate-output");
|
||||
args.add ("--getopenfilename");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isSave) args.add ("--getsavefilename");
|
||||
else if (isDirectory) args.add ("--getexistingdirectory");
|
||||
else args.add ("--getopenfilename");
|
||||
}
|
||||
|
||||
File startPath;
|
||||
|
||||
if (file.exists())
|
||||
{
|
||||
startPath = file;
|
||||
}
|
||||
else if (file.getParentDirectory().exists())
|
||||
{
|
||||
startPath = file.getParentDirectory();
|
||||
}
|
||||
else
|
||||
{
|
||||
startPath = File::getSpecialLocation (File::userHomeDirectory);
|
||||
|
||||
if (isSave)
|
||||
startPath = startPath.getChildFile (file.getFileName());
|
||||
}
|
||||
|
||||
args.add (startPath.getFullPathName());
|
||||
args.add (filters.replaceCharacter (';', ' '));
|
||||
}
|
||||
|
||||
static void addZenityArgs (StringArray& args, String& separator,
|
||||
const String& title, const File& file, const String& filters,
|
||||
bool isDirectory, bool isSave, bool selectMultipleFiles)
|
||||
{
|
||||
args.add ("zenity");
|
||||
args.add ("--file-selection");
|
||||
|
||||
if (title.isNotEmpty())
|
||||
args.add ("--title=" + title);
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = ":";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separator=" + separator);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isDirectory) args.add ("--directory");
|
||||
if (isSave) args.add ("--save");
|
||||
}
|
||||
|
||||
if (filters.isNotEmpty() && filters != "*" && filters != "*.*")
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (filters, ";,|", "\"");
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
args.add ("--file-filter=" + tokens[i]);
|
||||
}
|
||||
|
||||
if (file.isDirectory())
|
||||
file.setAsCurrentWorkingDirectory();
|
||||
else if (file.getParentDirectory().exists())
|
||||
file.getParentDirectory().setAsCurrentWorkingDirectory();
|
||||
else
|
||||
File::getSpecialLocation (File::userHomeDirectory).setAsCurrentWorkingDirectory();
|
||||
|
||||
if (! file.getFileName().isEmpty())
|
||||
args.add ("--filename=" + file.getFileName());
|
||||
|
||||
// supplying the window ID of the topmost window makes sure that Zenity pops up..
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
setenv ("WINDOWID", String (topWindowID).toRawUTF8(), true);
|
||||
}
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>& results,
|
||||
const String& title, const File& file, const String& filters,
|
||||
bool isDirectory, bool /* selectsFiles */,
|
||||
bool isSave, bool /* warnAboutOverwritingExistingFiles */,
|
||||
bool /*treatFilePackagesAsDirs*/,
|
||||
bool selectMultipleFiles, FilePreviewComponent*)
|
||||
{
|
||||
const File previousWorkingDirectory (File::getCurrentWorkingDirectory());
|
||||
|
||||
StringArray args;
|
||||
String separator;
|
||||
|
||||
// use kdialog for KDE sessions or if zenity is missing
|
||||
if (exeIsAvailable ("kdialog") && (isKdeFullSession() || ! exeIsAvailable ("zenity")))
|
||||
addKDialogArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles);
|
||||
else
|
||||
addZenityArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles);
|
||||
|
||||
ChildProcess child;
|
||||
|
||||
if (child.start (args, ChildProcess::wantStdOut))
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
while (child.isRunning())
|
||||
{
|
||||
if (! MessageManager::getInstance()->runDispatchLoopUntil(20))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const String result (child.readAllProcessOutput().trim());
|
||||
|
||||
if (result.isNotEmpty())
|
||||
{
|
||||
StringArray tokens;
|
||||
|
||||
if (selectMultipleFiles)
|
||||
tokens.addTokens (result, separator, "\"");
|
||||
else
|
||||
tokens.add (result);
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
results.add (File::getCurrentWorkingDirectory().getChildFile (tokens[i]));
|
||||
}
|
||||
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
}
|
||||
|
||||
previousWorkingDirectory.setAsCurrentWorkingDirectory();
|
||||
return new Native (owner, flags);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -27,95 +27,7 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
struct FileChooserDelegateClass : public ObjCClass <NSObject>
|
||||
{
|
||||
FileChooserDelegateClass() : ObjCClass <NSObject> ("JUCEFileChooser_")
|
||||
{
|
||||
addIvar<StringArray*> ("filters");
|
||||
addIvar<FilePreviewComponent*> ("filePreviewComponent");
|
||||
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@");
|
||||
addMethod (@selector (panelSelectionDidChange:), panelSelectionDidChange, "c@");
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
||||
addProtocol (@protocol (NSOpenSavePanelDelegate));
|
||||
#endif
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setFilters (id self, StringArray* filters) { object_setInstanceVariable (self, "filters", filters); }
|
||||
static void setFilePreviewComponent (id self, FilePreviewComponent* comp) { object_setInstanceVariable (self, "filePreviewComponent", comp); }
|
||||
static StringArray* getFilters (id self) { return getIvar<StringArray*> (self, "filters"); }
|
||||
static FilePreviewComponent* getFilePreviewComponent (id self) { return getIvar<FilePreviewComponent*> (self, "filePreviewComponent"); }
|
||||
|
||||
private:
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getFilters (self);
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
|
||||
{
|
||||
StringArray* const filters = getFilters (self);
|
||||
|
||||
const File f (nsStringToJuce (filename));
|
||||
|
||||
for (int i = filters->size(); --i >= 0;)
|
||||
if (f.getFileName().matchesWildcard ((*filters)[i], true))
|
||||
return true;
|
||||
|
||||
#if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
||||
NSError* error;
|
||||
NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: filename error: &error];
|
||||
|
||||
if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")])
|
||||
{
|
||||
FSRef ref;
|
||||
FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr);
|
||||
|
||||
Boolean targetIsFolder = false, wasAliased = false;
|
||||
FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0);
|
||||
|
||||
return wasAliased && targetIsFolder;
|
||||
}
|
||||
#endif
|
||||
|
||||
return f.isDirectory()
|
||||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename];
|
||||
}
|
||||
|
||||
static StringArray getSelectedPaths (id sender)
|
||||
{
|
||||
StringArray paths;
|
||||
|
||||
if ([sender isKindOfClass: [NSOpenPanel class]])
|
||||
{
|
||||
NSArray* urls = [(NSOpenPanel*) sender URLs];
|
||||
|
||||
for (NSUInteger i = 0; i < [urls count]; ++i)
|
||||
paths.add (nsStringToJuce ([[urls objectAtIndex: i] path]));
|
||||
}
|
||||
else if ([sender isKindOfClass: [NSSavePanel class]])
|
||||
{
|
||||
paths.add (nsStringToJuce ([[(NSSavePanel*) sender URL] path]));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static void panelSelectionDidChange (id self, SEL, id sender)
|
||||
{
|
||||
// NB: would need to extend FilePreviewComponent to handle the full list rather than just the first one
|
||||
if (FilePreviewComponent* const previewComp = getFilePreviewComponent (self))
|
||||
previewComp->selectedFileChanged (File (getSelectedPaths (sender)[0]));
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static NSMutableArray* createAllowedTypesArray (const StringArray& filters)
|
||||
{
|
||||
if (filters.size() == 0)
|
||||
|
|
@ -137,97 +49,159 @@ static NSMutableArray* createAllowedTypesArray (const StringArray& filters)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileChooser::showPlatformDialog (Array<File>& results,
|
||||
const String& title,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& filter,
|
||||
bool selectsDirectory,
|
||||
bool selectsFiles,
|
||||
bool isSaveDialogue,
|
||||
bool /*warnAboutOverwritingExistingFiles*/,
|
||||
bool selectMultipleFiles,
|
||||
bool treatFilePackagesAsDirs,
|
||||
FilePreviewComponent* extraInfoComponent)
|
||||
template <> struct ContainerDeletePolicy<NSSavePanel> { static void destroy (NSObject* o) { [o release]; } };
|
||||
|
||||
class FileChooser::Native : public Component, public FileChooser::Pimpl
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComponent)
|
||||
: owner (fileChooser), preview (previewComponent),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
|
||||
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
||||
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
|
||||
panel (isSave ? [[NSSavePanel alloc] init] : [[NSOpenPanel alloc] init])
|
||||
{
|
||||
ScopedPointer<TemporaryMainMenuWithStandardCommands> tempMenu;
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
tempMenu = new TemporaryMainMenuWithStandardCommands();
|
||||
setBounds (0, 0, 0, 0);
|
||||
setOpaque (true);
|
||||
|
||||
StringArray* filters = new StringArray();
|
||||
filters->addTokens (filter.replaceCharacters (",:", ";;"), ";", String());
|
||||
filters->trim();
|
||||
filters->removeEmptyStrings();
|
||||
static DelegateClass cls;
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
||||
typedef NSObject<NSOpenSavePanelDelegate> DelegateType;
|
||||
#else
|
||||
typedef NSObject DelegateType;
|
||||
#endif
|
||||
delegate = [cls.createInstance() init];
|
||||
object_setInstanceVariable (delegate, "cppObject", this);
|
||||
|
||||
static FileChooserDelegateClass cls;
|
||||
DelegateType* delegate = (DelegateType*) [[cls.createInstance() init] autorelease];
|
||||
FileChooserDelegateClass::setFilters (delegate, filters);
|
||||
[panel setDelegate:delegate];
|
||||
|
||||
NSSavePanel* panel = isSaveDialogue ? [NSSavePanel savePanel]
|
||||
: [NSOpenPanel openPanel];
|
||||
filters.addTokens (owner.filters.replaceCharacters (",:", ";;"), ";", String());
|
||||
filters.trim();
|
||||
filters.removeEmptyStrings();
|
||||
|
||||
[panel setTitle: juceStringToNS (title)];
|
||||
[panel setAllowedFileTypes: createAllowedTypesArray (*filters)];
|
||||
[panel setTitle: juceStringToNS (owner.title)];
|
||||
[panel setAllowedFileTypes: createAllowedTypesArray (filters)];
|
||||
|
||||
if (! isSaveDialogue)
|
||||
if (! isSave)
|
||||
{
|
||||
NSOpenPanel* openPanel = (NSOpenPanel*) panel;
|
||||
[openPanel setCanChooseDirectories: selectsDirectory];
|
||||
|
||||
[openPanel setCanChooseDirectories: selectsDirectories];
|
||||
[openPanel setCanChooseFiles: selectsFiles];
|
||||
[openPanel setAllowsMultipleSelection: selectMultipleFiles];
|
||||
[openPanel setAllowsMultipleSelection: selectMultiple];
|
||||
[openPanel setResolvesAliases: YES];
|
||||
|
||||
if (treatFilePackagesAsDirs)
|
||||
if (owner.treatFilePackagesAsDirs)
|
||||
[openPanel setTreatsFilePackagesAsDirectories: YES];
|
||||
}
|
||||
|
||||
if (extraInfoComponent != nullptr)
|
||||
if (preview != nullptr)
|
||||
{
|
||||
NSView* view = [[[NSView alloc] initWithFrame: makeNSRect (extraInfoComponent->getLocalBounds())] autorelease];
|
||||
extraInfoComponent->addToDesktop (0, (void*) view);
|
||||
extraInfoComponent->setVisible (true);
|
||||
FileChooserDelegateClass::setFilePreviewComponent (delegate, extraInfoComponent);
|
||||
nsViewPreview = [[NSView alloc] initWithFrame: makeNSRect (preview->getLocalBounds())];
|
||||
preview->addToDesktop (0, (void*) nsViewPreview);
|
||||
preview->setVisible (true);
|
||||
|
||||
[panel setAccessoryView: view];
|
||||
[panel setAccessoryView: nsViewPreview];
|
||||
}
|
||||
|
||||
[panel setDelegate: delegate];
|
||||
|
||||
if (isSaveDialogue || selectsDirectory)
|
||||
if (isSave || selectsDirectories)
|
||||
[panel setCanCreateDirectories: YES];
|
||||
|
||||
String directory, filename;
|
||||
[panel setLevel:NSModalPanelWindowLevel];
|
||||
|
||||
if (currentFileOrDirectory.isDirectory())
|
||||
if (owner.startingFile.isDirectory())
|
||||
{
|
||||
directory = currentFileOrDirectory.getFullPathName();
|
||||
startingDirectory = owner.startingFile.getFullPathName();
|
||||
}
|
||||
else
|
||||
{
|
||||
directory = currentFileOrDirectory.getParentDirectory().getFullPathName();
|
||||
filename = currentFileOrDirectory.getFileName();
|
||||
startingDirectory = owner.startingFile.getParentDirectory().getFullPathName();
|
||||
filename = owner.startingFile.getFileName();
|
||||
}
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)
|
||||
[panel setDirectoryURL: createNSURLFromFile (directory)];
|
||||
[panel setDirectoryURL: createNSURLFromFile (startingDirectory)];
|
||||
[panel setNameFieldStringValue: juceStringToNS (filename)];
|
||||
|
||||
if ([panel runModal] == 1 /*NSModalResponseOK*/)
|
||||
#else
|
||||
if ([panel runModalForDirectory: juceStringToNS (directory)
|
||||
file: juceStringToNS (filename)] == 1 /*NSModalResponseOK*/)
|
||||
#endif
|
||||
}
|
||||
|
||||
~Native()
|
||||
{
|
||||
exitModalState (0);
|
||||
removeFromDesktop();
|
||||
|
||||
if (panel != nil)
|
||||
{
|
||||
if (isSaveDialogue)
|
||||
[panel setDelegate:nil];
|
||||
|
||||
if (nsViewPreview != nil)
|
||||
{
|
||||
results.add (File (nsStringToJuce ([[panel URL] path])));
|
||||
[panel setAccessoryView: nil];
|
||||
|
||||
[nsViewPreview release];
|
||||
|
||||
nsViewPreview = nil;
|
||||
preview = nullptr;
|
||||
}
|
||||
|
||||
[panel close];
|
||||
}
|
||||
|
||||
panel = nullptr;
|
||||
|
||||
if (delegate != nil)
|
||||
{
|
||||
[delegate release];
|
||||
delegate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
if (panel != nil)
|
||||
{
|
||||
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
addToDesktop (0);
|
||||
|
||||
enterModalState (true);
|
||||
[panel beginWithCompletionHandler:CreateObjCBlock (this, &Native::finished)];
|
||||
}
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
ScopedPointer<TemporaryMainMenuWithStandardCommands> tempMenu;
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
tempMenu = new TemporaryMainMenuWithStandardCommands();
|
||||
|
||||
jassert (panel != nil);
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)
|
||||
auto result = [panel runModal];
|
||||
#else
|
||||
auto result = [panel runModalForDirectory: juceStringToNS (startingDirectory)
|
||||
file: juceStringToNS (filename)];
|
||||
#endif
|
||||
|
||||
finished (result);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
||||
typedef NSObject<NSOpenSavePanelDelegate> DelegateType;
|
||||
#else
|
||||
typedef NSObject DelegateType;
|
||||
#endif
|
||||
|
||||
void finished (NSInteger result)
|
||||
{
|
||||
Array<URL> chooserResults;
|
||||
|
||||
exitModalState (0);
|
||||
|
||||
if (panel != nil && result == NSFileHandlingPanelOKButton)
|
||||
{
|
||||
if (isSave)
|
||||
{
|
||||
chooserResults.add (URL (nsStringToJuce ([[panel URL] absoluteString])));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -235,12 +209,120 @@ void FileChooser::showPlatformDialog (Array<File>& results,
|
|||
NSArray* urls = [openPanel URLs];
|
||||
|
||||
for (unsigned int i = 0; i < [urls count]; ++i)
|
||||
results.add (File (nsStringToJuce ([[urls objectAtIndex: i] path])));
|
||||
chooserResults.add (URL (nsStringToJuce ([[urls objectAtIndex: i] absoluteString])));
|
||||
}
|
||||
}
|
||||
|
||||
[panel setDelegate: nil];
|
||||
owner.finished (chooserResults);
|
||||
}
|
||||
|
||||
bool shouldShowFilename (const String& filenameToTest)
|
||||
{
|
||||
const File f (filenameToTest);
|
||||
auto nsFilename = juceStringToNS (filenameToTest);
|
||||
|
||||
for (int i = filters.size(); --i >= 0;)
|
||||
if (f.getFileName().matchesWildcard (filters[i], true))
|
||||
return true;
|
||||
|
||||
#if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
||||
NSError* error;
|
||||
NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: nsFilename error: &error];
|
||||
|
||||
if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")])
|
||||
{
|
||||
FSRef ref;
|
||||
FSPathMakeRef ((const UInt8*) [nsFilename fileSystemRepresentation], &ref, nullptr);
|
||||
|
||||
Boolean targetIsFolder = false, wasAliased = false;
|
||||
FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0);
|
||||
|
||||
return wasAliased && targetIsFolder;
|
||||
}
|
||||
#endif
|
||||
|
||||
return f.isDirectory()
|
||||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: nsFilename];
|
||||
}
|
||||
|
||||
void panelSelectionDidChange (id sender)
|
||||
{
|
||||
// NB: would need to extend FilePreviewComponent to handle the full list rather than just the first one
|
||||
if (preview != nullptr)
|
||||
preview->selectedFileChanged (File (getSelectedPaths (sender)[0]));
|
||||
}
|
||||
|
||||
static StringArray getSelectedPaths (id sender)
|
||||
{
|
||||
StringArray paths;
|
||||
|
||||
if ([sender isKindOfClass: [NSOpenPanel class]])
|
||||
{
|
||||
NSArray* urls = [(NSOpenPanel*) sender URLs];
|
||||
|
||||
for (NSUInteger i = 0; i < [urls count]; ++i)
|
||||
paths.add (nsStringToJuce ([[urls objectAtIndex: i] path]));
|
||||
}
|
||||
else if ([sender isKindOfClass: [NSSavePanel class]])
|
||||
{
|
||||
paths.add (nsStringToJuce ([[(NSSavePanel*) sender URL] path]));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
FilePreviewComponent* preview;
|
||||
NSView* nsViewPreview = nullptr;
|
||||
bool selectsDirectories, selectsFiles, isSave, selectMultiple;
|
||||
|
||||
ScopedPointer<NSSavePanel> panel;
|
||||
DelegateType* delegate;
|
||||
|
||||
StringArray filters;
|
||||
String startingDirectory, filename;
|
||||
|
||||
//==============================================================================
|
||||
struct DelegateClass : ObjCClass<DelegateType>
|
||||
{
|
||||
DelegateClass() : ObjCClass <DelegateType> ("JUCEFileChooser_")
|
||||
{
|
||||
addIvar<Native*> ("cppObject");
|
||||
|
||||
addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@");
|
||||
addMethod (@selector (panelSelectionDidChange:), panelSelectionDidChange, "c@");
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
||||
addProtocol (@protocol (NSOpenSavePanelDelegate));
|
||||
#endif
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
|
||||
{
|
||||
auto* _this = getIvar<Native*> (self, "cppObject");
|
||||
|
||||
return _this->shouldShowFilename (nsStringToJuce (filename)) ? YES : NO;
|
||||
}
|
||||
|
||||
static void panelSelectionDidChange (id self, SEL, id sender)
|
||||
{
|
||||
auto* _this = getIvar<Native*> (self, "cppObject");
|
||||
|
||||
_this->panelSelectionDidChange (sender);
|
||||
}
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
|
||||
};
|
||||
|
||||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent* preview)
|
||||
{
|
||||
return new FileChooser::Native (owner, flags, preview);
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
|
|
@ -252,29 +334,4 @@ bool FileChooser::isPlatformDialogAvailable()
|
|||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>&,
|
||||
const String& /*title*/,
|
||||
const File& /*currentFileOrDirectory*/,
|
||||
const String& /*filter*/,
|
||||
bool /*selectsDirectory*/,
|
||||
bool /*selectsFiles*/,
|
||||
bool /*isSaveDialogue*/,
|
||||
bool /*warnAboutOverwritingExistingFiles*/,
|
||||
bool /*selectMultipleFiles*/,
|
||||
bool /*treatFilePackagesAsDirs*/,
|
||||
FilePreviewComponent*)
|
||||
{
|
||||
jassertfalse; //there's no such thing in iOS
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -27,74 +27,102 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
namespace FileChooserHelpers
|
||||
// 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 immedietely once the HWND will have been created a while later.
|
||||
class Win32NativeFileChooser : public ReferenceCountedObject, private Thread
|
||||
{
|
||||
struct FileChooserCallbackInfo
|
||||
public:
|
||||
typedef ReferenceCountedObjectPtr<Win32NativeFileChooser> Ptr;
|
||||
|
||||
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),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
|
||||
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
||||
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
||||
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
|
||||
nativeDialogRef (nullptr), shouldCancel (0)
|
||||
{
|
||||
String initialPath;
|
||||
String returnedString; // need this to get non-existent pathnames from the directory chooser
|
||||
ScopedPointer<Component> customComponent;
|
||||
};
|
||||
auto parentDirectory = startingFile.getParentDirectory();
|
||||
|
||||
static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
|
||||
{
|
||||
FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) lpData;
|
||||
// Handle nonexistent root directories in the same way as existing ones
|
||||
files.calloc (static_cast<size_t> (charsAvailableForResult) + 1);
|
||||
|
||||
if (msg == BFFM_INITIALIZED)
|
||||
SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) info->initialPath.toWideCharPointer());
|
||||
else if (msg == BFFM_VALIDATEFAILEDW)
|
||||
info->returnedString = (LPCWSTR) lParam;
|
||||
else if (msg == BFFM_VALIDATEFAILEDA)
|
||||
info->returnedString = (const char*) lParam;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UINT_PTR CALLBACK openCallback (HWND hdlg, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
|
||||
{
|
||||
if (uiMsg == WM_INITDIALOG)
|
||||
if (startingFile.isDirectory() ||startingFile.isRoot())
|
||||
{
|
||||
Component* customComp = ((FileChooserCallbackInfo*) (((OPENFILENAMEW*) lParam)->lCustData))->customComponent;
|
||||
|
||||
HWND dialogH = GetParent (hdlg);
|
||||
jassert (dialogH != 0);
|
||||
if (dialogH == 0)
|
||||
dialogH = hdlg;
|
||||
|
||||
RECT r, cr;
|
||||
GetWindowRect (dialogH, &r);
|
||||
GetClientRect (dialogH, &cr);
|
||||
|
||||
SetWindowPos (dialogH, 0,
|
||||
r.left, r.top,
|
||||
customComp->getWidth() + jmax (150, (int) (r.right - r.left)),
|
||||
jmax (150, (int) (r.bottom - r.top)),
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
|
||||
|
||||
customComp->setBounds (cr.right, cr.top, customComp->getWidth(), cr.bottom - cr.top);
|
||||
customComp->addToDesktop (0, dialogH);
|
||||
initialPath = startingFile.getFullPathName();
|
||||
}
|
||||
else if (uiMsg == WM_NOTIFY)
|
||||
else
|
||||
{
|
||||
LPOFNOTIFY ofn = (LPOFNOTIFY) lParam;
|
||||
|
||||
if (ofn->hdr.code == CDN_SELCHANGE)
|
||||
{
|
||||
FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) ofn->lpOFN->lCustData;
|
||||
|
||||
if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (info->customComponent->getChildComponent(0)))
|
||||
{
|
||||
WCHAR path [MAX_PATH * 2] = { 0 };
|
||||
CommDlg_OpenSave_GetFilePath (GetParent (hdlg), (LPARAM) &path, MAX_PATH);
|
||||
|
||||
comp->selectedFileChanged (File (path));
|
||||
}
|
||||
}
|
||||
startingFile.getFileName().copyToUTF16 (files,
|
||||
static_cast<size_t> (charsAvailableForResult) * sizeof (WCHAR));
|
||||
initialPath = parentDirectory.getFullPathName();
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (! selectsDirectories)
|
||||
{
|
||||
if (previewComp != nullptr)
|
||||
customComponent = new CustomComponentHolder (previewComp);
|
||||
|
||||
setupFilters();
|
||||
}
|
||||
}
|
||||
|
||||
~Win32NativeFileChooser()
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (-1);
|
||||
}
|
||||
|
||||
void open (bool async)
|
||||
{
|
||||
results.clear();
|
||||
|
||||
// the thread should not be running
|
||||
nativeDialogRef.set (nullptr);
|
||||
|
||||
if (async)
|
||||
{
|
||||
jassert (! isThreadRunning());
|
||||
|
||||
threadHasReference.reset();
|
||||
startThread();
|
||||
|
||||
threadHasReference.wait (-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
results = openDialog (false);
|
||||
owner->exitModalState (results.size() > 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
ScopedLock lock (deletingDialog);
|
||||
|
||||
customComponent = nullptr;
|
||||
shouldCancel.set (1);
|
||||
|
||||
if (auto hwnd = nativeDialogRef.get())
|
||||
EndDialog (hwnd, 0);
|
||||
}
|
||||
|
||||
Array<URL> results;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class CustomComponentHolder : public Component
|
||||
{
|
||||
public:
|
||||
|
|
@ -120,7 +148,413 @@ namespace FileChooserHelpers
|
|||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Component::SafePointer<Component> owner;
|
||||
String title, filtersString;
|
||||
ScopedPointer<CustomComponentHolder> customComponent;
|
||||
String initialPath, returnedString, defaultExtension;
|
||||
|
||||
WaitableEvent threadHasReference;
|
||||
CriticalSection deletingDialog;
|
||||
|
||||
bool selectsDirectories, selectsFiles, isSave, warnAboutOverwrite, selectMultiple;
|
||||
|
||||
HeapBlock<WCHAR> files;
|
||||
HeapBlock<WCHAR> filters;
|
||||
|
||||
Atomic<HWND> nativeDialogRef;
|
||||
Atomic<int> shouldCancel;
|
||||
|
||||
//==============================================================================
|
||||
Array<URL> openDialog (bool async)
|
||||
{
|
||||
Array<URL> selections;
|
||||
|
||||
if (selectsDirectories)
|
||||
{
|
||||
BROWSEINFO bi = { 0 };
|
||||
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 = { 0 };
|
||||
|
||||
#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)
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (filtersString, ";,", "\"'");
|
||||
tokens.trim();
|
||||
tokens.removeEmptyStrings();
|
||||
|
||||
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
|
||||
{
|
||||
defaultExtension = tokens[0].fromFirstOccurrenceOf (".", false, false);
|
||||
of.lpstrDefExt = defaultExtension.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()))));
|
||||
}
|
||||
}
|
||||
|
||||
getNativeDialogList().removeValue (this);
|
||||
|
||||
return selections;
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
// as long as the thread is running, don't delete this class
|
||||
Ptr safeThis (this);
|
||||
threadHasReference.signal();
|
||||
|
||||
Array<URL> r = openDialog (true);
|
||||
MessageManager::callAsync ([safeThis, r] ()
|
||||
{
|
||||
safeThis->results = r;
|
||||
|
||||
if (safeThis->owner != nullptr)
|
||||
safeThis->owner->exitModalState (r.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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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.get() != 0)
|
||||
{
|
||||
EndDialog (hdlg, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
nativeDialogRef.set (hdlg);
|
||||
|
||||
if (customComponent)
|
||||
{
|
||||
Component::SafePointer<Component> custom (customComponent);
|
||||
|
||||
RECT r, cr;
|
||||
GetWindowRect (hdlg, &r);
|
||||
GetClientRect (hdlg, &cr);
|
||||
|
||||
auto componentWidth = custom->getWidth();
|
||||
|
||||
SetWindowPos (hdlg, 0,
|
||||
r.left, r.top,
|
||||
componentWidth + jmax (150, (int) (r.right - r.left)),
|
||||
jmax (150, (int) (r.bottom - r.top)),
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
custom->setBounds (cr.right, cr.top, componentWidth, cr.bottom - cr.top);
|
||||
custom->addToDesktop (0, hdlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageManager::callAsync ([custom, cr, componentWidth, hdlg] () mutable
|
||||
{
|
||||
if (custom != nullptr)
|
||||
{
|
||||
custom->setBounds (cr.right, cr.top, componentWidth, cr.bottom - cr.top);
|
||||
custom->addToDesktop (0, hdlg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroyDialog (HWND hdlg)
|
||||
{
|
||||
ScopedLock exiting (deletingDialog);
|
||||
|
||||
getNativeDialogList().remove (hdlg);
|
||||
nativeDialogRef.set (nullptr);
|
||||
}
|
||||
|
||||
void selectionChanged (HWND hdlg)
|
||||
{
|
||||
ScopedLock lock (deletingDialog);
|
||||
|
||||
if (customComponent != nullptr && shouldCancel.get() == 0)
|
||||
{
|
||||
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 == 0)
|
||||
dialogH = hwnd;
|
||||
|
||||
return dialogH;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser)
|
||||
};
|
||||
|
||||
class FileChooser::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))
|
||||
{
|
||||
const Rectangle<int> mainMon (Desktop::getInstance().getDisplays().getMainDisplay().userArea);
|
||||
|
||||
setBounds (mainMon.getX() + mainMon.getWidth() / 4,
|
||||
mainMon.getY() + mainMon.getHeight() / 4,
|
||||
0, 0);
|
||||
|
||||
setOpaque (true);
|
||||
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
addToDesktop (0);
|
||||
}
|
||||
|
||||
~Native()
|
||||
{
|
||||
exitModalState (0);
|
||||
nativeFileChooser->cancel();
|
||||
nativeFileChooser = nullptr;
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
SafePointer<Native> safeThis (this);
|
||||
|
||||
enterModalState (true, ModalCallbackFunction::create(
|
||||
[safeThis] (int)
|
||||
{
|
||||
if (safeThis != nullptr)
|
||||
safeThis->owner.finished (safeThis->nativeFileChooser->results);
|
||||
}));
|
||||
|
||||
nativeFileChooser->open (true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
enterModalState (true);
|
||||
nativeFileChooser->open (false);
|
||||
exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0);
|
||||
nativeFileChooser->cancel();
|
||||
|
||||
owner.finished (nativeFileChooser->results);
|
||||
}
|
||||
|
||||
private:
|
||||
FileChooser& owner;
|
||||
Win32NativeFileChooser::Ptr nativeFileChooser;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
|
|
@ -132,172 +566,10 @@ bool FileChooser::isPlatformDialogAvailable()
|
|||
#endif
|
||||
}
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>& results, const String& title_, const File& currentFileOrDirectory,
|
||||
const String& filter, bool selectsDirectory, bool /*selectsFiles*/,
|
||||
bool isSaveDialogue, bool warnAboutOverwritingExistingFiles,
|
||||
bool selectMultipleFiles, bool /*treatFilePackagesAsDirs*/,
|
||||
FilePreviewComponent* extraInfoComponent)
|
||||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent* preview)
|
||||
{
|
||||
using namespace FileChooserHelpers;
|
||||
|
||||
const String title (title_);
|
||||
String defaultExtension; // scope of these strings must extend beyond dialog's lifetime.
|
||||
|
||||
HeapBlock<WCHAR> files;
|
||||
const size_t charsAvailableForResult = 32768;
|
||||
files.calloc (charsAvailableForResult + 1);
|
||||
int filenameOffset = 0;
|
||||
|
||||
FileChooserCallbackInfo info;
|
||||
|
||||
// use a modal window as the parent for this dialog box
|
||||
// to block input from other app windows
|
||||
Component parentWindow;
|
||||
const Rectangle<int> mainMon (Desktop::getInstance().getDisplays().getMainDisplay().userArea);
|
||||
parentWindow.setBounds (mainMon.getX() + mainMon.getWidth() / 4,
|
||||
mainMon.getY() + mainMon.getHeight() / 4,
|
||||
0, 0);
|
||||
parentWindow.setOpaque (true);
|
||||
parentWindow.setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
parentWindow.addToDesktop (0);
|
||||
|
||||
if (extraInfoComponent == nullptr)
|
||||
parentWindow.enterModalState();
|
||||
|
||||
auto parentDirectory = currentFileOrDirectory.getParentDirectory();
|
||||
|
||||
// Handle nonexistent root directories in the same way as existing ones
|
||||
if (currentFileOrDirectory.isDirectory() || currentFileOrDirectory.isRoot())
|
||||
{
|
||||
info.initialPath = currentFileOrDirectory.getFullPathName();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFileOrDirectory.getFileName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR));
|
||||
info.initialPath = parentDirectory.getFullPathName();
|
||||
}
|
||||
|
||||
if (selectsDirectory)
|
||||
{
|
||||
BROWSEINFO bi = { 0 };
|
||||
bi.hwndOwner = (HWND) parentWindow.getWindowHandle();
|
||||
bi.pszDisplayName = files;
|
||||
bi.lpszTitle = title.toWideCharPointer();
|
||||
bi.lParam = (LPARAM) &info;
|
||||
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;
|
||||
info.returnedString.clear();
|
||||
}
|
||||
|
||||
LPMALLOC al;
|
||||
if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
|
||||
al->Free (list);
|
||||
|
||||
if (info.returnedString.isNotEmpty())
|
||||
{
|
||||
results.add (File (String (files.get())).getSiblingFile (info.returnedString));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;
|
||||
|
||||
if (warnAboutOverwritingExistingFiles)
|
||||
flags |= OFN_OVERWRITEPROMPT;
|
||||
|
||||
if (selectMultipleFiles)
|
||||
flags |= OFN_ALLOWMULTISELECT;
|
||||
|
||||
if (extraInfoComponent != nullptr)
|
||||
{
|
||||
flags |= OFN_ENABLEHOOK;
|
||||
|
||||
info.customComponent = new CustomComponentHolder (extraInfoComponent);
|
||||
info.customComponent->enterModalState (false);
|
||||
}
|
||||
|
||||
const size_t filterSpaceNumChars = 2048;
|
||||
HeapBlock<WCHAR> filters;
|
||||
filters.calloc (filterSpaceNumChars);
|
||||
const size_t bytesWritten = filter.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
|
||||
filter.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
|
||||
((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));
|
||||
|
||||
for (size_t i = 0; i < filterSpaceNumChars; ++i)
|
||||
if (filters[i] == '|')
|
||||
filters[i] = 0;
|
||||
|
||||
OPENFILENAMEW of = { 0 };
|
||||
String localPath (info.initialPath);
|
||||
|
||||
#ifdef OPENFILENAME_SIZE_VERSION_400W
|
||||
of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
|
||||
#else
|
||||
of.lStructSize = sizeof (of);
|
||||
#endif
|
||||
of.hwndOwner = (HWND) parentWindow.getWindowHandle();
|
||||
of.lpstrFilter = filters.getData();
|
||||
of.nFilterIndex = 1;
|
||||
of.lpstrFile = files;
|
||||
of.nMaxFile = (DWORD) charsAvailableForResult;
|
||||
of.lpstrInitialDir = localPath.toWideCharPointer();
|
||||
of.lpstrTitle = title.toWideCharPointer();
|
||||
of.Flags = flags;
|
||||
of.lCustData = (LPARAM) &info;
|
||||
|
||||
if (extraInfoComponent != nullptr)
|
||||
of.lpfnHook = &openCallback;
|
||||
|
||||
if (isSaveDialogue)
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (filter, ";,", "\"'");
|
||||
tokens.trim();
|
||||
tokens.removeEmptyStrings();
|
||||
|
||||
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
|
||||
{
|
||||
defaultExtension = tokens[0].fromFirstOccurrenceOf (".", false, false);
|
||||
of.lpstrDefExt = defaultExtension.toWideCharPointer();
|
||||
}
|
||||
|
||||
if (! GetSaveFileName (&of))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! GetOpenFileName (&of))
|
||||
return;
|
||||
}
|
||||
|
||||
filenameOffset = of.nFileOffset;
|
||||
}
|
||||
|
||||
if (selectMultipleFiles && filenameOffset > 0 && files [filenameOffset - 1] == 0)
|
||||
{
|
||||
const WCHAR* filename = files + filenameOffset;
|
||||
|
||||
while (*filename != 0)
|
||||
{
|
||||
results.add (File (String (files.get())).getChildFile (String (filename)));
|
||||
filename += wcslen (filename) + 1;
|
||||
}
|
||||
}
|
||||
else if (files[0] != 0)
|
||||
{
|
||||
results.add (File (String (files.get())));
|
||||
}
|
||||
return new FileChooser::Native (owner, flags, preview);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue