fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).

This commit is contained in:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

View File

@ -0,0 +1,114 @@
/*
==============================================================================
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 <typename IDType>
class MultiTouchMapper
{
public:
MultiTouchMapper() {}
int getIndexOfTouch (ComponentPeer* peer, IDType touchID)
{
jassert (touchID != 0); // need to rethink this if IDs can be 0!
TouchInfo info {touchID, peer};
int touchIndex = currentTouches.indexOf (info);
if (touchIndex < 0)
{
auto emptyTouchIndex = currentTouches.indexOf ({});
touchIndex = (emptyTouchIndex >= 0 ? emptyTouchIndex : currentTouches.size());
currentTouches.set (touchIndex, info);
}
return touchIndex;
}
void clear()
{
currentTouches.clear();
}
void clearTouch (int index)
{
currentTouches.set (index, {});
}
bool areAnyTouchesActive() const noexcept
{
for (auto& t : currentTouches)
if (t.touchId != 0)
return true;
return false;
}
void deleteAllTouchesForPeer (ComponentPeer* peer)
{
for (auto& t : currentTouches)
if (t.owner == peer)
t.touchId = 0;
}
private:
//==============================================================================
struct TouchInfo
{
TouchInfo() noexcept : touchId (0), owner (nullptr) {}
TouchInfo (IDType idToUse, ComponentPeer* peer) noexcept : touchId (idToUse), owner (peer) {}
TouchInfo (const TouchInfo&) = default;
TouchInfo& operator= (const TouchInfo&) = default;
// VS2013 can't default move constructors
TouchInfo (TouchInfo&& other) noexcept : touchId (other.touchId), owner (other.owner) {}
// VS2013 can't default move assignments
TouchInfo& operator= (TouchInfo&& other) noexcept
{
touchId = other.touchId;
owner = other.owner;
return *this;
}
IDType touchId;
ComponentPeer* owner;
bool operator== (const TouchInfo& o) const noexcept { return (touchId == o.touchId); }
};
//==============================================================================
Array<TouchInfo> currentTouches;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTouchMapper)
};
} // namespace juce

View File

@ -0,0 +1,846 @@
/*
==============================================================================
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
{
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
FIELD (providers, "providers", "[Landroid/content/pm/ProviderInfo;")
DECLARE_JNI_CLASS (AndroidPackageInfo, "android/content/pm/PackageInfo");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
FIELD (authority, "authority", "Ljava/lang/String;")
DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "<init>", "(Landroid/os/ParcelFileDescriptor;JJ)V") \
METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \
METHOD (getLength, "getLength", "()J")
DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (close, "close", "()V")
DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "<init>", "(L" JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH ";JLjava/lang/String;I)V") \
METHOD (startWatching, "startWatching", "()V") \
METHOD (stopWatching, "stopWatching", "()V")
DECLARE_JNI_CLASS (JuceContentProviderFileObserver, JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH "$ProviderFileObserver");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \
METHOD (constructor, "<init>", "(L" JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH ";J[Ljava/lang/String;)V")
DECLARE_JNI_CLASS (JuceContentProviderFileObserverCursor, JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH "$ProviderCursor");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;")
DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor");
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidContentSharerCursor
{
public:
class Owner
{
public:
virtual ~Owner() {}
virtual void cursorClosed (const AndroidContentSharerCursor&) = 0;
};
AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env,
const LocalRef<jobject>& contentProvider,
const LocalRef<jobjectArray>& resultColumns)
: owner (ownerToUse),
cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserverCursor,
JuceContentProviderFileObserverCursor.constructor,
contentProvider.get(),
reinterpret_cast<jlong> (this),
resultColumns.get()))))
{
// the content provider must be created first
jassert (contentProvider.get() != 0);
}
jobject getNativeCursor() { return cursor.get(); }
void cursorClosed()
{
MessageManager::callAsync ([this] { owner.cursorClosed (*this); });
}
private:
Owner& owner;
GlobalRef cursor;
};
//==============================================================================
class AndroidContentSharerFileObserver
{
public:
class Owner
{
public:
virtual ~Owner() {}
virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0;
};
AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env,
const LocalRef<jobject>& contentProvider,
const String& filepathToUse)
: owner (ownerToUse),
filepath (filepathToUse),
fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
JuceContentProviderFileObserver.constructor,
contentProvider.get(),
reinterpret_cast<jlong> (this),
javaString (filepath).get(),
open | access | closeWrite | closeNoWrite))))
{
// the content provider must be created first
jassert (contentProvider.get() != 0);
env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching);
}
void onFileEvent (int event, const LocalRef<jstring>& path)
{
ignoreUnused (path);
if (event == open)
{
++numOpenedHandles;
}
else if (event == access)
{
fileWasRead = true;
}
else if (event == closeNoWrite || event == closeWrite)
{
--numOpenedHandles;
// numOpenedHandles may get negative if we don't receive open handle event.
if (fileWasRead && numOpenedHandles <= 0)
{
MessageManager::callAsync ([this]
{
getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
owner.fileHandleClosed (*this);
});
}
}
}
private:
static constexpr int open = 32;
static constexpr int access = 1;
static constexpr int closeWrite = 8;
static constexpr int closeNoWrite = 16;
bool fileWasRead = false;
int numOpenedHandles = 0;
Owner& owner;
String filepath;
GlobalRef fileObserver;
};
//==============================================================================
class AndroidContentSharerPrepareFilesThread : private Thread
{
public:
AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse,
const Array<URL>& fileUrlsToUse,
const String& packageNameToUse,
const String& uriBaseToUse)
: Thread ("AndroidContentSharerPrepareFilesThread"),
owner (ownerToUse),
fileUrls (fileUrlsToUse),
resultFileUris (GlobalRef (LocalRef<jobject> (getEnv()->NewObject (JavaArrayList,
JavaArrayList.constructor,
fileUrls.size())))),
packageName (packageNameToUse),
uriBase (uriBaseToUse)
{
startThread();
}
~AndroidContentSharerPrepareFilesThread()
{
signalThreadShouldExit();
waitForThreadToExit (10000);
for (auto& f : temporaryFilesFromAssetFiles)
f.deleteFile();
}
jobject getResultFileUris() { return resultFileUris.get(); }
const StringArray& getMimeTypes() const { return mimeTypes; }
const StringArray& getFilePaths() const { return filePaths; }
private:
struct StreamCloser
{
StreamCloser (jobject streamToUse)
: stream (GlobalRef (streamToUse))
{
}
~StreamCloser()
{
if (stream.get() != 0)
getEnv()->CallVoidMethod (stream, JavaCloseable.close);
}
GlobalRef stream;
};
void run() override
{
auto* env = getEnv();
bool canSpecifyMimeTypes = true;
for (auto f : fileUrls)
{
auto scheme = f.getScheme();
// Only "file://" scheme or no scheme (for files in app bundle) are allowed!
jassert (scheme.isEmpty() || scheme == "file");
if (scheme.isEmpty())
{
// Raw resource names need to be all lower case
jassert (f.toString (true).toLowerCase() == f.toString (true));
// This will get us a file with file:// URI
f = copyAssetFileToTemporaryFile (env, f.toString (true));
if (f.isEmpty())
continue;
}
if (threadShouldExit())
return;
auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false));
filePaths.add (filepath);
auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
auto fileExtension = filename.fromLastOccurrenceOf (".", false, true);
auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename;
auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
javaString (contentString).get()));
if (canSpecifyMimeTypes)
canSpecifyMimeTypes = fileExtension.isNotEmpty();
if (canSpecifyMimeTypes)
mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension));
else
mimeTypes.clear();
env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get());
}
owner.triggerAsyncUpdate();
}
URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
{
auto resources = LocalRef<jobject> (env->CallObjectMethod (android.activity, JuceAppActivity.getResources));
int fileId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (filename).get(),
javaString ("raw").get(), javaString (packageName).get());
// Raw resource not found. Please make sure that you include your file as a raw resource
// and that you specify just the file name, without an extention.
jassert (fileId != 0);
if (fileId == 0)
return {};
auto assetFd = LocalRef<jobject> (env->CallObjectMethod (resources,
AndroidResources.openRawResourceFd,
fileId));
auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd,
AssetFileDescriptor.createInputStream)));
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
if (exception != 0)
{
// Failed to open file stream for resource
jassertfalse;
env->ExceptionClear();
return {};
}
auto tempFile = File::createTempFile ({});
tempFile.createDirectory();
tempFile = tempFile.getChildFile (filename);
auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
JavaFileOutputStream.constructor,
javaString (tempFile.getFullPathName()).get())));
exception = LocalRef<jobject> (env->ExceptionOccurred());
if (exception != 0)
{
// Failed to open file stream for temporary file
jassertfalse;
env->ExceptionClear();
return {};
}
auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024));
int bytesRead = 0;
while (true)
{
if (threadShouldExit())
return {};
bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
exception = LocalRef<jobject> (env->ExceptionOccurred());
if (exception != 0)
{
// Failed to read from resource file.
jassertfalse;
env->ExceptionClear();
return {};
}
if (bytesRead < 0)
break;
env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
if (exception != 0)
{
// Failed to write to temporary file.
jassertfalse;
env->ExceptionClear();
return {};
}
}
temporaryFilesFromAssetFiles.add (tempFile);
return URL (tempFile);
}
AsyncUpdater& owner;
Array<URL> fileUrls;
GlobalRef resultFileUris;
String packageName;
String uriBase;
StringArray filePaths;
Array<File> temporaryFilesFromAssetFiles;
StringArray mimeTypes;
};
//==============================================================================
class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
public AndroidContentSharerFileObserver::Owner,
public AndroidContentSharerCursor::Owner,
public AsyncUpdater,
private Timer
{
public:
ContentSharerNativeImpl (ContentSharer& cs)
: owner (cs),
packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (android.activity,
JuceAppActivity.getPackageName)))),
uriBase ("content://" + packageName + ".sharingcontentprovider/")
{
}
~ContentSharerNativeImpl()
{
masterReference.clear();
}
void shareFiles (const Array<URL>& files) override
{
if (! isContentSharingEnabled())
{
// You need to enable "Content Sharing" in Projucer's Android exporter.
jassertfalse;
owner.sharingFinished (false, {});
}
prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase));
}
void shareText (const String& text) override
{
if (! isContentSharingEnabled())
{
// You need to enable "Content Sharing" in Projucer's Android exporter.
jassertfalse;
owner.sharingFinished (false, {});
}
auto* env = getEnv();
auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
env->CallObjectMethod (intent, AndroidIntent.setAction,
javaString ("android.intent.action.SEND").get());
env->CallObjectMethod (intent, AndroidIntent.putExtra,
javaString ("android.intent.extra.TEXT").get(),
javaString (text).get());
env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser,
intent.get(), javaString ("Choose share target").get()));
env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003);
}
//==============================================================================
void cursorClosed (const AndroidContentSharerCursor& cursor) override
{
cursors.removeObject (&cursor);
}
void fileHandleClosed (const AndroidContentSharerFileObserver&) override
{
decrementPendingFileCountAndNotifyOwnerIfReady();
}
//==============================================================================
void* openFile (const LocalRef<jobject>& contentProvider,
const LocalRef<jobject>& uri, const LocalRef<jstring>& mode)
{
ignoreUnused (mode);
WeakReference<ContentSharerNativeImpl> weakRef (this);
if (weakRef == nullptr)
return nullptr;
auto* env = getEnv();
auto uriElements = getContentUriElements (env, uri);
if (uriElements.filepath.isEmpty())
return nullptr;
return getAssetFileDescriptor (env, contentProvider, uriElements.filepath);
}
void* query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri,
const LocalRef<jobjectArray>& projection, const LocalRef<jobject>& selection,
const LocalRef<jobjectArray>& selectionArgs, const LocalRef<jobject>& sortOrder)
{
ignoreUnused (selection, selectionArgs, sortOrder);
StringArray requestedColumns = javaStringArrayToJuce (projection);
StringArray supportedColumns = getSupportedColumns();
StringArray resultColumns;
for (const auto& col : supportedColumns)
{
if (requestedColumns.contains (col))
resultColumns.add (col);
}
// Unsupported columns were queried, file sharing may fail.
if (resultColumns.isEmpty())
return nullptr;
auto resultJavaColumns = juceStringArrayToJava (resultColumns);
auto* env = getEnv();
auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider,
resultJavaColumns));
auto uriElements = getContentUriElements (env, uri);
if (uriElements.filepath.isEmpty())
return cursor->getNativeCursor();
auto values = LocalRef<jobjectArray> (env->NewObjectArray ((jsize) resultColumns.size(),
JavaObject, 0));
for (int i = 0; i < resultColumns.size(); ++i)
{
if (resultColumns.getReference (i) == "_display_name")
{
env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get());
}
else if (resultColumns.getReference (i) == "_size")
{
auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
javaString (uriElements.filepath).get()));
jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
env->SetObjectArrayElement (values, i, env->NewObject (JavaLong,
JavaLong.constructor,
fileLength));
}
}
auto nativeCursor = cursor->getNativeCursor();
env->CallVoidMethod (nativeCursor, JuceContentProviderFileObserverCursor.addRow, values.get());
return nativeCursor;
}
void* getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
{
auto* env = getEnv();
auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
if (extension.isEmpty())
return nullptr;
return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension),
juceString (mimeTypeFilter.get())));
}
void sharingFinished (int resultCode)
{
sharingActivityDidFinish = true;
succeeded = resultCode == -1;
// Give content sharer a chance to request file access.
if (nonAssetFilesPendingShare.get() == 0)
startTimer (2000);
else
notifyOwnerIfReady();
}
private:
bool isContentSharingEnabled() const
{
auto* env = getEnv();
auto packageManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
JuceAppActivity.getPackageManager));
constexpr int getProviders = 8;
auto packageInfo = LocalRef<jobject> (env->CallObjectMethod (packageManager,
AndroidPackageManager.getPackageInfo,
javaString (packageName).get(),
getProviders));
auto providers = LocalRef<jobjectArray> ((jobjectArray) env->GetObjectField (packageInfo,
AndroidPackageInfo.providers));
if (providers == nullptr)
return false;
auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider";
const int numProviders = env->GetArrayLength (providers.get());
for (int i = 0; i < numProviders; ++i)
{
auto providerInfo = LocalRef<jobject> (env->GetObjectArrayElement (providers, i));
auto authority = LocalRef<jstring> ((jstring) env->GetObjectField (providerInfo,
AndroidProviderInfo.authority));
if (juceString (authority) == sharingContentProviderAuthority)
return true;
}
return false;
}
void handleAsyncUpdate() override
{
jassert (prepareFilesThread != nullptr);
if (prepareFilesThread == nullptr)
return;
filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes());
}
void filesPrepared (jobject fileUris, const StringArray& mimeTypes)
{
auto* env = getEnv();
auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
env->CallObjectMethod (intent, AndroidIntent.setAction,
javaString ("android.intent.action.SEND_MULTIPLE").get());
env->CallObjectMethod (intent, AndroidIntent.setType,
javaString (getCommonMimeType (mimeTypes)).get());
constexpr int grantReadPermission = 1;
env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission);
env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra,
javaString ("android.intent.extra.STREAM").get(),
fileUris);
auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
AndroidIntent.createChooser,
intent.get(),
javaString ("Choose share target").get()));
env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003);
}
void decrementPendingFileCountAndNotifyOwnerIfReady()
{
--nonAssetFilesPendingShare;
notifyOwnerIfReady();
}
void notifyOwnerIfReady()
{
if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0)
owner.sharingFinished (succeeded, {});
}
void timerCallback() override
{
stopTimer();
notifyOwnerIfReady();
}
//==============================================================================
struct ContentUriElements
{
String index;
String filename;
String filepath;
};
ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
{
jassert (prepareFilesThread != nullptr);
if (prepareFilesThread == nullptr)
return {};
auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false)
.upToFirstOccurrenceOf ("/", false, true);
auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
}
static StringArray getSupportedColumns()
{
return StringArray ("_display_name", "_size");
}
void* getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider,
const String& filepath)
{
// This function can be called from multiple threads.
{
const ScopedLock sl (nonAssetFileOpenLock);
if (! nonAssetFilePathsPendingShare.contains (filepath))
{
nonAssetFilePathsPendingShare.add (filepath);
++nonAssetFilesPendingShare;
nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env,
contentProvider,
filepath));
}
}
auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
javaString (filepath).get()));
constexpr int modeReadOnly = 268435456;
auto parcelFileDescriptor = LocalRef<jobject> (env->CallStaticObjectMethod (ParcelFileDescriptor,
ParcelFileDescriptor.open,
javaFile.get(), modeReadOnly));
auto exception = LocalRef<jobject> (env->ExceptionOccurred());
if (exception != 0)
{
// Failed to create file descriptor. Have you provided a valid file path/resource name?
jassertfalse;
env->ExceptionClear();
return nullptr;
}
jlong startOffset = 0;
jlong unknownLength = -1;
assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
AssetFileDescriptor.constructor,
parcelFileDescriptor.get(),
startOffset, unknownLength))));
return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
}
ContentSharer& owner;
String packageName;
String uriBase;
std::unique_ptr<AndroidContentSharerPrepareFilesThread> prepareFilesThread;
bool succeeded = false;
String errorDescription;
bool sharingActivityDidFinish = false;
OwnedArray<AndroidContentSharerCursor> cursors;
Array<GlobalRef> assetFileDescriptors;
CriticalSection nonAssetFileOpenLock;
StringArray nonAssetFilePathsPendingShare;
Atomic<int> nonAssetFilesPendingShare { 0 };
OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers;
WeakReference<ContentSharerNativeImpl>::Master masterReference;
friend class WeakReference<ContentSharerNativeImpl>;
};
//==============================================================================
ContentSharer::Pimpl* ContentSharer::createPimpl()
{
return new ContentSharerNativeImpl (*this);
}
//==============================================================================
void* juce_contentSharerQuery (void* contentProvider, void* uri, void* projection,
void* selection, void* selectionArgs, void* sortOrder)
{
auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
LocalRef<jobject> (static_cast<jobject> (uri)),
LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)),
LocalRef<jobject> (static_cast<jobject> (selection)),
LocalRef<jobjectArray> (static_cast<jobjectArray> (selectionArgs)),
LocalRef<jobject> (static_cast<jobject> (sortOrder)));
}
void* juce_contentSharerOpenFile (void* contentProvider, void* uri, void* mode)
{
auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
LocalRef<jobject> (static_cast<jobject> (uri)),
LocalRef<jstring> (static_cast<jstring> (mode)));
}
void juce_contentSharingCompleted (int resultCode)
{
auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
return pimpl->sharingFinished (resultCode);
}
void* juce_contentSharerGetStreamTypes (void* uri, void* mimeTypeFilter)
{
auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
}
//==============================================================================
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerFileObserverEvent, void,
(JNIEnv* env, jobject /*fileObserver*/, jlong host, int event, jstring path))
{
setEnv (env);
reinterpret_cast<AndroidContentSharerFileObserver*> (host)->onFileEvent (event, LocalRef<jstring> (path));
}
//==============================================================================
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerQuery, jobject,
(JNIEnv* env, jobject contentProvider, jobject uri, jobjectArray projection,
jobject selection, jobjectArray selectionArgs, jobject sortOrder))
{
setEnv (env);
return (jobject) juce_contentSharerQuery (contentProvider, uri, projection, selection, selectionArgs, sortOrder);
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerCursorClosed, void,
(JNIEnv* env, jobject /*cursor*/, jlong host))
{
setEnv (env);
reinterpret_cast<AndroidContentSharerCursor*> (host)->cursorClosed();
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerOpenFile, jobject,
(JNIEnv* env, jobject contentProvider, jobject uri, jstring mode))
{
setEnv (env);
return (jobject) juce_contentSharerOpenFile ((void*) contentProvider, (void*) uri, (void*) mode);
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerGetStreamTypes, jobject,
(JNIEnv* env, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter))
{
setEnv (env);
return (jobject) juce_contentSharerGetStreamTypes ((void*) uri, (void*) mimeTypeFilter);
}
} // namespace juce

View File

@ -0,0 +1,228 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class FileChooser::Native : public FileChooser::Pimpl
{
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 save a directory
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 (AndroidUri, AndroidUri.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[i].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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,735 @@
/*
==============================================================================
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;
}
static StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter)
{
String filterToUse (filter.removeCharacters ("*"));
if (filterToUse.isEmpty() || filterToUse == "/")
return mimeTypes;
StringArray result;
for (const auto& type : mimeTypes)
if (String (type).contains (filterToUse))
result.add (type);
return result;
}
static String getCommonMimeType (const StringArray& mimeTypes)
{
if (mimeTypes.isEmpty())
return "*/*";
auto commonMime = mimeTypes[0];
bool lookForCommonGroup = false;
for (int i = 1; i < mimeTypes.size(); ++i)
{
if (mimeTypes[i] == commonMime)
continue;
if (! lookForCommonGroup)
{
lookForCommonGroup = true;
commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false);
}
if (! mimeTypes[i].startsWith (commonMime))
return "*/*";
}
return lookForCommonGroup ? commonMime + "*" : commonMime;
}
//==============================================================================
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

View File

@ -0,0 +1,210 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
private Component
{
public:
ContentSharerNativeImpl (ContentSharer& cs)
: owner (cs)
{
static PopoverDelegateClass cls;
popoverDelegate.reset ([cls.createInstance() init]);
}
~ContentSharerNativeImpl()
{
exitModalState (0);
}
void shareFiles (const Array<URL>& files) override
{
auto* urls = [NSMutableArray arrayWithCapacity: (NSUInteger) files.size()];
for (const auto& f : files)
{
NSString* nativeFilePath = nil;
if (f.isLocalFile())
{
nativeFilePath = juceStringToNS (f.getLocalFile().getFullPathName());
}
else
{
auto filePath = f.toString (false);
auto* fileDirectory = filePath.contains ("/")
? juceStringToNS (filePath.upToLastOccurrenceOf ("/", false, false))
: [NSString string];
auto fileName = juceStringToNS (filePath.fromLastOccurrenceOf ("/", false, false)
.upToLastOccurrenceOf (".", false, false));
auto fileExt = juceStringToNS (filePath.fromLastOccurrenceOf (".", false, false));
if ([fileDirectory length] == NSUInteger (0))
nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName
ofType: fileExt];
else
nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName
ofType: fileExt
inDirectory: fileDirectory];
}
if (nativeFilePath != nil)
[urls addObject: [NSURL fileURLWithPath: nativeFilePath]];
}
share (urls);
}
void shareText (const String& text) override
{
auto* array = [NSArray arrayWithObject: juceStringToNS (text)];
share (array);
}
private:
void share (NSArray* items)
{
if ([items count] == 0)
{
jassertfalse;
owner.sharingFinished (false, "No valid items found for sharing.");
return;
}
controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items
applicationActivities: nil]);
controller.get().excludedActivityTypes = nil;
controller.get().completionWithItemsHandler = ^ (UIActivityType type, BOOL completed,
NSArray* returnedItems, NSError* error)
{
ignoreUnused (type);
ignoreUnused (returnedItems);
succeeded = completed;
if (error != nil)
errorDescription = nsStringToJuce ([error localizedDescription]);
exitModalState (0);
};
controller.get().modalTransitionStyle = UIModalTransitionStyleCoverVertical;
auto bounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
setBounds (bounds);
setAlwaysOnTop (true);
addToDesktop (0);
enterModalState (true,
ModalCallbackFunction::create ([this] (int)
{
owner.sharingFinished (succeeded, errorDescription);
}),
false);
}
static bool isIPad()
{
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
}
//==============================================================================
void parentHierarchyChanged() override
{
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
if (peer != newPeer)
{
peer = newPeer;
if (isIPad())
{
controller.get().preferredContentSize = peer->view.frame.size;
auto screenBounds = [UIScreen mainScreen].bounds;
auto* popoverController = controller.get().popoverPresentationController;
popoverController.sourceView = peer->view;
popoverController.sourceRect = CGRectMake (0.f, screenBounds.size.height - 10.f, screenBounds.size.width, 10.f);
popoverController.canOverlapSourceViewRect = YES;
popoverController.delegate = popoverDelegate.get();
}
if (auto* parentController = peer->controller)
[parentController showViewController: controller.get() sender: parentController];
if (peer->view.window != nil)
peer->view.window.autoresizesSubviews = YES;
}
}
//==============================================================================
struct PopoverDelegateClass : public ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>>
{
PopoverDelegateClass() : ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>> ("PopoverDelegateClass_")
{
addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), willRepositionPopover, "v@:@@@");
registerClass();
}
//==============================================================================
static void willRepositionPopover (id, SEL, UIPopoverPresentationController*, CGRect* rect, UIView*)
{
auto screenBounds = [UIScreen mainScreen].bounds;
rect->origin.x = 0.f;
rect->origin.y = screenBounds.size.height - 10.f;
rect->size.width = screenBounds.size.width;
rect->size.height = 10.f;
}
};
ContentSharer& owner;
UIViewComponentPeer* peer = nullptr;
std::unique_ptr<UIActivityViewController, NSObjectDeleter> controller;
std::unique_ptr<NSObject<UIPopoverPresentationControllerDelegate>, NSObjectDeleter> popoverDelegate;
bool succeeded = false;
String errorDescription;
};
//==============================================================================
ContentSharer::Pimpl* ContentSharer::createPimpl()
{
return new ContentSharerNativeImpl (*this);
}
} // namespace juce

View File

@ -0,0 +1,321 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class FileChooser::Native : private Component,
public FileChooser::Pimpl
{
public:
Native (FileChooser& fileChooser, int flags)
: owner (fileChooser)
{
String firstFileExtension;
static FileChooserDelegateClass cls;
delegate.reset ([cls.createInstance() init]);
FileChooserDelegateClass::setOwner (delegate.get(), this);
auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
if ((flags & FileBrowserComponent::saveMode) != 0)
{
auto currentFileOrDirectory = owner.startingFile;
UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
? UIDocumentPickerModeExportToService
: UIDocumentPickerModeMoveToService;
if (! currentFileOrDirectory.existsAsFile())
{
auto filename = getFilename (currentFileOrDirectory, firstFileExtension);
auto tmpDirectory = File::createTempFile ("JUCE-filepath");
if (tmpDirectory.createDirectory().wasOk())
{
currentFileOrDirectory = tmpDirectory.getChildFile (filename);
currentFileOrDirectory.replaceWithText ("");
}
else
{
// Temporary directory creation failed! You need to specify a
// path you have write access to. Saving will not work for
// current path.
jassertfalse;
}
}
auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
controller.reset ([[UIDocumentPickerViewController alloc] initWithURL: url
inMode: pickerMode]);
[url release];
}
else
{
controller.reset ([[UIDocumentPickerViewController alloc] initWithDocumentTypes: utTypeArray
inMode: UIDocumentPickerModeOpen]);
}
[controller.get() setDelegate: delegate.get()];
[controller.get() 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.get() sender: parentController];
if (peer->view.window != nil)
peer->view.window.autoresizesSubviews = YES;
}
}
//==============================================================================
static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
{
auto filters = StringArray::fromTokens (filterWildcards, ";", "");
StringArray result;
firstExtension = {};
if (! filters.contains ("*") && filters.size() > 0)
{
for (auto filter : filters)
{
if (filter.isEmpty())
continue;
// iOS only supports file extension wild cards
jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
auto fileExtensionCF = fileExtension.toCFString();
if (firstExtension.isEmpty())
firstExtension = fileExtension;
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;
}
static String getFilename (const File& path, const String& fallbackExtension)
{
auto filename = path.getFileNameWithoutExtension();
auto extension = path.getFileExtension().substring (1);
if (filename.isEmpty())
filename = "Untitled";
if (extension.isEmpty())
extension = fallbackExtension;
if (extension.isNotEmpty())
filename += "." + extension;
return filename;
}
//==============================================================================
void didPickDocumentAtURL (NSURL* url)
{
bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
| controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
auto* fileAccessIntent = isWriting
? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
: [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
auto* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter: nil];
[fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
{
Array<URL> chooserResults;
if (err == nil)
{
[url startAccessingSecurityScopedResource];
NSError* error = nil;
NSData* bookmark = [url bookmarkDataWithOptions: 0
includingResourceValuesForKeys: nil
relativeToURL: nil
error: &error];
[bookmark retain];
[url stopAccessingSecurityScopedResource];
URL juceUrl (nsStringToJuce ([url absoluteString]));
if (error == nil)
{
setURLBookmark (juceUrl, (void*) bookmark);
}
else
{
auto* desc = [error localizedDescription];
ignoreUnused (desc);
jassertfalse;
}
chooserResults.add (juceUrl);
}
else
{
auto* desc = [err localizedDescription];
ignoreUnused (desc);
jassertfalse;
}
owner.finished (chooserResults);
}];
}
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;
std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate;
std::unique_ptr<UIDocumentPickerViewController, NSObjectDeleter> 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,753 @@
/*
==============================================================================
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
{
extern bool isIOSAppActive;
struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules
{
virtual ~AppInactivityCallback() {}
virtual void appBecomingInactive() = 0;
};
// This is an internal list of callbacks (but currently used between modules)
Array<AppInactivityCallback*> appBecomingInactiveCallbacks;
}
#if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate>
#else
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate>
#endif
{
UIBackgroundTaskIdentifier appSuspendTask;
}
@property (strong, nonatomic) UIWindow *window;
- (id)init;
- (void) applicationDidFinishLaunching: (UIApplication*) application;
- (void) applicationWillTerminate: (UIApplication*) application;
- (void) applicationDidEnterBackground: (UIApplication*) application;
- (void) applicationWillEnterForeground: (UIApplication*) application;
- (void) applicationDidBecomeActive: (UIApplication*) application;
- (void) applicationWillResignActive: (UIApplication*) application;
- (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier
completionHandler: (void (^)(void)) completionHandler;
- (void) applicationDidReceiveMemoryWarning: (UIApplication *) application;
#if JUCE_PUSH_NOTIFICATIONS
- (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings;
- (void) application: (UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken;
- (void) application: (UIApplication*) application didFailToRegisterForRemoteNotificationsWithError: (NSError*) error;
- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo;
- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo
fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler;
- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier
forRemoteNotification: (NSDictionary*) userInfo withResponseInfo: (NSDictionary*) responseInfo
completionHandler: (void(^)()) completionHandler;
- (void) application: (UIApplication*) application didReceiveLocalNotification: (UILocalNotification*) notification;
- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier
forLocalNotification: (UILocalNotification*) notification completionHandler: (void(^)()) completionHandler;
- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier
forLocalNotification: (UILocalNotification*) notification withResponseInfo: (NSDictionary*) responseInfo
completionHandler: (void(^)()) completionHandler;
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
- (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification
withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler;
- (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response
withCompletionHandler: (void(^)())completionHandler;
#endif
#endif
@end
@implementation JuceAppStartupDelegate
NSObject* _pushNotificationsDelegate;
- (id)init
{
self = [super init];
appSuspendTask = UIBackgroundTaskInvalid;
#if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
#endif
return self;
}
- (void) applicationDidFinishLaunching: (UIApplication*) application
{
ignoreUnused (application);
initialiseJuce_GUI();
if (auto* app = JUCEApplicationBase::createInstance())
{
if (! app->initialiseApp())
exit (app->shutdownApp());
}
else
{
jassertfalse; // you must supply an application object for an iOS app!
}
}
- (void) applicationWillTerminate: (UIApplication*) application
{
ignoreUnused (application);
JUCEApplicationBase::appWillTerminateByForce();
}
- (void) applicationDidEnterBackground: (UIApplication*) application
{
if (auto* app = JUCEApplicationBase::getInstance())
{
#if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK
appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{
if (appSuspendTask != UIBackgroundTaskInvalid)
{
[application endBackgroundTask:appSuspendTask];
appSuspendTask = UIBackgroundTaskInvalid;
}
}];
MessageManager::callAsync ([app] { app->suspended(); });
#else
ignoreUnused (application);
app->suspended();
#endif
}
}
- (void) applicationWillEnterForeground: (UIApplication*) application
{
ignoreUnused (application);
if (auto* app = JUCEApplicationBase::getInstance())
app->resumed();
}
- (void) applicationDidBecomeActive: (UIApplication*) application
{
application.applicationIconBadgeNumber = 0;
isIOSAppActive = true;
}
- (void) applicationWillResignActive: (UIApplication*) application
{
ignoreUnused (application);
isIOSAppActive = false;
for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;)
appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive();
}
- (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*)identifier
completionHandler: (void (^)(void))completionHandler
{
ignoreUnused (application);
URL::DownloadTask::juce_iosURLSessionNotify (nsStringToJuce (identifier));
completionHandler();
}
- (void) applicationDidReceiveMemoryWarning: (UIApplication*) application
{
ignoreUnused (application);
if (auto* app = JUCEApplicationBase::getInstance())
app->memoryWarningReceived();
}
- (void) setPushNotificationsDelegateToUse: (NSObject*) delegate
{
_pushNotificationsDelegate = delegate;
}
#if JUCE_PUSH_NOTIFICATIONS
- (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:didRegisterUserNotificationSettings:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &notificationSettings atIndex:3];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &deviceToken atIndex:3];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application didFailToRegisterForRemoteNotificationsWithError: (NSError*) error
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &error atIndex:3];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &userInfo atIndex:3];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo
fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:fetchCompletionHandler:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &userInfo atIndex:3];
[invocation setArgument: &completionHandler atIndex:4];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier
forRemoteNotification: (NSDictionary*) userInfo withResponseInfo: (NSDictionary*) responseInfo
completionHandler: (void(^)()) completionHandler
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &identifier atIndex:3];
[invocation setArgument: &userInfo atIndex:4];
[invocation setArgument: &responseInfo atIndex:5];
[invocation setArgument: &completionHandler atIndex:6];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application didReceiveLocalNotification: (UILocalNotification*) notification
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:didReceiveLocalNotification:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &notification atIndex:3];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier
forLocalNotification: (UILocalNotification*) notification completionHandler: (void(^)()) completionHandler
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:handleActionWithIdentifier:forLocalNotification:completionHandler:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &identifier atIndex:3];
[invocation setArgument: &notification atIndex:4];
[invocation setArgument: &completionHandler atIndex:5];
[invocation invoke];
}
}
- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier
forLocalNotification: (UILocalNotification*) notification withResponseInfo: (NSDictionary*) responseInfo
completionHandler: (void(^)()) completionHandler
{
ignoreUnused (application);
SEL selector = NSSelectorFromString (@"application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &application atIndex:2];
[invocation setArgument: &identifier atIndex:3];
[invocation setArgument: &notification atIndex:4];
[invocation setArgument: &responseInfo atIndex:5];
[invocation setArgument: &completionHandler atIndex:6];
[invocation invoke];
}
}
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
- (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification
withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler
{
ignoreUnused (center);
SEL selector = NSSelectorFromString (@"userNotificationCenter:willPresentNotification:withCompletionHandler:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &center atIndex:2];
[invocation setArgument: &notification atIndex:3];
[invocation setArgument: &completionHandler atIndex:4];
[invocation invoke];
}
}
- (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response
withCompletionHandler: (void(^)()) completionHandler
{
ignoreUnused (center);
SEL selector = NSSelectorFromString (@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:");
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
{
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
[invocation setSelector: selector];
[invocation setTarget: _pushNotificationsDelegate];
[invocation setArgument: &center atIndex:2];
[invocation setArgument: &response atIndex:3];
[invocation setArgument: &completionHandler atIndex:4];
[invocation invoke];
}
}
#endif
#endif
@end
namespace juce
{
int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr);
int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr)
{
Class delegateClass = (customDelegatePtr != nullptr ? reinterpret_cast<Class> (customDelegatePtr) : [JuceAppStartupDelegate class]);
return UIApplicationMain (argc, const_cast<char**> (argv), nil, NSStringFromClass (delegateClass));
}
//==============================================================================
void LookAndFeel::playAlertSound()
{
// TODO
}
//==============================================================================
class iOSMessageBox;
#if defined (__IPHONE_8_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0
#define JUCE_USE_NEW_IOS_ALERTWINDOW 1
#endif
#if ! JUCE_USE_NEW_IOS_ALERTWINDOW
} // (juce namespace)
@interface JuceAlertBoxDelegate : NSObject <UIAlertViewDelegate>
{
@public
iOSMessageBox* owner;
}
- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex;
@end
namespace juce
{
#endif
class iOSMessageBox
{
public:
iOSMessageBox (const String& title, const String& message,
NSString* button1, NSString* button2, NSString* button3,
ModalComponentManager::Callback* cb, const bool async)
: result (0), resultReceived (false), callback (cb), isAsync (async)
{
#if JUCE_USE_NEW_IOS_ALERTWINDOW
if (currentlyFocusedPeer != nullptr)
{
UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (title)
message: juceStringToNS (message)
preferredStyle: UIAlertControllerStyleAlert];
addButton (alert, button1, 0);
addButton (alert, button2, 1);
addButton (alert, button3, 2);
[currentlyFocusedPeer->controller presentViewController: alert
animated: YES
completion: nil];
}
else
{
// Since iOS8, alert windows need to be associated with a window, so you need to
// have at least one window on screen when you use this
jassertfalse;
}
#else
delegate = [[JuceAlertBoxDelegate alloc] init];
delegate->owner = this;
alert = [[UIAlertView alloc] initWithTitle: juceStringToNS (title)
message: juceStringToNS (message)
delegate: delegate
cancelButtonTitle: button1
otherButtonTitles: button2, button3, nil];
[alert retain];
[alert show];
#endif
}
~iOSMessageBox()
{
#if ! JUCE_USE_NEW_IOS_ALERTWINDOW
[alert release];
[delegate release];
#endif
}
int getResult()
{
jassert (callback == nullptr);
JUCE_AUTORELEASEPOOL
{
#if JUCE_USE_NEW_IOS_ALERTWINDOW
while (! resultReceived)
#else
while (! (alert.hidden || resultReceived))
#endif
[[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
}
return result;
}
void buttonClicked (const int buttonIndex) noexcept
{
result = buttonIndex;
resultReceived = true;
if (callback != nullptr)
callback->modalStateFinished (result);
if (isAsync)
delete this;
}
private:
int result;
bool resultReceived;
std::unique_ptr<ModalComponentManager::Callback> callback;
const bool isAsync;
#if JUCE_USE_NEW_IOS_ALERTWINDOW
void addButton (UIAlertController* alert, NSString* text, int index)
{
if (text != nil)
[alert addAction: [UIAlertAction actionWithTitle: text
style: UIAlertActionStyleDefault
handler: ^(UIAlertAction*) { this->buttonClicked (index); }]];
}
#else
UIAlertView* alert;
JuceAlertBoxDelegate* delegate;
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox)
};
#if ! JUCE_USE_NEW_IOS_ALERTWINDOW
} // (juce namespace)
@implementation JuceAlertBoxDelegate
- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex
{
owner->buttonClicked ((int) buttonIndex);
alertView.hidden = true;
}
@end
namespace juce
{
#endif
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/)
{
JUCE_AUTORELEASEPOOL
{
iOSMessageBox mb (title, message, @"OK", nil, nil, nullptr, false);
ignoreUnused (mb.getResult());
}
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
new iOSMessageBox (title, message, @"OK", nil, nil, callback, true);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
std::unique_ptr<iOSMessageBox> mb (new iOSMessageBox (title, message, @"Cancel", @"OK",
nil, callback, callback != nullptr));
if (callback == nullptr)
return mb->getResult() == 1;
mb.release();
return false;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
std::unique_ptr<iOSMessageBox> mb (new iOSMessageBox (title, message, @"Cancel", @"Yes", @"No", callback, callback != nullptr));
if (callback == nullptr)
return mb->getResult();
mb.release();
return 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
std::unique_ptr<iOSMessageBox> mb (new iOSMessageBox (title, message, @"No", @"Yes", nil, callback, callback != nullptr));
if (callback == nullptr)
return mb->getResult();
mb.release();
return 0;
}
//==============================================================================
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*)
{
jassertfalse; // no such thing on iOS!
return false;
}
bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*)
{
jassertfalse; // no such thing on iOS!
return false;
}
//==============================================================================
void Desktop::setScreenSaverEnabled (const bool isEnabled)
{
if (! SystemStats::isRunningInAppExtensionSandbox())
[[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled];
}
bool Desktop::isScreenSaverEnabled()
{
if (SystemStats::isRunningInAppExtensionSandbox())
return true;
return ! [[UIApplication sharedApplication] isIdleTimerDisabled];
}
//==============================================================================
bool juce_areThereAnyAlwaysOnTopWindows()
{
return false;
}
//==============================================================================
Image juce_createIconForFile (const File&)
{
return Image();
}
//==============================================================================
void SystemClipboard::copyTextToClipboard (const String& text)
{
[[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
forPasteboardType: @"public.text"];
}
String SystemClipboard::getTextFromClipboard()
{
return nsStringToJuce ([[UIPasteboard generalPasteboard] valueForPasteboardType: @"public.text"]);
}
//==============================================================================
bool MouseInputSource::SourceList::addSource()
{
addSource (sources.size(), MouseInputSource::InputSourceType::touch);
return true;
}
bool MouseInputSource::SourceList::canUseTouch()
{
return true;
}
bool Desktop::canUseSemiTransparentWindows() noexcept
{
return true;
}
Point<float> MouseInputSource::getCurrentRawMousePosition()
{
return juce_lastMousePos;
}
void MouseInputSource::setRawMousePosition (Point<float>)
{
}
double Desktop::getDefaultMasterScale()
{
return 1.0;
}
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
{
UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait
: [[UIApplication sharedApplication] statusBarOrientation];
return Orientations::convertToJuce (orientation);
}
void Desktop::Displays::findDisplays (float masterScale)
{
JUCE_AUTORELEASEPOOL
{
UIScreen* s = [UIScreen mainScreen];
Display d;
d.userArea = d.totalArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale;
d.isMain = true;
d.scale = masterScale;
if ([s respondsToSelector: @selector (scale)])
d.scale *= s.scale;
d.dpi = 160 * d.scale;
displays.add (d);
}
}
} // namespace juce

View File

@ -0,0 +1,256 @@
/*
==============================================================================
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
{
static bool exeIsAvailable (const char* const executable)
{
ChildProcess child;
const bool ok = child.start ("which " + String (executable))
&& child.readAllProcessOutput().trim().isNotEmpty();
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
return false;
#else
static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog");
return canUseNativeBox;
#endif
}
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*)
{
return new Native (owner, flags);
}
} // namespace juce

View File

@ -0,0 +1,341 @@
/*
==============================================================================
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
{
typedef void (*WindowMessageReceiveCallback) (XEvent&);
WindowMessageReceiveCallback dispatchWindowMessage = nullptr;
typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&);
SelectionRequestCallback handleSelectionRequest = nullptr;
::Window juce_messageWindowHandle;
XContext windowHandleXContext;
//==============================================================================
namespace X11ErrorHandling
{
static XErrorHandler oldErrorHandler = {};
static XIOErrorHandler oldIOErrorHandler = {};
//==============================================================================
// Usually happens when client-server connection is broken
int ioErrorHandler (::Display*)
{
DBG ("ERROR: connection to X server broken.. terminating.");
if (JUCEApplicationBase::isStandaloneApp())
MessageManager::getInstance()->stopDispatchLoop();
return 0;
}
int errorHandler (::Display* display, XErrorEvent* event)
{
ignoreUnused (display, event);
#if JUCE_DEBUG_XERRORS
char errorStr[64] = { 0 };
char requestStr[64] = { 0 };
XGetErrorText (display, event->error_code, errorStr, 64);
XGetErrorDatabaseText (display, "XRequest", String (event->request_code).toUTF8(), "Unknown", requestStr, 64);
DBG ("ERROR: X returned " << errorStr << " for operation " << requestStr);
#endif
return 0;
}
void installXErrorHandlers()
{
oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler);
oldErrorHandler = XSetErrorHandler (errorHandler);
}
void removeXErrorHandlers()
{
XSetIOErrorHandler (oldIOErrorHandler);
oldIOErrorHandler = {};
XSetErrorHandler (oldErrorHandler);
oldErrorHandler = {};
}
}
//==============================================================================
XWindowSystem::XWindowSystem() noexcept
{
if (JUCEApplicationBase::isStandaloneApp())
{
// Initialise xlib for multiple thread support
static bool initThreadCalled = false;
if (! initThreadCalled)
{
if (! XInitThreads())
{
// This is fatal! Print error and closedown
Logger::outputDebugString ("Failed to initialise xlib thread support.");
Process::terminate();
return;
}
initThreadCalled = true;
}
X11ErrorHandling::installXErrorHandlers();
}
}
XWindowSystem::~XWindowSystem() noexcept
{
if (JUCEApplicationBase::isStandaloneApp())
X11ErrorHandling::removeXErrorHandlers();
clearSingletonInstance();
}
::Display* XWindowSystem::displayRef() noexcept
{
if (++displayCount == 1)
{
jassert (display == nullptr);
String displayName (getenv ("DISPLAY"));
if (displayName.isEmpty())
displayName = ":0.0";
// it seems that on some systems XOpenDisplay will occasionally
// fail the first time, but succeed on a second attempt..
for (int retries = 2; --retries >= 0;)
{
display = XOpenDisplay (displayName.toUTF8());
if (display != nullptr)
break;
}
initialiseXDisplay();
}
return display;
}
::Display* XWindowSystem::displayUnref() noexcept
{
jassert (display != nullptr);
jassert (displayCount.get() > 0);
if (--displayCount == 0)
{
destroyXDisplay();
XCloseDisplay (display);
display = nullptr;
}
return display;
}
void XWindowSystem::initialiseXDisplay() noexcept
{
// This is fatal! Print error and closedown
if (display == nullptr)
{
Logger::outputDebugString ("Failed to connect to the X Server.");
Process::terminate();
}
// Create a context to store user data associated with Windows we create
windowHandleXContext = XUniqueContext();
// We're only interested in client messages for this window, which are always sent
XSetWindowAttributes swa;
swa.event_mask = NoEventMask;
// Create our message window (this will never be mapped)
const int screen = DefaultScreen (display);
juce_messageWindowHandle = XCreateWindow (display, RootWindow (display, screen),
0, 0, 1, 1, 0, 0, InputOnly,
DefaultVisual (display, screen),
CWEventMask, &swa);
XSync (display, False);
// Setup input event handler
int fd = XConnectionNumber (display);
LinuxEventLoop::setWindowSystemFd (fd,
[this](int /*fd*/)
{
do
{
XEvent evt;
{
ScopedXLock xlock (display);
if (! XPending (display))
return false;
XNextEvent (display, &evt);
}
if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle
&& handleSelectionRequest != nullptr)
{
handleSelectionRequest (evt.xselectionrequest);
}
else if (evt.xany.window != juce_messageWindowHandle
&& dispatchWindowMessage != nullptr)
{
dispatchWindowMessage (evt);
}
} while (display != nullptr);
return false;
});
}
void XWindowSystem::destroyXDisplay() noexcept
{
ScopedXLock xlock (display);
XDestroyWindow (display, juce_messageWindowHandle);
juce_messageWindowHandle = 0;
XSync (display, True);
LinuxEventLoop::removeWindowSystemFd();
}
JUCE_IMPLEMENT_SINGLETON (XWindowSystem)
//==============================================================================
ScopedXDisplay::ScopedXDisplay() : display (XWindowSystem::getInstance()->displayRef())
{
}
ScopedXDisplay::~ScopedXDisplay()
{
XWindowSystem::getInstance()->displayUnref();
}
//==============================================================================
ScopedXLock::ScopedXLock (::Display* d) : display (d)
{
if (display != nullptr)
XLockDisplay (display);
}
ScopedXLock::~ScopedXLock()
{
if (display != nullptr)
XUnlockDisplay (display);
}
//==============================================================================
Atoms::Atoms (::Display* display)
{
protocols = getIfExists (display, "WM_PROTOCOLS");
protocolList [TAKE_FOCUS] = getIfExists (display, "WM_TAKE_FOCUS");
protocolList [DELETE_WINDOW] = getIfExists (display, "WM_DELETE_WINDOW");
protocolList [PING] = getIfExists (display, "_NET_WM_PING");
changeState = getIfExists (display, "WM_CHANGE_STATE");
state = getIfExists (display, "WM_STATE");
userTime = getCreating (display, "_NET_WM_USER_TIME");
activeWin = getCreating (display, "_NET_ACTIVE_WINDOW");
pid = getCreating (display, "_NET_WM_PID");
windowType = getIfExists (display, "_NET_WM_WINDOW_TYPE");
windowState = getIfExists (display, "_NET_WM_STATE");
XdndAware = getCreating (display, "XdndAware");
XdndEnter = getCreating (display, "XdndEnter");
XdndLeave = getCreating (display, "XdndLeave");
XdndPosition = getCreating (display, "XdndPosition");
XdndStatus = getCreating (display, "XdndStatus");
XdndDrop = getCreating (display, "XdndDrop");
XdndFinished = getCreating (display, "XdndFinished");
XdndSelection = getCreating (display, "XdndSelection");
XdndTypeList = getCreating (display, "XdndTypeList");
XdndActionList = getCreating (display, "XdndActionList");
XdndActionCopy = getCreating (display, "XdndActionCopy");
XdndActionPrivate = getCreating (display, "XdndActionPrivate");
XdndActionDescription = getCreating (display, "XdndActionDescription");
XembedMsgType = getCreating (display, "_XEMBED");
XembedInfo = getCreating (display, "_XEMBED_INFO");
allowedMimeTypes[0] = getCreating (display, "UTF8_STRING");
allowedMimeTypes[1] = getCreating (display, "text/plain;charset=utf-8");
allowedMimeTypes[2] = getCreating (display, "text/plain");
allowedMimeTypes[3] = getCreating (display, "text/uri-list");
allowedActions[0] = getCreating (display, "XdndActionMove");
allowedActions[1] = XdndActionCopy;
allowedActions[2] = getCreating (display, "XdndActionLink");
allowedActions[3] = getCreating (display, "XdndActionAsk");
allowedActions[4] = XdndActionPrivate;
}
Atom Atoms::getIfExists (::Display* display, const char* name) { return XInternAtom (display, name, True); }
Atom Atoms::getCreating (::Display* display, const char* name) { return XInternAtom (display, name, False); }
String Atoms::getName (::Display* display, const Atom atom)
{
if (atom == None)
return "None";
return String (XGetAtomName (display, atom));
}
bool Atoms::isMimeTypeFile (::Display* display, const Atom atom)
{
return getName (display, atom).equalsIgnoreCase ("text/uri-list");
}
const unsigned long Atoms::DndVersion = 3;
//==============================================================================
GetXProperty::GetXProperty (::Display* display, Window window, Atom atom,
long offset, long length, bool shouldDelete,
Atom requestedType)
{
success = (XGetWindowProperty (display, window, atom, offset, length,
(Bool) shouldDelete, requestedType, &actualType,
&actualFormat, &numItems, &bytesLeft, &data) == Success)
&& data != nullptr;
}
GetXProperty::~GetXProperty()
{
if (data != nullptr)
XFree (data);
}
} // namespace juce

View File

@ -0,0 +1,142 @@
/*
==============================================================================
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.
==============================================================================
*/
struct _XDisplay;
namespace juce
{
typedef ::_XDisplay* XDisplay;
typedef unsigned long AtomType;
typedef unsigned long WindowType;
//==============================================================================
class XWindowSystem : public DeletedAtShutdown
{
public:
XDisplay displayRef() noexcept;
XDisplay displayUnref() noexcept;
JUCE_DECLARE_SINGLETON (XWindowSystem, false)
private:
XDisplay display = {};
Atomic<int> displayCount;
XWindowSystem() noexcept;
~XWindowSystem() noexcept;
void initialiseXDisplay() noexcept;
void destroyXDisplay() noexcept;
};
//==============================================================================
/** Creates and holds a reference to the X display.
@tags{GUI}
*/
struct ScopedXDisplay
{
ScopedXDisplay();
~ScopedXDisplay();
const XDisplay display;
};
//==============================================================================
/** A handy class that uses XLockDisplay and XUnlockDisplay to lock the X server
using RAII (Only available in Linux!).
@tags{GUI}
*/
class ScopedXLock
{
public:
/** Creating a ScopedXLock object locks the X display.
This uses XLockDisplay() to grab the display that JUCE is using.
*/
ScopedXLock (XDisplay);
/** Deleting a ScopedXLock object unlocks the X display.
This calls XUnlockDisplay() to release the lock.
*/
~ScopedXLock();
private:
// defined in juce_linux_X11.h
XDisplay display;
};
//==============================================================================
struct Atoms
{
Atoms (XDisplay);
enum ProtocolItems
{
TAKE_FOCUS = 0,
DELETE_WINDOW = 1,
PING = 2
};
AtomType protocols, protocolList[3], changeState, state, userTime,
activeWin, pid, windowType, windowState,
XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus,
XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList,
XdndActionDescription, XdndActionCopy, XdndActionPrivate,
XembedMsgType, XembedInfo,
allowedActions[5],
allowedMimeTypes[4];
static const unsigned long DndVersion;
static AtomType getIfExists (XDisplay, const char* name);
static AtomType getCreating (XDisplay, const char* name);
static String getName (XDisplay, AtomType);
static bool isMimeTypeFile (XDisplay, AtomType);
};
//==============================================================================
struct GetXProperty
{
GetXProperty (XDisplay, WindowType, AtomType,
long offset, long length, bool shouldDelete,
AtomType requestedType);
~GetXProperty();
bool success;
unsigned char* data = nullptr;
unsigned long numItems, bytesLeft;
AtomType actualType;
int actualFormat;
};
} // namespace juce

View File

@ -0,0 +1,280 @@
/*
==============================================================================
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
{
extern ::Window juce_messageWindowHandle;
namespace ClipboardHelpers
{
static String localClipboardContent;
static Atom atom_UTF8_STRING;
static Atom atom_CLIPBOARD;
static Atom atom_TARGETS;
//==============================================================================
static void initSelectionAtoms (::Display* display)
{
static bool isInitialised = false;
if (! isInitialised)
{
isInitialised = true;
atom_UTF8_STRING = Atoms::getCreating (display, "UTF8_STRING");
atom_CLIPBOARD = Atoms::getCreating (display, "CLIPBOARD");
atom_TARGETS = Atoms::getCreating (display, "TARGETS");
}
}
//==============================================================================
// Read the content of a window property as either a locale-dependent string or an utf8 string
// works only for strings shorter than 1000000 bytes
static String readWindowProperty (::Display* display, Window window, Atom prop)
{
String returnData;
if (display != nullptr)
{
char* clipData;
Atom actualType;
int actualFormat;
unsigned long numItems, bytesLeft;
if (XGetWindowProperty (display, window, prop,
0L /* offset */, 1000000 /* length (max) */, False,
AnyPropertyType /* format */,
&actualType, &actualFormat, &numItems, &bytesLeft,
(unsigned char**) &clipData) == Success)
{
if (actualType == atom_UTF8_STRING && actualFormat == 8)
returnData = String::fromUTF8 (clipData, (int) numItems);
else if (actualType == XA_STRING && actualFormat == 8)
returnData = String (clipData, numItems);
if (clipData != nullptr)
XFree (clipData);
jassert (bytesLeft == 0 || numItems == 1000000);
}
XDeleteProperty (display, window, prop);
}
return returnData;
}
//==============================================================================
// Send a SelectionRequest to the window owning the selection and waits for its answer (with a timeout) */
static bool requestSelectionContent (::Display* display, String& selectionContent,
Atom selection, Atom requestedFormat)
{
Atom property_name = XInternAtom (display, "JUCE_SEL", false);
// The selection owner will be asked to set the JUCE_SEL property on the
// juce_messageWindowHandle with the selection content
XConvertSelection (display, selection, requestedFormat, property_name,
juce_messageWindowHandle, CurrentTime);
int count = 50; // will wait at most for 200 ms
while (--count >= 0)
{
XEvent event;
if (XCheckTypedWindowEvent (display, juce_messageWindowHandle, SelectionNotify, &event))
{
if (event.xselection.property == property_name)
{
jassert (event.xselection.requestor == juce_messageWindowHandle);
selectionContent = readWindowProperty (display, event.xselection.requestor,
event.xselection.property);
return true;
}
return false; // the format we asked for was denied.. (event.xselection.property == None)
}
// not very elegant.. we could do a select() or something like that...
// however clipboard content requesting is inherently slow on x11, it
// often takes 50ms or more so...
Thread::sleep (4);
}
return false;
}
//==============================================================================
// Called from the event loop in juce_linux_Messaging in response to SelectionRequest events
static void handleSelection (XSelectionRequestEvent& evt)
{
ClipboardHelpers::initSelectionAtoms (evt.display);
// the selection content is sent to the target window as a window property
XSelectionEvent reply;
reply.type = SelectionNotify;
reply.display = evt.display;
reply.requestor = evt.requestor;
reply.selection = evt.selection;
reply.target = evt.target;
reply.property = None; // == "fail"
reply.time = evt.time;
HeapBlock<char> data;
int propertyFormat = 0;
size_t numDataItems = 0;
if (evt.selection == XA_PRIMARY || evt.selection == ClipboardHelpers::atom_CLIPBOARD)
{
if (evt.target == XA_STRING || evt.target == ClipboardHelpers::atom_UTF8_STRING)
{
// translate to utf8
numDataItems = ClipboardHelpers::localClipboardContent.getNumBytesAsUTF8() + 1;
data.calloc (numDataItems + 1);
ClipboardHelpers::localClipboardContent.copyToUTF8 (data, numDataItems);
propertyFormat = 8; // bits/item
}
else if (evt.target == ClipboardHelpers::atom_TARGETS)
{
// another application wants to know what we are able to send
numDataItems = 2;
propertyFormat = 32; // atoms are 32-bit
data.calloc (numDataItems * 4);
Atom* atoms = reinterpret_cast<Atom*> (data.getData());
atoms[0] = ClipboardHelpers::atom_UTF8_STRING;
atoms[1] = XA_STRING;
evt.target = XA_ATOM;
}
}
else
{
DBG ("requested unsupported clipboard");
}
if (data != nullptr)
{
const size_t maxReasonableSelectionSize = 1000000;
// for very big chunks of data, we should use the "INCR" protocol , which is a pain in the *ss
if (evt.property != None && numDataItems < maxReasonableSelectionSize)
{
XChangeProperty (evt.display, evt.requestor,
evt.property, evt.target,
propertyFormat /* 8 or 32 */, PropModeReplace,
reinterpret_cast<const unsigned char*> (data.getData()), (int) numDataItems);
reply.property = evt.property; // " == success"
}
}
XSendEvent (evt.display, evt.requestor, 0, NoEventMask, (XEvent*) &reply);
}
}
//==============================================================================
typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&);
extern SelectionRequestCallback handleSelectionRequest;
struct ClipboardCallbackInitialiser
{
ClipboardCallbackInitialiser()
{
handleSelectionRequest = ClipboardHelpers::handleSelection;
}
};
static ClipboardCallbackInitialiser clipboardInitialiser;
//==============================================================================
void SystemClipboard::copyTextToClipboard (const String& clipText)
{
ScopedXDisplay xDisplay;
if (auto display = xDisplay.display)
{
ClipboardHelpers::initSelectionAtoms (display);
ClipboardHelpers::localClipboardContent = clipText;
XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime);
XSetSelectionOwner (display, ClipboardHelpers::atom_CLIPBOARD, juce_messageWindowHandle, CurrentTime);
}
}
String SystemClipboard::getTextFromClipboard()
{
String content;
ScopedXDisplay xDisplay;
if (auto display = xDisplay.display)
{
ClipboardHelpers::initSelectionAtoms (display);
/* 1) try to read from the "CLIPBOARD" selection first (the "high
level" clipboard that is supposed to be filled by ctrl-C
etc). When a clipboard manager is running, the content of this
selection is preserved even when the original selection owner
exits.
2) and then try to read from "PRIMARY" selection (the "legacy" selection
filled by good old x11 apps such as xterm)
*/
Atom selection = XA_PRIMARY;
Window selectionOwner = None;
if ((selectionOwner = XGetSelectionOwner (display, selection)) == None)
{
selection = ClipboardHelpers::atom_CLIPBOARD;
selectionOwner = XGetSelectionOwner (display, selection);
}
if (selectionOwner != None)
{
if (selectionOwner == juce_messageWindowHandle)
{
content = ClipboardHelpers::localClipboardContent;
}
else
{
// first try: we want an utf8 string
bool ok = ClipboardHelpers::requestSelectionContent (display, content,
selection, ClipboardHelpers::atom_UTF8_STRING);
if (! ok)
{
// second chance, ask for a good old locale-dependent string ..
ok = ClipboardHelpers::requestSelectionContent (display, content,
selection, XA_STRING);
}
}
}
}
return content;
}
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,349 @@
/*
==============================================================================
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
{
//==============================================================================
static NSMutableArray* createAllowedTypesArray (const StringArray& filters)
{
if (filters.size() == 0)
return nil;
NSMutableArray* filterArray = [[[NSMutableArray alloc] init] autorelease];
for (int i = 0; i < filters.size(); ++i)
{
#if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)
// From OS X 10.6 you can only specify allowed extensions, so any filters containing wildcards
// must be of the form "*.extension"
jassert (filters[i] == "*"
|| (filters[i].startsWith ("*.") && filters[i].lastIndexOfChar ('*') == 0));
#endif
const String f (filters[i].replace ("*.", ""));
if (f == "*")
return nil;
[filterArray addObject: juceStringToNS (f)];
}
return filterArray;
}
//==============================================================================
class FileChooser::Native : public Component,
public FileChooser::Pimpl
{
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])
{
setBounds (0, 0, 0, 0);
setOpaque (true);
static DelegateClass cls;
delegate = [cls.createInstance() init];
object_setInstanceVariable (delegate, "cppObject", this);
[panel setDelegate: delegate];
filters.addTokens (owner.filters.replaceCharacters (",:", ";;"), ";", String());
filters.trim();
filters.removeEmptyStrings();
[panel setTitle: juceStringToNS (owner.title)];
[panel setAllowedFileTypes: createAllowedTypesArray (filters)];
if (! isSave)
{
NSOpenPanel* openPanel = (NSOpenPanel*) panel;
[openPanel setCanChooseDirectories: selectsDirectories];
[openPanel setCanChooseFiles: selectsFiles];
[openPanel setAllowsMultipleSelection: selectMultiple];
[openPanel setResolvesAliases: YES];
if (owner.treatFilePackagesAsDirs)
[openPanel setTreatsFilePackagesAsDirectories: YES];
}
if (preview != nullptr)
{
nsViewPreview = [[NSView alloc] initWithFrame: makeNSRect (preview->getLocalBounds())];
preview->addToDesktop (0, (void*) nsViewPreview);
preview->setVisible (true);
[panel setAccessoryView: nsViewPreview];
}
if (isSave || selectsDirectories)
[panel setCanCreateDirectories: YES];
[panel setLevel:NSModalPanelWindowLevel];
if (owner.startingFile.isDirectory())
{
startingDirectory = owner.startingFile.getFullPathName();
}
else
{
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 (startingDirectory)];
[panel setNameFieldStringValue: juceStringToNS (filename)];
#endif
}
~Native()
{
exitModalState (0);
removeFromDesktop();
if (panel != nil)
{
[panel setDelegate: nil];
if (nsViewPreview != nil)
{
[panel setAccessoryView: nil];
[nsViewPreview release];
nsViewPreview = nil;
preview = nullptr;
}
[panel close];
[panel release];
}
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
{
std::unique_ptr<TemporaryMainMenuWithStandardCommands> tempMenu;
if (JUCEApplicationBase::isStandaloneApp())
tempMenu.reset (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)
{
auto addURLResult = [&chooserResults] (NSURL* urlToAdd)
{
auto scheme = nsStringToJuce ([urlToAdd scheme]);
auto path = nsStringToJuce ([urlToAdd path]);
chooserResults.add (URL (scheme + "://" + path));
};
if (isSave)
{
addURLResult ([panel URL]);
}
else
{
auto* openPanel = (NSOpenPanel*) panel;
auto* urls = [openPanel URLs];
for (unsigned int i = 0; i < [urls count]; ++i)
addURLResult ([urls objectAtIndex: i]);
}
}
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;
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()
{
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
return false;
#else
return true;
#endif
}
}

View File

@ -0,0 +1,811 @@
/*
==============================================================================
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 JuceMainMenuBarHolder : private DeletedAtShutdown
{
JuceMainMenuBarHolder()
: mainMenuBar ([[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")])
{
auto* item = [mainMenuBar addItemWithTitle: nsStringLiteral ("Apple")
action: nil
keyEquivalent: nsEmptyString()];
auto* appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")];
[NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
[mainMenuBar setSubmenu: appMenu forItem: item];
[appMenu release];
[NSApp setMainMenu: mainMenuBar];
}
~JuceMainMenuBarHolder()
{
clearSingletonInstance();
[NSApp setMainMenu: nil];
[mainMenuBar release];
}
NSMenu* mainMenuBar = nil;
JUCE_DECLARE_SINGLETON_SINGLETHREADED (JuceMainMenuBarHolder, true)
};
JUCE_IMPLEMENT_SINGLETON (JuceMainMenuBarHolder)
//==============================================================================
class JuceMainMenuHandler : private MenuBarModel::Listener,
private DeletedAtShutdown
{
public:
JuceMainMenuHandler()
{
static JuceMenuCallbackClass cls;
callback = [cls.createInstance() init];
JuceMenuCallbackClass::setOwner (callback, this);
}
~JuceMainMenuHandler()
{
setMenu (nullptr, nullptr, String());
jassert (instance == this);
instance = nullptr;
[callback release];
}
void setMenu (MenuBarModel* const newMenuBarModel,
const PopupMenu* newExtraAppleMenuItems,
const String& recentItemsName)
{
recentItemsMenuName = recentItemsName;
if (currentModel != newMenuBarModel)
{
if (currentModel != nullptr)
currentModel->removeListener (this);
currentModel = newMenuBarModel;
if (currentModel != nullptr)
currentModel->addListener (this);
menuBarItemsChanged (nullptr);
}
extraAppleMenuItems.reset (createCopyIfNotNull (newExtraAppleMenuItems));
}
void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex)
{
NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
action: nil
keyEquivalent: nsEmptyString()];
NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true);
[parent setSubmenu: sub forItem: item];
[sub setAutoenablesItems: false];
[sub release];
}
void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex)
{
// Note: This method used to update the contents of the existing menu in-place, but that caused
// weird side-effects which messed-up keyboard focus when switching between windows. By creating
// a new menu and replacing the old one with it, that problem seems to be avoided..
NSMenu* menu = [[NSMenu alloc] initWithTitle: juceStringToNS (name)];
for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();)
addMenuItem (iter, menu, menuId, topLevelIndex);
[menu setAutoenablesItems: false];
[menu update];
removeItemRecursive ([parentItem submenu]);
[parentItem setSubmenu: menu];
[menu release];
}
void updateTopLevelMenu (NSMenu* menu)
{
NSMenu* superMenu = [menu supermenu];
auto menuNames = currentModel->getMenuBarNames();
auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1;
if (indexOfMenu >= 0)
{
removeItemRecursive (menu);
auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu, menuNames[indexOfMenu]);
for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();)
addMenuItem (iter, menu, 1, indexOfMenu);
[menu update];
}
}
void menuBarItemsChanged (MenuBarModel*) override
{
if (isOpen)
{
defferedUpdateRequested = true;
return;
}
lastUpdateTime = Time::getMillisecondCounter();
StringArray menuNames;
if (currentModel != nullptr)
menuNames = currentModel->getMenuBarNames();
auto* menuBar = getMainMenuBar();
while ([menuBar numberOfItems] > 1 + menuNames.size())
removeItemRecursive (menuBar, static_cast<int> ([menuBar numberOfItems] - 1));
int menuId = 1;
for (int i = 0; i < menuNames.size(); ++i)
{
const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames[i]));
if (i >= [menuBar numberOfItems] - 1)
addTopLevelMenu (menuBar, menu, menuNames[i], menuId, i);
else
updateTopLevelMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
}
}
void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) override
{
if ((info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0
&& info.invocationMethod != ApplicationCommandTarget::InvocationInfo::fromKeyPress)
if (auto* item = findMenuItemWithCommandID (getMainMenuBar(), info.commandID))
flashMenuBar ([item menu]);
}
void invoke (const PopupMenu::Item& item, int topLevelIndex) const
{
if (currentModel != nullptr)
{
if (item.customCallback != nullptr)
if (! item.customCallback->menuItemTriggered())
return;
if (item.commandManager != nullptr)
{
ApplicationCommandTarget::InvocationInfo info (item.itemID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
item.commandManager->invoke (info, true);
}
MessageManager::callAsync ([=]
{
if (instance != nullptr)
instance->invokeDirectly (item.itemID, topLevelIndex);
});
}
}
void invokeDirectly (int commandId, int topLevelIndex)
{
if (currentModel != nullptr)
currentModel->menuItemSelected (commandId, topLevelIndex);
}
void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
const int topLevelMenuId, const int topLevelIndex)
{
const PopupMenu::Item& i = iter.getItem();
NSString* text = juceStringToNS (i.text);
if (text == nil)
text = nsEmptyString();
if (i.isSeparator)
{
[menuToAddTo addItem: [NSMenuItem separatorItem]];
}
else if (i.isSectionHeader)
{
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
action: nil
keyEquivalent: nsEmptyString()];
[item setEnabled: false];
}
else if (i.subMenu != nullptr)
{
if (i.text == recentItemsMenuName)
{
if (recent == nullptr)
recent.reset (new RecentFilesMenuItem());
if (recent->recentItem != nil)
{
if (NSMenu* parent = [recent->recentItem menu])
[parent removeItem: recent->recentItem];
[menuToAddTo addItem: recent->recentItem];
return;
}
}
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
action: nil
keyEquivalent: nsEmptyString()];
[item setTag: i.itemID];
[item setEnabled: i.isEnabled];
NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false);
[menuToAddTo setSubmenu: sub forItem: item];
[sub release];
}
else
{
auto* item = [[NSMenuItem alloc] initWithTitle: text
action: @selector (menuItemInvoked:)
keyEquivalent: nsEmptyString()];
[item setTag: topLevelIndex];
[item setEnabled: i.isEnabled];
[item setState: i.isTicked ? NSOnState : NSOffState];
[item setTarget: (id) callback];
auto* juceItem = new PopupMenu::Item (i);
juceItem->customComponent = nullptr;
[item setRepresentedObject: [createNSObjectFromJuceClass (juceItem) autorelease]];
if (i.commandManager != nullptr)
{
for (auto& kp : i.commandManager->getKeyMappings()->getKeyPressesAssignedToCommand (i.itemID))
{
if (kp != KeyPress::backspaceKey // (adding these is annoying because it flashes the menu bar
&& kp != KeyPress::deleteKey) // every time you press the key while editing text)
{
juce_wchar key = kp.getTextCharacter();
if (key == 0)
key = (juce_wchar) kp.getKeyCode();
[item setKeyEquivalent: juceStringToNS (String::charToString (key).toLowerCase())];
[item setKeyEquivalentModifierMask: juceModsToNSMods (kp.getModifiers())];
}
break;
}
}
[menuToAddTo addItem: item];
[item release];
}
}
NSMenu* createMenu (const PopupMenu menu,
const String& menuName,
const int topLevelMenuId,
const int topLevelIndex,
const bool addDelegate)
{
NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
[m setAutoenablesItems: false];
if (addDelegate)
{
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
[m setDelegate: (id<NSMenuDelegate>) callback];
#else
[m setDelegate: callback];
#endif
}
for (PopupMenu::MenuItemIterator iter (menu); iter.next();)
addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
[m update];
return m;
}
static JuceMainMenuHandler* instance;
MenuBarModel* currentModel = nullptr;
std::unique_ptr<PopupMenu> extraAppleMenuItems;
uint32 lastUpdateTime = 0;
NSObject* callback = nil;
String recentItemsMenuName;
bool isOpen = false, defferedUpdateRequested = false;
private:
struct RecentFilesMenuItem
{
RecentFilesMenuItem() : recentItem (nil)
{
if (NSNib* menuNib = [[[NSNib alloc] initWithNibNamed: @"RecentFilesMenuTemplate" bundle: nil] autorelease])
{
NSArray* array = nil;
#if (! defined (MAC_OS_X_VERSION_10_8)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
[menuNib instantiateNibWithOwner: NSApp topLevelObjects: &array];
#else
[menuNib instantiateWithOwner: NSApp topLevelObjects: &array];
#endif
for (id object in array)
{
if ([object isKindOfClass: [NSMenu class]])
{
if (NSArray* items = [object itemArray])
{
if (NSMenuItem* item = findRecentFilesItem (items))
{
recentItem = [item retain];
break;
}
}
}
}
}
}
~RecentFilesMenuItem()
{
[recentItem release];
}
static NSMenuItem* findRecentFilesItem (NSArray* const items)
{
for (id object in items)
if (NSArray* subMenuItems = [[object submenu] itemArray])
for (id subObject in subMenuItems)
if ([subObject isKindOfClass: [NSMenuItem class]])
return subObject;
return nil;
}
NSMenuItem* recentItem;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecentFilesMenuItem)
};
std::unique_ptr<RecentFilesMenuItem> recent;
//==============================================================================
static NSMenuItem* findMenuItemWithCommandID (NSMenu* const menu, int commandID)
{
for (NSInteger i = [menu numberOfItems]; --i >= 0;)
{
NSMenuItem* m = [menu itemAtIndex: i];
if (auto* menuItem = getJuceClassFromNSObject<PopupMenu::Item> ([m representedObject]))
if (menuItem->itemID == commandID)
return m;
if (NSMenu* sub = [m submenu])
if (NSMenuItem* found = findMenuItemWithCommandID (sub, commandID))
return found;
}
return nil;
}
static void flashMenuBar (NSMenu* menu)
{
if ([[menu title] isEqualToString: nsStringLiteral ("Apple")])
return;
[menu retain];
const unichar f35Key = NSF35FunctionKey;
NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];
NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: nsStringLiteral ("x")
action: nil
keyEquivalent: f35String];
[item setTarget: nil];
[menu insertItem: item atIndex: [menu numberOfItems]];
[item release];
if ([menu indexOfItem: item] >= 0)
{
NSEvent* f35Event = [NSEvent keyEventWithType: NSEventTypeKeyDown
location: NSZeroPoint
modifierFlags: NSEventModifierFlagCommand
timestamp: 0
windowNumber: 0
context: [NSGraphicsContext currentContext]
characters: f35String
charactersIgnoringModifiers: f35String
isARepeat: NO
keyCode: 0];
[menu performKeyEquivalent: f35Event];
if ([menu indexOfItem: item] >= 0)
[menu removeItem: item]; // (this throws if the item isn't actually in the menu)
}
[menu release];
}
static unsigned int juceModsToNSMods (const ModifierKeys mods)
{
unsigned int m = 0;
if (mods.isShiftDown()) m |= NSEventModifierFlagShift;
if (mods.isCtrlDown()) m |= NSEventModifierFlagControl;
if (mods.isAltDown()) m |= NSEventModifierFlagOption;
if (mods.isCommandDown()) m |= NSEventModifierFlagCommand;
return m;
}
// Apple Bug: For some reason [NSMenu removeAllItems] seems to leak it's objects
// on shutdown, so we need this method to release the items one-by-one manually
static void removeItemRecursive (NSMenu* parentMenu, int menuItemIndex)
{
if (isPositiveAndBelow (menuItemIndex, (int) [parentMenu numberOfItems]))
{
auto* menuItem = [parentMenu itemAtIndex:menuItemIndex];
if (auto* submenu = [menuItem submenu])
removeItemRecursive (submenu);
[parentMenu removeItem:menuItem];
}
else
jassertfalse;
}
static void removeItemRecursive (NSMenu* menu)
{
if (menu != nullptr)
{
auto n = static_cast<int> ([menu numberOfItems]);
for (auto i = n; --i >= 0;)
removeItemRecursive (menu, i);
}
}
static NSMenu* getMainMenuBar()
{
return JuceMainMenuBarHolder::getInstance()->mainMenuBar;
}
//==============================================================================
struct JuceMenuCallbackClass : public ObjCClass<NSObject>
{
JuceMenuCallbackClass() : ObjCClass<NSObject> ("JUCEMainMenu_")
{
addIvar<JuceMainMenuHandler*> ("owner");
addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@");
addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@");
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
addProtocol (@protocol (NSMenuDelegate));
#endif
registerClass();
}
static void setOwner (id self, JuceMainMenuHandler* owner)
{
object_setInstanceVariable (self, "owner", owner);
}
private:
static void menuItemInvoked (id self, SEL, NSMenuItem* item)
{
auto owner = getIvar<JuceMainMenuHandler*> (self, "owner");
if (auto* juceItem = getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject]))
{
// If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to
// our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back
// into the focused component and let it trigger the menu item indirectly.
NSEvent* e = [NSApp currentEvent];
if ([e type] == NSEventTypeKeyDown || [e type] == NSEventTypeKeyUp)
{
if (auto* focused = juce::Component::getCurrentlyFocusedComponent())
{
if (auto peer = dynamic_cast<juce::NSViewComponentPeer*> (focused->getPeer()))
{
if ([e type] == NSEventTypeKeyDown)
peer->redirectKeyDown (e);
else
peer->redirectKeyUp (e);
return;
}
}
}
owner->invoke (*juceItem, static_cast<int> ([item tag]));
}
}
static void menuNeedsUpdate (id self, SEL, NSMenu* menu)
{
getIvar<JuceMainMenuHandler*> (self, "owner")->updateTopLevelMenu (menu);
}
};
};
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
//==============================================================================
class TemporaryMainMenuWithStandardCommands
{
public:
TemporaryMainMenuWithStandardCommands()
: oldMenu (MenuBarModel::getMacMainMenu())
{
if (auto* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu())
oldAppleMenu.reset (new PopupMenu (*appleMenu));
if (auto* handler = JuceMainMenuHandler::instance)
oldRecentItems = handler->recentItemsMenuName;
MenuBarModel::setMacMainMenu (nullptr);
if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
{
NSMenu* menu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Edit")];
NSMenuItem* item;
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Cut"), nil)
action: @selector (cut:) keyEquivalent: nsStringLiteral ("x")];
[menu addItem: item];
[item release];
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Copy"), nil)
action: @selector (copy:) keyEquivalent: nsStringLiteral ("c")];
[menu addItem: item];
[item release];
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Paste"), nil)
action: @selector (paste:) keyEquivalent: nsStringLiteral ("v")];
[menu addItem: item];
[item release];
editMenuIndex = [mainMenu numberOfItems];
item = [mainMenu addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil)
action: nil keyEquivalent: nsEmptyString()];
[mainMenu setSubmenu: menu forItem: item];
[menu release];
}
// use a dummy modal component so that apps can tell that something is currently modal.
dummyModalComponent.enterModalState (false);
}
~TemporaryMainMenuWithStandardCommands()
{
if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
[mainMenu removeItemAtIndex:editMenuIndex];
MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu.get(), oldRecentItems);
}
private:
MenuBarModel* const oldMenu;
std::unique_ptr<PopupMenu> oldAppleMenu;
String oldRecentItems;
NSInteger editMenuIndex;
// The OS view already plays an alert when clicking outside
// the modal comp, so this override avoids adding extra
// inappropriate noises when the cancel button is pressed.
// This override is also important because it stops the base class
// calling ModalComponentManager::bringToFront, which can get
// recursive when file dialogs are involved
struct SilentDummyModalComp : public Component
{
SilentDummyModalComp() {}
void inputAttemptWhenModal() override {}
};
SilentDummyModalComp dummyModalComponent;
};
//==============================================================================
namespace MainMenuHelpers
{
static NSString* translateMenuName (const String& name)
{
return NSLocalizedString (juceStringToNS (TRANS (name)), nil);
}
static NSMenuItem* createMenuItem (NSMenu* menu, const String& name, SEL sel, NSString* key)
{
NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle: translateMenuName (name)
action: sel
keyEquivalent: key] autorelease];
[item setTarget: NSApp];
[menu addItem: item];
return item;
}
static void createStandardAppMenu (NSMenu* menu, const String& appName, const PopupMenu* extraItems)
{
if (extraItems != nullptr && JuceMainMenuHandler::instance != nullptr && extraItems->getNumItems() > 0)
{
for (PopupMenu::MenuItemIterator iter (*extraItems); iter.next();)
JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
[menu addItem: [NSMenuItem separatorItem]];
}
// Services...
NSMenuItem* services = [[[NSMenuItem alloc] initWithTitle: translateMenuName ("Services")
action: nil keyEquivalent: nsEmptyString()] autorelease];
[menu addItem: services];
NSMenu* servicesMenu = [[[NSMenu alloc] initWithTitle: translateMenuName ("Services")] autorelease];
[menu setSubmenu: servicesMenu forItem: services];
[NSApp setServicesMenu: servicesMenu];
[menu addItem: [NSMenuItem separatorItem]];
createMenuItem (menu, TRANS("Hide") + String (" ") + appName, @selector (hide:), nsStringLiteral ("h"));
[createMenuItem (menu, TRANS("Hide Others"), @selector (hideOtherApplications:), nsStringLiteral ("h"))
setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
createMenuItem (menu, TRANS("Show All"), @selector (unhideAllApplications:), nsEmptyString());
[menu addItem: [NSMenuItem separatorItem]];
createMenuItem (menu, TRANS("Quit") + String (" ") + appName, @selector (terminate:), nsStringLiteral ("q"));
}
// Since our app has no NIB, this initialises a standard app menu...
static void rebuildMainMenu (const PopupMenu* extraItems)
{
// this can't be used in a plugin!
jassert (JUCEApplicationBase::isStandaloneApp());
if (auto* app = JUCEApplicationBase::getInstance())
{
if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
{
if ([mainMenu numberOfItems] > 0)
{
if (auto* appMenu = [[mainMenu itemAtIndex:0] submenu])
{
[appMenu removeAllItems];
MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);
}
}
}
}
}
}
void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
const PopupMenu* extraAppleMenuItems,
const String& recentItemsMenuName)
{
if (getMacMainMenu() != newMenuBarModel)
{
JUCE_AUTORELEASEPOOL
{
if (newMenuBarModel == nullptr)
{
delete JuceMainMenuHandler::instance;
jassert (JuceMainMenuHandler::instance == nullptr); // should be zeroed in the destructor
jassert (extraAppleMenuItems == nullptr); // you can't specify some extra items without also supplying a model
extraAppleMenuItems = nullptr;
}
else
{
if (JuceMainMenuHandler::instance == nullptr)
JuceMainMenuHandler::instance = new JuceMainMenuHandler();
JuceMainMenuHandler::instance->setMenu (newMenuBarModel, extraAppleMenuItems, recentItemsMenuName);
}
}
}
MainMenuHelpers::rebuildMainMenu (extraAppleMenuItems);
if (newMenuBarModel != nullptr)
newMenuBarModel->menuItemsChanged();
}
MenuBarModel* MenuBarModel::getMacMainMenu()
{
if (auto* mm = JuceMainMenuHandler::instance)
return mm->currentModel;
return nullptr;
}
const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()
{
if (auto* mm = JuceMainMenuHandler::instance)
return mm->extraAppleMenuItems.get();
return nullptr;
}
typedef void (*MenuTrackingChangedCallback) (bool);
extern MenuTrackingChangedCallback menuTrackingChangedCallback;
static void mainMenuTrackingChanged (bool isTracking)
{
PopupMenu::dismissAllActiveMenus();
if (auto* menuHandler = JuceMainMenuHandler::instance)
{
menuHandler->isOpen = isTracking;
if (auto* model = menuHandler->currentModel)
model->handleMenuBarActivate (isTracking);
if (menuHandler->defferedUpdateRequested && ! isTracking)
{
menuHandler->defferedUpdateRequested = false;
menuHandler->menuBarItemsChanged (menuHandler->currentModel);
}
}
}
void juce_initialiseMacMainMenu()
{
menuTrackingChangedCallback = mainMenuTrackingChanged;
if (JuceMainMenuHandler::instance == nullptr)
MainMenuHelpers::rebuildMainMenu (nullptr);
}
// (used from other modules that need to create an NSMenu)
NSMenu* createNSMenu (const PopupMenu&, const String&, int, int, bool);
NSMenu* createNSMenu (const PopupMenu& menu, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate)
{
juce_initialiseMacMainMenu();
if (auto* mm = JuceMainMenuHandler::instance)
return mm->createMenu (menu, name, topLevelMenuId, topLevelIndex, addDelegate);
jassertfalse; // calling this before making sure the OSX main menu stuff was initialised?
return nil;
}
} // namespace juce

View File

@ -0,0 +1,192 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_MAC
//==============================================================================
namespace MouseCursorHelpers
{
static NSCursor* fromNSImage (NSImage* im, NSPoint hotspot)
{
NSCursor* c = [[NSCursor alloc] initWithImage: im
hotSpot: hotspot];
[im release];
return c;
}
static void* fromHIServices (const char* filename)
{
JUCE_AUTORELEASEPOOL
{
auto cursorPath = String ("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/"
"HIServices.framework/Versions/A/Resources/cursors/")
+ filename;
NSImage* originalImage = [[NSImage alloc] initByReferencingFile: juceStringToNS (cursorPath + "/cursor.pdf")];
NSSize originalSize = [originalImage size];
NSImage* resultImage = [[NSImage alloc] initWithSize: originalSize];
for (int scale = 1; scale <= 4; ++scale)
{
NSAffineTransform* scaleTransform = [NSAffineTransform transform];
[scaleTransform scaleBy: (float) scale];
if (CGImageRef rasterCGImage = [originalImage CGImageForProposedRect: nil
context: nil
hints: [NSDictionary dictionaryWithObjectsAndKeys:
NSImageHintCTM, scaleTransform, nil]])
{
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: rasterCGImage];
[imageRep setSize: originalSize];
[resultImage addRepresentation: imageRep];
[imageRep release];
}
else
{
return nil;
}
}
[originalImage release];
NSDictionary* info = [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (cursorPath + "/info.plist")];
auto hotspotX = (float) [[info valueForKey: nsStringLiteral ("hotx")] doubleValue];
auto hotspotY = (float) [[info valueForKey: nsStringLiteral ("hoty")] doubleValue];
return fromNSImage (resultImage, NSMakePoint (hotspotX, hotspotY));
}
}
}
void* CustomMouseCursorInfo::create() const
{
return MouseCursorHelpers::fromNSImage (imageToNSImage (image, scaleFactor),
NSMakePoint (hotspot.x, hotspot.y));
}
void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type)
{
JUCE_AUTORELEASEPOOL
{
NSCursor* c = nil;
switch (type)
{
case NormalCursor:
case ParentCursor: c = [NSCursor arrowCursor]; break;
case NoCursor: return CustomMouseCursorInfo (Image (Image::ARGB, 8, 8, true), {}).create();
case DraggingHandCursor: c = [NSCursor openHandCursor]; break;
case WaitCursor: c = [NSCursor arrowCursor]; break; // avoid this on the mac, let the OS provide the beachball
case IBeamCursor: c = [NSCursor IBeamCursor]; break;
case PointingHandCursor: c = [NSCursor pointingHandCursor]; break;
case LeftEdgeResizeCursor: c = [NSCursor resizeLeftCursor]; break;
case RightEdgeResizeCursor: c = [NSCursor resizeRightCursor]; break;
case CrosshairCursor: c = [NSCursor crosshairCursor]; break;
case CopyingCursor:
{
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6
if (void* m = MouseCursorHelpers::fromHIServices ("copy"))
return m;
#endif
c = [NSCursor dragCopyCursor]; // added in 10.6
break;
}
case UpDownResizeCursor:
case TopEdgeResizeCursor:
case BottomEdgeResizeCursor:
if (void* m = MouseCursorHelpers::fromHIServices ("resizenorthsouth"))
return m;
c = [NSCursor resizeUpDownCursor];
break;
case LeftRightResizeCursor:
if (void* m = MouseCursorHelpers::fromHIServices ("resizeeastwest"))
return m;
c = [NSCursor resizeLeftRightCursor];
break;
case TopLeftCornerResizeCursor:
case BottomRightCornerResizeCursor:
return MouseCursorHelpers::fromHIServices ("resizenorthwestsoutheast");
case TopRightCornerResizeCursor:
case BottomLeftCornerResizeCursor:
return MouseCursorHelpers::fromHIServices ("resizenortheastsouthwest");
case UpDownLeftRightResizeCursor:
return MouseCursorHelpers::fromHIServices ("move");
default:
jassertfalse;
break;
}
[c retain];
return c;
}
}
void MouseCursor::deleteMouseCursor (void* const cursorHandle, const bool /*isStandard*/)
{
[((NSCursor*) cursorHandle) release];
}
void MouseCursor::showInAllWindows() const
{
showInWindow (nullptr);
}
void MouseCursor::showInWindow (ComponentPeer*) const
{
auto c = (NSCursor*) getHandle();
if (c == nil)
c = [NSCursor arrowCursor];
[c set];
}
#else
void* CustomMouseCursorInfo::create() const { return nullptr; }
void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType) { return nullptr; }
void MouseCursor::deleteMouseCursor (void*, bool) {}
void MouseCursor::showInAllWindows() const {}
void MouseCursor::showInWindow (ComponentPeer*) const {}
#endif
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,609 @@
/*
==============================================================================
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
{
void LookAndFeel::playAlertSound()
{
NSBeep();
}
//==============================================================================
class OSXMessageBox : private AsyncUpdater
{
public:
OSXMessageBox (AlertWindow::AlertIconType type, const String& t, const String& m,
const char* b1, const char* b2, const char* b3,
ModalComponentManager::Callback* c, const bool runAsync)
: iconType (type), title (t), message (m), callback (c),
button1 (b1), button2 (b2), button3 (b3)
{
if (runAsync)
triggerAsyncUpdate();
}
int getResult() const
{
switch (getRawResult())
{
case NSAlertFirstButtonReturn: return 1;
case NSAlertThirdButtonReturn: return 2;
default: return 0;
}
}
static int show (AlertWindow::AlertIconType iconType, const String& title, const String& message,
ModalComponentManager::Callback* callback, const char* b1, const char* b2, const char* b3,
bool runAsync)
{
std::unique_ptr<OSXMessageBox> mb (new OSXMessageBox (iconType, title, message, b1, b2, b3,
callback, runAsync));
if (! runAsync)
return mb->getResult();
mb.release();
return 0;
}
private:
AlertWindow::AlertIconType iconType;
String title, message;
std::unique_ptr<ModalComponentManager::Callback> callback;
const char* button1;
const char* button2;
const char* button3;
void handleAsyncUpdate() override
{
auto result = getResult();
if (callback != nullptr)
callback->modalStateFinished (result);
delete this;
}
NSInteger getRawResult() const
{
NSAlert* alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText: juceStringToNS (title)];
[alert setInformativeText: juceStringToNS (message)];
[alert setAlertStyle: iconType == AlertWindow::WarningIcon ? NSAlertStyleCritical
: NSAlertStyleInformational];
addButton (alert, button1);
addButton (alert, button2);
addButton (alert, button3);
return [alert runModal];
}
static void addButton (NSAlert* alert, const char* button)
{
if (button != nullptr)
[alert addButtonWithTitle: juceStringToNS (TRANS (button))];
}
};
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType,
const String& title, const String& message,
Component* /*associatedComponent*/)
{
OSXMessageBox::show (iconType, title, message, nullptr, "OK", nullptr, nullptr, false);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
OSXMessageBox::show (iconType, title, message, callback, "OK", nullptr, nullptr, true);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
return OSXMessageBox::show (iconType, title, message, callback,
"OK", "Cancel", nullptr, callback != nullptr) == 1;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
return OSXMessageBox::show (iconType, title, message, callback,
"Yes", "Cancel", "No", callback != nullptr);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType iconType,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
return OSXMessageBox::show (iconType, title, message, callback,
"Yes", "No", nullptr, callback != nullptr);
}
//==============================================================================
static NSRect getDragRect (NSView* view, NSEvent* event)
{
auto eventPos = [event locationInWindow];
return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
fromView: nil];
}
static NSView* getNSViewForDragEvent (Component* sourceComp)
{
if (sourceComp == nullptr)
if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource(0))
sourceComp = draggingSource->getComponentUnderMouse();
if (sourceComp != nullptr)
return (NSView*) sourceComp->getWindowHandle();
jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
return nil;
}
struct TextDragDataProviderClass : public ObjCClass<NSObject>
{
TextDragDataProviderClass() : ObjCClass<NSObject> ("JUCE_NSTextDragDataProvider_")
{
addIvar<String*> ("text");
addMethod (@selector (dealloc), dealloc, "v@:");
addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
addProtocol (@protocol (NSPasteboardItemDataProvider));
registerClass();
}
static void setText (id self, const String& text)
{
object_setInstanceVariable (self, "text", new String (text));
}
private:
static void dealloc (id self, SEL)
{
delete getIvar<String*> (self, "text");
sendSuperclassMessage (self, @selector (dealloc));
}
static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
{
if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
if (auto* text = getIvar<String*> (self, "text"))
[sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
forType: NSPasteboardTypeString];
}
};
bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent)
{
if (text.isEmpty())
return false;
if (auto* view = getNSViewForDragEvent (sourceComponent))
{
JUCE_AUTORELEASEPOOL
{
if (auto* event = [[view window] currentEvent])
{
static TextDragDataProviderClass dataProviderClass;
id delegate = [dataProviderClass.createInstance() init];
TextDragDataProviderClass::setText (delegate, text);
auto* pasteboardItem = [[NSPasteboardItem new] autorelease];
[pasteboardItem setDataProvider: delegate
forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
[dragItem setDraggingFrame: getDragRect (view, event) contents: image];
auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
event: event
source: delegate];
draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
draggingSession.draggingFormation = NSDraggingFormationNone;
return true;
}
}
}
return false;
}
struct NSDraggingSourceHelper : public ObjCClass<NSObject<NSDraggingSource>>
{
NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
{
addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
registerClass();
}
static NSDragOperation sourceOperationMaskForDraggingContext (id, SEL, NSDraggingSession*, NSDraggingContext)
{
return NSDragOperationCopy;
}
};
static NSDraggingSourceHelper draggingSourceHelper;
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/,
Component* sourceComponent)
{
if (files.isEmpty())
return false;
if (auto* view = getNSViewForDragEvent (sourceComponent))
{
JUCE_AUTORELEASEPOOL
{
if (auto* event = [[view window] currentEvent])
{
auto* dragItems = [[[NSMutableArray alloc] init] autorelease];
for (auto& filename : files)
{
auto* nsFilename = juceStringToNS (filename);
auto* fileURL = [NSURL fileURLWithPath: nsFilename];
auto* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
auto eventPos = [event locationInWindow];
auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
fromView: nil];
auto* dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
[dragItem setDraggingFrame: dragRect
contents: dragImage];
[dragItems addObject: dragItem];
[dragItem release];
}
auto* helper = [draggingSourceHelper.createInstance() autorelease];
return [view beginDraggingSessionWithItems: dragItems
event: event
source: helper];
}
}
}
return false;
}
//==============================================================================
bool Desktop::canUseSemiTransparentWindows() noexcept
{
return true;
}
Point<float> MouseInputSource::getCurrentRawMousePosition()
{
JUCE_AUTORELEASEPOOL
{
auto p = [NSEvent mouseLocation];
return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
}
}
void MouseInputSource::setRawMousePosition (Point<float> newPosition)
{
// this rubbish needs to be done around the warp call, to avoid causing a
// bizarre glitch..
CGAssociateMouseAndMouseCursorPosition (false);
CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
CGAssociateMouseAndMouseCursorPosition (true);
}
double Desktop::getDefaultMasterScale()
{
return 1.0;
}
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
{
return upright;
}
//==============================================================================
#if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
#define JUCE_USE_IOPM_SCREENSAVER_DEFEAT 1
#endif
#if ! (defined (JUCE_USE_IOPM_SCREENSAVER_DEFEAT) || defined (__POWER__))
extern "C" { extern OSErr UpdateSystemActivity (UInt8); } // Some versions of the SDK omit this function..
#endif
class ScreenSaverDefeater : public Timer
{
public:
#if JUCE_USE_IOPM_SCREENSAVER_DEFEAT
ScreenSaverDefeater()
{
startTimer (5000);
timerCallback();
}
void timerCallback() override
{
if (Process::isForegroundProcess())
{
if (assertion == nullptr)
assertion.reset (new PMAssertion());
}
else
{
assertion.reset();
}
}
struct PMAssertion
{
PMAssertion() : assertionID (kIOPMNullAssertionID)
{
IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
kIOPMAssertionLevelOn,
CFSTR ("JUCE Playback"),
&assertionID);
jassert (res == kIOReturnSuccess); ignoreUnused (res);
}
~PMAssertion()
{
if (assertionID != kIOPMNullAssertionID)
IOPMAssertionRelease (assertionID);
}
IOPMAssertionID assertionID;
};
std::unique_ptr<PMAssertion> assertion;
#else
ScreenSaverDefeater()
{
startTimer (10000);
timerCallback();
}
void timerCallback() override
{
if (Process::isForegroundProcess())
UpdateSystemActivity (1 /*UsrActivity*/);
}
#endif
};
static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
void Desktop::setScreenSaverEnabled (const bool isEnabled)
{
if (isEnabled)
screenSaverDefeater.reset();
else if (screenSaverDefeater == nullptr)
screenSaverDefeater.reset (new ScreenSaverDefeater());
}
bool Desktop::isScreenSaverEnabled()
{
return screenSaverDefeater == nullptr;
}
//==============================================================================
struct DisplaySettingsChangeCallback : private DeletedAtShutdown
{
DisplaySettingsChangeCallback()
{
CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, 0);
}
~DisplaySettingsChangeCallback()
{
CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, 0);
clearSingletonInstance();
}
static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
{
const_cast<Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
}
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (DisplaySettingsChangeCallback)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
};
JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback)
static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
{
r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
return convertToRectInt (r);
}
static Desktop::Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
{
Desktop::Displays::Display d;
d.isMain = (mainScreenBottom == 0);
if (d.isMain)
mainScreenBottom = [s frame].size.height;
d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
d.scale = masterScale;
#if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
if ([s respondsToSelector: @selector (backingScaleFactor)])
d.scale *= s.backingScaleFactor;
#endif
NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
d.dpi = (dpi.width + dpi.height) / 2.0;
return d;
}
void Desktop::Displays::findDisplays (const float masterScale)
{
JUCE_AUTORELEASEPOOL
{
DisplaySettingsChangeCallback::getInstance();
CGFloat mainScreenBottom = 0;
for (NSScreen* s in [NSScreen screens])
displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
}
}
//==============================================================================
bool juce_areThereAnyAlwaysOnTopWindows()
{
for (NSWindow* window in [NSApp windows])
if ([window level] > NSNormalWindowLevel)
return true;
return false;
}
//==============================================================================
static void selectImageForDrawing (const Image& image)
{
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
flipped: false]];
}
static void releaseImageAfterDrawing()
{
[[NSGraphicsContext currentContext] flushGraphics];
[NSGraphicsContext restoreGraphicsState];
}
Image juce_createIconForFile (const File& file)
{
JUCE_AUTORELEASEPOOL
{
NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
selectImageForDrawing (result);
[image drawAtPoint: NSMakePoint (0, 0)
fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
operation: NSCompositingOperationSourceOver fraction: 1.0f];
releaseImageAfterDrawing();
return result;
}
}
static Image createNSWindowSnapshot (NSWindow* nsWindow)
{
JUCE_AUTORELEASEPOOL
{
CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
kCGWindowListOptionIncludingWindow,
(CGWindowID) [nsWindow windowNumber],
kCGWindowImageBoundsIgnoreFraming);
NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
selectImageForDrawing (result);
[bitmapRep drawAtPoint: NSMakePoint (0, 0)];
releaseImageAfterDrawing();
[bitmapRep release];
CGImageRelease (screenShot);
return result;
}
}
Image createSnapshotOfNativeWindow (void*);
Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
{
if (id windowOrView = (id) nativeWindowHandle)
{
if ([windowOrView isKindOfClass: [NSWindow class]])
return createNSWindowSnapshot ((NSWindow*) windowOrView);
if ([windowOrView isKindOfClass: [NSView class]])
return createNSWindowSnapshot ([(NSView*) windowOrView window]);
}
return {};
}
//==============================================================================
void SystemClipboard::copyTextToClipboard (const String& text)
{
NSPasteboard* pb = [NSPasteboard generalPasteboard];
[pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
owner: nil];
[pb setString: juceStringToNS (text)
forType: NSStringPboardType];
}
String SystemClipboard::getTextFromClipboard()
{
return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]);
}
void Process::setDockIconVisible (bool isVisible)
{
ProcessSerialNumber psn { 0, kCurrentProcess };
OSStatus err = TransformProcessType (&psn, isVisible ? kProcessTransformToForegroundApplication
: kProcessTransformToUIElementApplication);
jassert (err == 0);
ignoreUnused (err);
}
bool Desktop::isOSXDarkModeActive()
{
return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
isEqualToString: nsStringLiteral ("Dark")];
}
} // namespace juce

View File

@ -0,0 +1,289 @@
/*
==============================================================================
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
{
namespace DragAndDropHelpers
{
//==============================================================================
struct JuceDropSource : public ComBaseClassHelper<IDropSource>
{
JuceDropSource() {}
JUCE_COMRESULT QueryContinueDrag (BOOL escapePressed, DWORD keys) override
{
if (escapePressed)
return DRAGDROP_S_CANCEL;
if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0)
return DRAGDROP_S_DROP;
return S_OK;
}
JUCE_COMRESULT GiveFeedback (DWORD) override
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}
};
//==============================================================================
struct JuceEnumFormatEtc : public ComBaseClassHelper<IEnumFORMATETC>
{
JuceEnumFormatEtc (const FORMATETC* f) : format (f) {}
JUCE_COMRESULT Clone (IEnumFORMATETC** result) override
{
if (result == 0)
return E_POINTER;
auto newOne = new JuceEnumFormatEtc (format);
newOne->index = index;
*result = newOne;
return S_OK;
}
JUCE_COMRESULT Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched) override
{
if (pceltFetched != nullptr)
*pceltFetched = 0;
else if (celt != 1)
return S_FALSE;
if (index == 0 && celt > 0 && lpFormatEtc != 0)
{
copyFormatEtc (lpFormatEtc [0], *format);
++index;
if (pceltFetched != nullptr)
*pceltFetched = 1;
return S_OK;
}
return S_FALSE;
}
JUCE_COMRESULT Skip (ULONG celt) override
{
if (index + (int) celt >= 1)
return S_FALSE;
index += celt;
return S_OK;
}
JUCE_COMRESULT Reset() override
{
index = 0;
return S_OK;
}
private:
const FORMATETC* const format;
int index = 0;
static void copyFormatEtc (FORMATETC& dest, const FORMATETC& source)
{
dest = source;
if (source.ptd != 0)
{
dest.ptd = (DVTARGETDEVICE*) CoTaskMemAlloc (sizeof (DVTARGETDEVICE));
*(dest.ptd) = *(source.ptd);
}
}
JUCE_DECLARE_NON_COPYABLE (JuceEnumFormatEtc)
};
//==============================================================================
class JuceDataObject : public ComBaseClassHelper <IDataObject>
{
public:
JuceDataObject (JuceDropSource* s, const FORMATETC* f, const STGMEDIUM* m)
: dropSource (s), format (f), medium (m)
{
}
~JuceDataObject()
{
jassert (refCount == 0);
}
JUCE_COMRESULT GetData (FORMATETC* pFormatEtc, STGMEDIUM* pMedium)
{
if ((pFormatEtc->tymed & format->tymed) != 0
&& pFormatEtc->cfFormat == format->cfFormat
&& pFormatEtc->dwAspect == format->dwAspect)
{
pMedium->tymed = format->tymed;
pMedium->pUnkForRelease = 0;
if (format->tymed == TYMED_HGLOBAL)
{
auto len = GlobalSize (medium->hGlobal);
void* const src = GlobalLock (medium->hGlobal);
void* const dst = GlobalAlloc (GMEM_FIXED, len);
memcpy (dst, src, len);
GlobalUnlock (medium->hGlobal);
pMedium->hGlobal = dst;
return S_OK;
}
}
return DV_E_FORMATETC;
}
JUCE_COMRESULT QueryGetData (FORMATETC* f)
{
if (f == 0)
return E_INVALIDARG;
if (f->tymed == format->tymed
&& f->cfFormat == format->cfFormat
&& f->dwAspect == format->dwAspect)
return S_OK;
return DV_E_FORMATETC;
}
JUCE_COMRESULT GetCanonicalFormatEtc (FORMATETC*, FORMATETC* pFormatEtcOut)
{
pFormatEtcOut->ptd = 0;
return E_NOTIMPL;
}
JUCE_COMRESULT EnumFormatEtc (DWORD direction, IEnumFORMATETC** result)
{
if (result == 0)
return E_POINTER;
if (direction == DATADIR_GET)
{
*result = new JuceEnumFormatEtc (format);
return S_OK;
}
*result = 0;
return E_NOTIMPL;
}
JUCE_COMRESULT GetDataHere (FORMATETC*, STGMEDIUM*) { return DATA_E_FORMATETC; }
JUCE_COMRESULT SetData (FORMATETC*, STGMEDIUM*, BOOL) { return E_NOTIMPL; }
JUCE_COMRESULT DAdvise (FORMATETC*, DWORD, IAdviseSink*, DWORD*) { return OLE_E_ADVISENOTSUPPORTED; }
JUCE_COMRESULT DUnadvise (DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT EnumDAdvise (IEnumSTATDATA**) { return OLE_E_ADVISENOTSUPPORTED; }
private:
JuceDropSource* const dropSource;
const FORMATETC* const format;
const STGMEDIUM* const medium;
JUCE_DECLARE_NON_COPYABLE (JuceDataObject)
};
//==============================================================================
HDROP createHDrop (const StringArray& fileNames)
{
int totalBytes = 0;
for (int i = fileNames.size(); --i >= 0;)
totalBytes += (int) CharPointer_UTF16::getBytesRequiredFor (fileNames[i].getCharPointer()) + sizeof (WCHAR);
HDROP hDrop = (HDROP) GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof (DROPFILES) + totalBytes + 4);
if (hDrop != 0)
{
auto pDropFiles = (LPDROPFILES) GlobalLock (hDrop);
pDropFiles->pFiles = sizeof (DROPFILES);
pDropFiles->fWide = true;
auto* fname = reinterpret_cast<WCHAR*> (addBytesToPointer (pDropFiles, sizeof (DROPFILES)));
for (int i = 0; i < fileNames.size(); ++i)
{
auto bytesWritten = fileNames[i].copyToUTF16 (fname, 2048);
fname = reinterpret_cast<WCHAR*> (addBytesToPointer (fname, bytesWritten));
}
*fname = 0;
GlobalUnlock (hDrop);
}
return hDrop;
}
bool performDragDrop (FORMATETC* const format, STGMEDIUM* const medium, const DWORD whatToDo)
{
auto source = new JuceDropSource();
auto data = new JuceDataObject (source, format, medium);
DWORD effect;
auto res = DoDragDrop (data, source, whatToDo, &effect);
data->Release();
source->Release();
return res == DRAGDROP_S_DROP;
}
}
//==============================================================================
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove, Component*)
{
FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };
medium.hGlobal = DragAndDropHelpers::createHDrop (files);
return DragAndDropHelpers::performDragDrop (&format, &medium, canMove ? (DWORD) (DROPEFFECT_COPY | DROPEFFECT_MOVE)
: (DWORD) DROPEFFECT_COPY);
}
bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component*)
{
FORMATETC format = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };
auto numBytes = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
medium.hGlobal = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, numBytes + 2);
WCHAR* const data = static_cast<WCHAR*> (GlobalLock (medium.hGlobal));
text.copyToUTF16 (data, numBytes);
format.cfFormat = CF_UNICODETEXT;
GlobalUnlock (medium.hGlobal);
return DragAndDropHelpers::performDragDrop (&format, &medium, DROPEFFECT_COPY | DROPEFFECT_MOVE);
}
} // namespace juce

View File

@ -0,0 +1,577 @@
/*
==============================================================================
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
{
// 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
{
public:
using Ptr = ReferenceCountedObjectPtr<Win32NativeFileChooser>;
enum { charsAvailableForResult = 32768 };
Win32NativeFileChooser (Component* parent, int flags, FilePreviewComponent* previewComp,
const File& startingFile, const String& titleToUse,
const String& filtersToUse)
: Thread ("Native Win32 FileChooser"),
owner (parent), title (titleToUse), filtersString (filtersToUse),
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)
{
auto parentDirectory = startingFile.getParentDirectory();
// Handle nonexistent root directories in the same way as existing ones
files.calloc (static_cast<size_t> (charsAvailableForResult) + 1);
if (startingFile.isDirectory() ||startingFile.isRoot())
{
initialPath = startingFile.getFullPathName();
}
else
{
startingFile.getFileName().copyToUTF16 (files,
static_cast<size_t> (charsAvailableForResult) * sizeof (WCHAR));
initialPath = parentDirectory.getFullPathName();
}
if (! selectsDirectories)
{
if (previewComp != nullptr)
customComponent.reset (new CustomComponentHolder (previewComp));
setupFilters();
}
}
~Win32NativeFileChooser()
{
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:
CustomComponentHolder (Component* const customComp)
{
setVisible (true);
setOpaque (true);
addAndMakeVisible (customComp);
setSize (jlimit (20, 800, customComp->getWidth()), customComp->getHeight());
}
void paint (Graphics& g) override
{
g.fillAll (Colours::lightgrey);
}
void resized() override
{
if (Component* const c = getChildComponent(0))
c->setBounds (getLocalBounds());
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
};
//==============================================================================
Component::SafePointer<Component> owner;
String title, filtersString;
std::unique_ptr<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.get());
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))
{
auto 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()
{
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
return false;
#else
return true;
#endif
}
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
FilePreviewComponent* preview)
{
return new FileChooser::Native (owner, flags, preview);
}
} // namespace juce

File diff suppressed because it is too large Load Diff