mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-13 00:04:19 +00:00
Added Animated App template and examples
This commit is contained in:
parent
fefcf7aca6
commit
ff6520a89a
1141 changed files with 438491 additions and 94 deletions
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MULTITOUCHMAPPER_H_INCLUDED
|
||||
#define JUCE_MULTITOUCHMAPPER_H_INCLUDED
|
||||
|
||||
template <typename IDType>
|
||||
class MultiTouchMapper
|
||||
{
|
||||
public:
|
||||
MultiTouchMapper() {}
|
||||
|
||||
int getIndexOfTouch (IDType touchID)
|
||||
{
|
||||
jassert (touchID != 0); // need to rethink this if IDs can be 0!
|
||||
|
||||
int touchIndex = currentTouches.indexOf (touchID);
|
||||
|
||||
if (touchIndex < 0)
|
||||
{
|
||||
for (touchIndex = 0; touchIndex < currentTouches.size(); ++touchIndex)
|
||||
if (currentTouches.getUnchecked (touchIndex) == 0)
|
||||
break;
|
||||
|
||||
currentTouches.set (touchIndex, touchID);
|
||||
}
|
||||
|
||||
return touchIndex;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
currentTouches.clear();
|
||||
}
|
||||
|
||||
void clearTouch (int index)
|
||||
{
|
||||
currentTouches.set (index, 0);
|
||||
}
|
||||
|
||||
bool areAnyTouchesActive() const noexcept
|
||||
{
|
||||
for (int i = currentTouches.size(); --i >= 0;)
|
||||
if (currentTouches.getUnchecked(i) != 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Array<IDType> currentTouches;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTouchMapper)
|
||||
};
|
||||
|
||||
#endif // JUCE_MULTITOUCHMAPPER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>& results,
|
||||
const String& title,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& filter,
|
||||
bool selectsDirectory,
|
||||
bool selectsFiles,
|
||||
bool isSaveDialogue,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
bool selectMultipleFiles,
|
||||
FilePreviewComponent* extraInfoComponent)
|
||||
{
|
||||
// TODO
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,837 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
} // (juce namespace)
|
||||
|
||||
extern juce::JUCEApplicationBase* juce_CreateApplication(); // (from START_JUCE_APPLICATION)
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity,
|
||||
jstring appFile, jstring appDataDir))
|
||||
{
|
||||
android.initialise (env, activity, appFile, appDataDir);
|
||||
|
||||
DBG (SystemStats::getJUCEVersion());
|
||||
|
||||
JUCEApplicationBase::createInstance = &juce_CreateApplication;
|
||||
|
||||
initialiseJuce_GUI();
|
||||
|
||||
JUCEApplicationBase* app = JUCEApplicationBase::createInstance();
|
||||
if (! app->initialiseApp())
|
||||
exit (app->getApplicationReturnValue());
|
||||
|
||||
jassert (MessageManager::getInstance()->isThisTheMessageThread());
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, suspendApp, void, (JNIEnv* env, jobject activity))
|
||||
{
|
||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
|
||||
app->suspended();
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject activity))
|
||||
{
|
||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
|
||||
app->resumed();
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject activity))
|
||||
{
|
||||
JUCEApplicationBase::appWillTerminateByForce();
|
||||
|
||||
android.shutdown (env);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (drawBitmap, "drawBitmap", "([IIIFFIIZLandroid/graphics/Paint;)V") \
|
||||
METHOD (getClipBounds, "getClipBounds", "()Landroid/graphics/Rect;")
|
||||
|
||||
DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (setViewName, "setViewName", "(Ljava/lang/String;)V") \
|
||||
METHOD (layout, "layout", "(IIII)V") \
|
||||
METHOD (getLeft, "getLeft", "()I") \
|
||||
METHOD (getTop, "getTop", "()I") \
|
||||
METHOD (getWidth, "getWidth", "()I") \
|
||||
METHOD (getHeight, "getHeight", "()I") \
|
||||
METHOD (getLocationOnScreen, "getLocationOnScreen", "([I)V") \
|
||||
METHOD (bringToFront, "bringToFront", "()V") \
|
||||
METHOD (requestFocus, "requestFocus", "()Z") \
|
||||
METHOD (setVisible, "setVisible", "(Z)V") \
|
||||
METHOD (isVisible, "isVisible", "()Z") \
|
||||
METHOD (hasFocus, "hasFocus", "()Z") \
|
||||
METHOD (invalidate, "invalidate", "(IIII)V") \
|
||||
METHOD (containsPoint, "containsPoint", "(II)Z") \
|
||||
METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \
|
||||
METHOD (createGLView, "createGLView", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;") \
|
||||
|
||||
DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class AndroidComponentPeer : public ComponentPeer
|
||||
{
|
||||
public:
|
||||
AndroidComponentPeer (Component& comp, const int windowStyleFlags)
|
||||
: ComponentPeer (comp, windowStyleFlags),
|
||||
usingAndroidGraphics (false),
|
||||
fullScreen (false),
|
||||
sizeAllocated (0)
|
||||
{
|
||||
// NB: must not put this in the initialiser list, as it invokes a callback,
|
||||
// which will fail if the peer is only half-constructed.
|
||||
view = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.createNewView,
|
||||
component.isOpaque(), (jlong) this));
|
||||
|
||||
if (isFocused())
|
||||
handleFocusGain();
|
||||
}
|
||||
|
||||
~AndroidComponentPeer()
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
android.activity.callVoidMethod (JuceAppActivity.deleteView, view.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
struct ViewDeleter : public CallbackMessage
|
||||
{
|
||||
ViewDeleter (const GlobalRef& view_) : view (view_) {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
android.activity.callVoidMethod (JuceAppActivity.deleteView, view.get());
|
||||
}
|
||||
|
||||
private:
|
||||
GlobalRef view;
|
||||
};
|
||||
|
||||
(new ViewDeleter (view))->post();
|
||||
}
|
||||
|
||||
view.clear();
|
||||
}
|
||||
|
||||
void* getNativeHandle() const override
|
||||
{
|
||||
return (void*) view.get();
|
||||
}
|
||||
|
||||
void setVisible (bool shouldBeVisible) override
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.setVisible, shouldBeVisible);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct VisibilityChanger : public CallbackMessage
|
||||
{
|
||||
VisibilityChanger (const GlobalRef& view_, bool shouldBeVisible_)
|
||||
: view (view_), shouldBeVisible (shouldBeVisible_)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.setVisible, shouldBeVisible);
|
||||
}
|
||||
|
||||
private:
|
||||
GlobalRef view;
|
||||
bool shouldBeVisible;
|
||||
};
|
||||
|
||||
(new VisibilityChanger (view, shouldBeVisible))->post();
|
||||
}
|
||||
}
|
||||
|
||||
void setTitle (const String& title) override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.setViewName, javaString (title).get());
|
||||
}
|
||||
|
||||
void setBounds (const Rectangle<int>& r, bool isNowFullScreen) override
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
fullScreen = isNowFullScreen;
|
||||
view.callVoidMethod (ComponentPeerView.layout,
|
||||
r.getX(), r.getY(), r.getRight(), r.getBottom());
|
||||
}
|
||||
else
|
||||
{
|
||||
class ViewMover : public CallbackMessage
|
||||
{
|
||||
public:
|
||||
ViewMover (const GlobalRef& v, const Rectangle<int>& r) : view (v), bounds (r) {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.layout,
|
||||
bounds.getX(), bounds.getY(), bounds.getRight(), bounds.getBottom());
|
||||
}
|
||||
|
||||
private:
|
||||
GlobalRef view;
|
||||
Rectangle<int> bounds;
|
||||
};
|
||||
|
||||
(new ViewMover (view, r))->post();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> getBounds() const override
|
||||
{
|
||||
return Rectangle<int> (view.callIntMethod (ComponentPeerView.getLeft),
|
||||
view.callIntMethod (ComponentPeerView.getTop),
|
||||
view.callIntMethod (ComponentPeerView.getWidth),
|
||||
view.callIntMethod (ComponentPeerView.getHeight));
|
||||
}
|
||||
|
||||
void handleScreenSizeChange()
|
||||
{
|
||||
ComponentPeer::handleScreenSizeChange();
|
||||
|
||||
if (isFullScreen())
|
||||
setFullScreen (true);
|
||||
}
|
||||
|
||||
Point<int> getScreenPosition() const
|
||||
{
|
||||
return Point<int> (view.callIntMethod (ComponentPeerView.getLeft),
|
||||
view.callIntMethod (ComponentPeerView.getTop));
|
||||
}
|
||||
|
||||
Point<float> localToGlobal (Point<float> relativePosition) override
|
||||
{
|
||||
return relativePosition + getScreenPosition().toFloat();
|
||||
}
|
||||
|
||||
Point<float> globalToLocal (Point<float> screenPosition) override
|
||||
{
|
||||
return screenPosition - getScreenPosition().toFloat();
|
||||
}
|
||||
|
||||
void setMinimised (bool shouldBeMinimised) override
|
||||
{
|
||||
// n/a
|
||||
}
|
||||
|
||||
bool isMinimised() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void setFullScreen (bool shouldBeFullScreen) override
|
||||
{
|
||||
Rectangle<int> r (shouldBeFullScreen ? Desktop::getInstance().getDisplays().getMainDisplay().userArea
|
||||
: lastNonFullscreenBounds);
|
||||
|
||||
if ((! shouldBeFullScreen) && r.isEmpty())
|
||||
r = getBounds();
|
||||
|
||||
// (can't call the component's setBounds method because that'll reset our fullscreen flag)
|
||||
if (! r.isEmpty())
|
||||
setBounds (r, shouldBeFullScreen);
|
||||
|
||||
component.repaint();
|
||||
}
|
||||
|
||||
bool isFullScreen() const override
|
||||
{
|
||||
return fullScreen;
|
||||
}
|
||||
|
||||
void setIcon (const Image& newIcon) override
|
||||
{
|
||||
// n/a
|
||||
}
|
||||
|
||||
bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
|
||||
{
|
||||
return isPositiveAndBelow (localPos.x, component.getWidth())
|
||||
&& isPositiveAndBelow (localPos.y, component.getHeight())
|
||||
&& ((! trueIfInAChildWindow) || view.callBooleanMethod (ComponentPeerView.containsPoint,
|
||||
localPos.x, localPos.y));
|
||||
}
|
||||
|
||||
BorderSize<int> getFrameSize() const override
|
||||
{
|
||||
// TODO
|
||||
return BorderSize<int>();
|
||||
}
|
||||
|
||||
bool setAlwaysOnTop (bool alwaysOnTop) override
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
void toFront (bool makeActive) override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.bringToFront);
|
||||
|
||||
if (makeActive)
|
||||
grabFocus();
|
||||
|
||||
handleBroughtToFront();
|
||||
}
|
||||
|
||||
void toBehind (ComponentPeer* other) override
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleMouseDownCallback (int index, Point<float> pos, int64 time)
|
||||
{
|
||||
lastMousePos = pos;
|
||||
|
||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
|
||||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time);
|
||||
|
||||
if (isValidPeer (this))
|
||||
handleMouseDragCallback (index, pos, time);
|
||||
}
|
||||
|
||||
void handleMouseDragCallback (int index, Point<float> pos, int64 time)
|
||||
{
|
||||
lastMousePos = pos;
|
||||
|
||||
jassert (index < 64);
|
||||
touchesDown = (touchesDown | (1 << (index & 63)));
|
||||
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
|
||||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons()
|
||||
.withFlags (ModifierKeys::leftButtonModifier), time);
|
||||
}
|
||||
|
||||
void handleMouseUpCallback (int index, Point<float> pos, int64 time)
|
||||
{
|
||||
lastMousePos = pos;
|
||||
|
||||
jassert (index < 64);
|
||||
touchesDown = (touchesDown & ~(1 << (index & 63)));
|
||||
|
||||
if (touchesDown == 0)
|
||||
currentModifiers = currentModifiers.withoutMouseButtons();
|
||||
|
||||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time);
|
||||
}
|
||||
|
||||
void handleKeyDownCallback (int k, int kc)
|
||||
{
|
||||
handleKeyPress (k, kc);
|
||||
}
|
||||
|
||||
void handleKeyUpCallback (int k, int kc)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isFocused() const override
|
||||
{
|
||||
return view.callBooleanMethod (ComponentPeerView.hasFocus);
|
||||
}
|
||||
|
||||
void grabFocus() override
|
||||
{
|
||||
view.callBooleanMethod (ComponentPeerView.requestFocus);
|
||||
}
|
||||
|
||||
void handleFocusChangeCallback (bool hasFocus)
|
||||
{
|
||||
if (hasFocus)
|
||||
handleFocusGain();
|
||||
else
|
||||
handleFocusLoss();
|
||||
}
|
||||
|
||||
static const char* getVirtualKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TextInputTarget::textKeyboard: return "text";
|
||||
case TextInputTarget::numericKeyboard: return "number";
|
||||
case TextInputTarget::urlKeyboard: return "textUri";
|
||||
case TextInputTarget::emailAddressKeyboard: return "textEmailAddress";
|
||||
case TextInputTarget::phoneNumberKeyboard: return "phone";
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
return "text";
|
||||
}
|
||||
|
||||
void textInputRequired (Point<int>, TextInputTarget& target) override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.showKeyboard,
|
||||
javaString (getVirtualKeyboardType (target.getKeyboardType())).get());
|
||||
}
|
||||
|
||||
void dismissPendingTextInput() override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.showKeyboard, javaString ("").get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handlePaintCallback (JNIEnv* env, jobject canvas)
|
||||
{
|
||||
jobject rect = env->CallObjectMethod (canvas, CanvasMinimal.getClipBounds);
|
||||
const int left = env->GetIntField (rect, RectClass.left);
|
||||
const int top = env->GetIntField (rect, RectClass.top);
|
||||
const int right = env->GetIntField (rect, RectClass.right);
|
||||
const int bottom = env->GetIntField (rect, RectClass.bottom);
|
||||
env->DeleteLocalRef (rect);
|
||||
|
||||
const Rectangle<int> clip (left, top, right - left, bottom - top);
|
||||
|
||||
const int sizeNeeded = clip.getWidth() * clip.getHeight();
|
||||
if (sizeAllocated < sizeNeeded)
|
||||
{
|
||||
buffer.clear();
|
||||
sizeAllocated = sizeNeeded;
|
||||
buffer = GlobalRef (env->NewIntArray (sizeNeeded));
|
||||
}
|
||||
|
||||
if (jint* dest = env->GetIntArrayElements ((jintArray) buffer.get(), 0))
|
||||
{
|
||||
{
|
||||
Image temp (new PreallocatedImage (clip.getWidth(), clip.getHeight(),
|
||||
dest, ! component.isOpaque()));
|
||||
|
||||
{
|
||||
LowLevelGraphicsSoftwareRenderer g (temp);
|
||||
g.setOrigin (-clip.getPosition());
|
||||
handlePaint (g);
|
||||
}
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements ((jintArray) buffer.get(), dest, 0);
|
||||
|
||||
env->CallVoidMethod (canvas, CanvasMinimal.drawBitmap, (jintArray) buffer.get(), 0, clip.getWidth(),
|
||||
(jfloat) clip.getX(), (jfloat) clip.getY(),
|
||||
clip.getWidth(), clip.getHeight(), true, (jobject) 0);
|
||||
}
|
||||
}
|
||||
|
||||
void repaint (const Rectangle<int>& area) override
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.invalidate, area.getX(), area.getY(), area.getRight(), area.getBottom());
|
||||
}
|
||||
else
|
||||
{
|
||||
struct ViewRepainter : public CallbackMessage
|
||||
{
|
||||
ViewRepainter (const GlobalRef& view_, const Rectangle<int>& area_)
|
||||
: view (view_), area (area_) {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
view.callVoidMethod (ComponentPeerView.invalidate, area.getX(), area.getY(),
|
||||
area.getRight(), area.getBottom());
|
||||
}
|
||||
|
||||
private:
|
||||
GlobalRef view;
|
||||
const Rectangle<int> area;
|
||||
};
|
||||
|
||||
(new ViewRepainter (view, area))->post();
|
||||
}
|
||||
}
|
||||
|
||||
void performAnyPendingRepaintsNow() override
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void setAlpha (float newAlpha) override
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
StringArray getAvailableRenderingEngines() override
|
||||
{
|
||||
return StringArray ("Software Renderer");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static ModifierKeys currentModifiers;
|
||||
static Point<float> lastMousePos;
|
||||
static int64 touchesDown;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
GlobalRef view;
|
||||
GlobalRef buffer;
|
||||
bool usingAndroidGraphics, fullScreen;
|
||||
int sizeAllocated;
|
||||
|
||||
class PreallocatedImage : public ImagePixelData
|
||||
{
|
||||
public:
|
||||
PreallocatedImage (const int width_, const int height_, jint* data_, bool hasAlpha_)
|
||||
: ImagePixelData (Image::ARGB, width_, height_), data (data_), hasAlpha (hasAlpha_)
|
||||
{
|
||||
if (hasAlpha_)
|
||||
zeromem (data_, width * height * sizeof (jint));
|
||||
}
|
||||
|
||||
~PreallocatedImage()
|
||||
{
|
||||
if (hasAlpha)
|
||||
{
|
||||
PixelARGB* pix = (PixelARGB*) data;
|
||||
|
||||
for (int i = width * height; --i >= 0;)
|
||||
{
|
||||
pix->unpremultiply();
|
||||
++pix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageType* createType() const override { return new SoftwareImageType(); }
|
||||
LowLevelGraphicsContext* createLowLevelContext() override { return new LowLevelGraphicsSoftwareRenderer (Image (this)); }
|
||||
|
||||
void initialiseBitmapData (Image::BitmapData& bm, int x, int y, Image::BitmapData::ReadWriteMode mode)
|
||||
{
|
||||
bm.lineStride = width * sizeof (jint);
|
||||
bm.pixelStride = sizeof (jint);
|
||||
bm.pixelFormat = Image::ARGB;
|
||||
bm.data = (uint8*) (data + x + y * width);
|
||||
}
|
||||
|
||||
ImagePixelData* clone()
|
||||
{
|
||||
PreallocatedImage* s = new PreallocatedImage (width, height, 0, hasAlpha);
|
||||
s->allocatedData.malloc (sizeof (jint) * width * height);
|
||||
s->data = s->allocatedData;
|
||||
memcpy (s->data, data, sizeof (jint) * width * height);
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
jint* data;
|
||||
HeapBlock<jint> allocatedData;
|
||||
bool hasAlpha;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreallocatedImage)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidComponentPeer)
|
||||
};
|
||||
|
||||
ModifierKeys AndroidComponentPeer::currentModifiers = 0;
|
||||
Point<float> AndroidComponentPeer::lastMousePos;
|
||||
int64 AndroidComponentPeer::touchesDown = 0;
|
||||
|
||||
//==============================================================================
|
||||
#define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \
|
||||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \
|
||||
{ \
|
||||
if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \
|
||||
peer->juceMethodInvocation; \
|
||||
}
|
||||
|
||||
JUCE_VIEW_CALLBACK (void, handlePaint, (JNIEnv* env, jobject view, jlong host, jobject canvas), handlePaintCallback (env, canvas))
|
||||
JUCE_VIEW_CALLBACK (void, handleMouseDown, (JNIEnv* env, jobject view, jlong host, jint i, jfloat x, jfloat y, jlong time), handleMouseDownCallback (i, Point<float> ((float) x, (float) y), (int64) time))
|
||||
JUCE_VIEW_CALLBACK (void, handleMouseDrag, (JNIEnv* env, jobject view, jlong host, jint i, jfloat x, jfloat y, jlong time), handleMouseDragCallback (i, Point<float> ((float) x, (float) y), (int64) time))
|
||||
JUCE_VIEW_CALLBACK (void, handleMouseUp, (JNIEnv* env, jobject view, jlong host, jint i, jfloat x, jfloat y, jlong time), handleMouseUpCallback (i, Point<float> ((float) x, (float) y), (int64) time))
|
||||
JUCE_VIEW_CALLBACK (void, viewSizeChanged, (JNIEnv* env, jobject view, jlong host), handleMovedOrResized())
|
||||
JUCE_VIEW_CALLBACK (void, focusChanged, (JNIEnv* env, jobject view, jlong host, jboolean hasFocus), handleFocusChangeCallback (hasFocus))
|
||||
JUCE_VIEW_CALLBACK (void, handleKeyDown, (JNIEnv* env, jobject view, jlong host, jint k, jint kc), handleKeyDownCallback ((int) k, (int) kc))
|
||||
JUCE_VIEW_CALLBACK (void, handleKeyUp, (JNIEnv* env, jobject view, jlong host, jint k, jint kc), handleKeyUpCallback ((int) k, (int) kc))
|
||||
|
||||
//==============================================================================
|
||||
ComponentPeer* Component::createNewPeer (int styleFlags, void*)
|
||||
{
|
||||
return new AndroidComponentPeer (*this, styleFlags);
|
||||
}
|
||||
|
||||
jobject createOpenGLView (ComponentPeer* peer)
|
||||
{
|
||||
jobject parentView = static_cast <jobject> (peer->getNativeHandle());
|
||||
return getEnv()->CallObjectMethod (parentView, ComponentPeerView.createGLView);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Desktop::canUseSemiTransparentWindows() noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
double Desktop::getDefaultMasterScale()
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
|
||||
{
|
||||
// TODO
|
||||
return upright;
|
||||
}
|
||||
|
||||
bool MouseInputSource::SourceList::addSource()
|
||||
{
|
||||
addSource (sources.size(), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Point<float> MouseInputSource::getCurrentRawMousePosition()
|
||||
{
|
||||
return AndroidComponentPeer::lastMousePos;
|
||||
}
|
||||
|
||||
void MouseInputSource::setRawMousePosition (Point<float>)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyPress::isKeyCurrentlyDown (const int keyCode)
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModifierKeys::updateCurrentModifiers() noexcept
|
||||
{
|
||||
currentModifiers = AndroidComponentPeer::currentModifiers;
|
||||
}
|
||||
|
||||
ModifierKeys ModifierKeys::getCurrentModifiersRealtime() noexcept
|
||||
{
|
||||
return AndroidComponentPeer::currentModifiers;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// TODO
|
||||
JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return true; }
|
||||
JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() {}
|
||||
JUCE_API void JUCE_CALLTYPE Process::hide() {}
|
||||
|
||||
//==============================================================================
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
android.activity.callVoidMethod (JuceAppActivity.showMessageBox, javaString (title).get(),
|
||||
javaString (message).get(), (jlong) (pointer_sized_int) callback);
|
||||
}
|
||||
|
||||
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
jassert (callback != nullptr); // on android, all alerts must be non-modal!!
|
||||
|
||||
android.activity.callVoidMethod (JuceAppActivity.showOkCancelBox, javaString (title).get(),
|
||||
javaString (message).get(), (jlong) (pointer_sized_int) callback);
|
||||
return false;
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
jassert (callback != nullptr); // on android, all alerts must be non-modal!!
|
||||
|
||||
android.activity.callVoidMethod (JuceAppActivity.showYesNoCancelBox, javaString (title).get(),
|
||||
javaString (message).get(), (jlong) (pointer_sized_int) callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject activity,
|
||||
jlong callbackAsLong, jint result))
|
||||
{
|
||||
if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong)
|
||||
{
|
||||
callback->modalStateFinished (result);
|
||||
delete callback;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setScreenSaverEnabled (const bool isEnabled)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool Desktop::isScreenSaverEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool allowMenusAndBars)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool juce_areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::Displays::findDisplays (float masterScale)
|
||||
{
|
||||
Display d;
|
||||
d.userArea = d.totalArea = Rectangle<int> (android.screenWidth,
|
||||
android.screenHeight) / masterScale;
|
||||
d.isMain = true;
|
||||
d.scale = masterScale;
|
||||
d.dpi = android.dpi;
|
||||
|
||||
displays.add (d);
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv* env, jobject activity,
|
||||
jint screenWidth, jint screenHeight,
|
||||
jint dpi))
|
||||
{
|
||||
android.screenWidth = screenWidth;
|
||||
android.screenHeight = screenHeight;
|
||||
android.dpi = dpi;
|
||||
|
||||
const_cast<Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Image juce_createIconForFile (const File& file)
|
||||
{
|
||||
return Image::null;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void* CustomMouseCursorInfo::create() const { return nullptr; }
|
||||
void* MouseCursor::createStandardMouseCursor (const MouseCursor::StandardCursorType) { return nullptr; }
|
||||
void MouseCursor::deleteMouseCursor (void* const /*cursorHandle*/, const bool /*isStandard*/) {}
|
||||
|
||||
//==============================================================================
|
||||
void MouseCursor::showInWindow (ComponentPeer*) const {}
|
||||
void MouseCursor::showInAllWindows() const {}
|
||||
|
||||
//==============================================================================
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String& text)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void LookAndFeel::playAlertSound()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SystemClipboard::copyTextToClipboard (const String& text)
|
||||
{
|
||||
const LocalRef<jstring> t (javaString (text));
|
||||
android.activity.callVoidMethod (JuceAppActivity.setClipboardContent, t.get());
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
const LocalRef<jstring> text ((jstring) android.activity.callObjectMethod (JuceAppActivity.getClipboardContent));
|
||||
return juceString (text);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const int extendedKeyModifier = 0x10000;
|
||||
|
||||
const int KeyPress::spaceKey = ' ';
|
||||
const int KeyPress::returnKey = 66;
|
||||
const int KeyPress::escapeKey = 4;
|
||||
const int KeyPress::backspaceKey = 67;
|
||||
const int KeyPress::leftKey = extendedKeyModifier + 1;
|
||||
const int KeyPress::rightKey = extendedKeyModifier + 2;
|
||||
const int KeyPress::upKey = extendedKeyModifier + 3;
|
||||
const int KeyPress::downKey = extendedKeyModifier + 4;
|
||||
const int KeyPress::pageUpKey = extendedKeyModifier + 5;
|
||||
const int KeyPress::pageDownKey = extendedKeyModifier + 6;
|
||||
const int KeyPress::endKey = extendedKeyModifier + 7;
|
||||
const int KeyPress::homeKey = extendedKeyModifier + 8;
|
||||
const int KeyPress::deleteKey = extendedKeyModifier + 9;
|
||||
const int KeyPress::insertKey = -1;
|
||||
const int KeyPress::tabKey = 61;
|
||||
const int KeyPress::F1Key = extendedKeyModifier + 10;
|
||||
const int KeyPress::F2Key = extendedKeyModifier + 11;
|
||||
const int KeyPress::F3Key = extendedKeyModifier + 12;
|
||||
const int KeyPress::F4Key = extendedKeyModifier + 13;
|
||||
const int KeyPress::F5Key = extendedKeyModifier + 14;
|
||||
const int KeyPress::F6Key = extendedKeyModifier + 16;
|
||||
const int KeyPress::F7Key = extendedKeyModifier + 17;
|
||||
const int KeyPress::F8Key = extendedKeyModifier + 18;
|
||||
const int KeyPress::F9Key = extendedKeyModifier + 19;
|
||||
const int KeyPress::F10Key = extendedKeyModifier + 20;
|
||||
const int KeyPress::F11Key = extendedKeyModifier + 21;
|
||||
const int KeyPress::F12Key = extendedKeyModifier + 22;
|
||||
const int KeyPress::F13Key = extendedKeyModifier + 23;
|
||||
const int KeyPress::F14Key = extendedKeyModifier + 24;
|
||||
const int KeyPress::F15Key = extendedKeyModifier + 25;
|
||||
const int KeyPress::F16Key = extendedKeyModifier + 26;
|
||||
const int KeyPress::numberPad0 = extendedKeyModifier + 27;
|
||||
const int KeyPress::numberPad1 = extendedKeyModifier + 28;
|
||||
const int KeyPress::numberPad2 = extendedKeyModifier + 29;
|
||||
const int KeyPress::numberPad3 = extendedKeyModifier + 30;
|
||||
const int KeyPress::numberPad4 = extendedKeyModifier + 31;
|
||||
const int KeyPress::numberPad5 = extendedKeyModifier + 32;
|
||||
const int KeyPress::numberPad6 = extendedKeyModifier + 33;
|
||||
const int KeyPress::numberPad7 = extendedKeyModifier + 34;
|
||||
const int KeyPress::numberPad8 = extendedKeyModifier + 35;
|
||||
const int KeyPress::numberPad9 = extendedKeyModifier + 36;
|
||||
const int KeyPress::numberPadAdd = extendedKeyModifier + 37;
|
||||
const int KeyPress::numberPadSubtract = extendedKeyModifier + 38;
|
||||
const int KeyPress::numberPadMultiply = extendedKeyModifier + 39;
|
||||
const int KeyPress::numberPadDivide = extendedKeyModifier + 40;
|
||||
const int KeyPress::numberPadSeparator = extendedKeyModifier + 41;
|
||||
const int KeyPress::numberPadDecimalPoint = extendedKeyModifier + 42;
|
||||
const int KeyPress::numberPadEquals = extendedKeyModifier + 43;
|
||||
const int KeyPress::numberPadDelete = extendedKeyModifier + 44;
|
||||
const int KeyPress::playKey = extendedKeyModifier + 45;
|
||||
const int KeyPress::stopKey = extendedKeyModifier + 46;
|
||||
const int KeyPress::fastForwardKey = extendedKeyModifier + 47;
|
||||
const int KeyPress::rewindKey = extendedKeyModifier + 48;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
extern bool isIOSAppActive;
|
||||
|
||||
} // (juce namespace)
|
||||
|
||||
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate>
|
||||
{
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching: (UIApplication*) application;
|
||||
- (void) applicationWillTerminate: (UIApplication*) application;
|
||||
- (void) applicationDidEnterBackground: (UIApplication*) application;
|
||||
- (void) applicationWillEnterForeground: (UIApplication*) application;
|
||||
- (void) applicationDidBecomeActive: (UIApplication*) application;
|
||||
- (void) applicationWillResignActive: (UIApplication*) application;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JuceAppStartupDelegate
|
||||
|
||||
- (void) applicationDidFinishLaunching: (UIApplication*) application
|
||||
{
|
||||
(void) application;
|
||||
initialiseJuce_GUI();
|
||||
|
||||
JUCEApplicationBase* app = JUCEApplicationBase::createInstance();
|
||||
|
||||
if (! app->initialiseApp())
|
||||
exit (0);
|
||||
}
|
||||
|
||||
- (void) applicationWillTerminate: (UIApplication*) application
|
||||
{
|
||||
(void) application;
|
||||
JUCEApplicationBase::appWillTerminateByForce();
|
||||
}
|
||||
|
||||
- (void) applicationDidEnterBackground: (UIApplication*) application
|
||||
{
|
||||
(void) application;
|
||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
|
||||
app->suspended();
|
||||
}
|
||||
|
||||
- (void) applicationWillEnterForeground: (UIApplication*) application
|
||||
{
|
||||
(void) application;
|
||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
|
||||
app->resumed();
|
||||
}
|
||||
|
||||
- (void) applicationDidBecomeActive: (UIApplication*) application
|
||||
{
|
||||
(void) application;
|
||||
isIOSAppActive = true;
|
||||
}
|
||||
|
||||
- (void) applicationWillResignActive: (UIApplication*) application
|
||||
{
|
||||
(void) application;
|
||||
isIOSAppActive = false;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
int juce_iOSMain (int argc, const char* argv[]);
|
||||
int juce_iOSMain (int argc, const char* argv[])
|
||||
{
|
||||
return UIApplicationMain (argc, const_cast<char**> (argv), nil, @"JuceAppStartupDelegate");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void LookAndFeel::playAlertSound()
|
||||
{
|
||||
//xxx
|
||||
//AudioServicesPlaySystemSound ();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class iOSMessageBox;
|
||||
|
||||
} // (juce namespace)
|
||||
|
||||
@interface JuceAlertBoxDelegate : NSObject <UIAlertViewDelegate>
|
||||
{
|
||||
@public
|
||||
iOSMessageBox* owner;
|
||||
}
|
||||
|
||||
- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex;
|
||||
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
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), delegate (nil), alert (nil),
|
||||
callback (cb), isYesNo (button3 != nil), isAsync (async)
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
~iOSMessageBox()
|
||||
{
|
||||
[alert release];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
int getResult()
|
||||
{
|
||||
jassert (callback == nullptr);
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
while (! (alert.hidden || resultReceived))
|
||||
[[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;
|
||||
JuceAlertBoxDelegate* delegate;
|
||||
UIAlertView* alert;
|
||||
ScopedPointer<ModalComponentManager::Callback> callback;
|
||||
const bool isYesNo, isAsync;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox)
|
||||
};
|
||||
|
||||
} // (juce namespace)
|
||||
|
||||
@implementation JuceAlertBoxDelegate
|
||||
|
||||
- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex
|
||||
{
|
||||
owner->buttonClicked ((int) buttonIndex);
|
||||
alertView.hidden = true;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#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);
|
||||
(void) 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)
|
||||
{
|
||||
ScopedPointer<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)
|
||||
{
|
||||
ScopedPointer<iOSMessageBox> mb (new iOSMessageBox (title, message, @"Cancel", @"Yes", @"No", callback, callback != nullptr));
|
||||
|
||||
if (callback == nullptr)
|
||||
return mb->getResult();
|
||||
|
||||
mb.release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool)
|
||||
{
|
||||
jassertfalse; // no such thing on iOS!
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String&)
|
||||
{
|
||||
jassertfalse; // no such thing on iOS!
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setScreenSaverEnabled (const bool isEnabled)
|
||||
{
|
||||
[[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled];
|
||||
}
|
||||
|
||||
bool Desktop::isScreenSaverEnabled()
|
||||
{
|
||||
return ! [[UIApplication sharedApplication] isIdleTimerDisabled];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool juce_areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Image juce_createIconForFile (const File&)
|
||||
{
|
||||
return Image::null;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SystemClipboard::copyTextToClipboard (const String& text)
|
||||
{
|
||||
[[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
|
||||
forPasteboardType: @"public.text"];
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
NSString* text = [[UIPasteboard generalPasteboard] valueForPasteboardType: @"public.text"];
|
||||
|
||||
return text == nil ? String::empty
|
||||
: nsStringToJuce (text);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MouseInputSource::SourceList::addSource()
|
||||
{
|
||||
addSource (sources.size(), false);
|
||||
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
|
||||
{
|
||||
return Orientations::convertToJuce ([[UIApplication sharedApplication] statusBarOrientation]);
|
||||
}
|
||||
|
||||
void Desktop::Displays::findDisplays (float masterScale)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
UIScreen* s = [UIScreen mainScreen];
|
||||
|
||||
Display d;
|
||||
d.userArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s applicationFrame])) / masterScale;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
extern Display* display;
|
||||
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()
|
||||
{
|
||||
static bool isInitialised = false;
|
||||
if (! isInitialised)
|
||||
{
|
||||
atom_UTF8_STRING = XInternAtom (display, "UTF8_STRING", False);
|
||||
atom_CLIPBOARD = XInternAtom (display, "CLIPBOARD", False);
|
||||
atom_TARGETS = XInternAtom (display, "TARGETS", False);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// 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 (Window window, Atom prop)
|
||||
{
|
||||
String returnData;
|
||||
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, 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 (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 (event.xselection.requestor,
|
||||
event.xselection.property);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
|
||||
// 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()), 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)
|
||||
{
|
||||
ClipboardHelpers::initSelectionAtoms();
|
||||
ClipboardHelpers::localClipboardContent = clipText;
|
||||
|
||||
XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime);
|
||||
XSetSelectionOwner (display, ClipboardHelpers::atom_CLIPBOARD, juce_messageWindowHandle, CurrentTime);
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
ClipboardHelpers::initSelectionAtoms();
|
||||
|
||||
/* 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)
|
||||
*/
|
||||
String content;
|
||||
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 (content, selection, ClipboardHelpers::atom_UTF8_STRING);
|
||||
|
||||
if (! ok)
|
||||
{
|
||||
// second chance, ask for a good old locale-dependent string ..
|
||||
ok = ClipboardHelpers::requestSelectionContent (content, selection, XA_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog");
|
||||
return canUseNativeBox;
|
||||
#endif
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
static void addKDialogArgs (StringArray& args, String& separator,
|
||||
const String& title, const File& file, const String& filters,
|
||||
bool isDirectory, bool isSave, bool selectMultipleFiles)
|
||||
{
|
||||
args.add ("kdialog");
|
||||
|
||||
if (title.isNotEmpty())
|
||||
args.add ("--title=" + title);
|
||||
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
{
|
||||
args.add ("--attach");
|
||||
args.add (String (topWindowID));
|
||||
}
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = "\n";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separate-output");
|
||||
args.add ("--getopenfilename");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isSave) args.add ("--getsavefilename");
|
||||
else if (isDirectory) args.add ("--getexistingdirectory");
|
||||
else args.add ("--getopenfilename");
|
||||
}
|
||||
|
||||
File startPath;
|
||||
|
||||
if (file.exists())
|
||||
{
|
||||
startPath = file;
|
||||
}
|
||||
else if (file.getParentDirectory().exists())
|
||||
{
|
||||
startPath = file.getParentDirectory();
|
||||
}
|
||||
else
|
||||
{
|
||||
startPath = File::getSpecialLocation (File::userHomeDirectory);
|
||||
|
||||
if (isSave)
|
||||
startPath = startPath.getChildFile (file.getFileName());
|
||||
}
|
||||
|
||||
args.add (startPath.getFullPathName());
|
||||
args.add (filters.replaceCharacter (';', ' '));
|
||||
}
|
||||
|
||||
static void addZenityArgs (StringArray& args, String& separator,
|
||||
const String& title, const File& file, const String& filters,
|
||||
bool isDirectory, bool isSave, bool selectMultipleFiles)
|
||||
{
|
||||
args.add ("zenity");
|
||||
args.add ("--file-selection");
|
||||
|
||||
if (title.isNotEmpty())
|
||||
args.add ("--title=" + title);
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = ":";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separator=" + separator);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isDirectory) args.add ("--directory");
|
||||
if (isSave) args.add ("--save");
|
||||
}
|
||||
|
||||
if (filters.isNotEmpty() && filters != "*" && filters != "*.*")
|
||||
{
|
||||
args.add ("--file-filter");
|
||||
args.add (filters.replaceCharacter (';', ' '));
|
||||
|
||||
args.add ("--file-filter");
|
||||
args.add ("All files | *");
|
||||
}
|
||||
|
||||
if (file.isDirectory())
|
||||
file.setAsCurrentWorkingDirectory();
|
||||
else if (file.getParentDirectory().exists())
|
||||
file.getParentDirectory().setAsCurrentWorkingDirectory();
|
||||
else
|
||||
File::getSpecialLocation (File::userHomeDirectory).setAsCurrentWorkingDirectory();
|
||||
|
||||
if (! file.getFileName().isEmpty())
|
||||
args.add ("--filename=" + file.getFileName());
|
||||
|
||||
// supplying the window ID of the topmost window makes sure that Zenity pops up..
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
setenv ("WINDOWID", String (topWindowID).toRawUTF8(), true);
|
||||
}
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>& results,
|
||||
const String& title, const File& file, const String& filters,
|
||||
bool isDirectory, bool /* selectsFiles */,
|
||||
bool isSave, bool /* warnAboutOverwritingExistingFiles */,
|
||||
bool selectMultipleFiles, FilePreviewComponent*)
|
||||
{
|
||||
const File previousWorkingDirectory (File::getCurrentWorkingDirectory());
|
||||
|
||||
StringArray args;
|
||||
String separator;
|
||||
|
||||
// use kdialog for KDE sessions or if zenity is missing
|
||||
if (exeIsAvailable ("kdialog") && (isKdeFullSession() || ! exeIsAvailable ("zenity")))
|
||||
addKDialogArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles);
|
||||
else
|
||||
addZenityArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles);
|
||||
|
||||
args.add ("2>/dev/null"); // (to avoid logging info ending up in the results)
|
||||
|
||||
ChildProcess child;
|
||||
|
||||
if (child.start (args, ChildProcess::wantStdOut))
|
||||
{
|
||||
const String result (child.readAllProcessOutput().trim());
|
||||
|
||||
if (result.isNotEmpty())
|
||||
{
|
||||
StringArray tokens;
|
||||
|
||||
if (selectMultipleFiles)
|
||||
tokens.addTokens (result, separator, "\"");
|
||||
else
|
||||
tokens.add (result);
|
||||
|
||||
for (int i = 0; i < tokens.size(); ++i)
|
||||
results.add (File::getCurrentWorkingDirectory().getChildFile (tokens[i]));
|
||||
}
|
||||
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
}
|
||||
|
||||
previousWorkingDirectory.setAsCurrentWorkingDirectory();
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
struct FileChooserDelegateClass : public ObjCClass <NSObject>
|
||||
{
|
||||
FileChooserDelegateClass() : ObjCClass <NSObject> ("JUCEFileChooser_")
|
||||
{
|
||||
addIvar<StringArray*> ("filters");
|
||||
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@");
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
||||
addProtocol (@protocol (NSOpenSavePanelDelegate));
|
||||
#endif
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setFilters (id self, StringArray* filters)
|
||||
{
|
||||
object_setInstanceVariable (self, "filters", filters);
|
||||
}
|
||||
|
||||
private:
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getIvar<StringArray*> (self, "filters");
|
||||
sendSuperclassMessage (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
|
||||
{
|
||||
StringArray* const filters = getIvar<StringArray*> (self, "filters");
|
||||
|
||||
const File f (nsStringToJuce (filename));
|
||||
|
||||
for (int i = filters->size(); --i >= 0;)
|
||||
if (f.getFileName().matchesWildcard ((*filters)[i], true))
|
||||
return true;
|
||||
|
||||
#if (! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
||||
NSError* error;
|
||||
NSString* name = [[NSWorkspace sharedWorkspace] typeOfFile: filename error: &error];
|
||||
|
||||
if ([name isEqualToString: nsStringLiteral ("com.apple.alias-file")])
|
||||
{
|
||||
FSRef ref;
|
||||
FSPathMakeRef ((const UInt8*) [filename fileSystemRepresentation], &ref, nullptr);
|
||||
|
||||
Boolean targetIsFolder = false, wasAliased = false;
|
||||
FSResolveAliasFileWithMountFlags (&ref, true, &targetIsFolder, &wasAliased, 0);
|
||||
|
||||
return wasAliased && targetIsFolder;
|
||||
}
|
||||
#endif
|
||||
|
||||
return f.isDirectory()
|
||||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: filename];
|
||||
}
|
||||
};
|
||||
|
||||
static 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)
|
||||
{
|
||||
const String f (filters[i].replace ("*.", ""));
|
||||
|
||||
if (f == "*")
|
||||
return nil;
|
||||
|
||||
[filterArray addObject: juceStringToNS (f)];
|
||||
}
|
||||
|
||||
return filterArray;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileChooser::showPlatformDialog (Array<File>& results,
|
||||
const String& title,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& filter,
|
||||
bool selectsDirectory,
|
||||
bool selectsFiles,
|
||||
bool isSaveDialogue,
|
||||
bool /*warnAboutOverwritingExistingFiles*/,
|
||||
bool selectMultipleFiles,
|
||||
FilePreviewComponent* /*extraInfoComponent*/)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
ScopedPointer<TemporaryMainMenuWithStandardCommands> tempMenu;
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
tempMenu = new TemporaryMainMenuWithStandardCommands();
|
||||
|
||||
StringArray* filters = new StringArray();
|
||||
filters->addTokens (filter.replaceCharacters (",:", ";;"), ";", String::empty);
|
||||
filters->trim();
|
||||
filters->removeEmptyStrings();
|
||||
|
||||
#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
|
||||
|
||||
static FileChooserDelegateClass cls;
|
||||
DelegateType* delegate = (DelegateType*) [[cls.createInstance() init] autorelease];
|
||||
FileChooserDelegateClass::setFilters (delegate, filters);
|
||||
|
||||
NSSavePanel* panel = isSaveDialogue ? [NSSavePanel savePanel]
|
||||
: [NSOpenPanel openPanel];
|
||||
|
||||
[panel setTitle: juceStringToNS (title)];
|
||||
[panel setAllowedFileTypes: createAllowedTypesArray (*filters)];
|
||||
|
||||
if (! isSaveDialogue)
|
||||
{
|
||||
NSOpenPanel* openPanel = (NSOpenPanel*) panel;
|
||||
[openPanel setCanChooseDirectories: selectsDirectory];
|
||||
[openPanel setCanChooseFiles: selectsFiles];
|
||||
[openPanel setAllowsMultipleSelection: selectMultipleFiles];
|
||||
[openPanel setResolvesAliases: YES];
|
||||
}
|
||||
|
||||
[panel setDelegate: delegate];
|
||||
|
||||
if (isSaveDialogue || selectsDirectory)
|
||||
[panel setCanCreateDirectories: YES];
|
||||
|
||||
String directory, filename;
|
||||
|
||||
if (currentFileOrDirectory.isDirectory())
|
||||
{
|
||||
directory = currentFileOrDirectory.getFullPathName();
|
||||
}
|
||||
else
|
||||
{
|
||||
directory = currentFileOrDirectory.getParentDirectory().getFullPathName();
|
||||
filename = currentFileOrDirectory.getFileName();
|
||||
}
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)
|
||||
[panel setDirectoryURL: [NSURL fileURLWithPath: juceStringToNS (directory)]];
|
||||
[panel setNameFieldStringValue: juceStringToNS (filename)];
|
||||
|
||||
if ([panel runModal] == NSModalResponseOK)
|
||||
#else
|
||||
if ([panel runModalForDirectory: juceStringToNS (directory)
|
||||
file: juceStringToNS (filename)] == NSModalResponseOK)
|
||||
#endif
|
||||
{
|
||||
if (isSaveDialogue)
|
||||
{
|
||||
results.add (File (nsStringToJuce ([[panel URL] path])));
|
||||
}
|
||||
else
|
||||
{
|
||||
NSOpenPanel* openPanel = (NSOpenPanel*) panel;
|
||||
NSArray* urls = [openPanel URLs];
|
||||
|
||||
for (unsigned int i = 0; i < [urls count]; ++i)
|
||||
results.add (File (nsStringToJuce ([[urls objectAtIndex: i] path])));
|
||||
}
|
||||
}
|
||||
|
||||
[panel setDelegate: nil];
|
||||
}
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>&,
|
||||
const String& /*title*/,
|
||||
const File& /*currentFileOrDirectory*/,
|
||||
const String& /*filter*/,
|
||||
bool /*selectsDirectory*/,
|
||||
bool /*selectsFiles*/,
|
||||
bool /*isSaveDialogue*/,
|
||||
bool /*warnAboutOverwritingExistingFiles*/,
|
||||
bool /*selectMultipleFiles*/,
|
||||
FilePreviewComponent*)
|
||||
{
|
||||
jassertfalse; //there's no such thing in iOS
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,750 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class JuceMainMenuHandler : private MenuBarModel::Listener,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
JuceMainMenuHandler()
|
||||
: currentModel (nullptr),
|
||||
lastUpdateTime (0),
|
||||
isOpen (false)
|
||||
{
|
||||
static JuceMenuCallbackClass cls;
|
||||
callback = [cls.createInstance() init];
|
||||
JuceMenuCallbackClass::setOwner (callback, this);
|
||||
}
|
||||
|
||||
~JuceMainMenuHandler()
|
||||
{
|
||||
setMenu (nullptr, nullptr, String::empty);
|
||||
|
||||
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 = createCopyIfNotNull (newExtraAppleMenuItems);
|
||||
}
|
||||
|
||||
void addTopLevelMenu (NSMenu* parent, const PopupMenu& child,
|
||||
const String& name, const int menuId, const int tag)
|
||||
{
|
||||
NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
|
||||
action: nil
|
||||
keyEquivalent: nsEmptyString()];
|
||||
[item setTag: tag];
|
||||
|
||||
NSMenu* sub = createMenu (child, name, menuId, tag, true);
|
||||
|
||||
[parent setSubmenu: sub forItem: item];
|
||||
[sub setAutoenablesItems: false];
|
||||
[sub release];
|
||||
}
|
||||
|
||||
void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy,
|
||||
const String& name, const int menuId, const int tag)
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
|
||||
static bool is10_4 = (SystemStats::getOperatingSystemType() == SystemStats::MacOSX_10_4);
|
||||
|
||||
if (is10_4)
|
||||
{
|
||||
[parentItem setTag: tag];
|
||||
NSMenu* menu = [parentItem submenu];
|
||||
|
||||
[menu setTitle: juceStringToNS (name)];
|
||||
|
||||
while ([menu numberOfItems] > 0)
|
||||
[menu removeItemAtIndex: 0];
|
||||
|
||||
for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();)
|
||||
addMenuItem (iter, menu, menuId, tag);
|
||||
|
||||
[menu setAutoenablesItems: false];
|
||||
[menu update];
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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, tag);
|
||||
|
||||
[menu setAutoenablesItems: false];
|
||||
[menu update];
|
||||
[parentItem setTag: tag];
|
||||
[parentItem setSubmenu: menu];
|
||||
[menu release];
|
||||
}
|
||||
|
||||
void menuBarItemsChanged (MenuBarModel*)
|
||||
{
|
||||
if (isOpen)
|
||||
return;
|
||||
|
||||
lastUpdateTime = Time::getMillisecondCounter();
|
||||
|
||||
StringArray menuNames;
|
||||
if (currentModel != nullptr)
|
||||
menuNames = currentModel->getMenuBarNames();
|
||||
|
||||
NSMenu* menuBar = [[NSApp mainMenu] retain];
|
||||
|
||||
while ([menuBar numberOfItems] > 1 + menuNames.size())
|
||||
[menuBar removeItemAtIndex: [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);
|
||||
}
|
||||
|
||||
[menuBar release];
|
||||
}
|
||||
|
||||
void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
|
||||
{
|
||||
if (NSMenuItem* item = findMenuItem ([NSApp mainMenu], info))
|
||||
flashMenuBar ([item menu]);
|
||||
}
|
||||
|
||||
void updateMenus (NSMenu* menu)
|
||||
{
|
||||
if (PopupMenu::dismissAllActiveMenus())
|
||||
{
|
||||
// If we were running a juce menu, then we should let that modal loop finish before allowing
|
||||
// the OS menus to start their own modal loop - so cancel the menu that was being opened..
|
||||
if ([menu respondsToSelector: @selector (cancelTracking)])
|
||||
[menu performSelector: @selector (cancelTracking)];
|
||||
}
|
||||
|
||||
if (Time::getMillisecondCounter() > lastUpdateTime + 100)
|
||||
(new AsyncMenuUpdater())->post();
|
||||
}
|
||||
|
||||
void invoke (const int commandId, ApplicationCommandManager* const commandManager, const int topLevelIndex) const
|
||||
{
|
||||
if (currentModel != nullptr)
|
||||
{
|
||||
if (commandManager != nullptr)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandId);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
|
||||
|
||||
commandManager->invoke (info, true);
|
||||
}
|
||||
|
||||
(new AsyncCommandInvoker (commandId, topLevelIndex))->post();
|
||||
}
|
||||
}
|
||||
|
||||
void invokeDirectly (const int commandId, const int topLevelIndex)
|
||||
{
|
||||
if (currentModel != nullptr)
|
||||
currentModel->menuItemSelected (commandId, topLevelIndex);
|
||||
}
|
||||
|
||||
void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
|
||||
const int topLevelMenuId, const int topLevelIndex)
|
||||
{
|
||||
NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf ("<end>", false, true));
|
||||
|
||||
if (text == nil)
|
||||
text = nsEmptyString();
|
||||
|
||||
if (iter.isSeparator)
|
||||
{
|
||||
[menuToAddTo addItem: [NSMenuItem separatorItem]];
|
||||
}
|
||||
else if (iter.isSectionHeader)
|
||||
{
|
||||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
|
||||
action: nil
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
[item setEnabled: false];
|
||||
}
|
||||
else if (iter.subMenu != nullptr)
|
||||
{
|
||||
if (iter.itemName == recentItemsMenuName)
|
||||
{
|
||||
if (recent == nullptr)
|
||||
recent = 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: iter.itemId];
|
||||
[item setEnabled: iter.isEnabled];
|
||||
|
||||
NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex, false);
|
||||
[menuToAddTo setSubmenu: sub forItem: item];
|
||||
[sub release];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
|
||||
action: @selector (menuItemInvoked:)
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
[item setTag: iter.itemId];
|
||||
[item setEnabled: iter.isEnabled];
|
||||
[item setState: iter.isTicked ? NSOnState : NSOffState];
|
||||
[item setTarget: (id) callback];
|
||||
|
||||
NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) iter.commandManager]];
|
||||
[info addObject: [NSNumber numberWithInt: topLevelIndex]];
|
||||
[item setRepresentedObject: info];
|
||||
|
||||
if (iter.commandManager != nullptr)
|
||||
{
|
||||
const Array<KeyPress> keyPresses (iter.commandManager->getKeyMappings()
|
||||
->getKeyPressesAssignedToCommand (iter.itemId));
|
||||
|
||||
if (keyPresses.size() > 0)
|
||||
{
|
||||
const KeyPress& kp = keyPresses.getReference(0);
|
||||
|
||||
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())];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static JuceMainMenuHandler* instance;
|
||||
|
||||
MenuBarModel* currentModel;
|
||||
ScopedPointer<PopupMenu> extraAppleMenuItems;
|
||||
uint32 lastUpdateTime;
|
||||
NSObject* callback;
|
||||
String recentItemsMenuName;
|
||||
bool isOpen;
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
ScopedPointer<RecentFilesMenuItem> recent;
|
||||
|
||||
//==============================================================================
|
||||
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 NSMenuItem* findMenuItem (NSMenu* const menu, const ApplicationCommandTarget::InvocationInfo& info)
|
||||
{
|
||||
for (NSInteger i = [menu numberOfItems]; --i >= 0;)
|
||||
{
|
||||
NSMenuItem* m = [menu itemAtIndex: i];
|
||||
if ([m tag] == info.commandID)
|
||||
return m;
|
||||
|
||||
if (NSMenu* sub = [m submenu])
|
||||
if (NSMenuItem* found = findMenuItem (sub, info))
|
||||
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: NSKeyDown
|
||||
location: NSZeroPoint
|
||||
modifierFlags: NSCommandKeyMask
|
||||
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 |= NSShiftKeyMask;
|
||||
if (mods.isCtrlDown()) m |= NSControlKeyMask;
|
||||
if (mods.isAltDown()) m |= NSAlternateKeyMask;
|
||||
if (mods.isCommandDown()) m |= NSCommandKeyMask;
|
||||
return m;
|
||||
}
|
||||
|
||||
class AsyncMenuUpdater : public CallbackMessage
|
||||
{
|
||||
public:
|
||||
AsyncMenuUpdater() {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->menuBarItemsChanged (nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE (AsyncMenuUpdater)
|
||||
};
|
||||
|
||||
class AsyncCommandInvoker : public CallbackMessage
|
||||
{
|
||||
public:
|
||||
AsyncCommandInvoker (const int commandId_, const int topLevelIndex_)
|
||||
: commandId (commandId_), topLevelIndex (topLevelIndex_)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->invokeDirectly (commandId, topLevelIndex);
|
||||
}
|
||||
|
||||
private:
|
||||
const int commandId, topLevelIndex;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (AsyncCommandInvoker)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
{
|
||||
JuceMainMenuHandler* const owner = getIvar<JuceMainMenuHandler*> (self, "owner");
|
||||
|
||||
if ([[item representedObject] isKindOfClass: [NSArray class]])
|
||||
{
|
||||
// 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] == NSKeyDown || [e type] == NSKeyUp)
|
||||
{
|
||||
if (juce::Component* focused = juce::Component::getCurrentlyFocusedComponent())
|
||||
{
|
||||
if (juce::NSViewComponentPeer* peer = dynamic_cast <juce::NSViewComponentPeer*> (focused->getPeer()))
|
||||
{
|
||||
if ([e type] == NSKeyDown)
|
||||
peer->redirectKeyDown (e);
|
||||
else
|
||||
peer->redirectKeyUp (e);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSArray* info = (NSArray*) [item representedObject];
|
||||
|
||||
owner->invoke ((int) [item tag],
|
||||
(ApplicationCommandManager*) (pointer_sized_int)
|
||||
[((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
|
||||
(int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
|
||||
}
|
||||
}
|
||||
|
||||
static void menuNeedsUpdate (id, SEL, NSMenu* menu)
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->updateMenus (menu);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
class TemporaryMainMenuWithStandardCommands
|
||||
{
|
||||
public:
|
||||
TemporaryMainMenuWithStandardCommands()
|
||||
: oldMenu (MenuBarModel::getMacMainMenu()), oldAppleMenu (nullptr)
|
||||
{
|
||||
if (const PopupMenu* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu())
|
||||
oldAppleMenu = new PopupMenu (*appleMenu);
|
||||
|
||||
if (JuceMainMenuHandler::instance != nullptr)
|
||||
oldRecentItems = JuceMainMenuHandler::instance->recentItemsMenuName;
|
||||
|
||||
MenuBarModel::setMacMainMenu (nullptr);
|
||||
|
||||
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];
|
||||
|
||||
item = [[NSApp mainMenu] addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil)
|
||||
action: nil keyEquivalent: nsEmptyString()];
|
||||
[[NSApp mainMenu] setSubmenu: menu forItem: item];
|
||||
[menu release];
|
||||
|
||||
// use a dummy modal component so that apps can tell that something is currently modal.
|
||||
dummyModalComponent.enterModalState();
|
||||
}
|
||||
|
||||
~TemporaryMainMenuWithStandardCommands()
|
||||
{
|
||||
MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu, oldRecentItems);
|
||||
}
|
||||
|
||||
private:
|
||||
MenuBarModel* oldMenu;
|
||||
ScopedPointer<PopupMenu> oldAppleMenu;
|
||||
String oldRecentItems;
|
||||
|
||||
// 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
|
||||
class SilentDummyModalComp : public Component
|
||||
{
|
||||
public:
|
||||
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, "Hide " + appName, @selector (hide:), nsStringLiteral ("h"));
|
||||
|
||||
[createMenuItem (menu, "Hide Others", @selector (hideOtherApplications:), nsStringLiteral ("h"))
|
||||
setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
|
||||
|
||||
createMenuItem (menu, "Show All", @selector (unhideAllApplications:), nsEmptyString());
|
||||
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
|
||||
createMenuItem (menu, "Quit " + 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 (JUCEApplicationBase* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")];
|
||||
NSMenuItem* item = [mainMenu addItemWithTitle: nsStringLiteral ("Apple") action: nil keyEquivalent: nsEmptyString()];
|
||||
|
||||
NSMenu* appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")];
|
||||
|
||||
[NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
|
||||
[mainMenu setSubmenu: appMenu forItem: item];
|
||||
|
||||
[NSApp setMainMenu: mainMenu];
|
||||
MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);
|
||||
|
||||
[appMenu release];
|
||||
[mainMenu release];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return JuceMainMenuHandler::instance != nullptr
|
||||
? JuceMainMenuHandler::instance->currentModel : nullptr;
|
||||
}
|
||||
|
||||
const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()
|
||||
{
|
||||
return JuceMainMenuHandler::instance != nullptr
|
||||
? JuceMainMenuHandler::instance->extraAppleMenuItems.get() : nullptr;
|
||||
}
|
||||
|
||||
typedef void (*MenuTrackingChangedCallback) (bool);
|
||||
extern MenuTrackingChangedCallback menuTrackingChangedCallback;
|
||||
|
||||
static void mainMenuTrackingChanged (bool isTracking)
|
||||
{
|
||||
PopupMenu::dismissAllActiveMenus();
|
||||
|
||||
if (JuceMainMenuHandler::instance != nullptr)
|
||||
JuceMainMenuHandler::instance->isOpen = isTracking;
|
||||
}
|
||||
|
||||
void juce_initialiseMacMainMenu()
|
||||
{
|
||||
menuTrackingChangedCallback = mainMenuTrackingChanged;
|
||||
|
||||
if (JuceMainMenuHandler::instance == nullptr)
|
||||
MainMenuHelpers::rebuildMainMenu (nullptr);
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
//==============================================================================
|
||||
namespace MouseCursorHelpers
|
||||
{
|
||||
NSImage* createNSImage (const Image&);
|
||||
NSImage* createNSImage (const Image& image)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSImage* im = [[NSImage alloc] init];
|
||||
[im setSize: NSMakeSize (image.getWidth(), image.getHeight())];
|
||||
[im lockFocus];
|
||||
|
||||
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGImageRef imageRef = juce_createCoreGraphicsImage (image, colourSpace, false);
|
||||
CGColorSpaceRelease (colourSpace);
|
||||
|
||||
CGContextRef cg = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextDrawImage (cg, convertToCGRect (image.getBounds()), imageRef);
|
||||
|
||||
CGImageRelease (imageRef);
|
||||
[im unlockFocus];
|
||||
|
||||
return im;
|
||||
}
|
||||
}
|
||||
|
||||
static void* fromWebKitFile (const char* filename, float hx, float hy)
|
||||
{
|
||||
FileInputStream fileStream (File ("/System/Library/Frameworks/WebKit.framework/Frameworks/WebCore.framework/Resources")
|
||||
.getChildFile (filename));
|
||||
|
||||
if (fileStream.openedOk())
|
||||
{
|
||||
BufferedInputStream buf (fileStream, 4096);
|
||||
|
||||
PNGImageFormat pngFormat;
|
||||
Image im (pngFormat.decodeImage (buf));
|
||||
|
||||
if (im.isValid())
|
||||
return CustomMouseCursorInfo (im, (int) (hx * im.getWidth()),
|
||||
(int) (hy * im.getHeight())).create();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void* CustomMouseCursorInfo::create() const
|
||||
{
|
||||
NSImage* im = MouseCursorHelpers::createNSImage (image);
|
||||
NSCursor* c = [[NSCursor alloc] initWithImage: im
|
||||
hotSpot: NSMakePoint (hotspot.x, hotspot.y)];
|
||||
[im release];
|
||||
return c;
|
||||
}
|
||||
|
||||
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), 0, 0).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::fromWebKitFile ("copyCursor.png", 0, 0))
|
||||
return m;
|
||||
#endif
|
||||
c = [NSCursor dragCopyCursor]; // added in 10.6
|
||||
break;
|
||||
}
|
||||
|
||||
case UpDownResizeCursor:
|
||||
case TopEdgeResizeCursor:
|
||||
case BottomEdgeResizeCursor:
|
||||
return MouseCursorHelpers::fromWebKitFile ("northSouthResizeCursor.png", 0.5f, 0.5f);
|
||||
|
||||
case LeftRightResizeCursor:
|
||||
if (void* m = MouseCursorHelpers::fromWebKitFile ("eastWestResizeCursor.png", 0.5f, 0.5f))
|
||||
return m;
|
||||
|
||||
c = [NSCursor resizeLeftRightCursor];
|
||||
break;
|
||||
|
||||
case TopLeftCornerResizeCursor:
|
||||
case BottomRightCornerResizeCursor:
|
||||
return MouseCursorHelpers::fromWebKitFile ("northWestSouthEastResizeCursor.png", 0.5f, 0.5f);
|
||||
|
||||
case TopRightCornerResizeCursor:
|
||||
case BottomLeftCornerResizeCursor:
|
||||
return MouseCursorHelpers::fromWebKitFile ("northEastSouthWestResizeCursor.png", 0.5f, 0.5f);
|
||||
|
||||
case UpDownLeftRightResizeCursor:
|
||||
return MouseCursorHelpers::fromWebKitFile ("moveCursor.png", 0.5f, 0.5f);
|
||||
|
||||
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
|
||||
{
|
||||
NSCursor* 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
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,456 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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 NSAlertDefaultReturn: return 1;
|
||||
case NSAlertOtherReturn: 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)
|
||||
{
|
||||
ScopedPointer<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;
|
||||
ScopedPointer<ModalComponentManager::Callback> callback;
|
||||
const char* button1;
|
||||
const char* button2;
|
||||
const char* button3;
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
const int result = getResult();
|
||||
|
||||
if (callback != nullptr)
|
||||
callback->modalStateFinished (result);
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
static NSString* translateIfNotNull (const char* s)
|
||||
{
|
||||
return s != nullptr ? juceStringToNS (TRANS (s)) : nil;
|
||||
}
|
||||
|
||||
NSInteger getRawResult() const
|
||||
{
|
||||
NSString* msg = juceStringToNS (message);
|
||||
NSString* ttl = juceStringToNS (title);
|
||||
NSString* b1 = translateIfNotNull (button1);
|
||||
NSString* b2 = translateIfNotNull (button2);
|
||||
NSString* b3 = translateIfNotNull (button3);
|
||||
|
||||
switch (iconType)
|
||||
{
|
||||
case AlertWindow::InfoIcon: return NSRunInformationalAlertPanel (ttl, msg, b1, b2, b3);
|
||||
case AlertWindow::WarningIcon: return NSRunCriticalAlertPanel (ttl, msg, b1, b2, b3);
|
||||
default: return NSRunAlertPanel (ttl, msg, b1, b2, b3);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool /*canMoveFiles*/)
|
||||
{
|
||||
if (files.size() == 0)
|
||||
return false;
|
||||
|
||||
MouseInputSource* draggingSource = Desktop::getInstance().getDraggingMouseSource(0);
|
||||
|
||||
if (draggingSource == nullptr)
|
||||
{
|
||||
jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
|
||||
return false;
|
||||
}
|
||||
|
||||
Component* sourceComp = draggingSource->getComponentUnderMouse();
|
||||
|
||||
if (sourceComp == nullptr)
|
||||
{
|
||||
jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
|
||||
return false;
|
||||
}
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSView* view = (NSView*) sourceComp->getWindowHandle();
|
||||
|
||||
if (view == nil)
|
||||
return false;
|
||||
|
||||
NSPasteboard* pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
|
||||
[pboard declareTypes: [NSArray arrayWithObject: NSFilenamesPboardType]
|
||||
owner: nil];
|
||||
|
||||
NSMutableArray* filesArray = [NSMutableArray arrayWithCapacity: 4];
|
||||
for (int i = 0; i < files.size(); ++i)
|
||||
[filesArray addObject: juceStringToNS (files[i])];
|
||||
|
||||
[pboard setPropertyList: filesArray
|
||||
forType: NSFilenamesPboardType];
|
||||
|
||||
NSPoint dragPosition = [view convertPoint: [[[view window] currentEvent] locationInWindow]
|
||||
fromView: nil];
|
||||
dragPosition.x -= 16;
|
||||
dragPosition.y -= 16;
|
||||
|
||||
[view dragImage: [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (files[0])]
|
||||
at: dragPosition
|
||||
offset: NSMakeSize (0, 0)
|
||||
event: [[view window] currentEvent]
|
||||
pasteboard: pboard
|
||||
source: view
|
||||
slideBack: YES];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String& /*text*/)
|
||||
{
|
||||
jassertfalse; // not implemented!
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Desktop::canUseSemiTransparentWindows() noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Point<float> MouseInputSource::getCurrentRawMousePosition()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
const NSPoint p ([NSEvent mouseLocation]);
|
||||
return Point<float> ((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 = new PMAssertion();
|
||||
}
|
||||
else
|
||||
{
|
||||
assertion = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
struct PMAssertion
|
||||
{
|
||||
PMAssertion() : assertionID (kIOPMNullAssertionID)
|
||||
{
|
||||
IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
|
||||
kIOPMAssertionLevelOn,
|
||||
CFSTR ("JUCE Playback"),
|
||||
&assertionID);
|
||||
jassert (res == kIOReturnSuccess); (void) res;
|
||||
}
|
||||
|
||||
~PMAssertion()
|
||||
{
|
||||
if (assertionID != kIOPMNullAssertionID)
|
||||
IOPMAssertionRelease (assertionID);
|
||||
}
|
||||
|
||||
IOPMAssertionID assertionID;
|
||||
};
|
||||
|
||||
ScopedPointer<PMAssertion> assertion;
|
||||
#else
|
||||
ScreenSaverDefeater()
|
||||
{
|
||||
startTimer (10000);
|
||||
timerCallback();
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (Process::isForegroundProcess())
|
||||
UpdateSystemActivity (1 /*UsrActivity*/);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
static ScopedPointer<ScreenSaverDefeater> screenSaverDefeater;
|
||||
|
||||
void Desktop::setScreenSaverEnabled (const bool isEnabled)
|
||||
{
|
||||
if (isEnabled)
|
||||
screenSaverDefeater = nullptr;
|
||||
else if (screenSaverDefeater == nullptr)
|
||||
screenSaverDefeater = new ScreenSaverDefeater();
|
||||
}
|
||||
|
||||
bool Desktop::isScreenSaverEnabled()
|
||||
{
|
||||
return screenSaverDefeater == nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class DisplaySettingsChangeCallback : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
DisplaySettingsChangeCallback()
|
||||
{
|
||||
CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, 0);
|
||||
}
|
||||
|
||||
~DisplaySettingsChangeCallback()
|
||||
{
|
||||
CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, 0);
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
|
||||
{
|
||||
const_cast <Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
|
||||
}
|
||||
|
||||
juce_DeclareSingleton_SingleThreaded_Minimal (DisplaySettingsChangeCallback);
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
|
||||
};
|
||||
|
||||
juce_ImplementSingleton_SingleThreaded (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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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);
|
||||
|
||||
[NSGraphicsContext saveGraphicsState];
|
||||
[NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (result) flipped: false]];
|
||||
|
||||
[image drawAtPoint: NSMakePoint (0, 0)
|
||||
fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
|
||||
operation: NSCompositeSourceOver fraction: 1.0f];
|
||||
|
||||
[[NSGraphicsContext currentContext] flushGraphics];
|
||||
[NSGraphicsContext restoreGraphicsState];
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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()
|
||||
{
|
||||
NSString* text = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType];
|
||||
|
||||
return text == nil ? String()
|
||||
: nsStringToJuce (text);
|
||||
}
|
||||
|
||||
void Process::setDockIconVisible (bool isVisible)
|
||||
{
|
||||
#if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)
|
||||
[NSApp setActivationPolicy: isVisible ? NSApplicationActivationPolicyRegular
|
||||
: NSApplicationActivationPolicyProhibited];
|
||||
#else
|
||||
(void) isVisible;
|
||||
jassertfalse; // sorry, not available in 10.5!
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace DragAndDropHelpers
|
||||
{
|
||||
//==============================================================================
|
||||
class JuceDropSource : public ComBaseClassHelper <IDropSource>
|
||||
{
|
||||
public:
|
||||
JuceDropSource() {}
|
||||
|
||||
JUCE_COMRESULT QueryContinueDrag (BOOL escapePressed, DWORD keys)
|
||||
{
|
||||
if (escapePressed)
|
||||
return DRAGDROP_S_CANCEL;
|
||||
|
||||
if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0)
|
||||
return DRAGDROP_S_DROP;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GiveFeedback (DWORD)
|
||||
{
|
||||
return DRAGDROP_S_USEDEFAULTCURSORS;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JuceEnumFormatEtc : public ComBaseClassHelper <IEnumFORMATETC>
|
||||
{
|
||||
public:
|
||||
JuceEnumFormatEtc (const FORMATETC* const format_)
|
||||
: format (format_),
|
||||
index (0)
|
||||
{
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Clone (IEnumFORMATETC** result)
|
||||
{
|
||||
if (result == 0)
|
||||
return E_POINTER;
|
||||
|
||||
JuceEnumFormatEtc* const newOne = new JuceEnumFormatEtc (format);
|
||||
newOne->index = index;
|
||||
|
||||
*result = newOne;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (index + (int) celt >= 1)
|
||||
return S_FALSE;
|
||||
|
||||
index += celt;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Reset()
|
||||
{
|
||||
index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const FORMATETC* const format;
|
||||
int index;
|
||||
|
||||
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* const dropSource_,
|
||||
const FORMATETC* const format_,
|
||||
const STGMEDIUM* const medium_)
|
||||
: dropSource (dropSource_),
|
||||
format (format_),
|
||||
medium (medium_)
|
||||
{
|
||||
}
|
||||
|
||||
~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)
|
||||
{
|
||||
const SIZE_T 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)
|
||||
{
|
||||
LPDROPFILES pDropFiles = (LPDROPFILES) GlobalLock (hDrop);
|
||||
pDropFiles->pFiles = sizeof (DROPFILES);
|
||||
pDropFiles->fWide = true;
|
||||
|
||||
WCHAR* fname = reinterpret_cast<WCHAR*> (addBytesToPointer (pDropFiles, sizeof (DROPFILES)));
|
||||
|
||||
for (int i = 0; i < fileNames.size(); ++i)
|
||||
{
|
||||
const size_t 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)
|
||||
{
|
||||
JuceDropSource* const source = new JuceDropSource();
|
||||
JuceDataObject* const data = new JuceDataObject (source, format, medium);
|
||||
|
||||
DWORD effect;
|
||||
const HRESULT res = DoDragDrop (data, source, whatToDo, &effect);
|
||||
|
||||
data->Release();
|
||||
source->Release();
|
||||
|
||||
return res == DRAGDROP_S_DROP;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove)
|
||||
{
|
||||
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)
|
||||
{
|
||||
FORMATETC format = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
||||
STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };
|
||||
|
||||
const size_t 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace FileChooserHelpers
|
||||
{
|
||||
struct FileChooserCallbackInfo
|
||||
{
|
||||
String initialPath;
|
||||
String returnedString; // need this to get non-existent pathnames from the directory chooser
|
||||
ScopedPointer<Component> customComponent;
|
||||
};
|
||||
|
||||
static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
|
||||
{
|
||||
FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) lpData;
|
||||
|
||||
if (msg == BFFM_INITIALIZED)
|
||||
SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) info->initialPath.toWideCharPointer());
|
||||
else if (msg == BFFM_VALIDATEFAILEDW)
|
||||
info->returnedString = (LPCWSTR) lParam;
|
||||
else if (msg == BFFM_VALIDATEFAILEDA)
|
||||
info->returnedString = (const char*) lParam;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UINT_PTR CALLBACK openCallback (HWND hdlg, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
|
||||
{
|
||||
if (uiMsg == WM_INITDIALOG)
|
||||
{
|
||||
Component* customComp = ((FileChooserCallbackInfo*) (((OPENFILENAMEW*) lParam)->lCustData))->customComponent;
|
||||
|
||||
HWND dialogH = GetParent (hdlg);
|
||||
jassert (dialogH != 0);
|
||||
if (dialogH == 0)
|
||||
dialogH = hdlg;
|
||||
|
||||
RECT r, cr;
|
||||
GetWindowRect (dialogH, &r);
|
||||
GetClientRect (dialogH, &cr);
|
||||
|
||||
SetWindowPos (dialogH, 0,
|
||||
r.left, r.top,
|
||||
customComp->getWidth() + jmax (150, (int) (r.right - r.left)),
|
||||
jmax (150, (int) (r.bottom - r.top)),
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
|
||||
|
||||
customComp->setBounds (cr.right, cr.top, customComp->getWidth(), cr.bottom - cr.top);
|
||||
customComp->addToDesktop (0, dialogH);
|
||||
}
|
||||
else if (uiMsg == WM_NOTIFY)
|
||||
{
|
||||
LPOFNOTIFY ofn = (LPOFNOTIFY) lParam;
|
||||
|
||||
if (ofn->hdr.code == CDN_SELCHANGE)
|
||||
{
|
||||
FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) ofn->lpOFN->lCustData;
|
||||
|
||||
if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (info->customComponent->getChildComponent(0)))
|
||||
{
|
||||
WCHAR path [MAX_PATH * 2] = { 0 };
|
||||
CommDlg_OpenSave_GetFilePath (GetParent (hdlg), (LPARAM) &path, MAX_PATH);
|
||||
|
||||
comp->selectedFileChanged (File (path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileChooser::showPlatformDialog (Array<File>& results, const String& title_, const File& currentFileOrDirectory,
|
||||
const String& filter, bool selectsDirectory, bool /*selectsFiles*/,
|
||||
bool isSaveDialogue, bool warnAboutOverwritingExistingFiles,
|
||||
bool selectMultipleFiles, FilePreviewComponent* extraInfoComponent)
|
||||
{
|
||||
using namespace FileChooserHelpers;
|
||||
|
||||
const String title (title_);
|
||||
String defaultExtension; // scope of these strings must extend beyond dialog's lifetime.
|
||||
|
||||
HeapBlock<WCHAR> files;
|
||||
const size_t charsAvailableForResult = 32768;
|
||||
files.calloc (charsAvailableForResult + 1);
|
||||
int filenameOffset = 0;
|
||||
|
||||
FileChooserCallbackInfo info;
|
||||
|
||||
// use a modal window as the parent for this dialog box
|
||||
// to block input from other app windows
|
||||
Component parentWindow (String::empty);
|
||||
const Rectangle<int> mainMon (Desktop::getInstance().getDisplays().getMainDisplay().userArea);
|
||||
parentWindow.setBounds (mainMon.getX() + mainMon.getWidth() / 4,
|
||||
mainMon.getY() + mainMon.getHeight() / 4,
|
||||
0, 0);
|
||||
parentWindow.setOpaque (true);
|
||||
parentWindow.setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
parentWindow.addToDesktop (0);
|
||||
|
||||
if (extraInfoComponent == nullptr)
|
||||
parentWindow.enterModalState();
|
||||
|
||||
if (currentFileOrDirectory.isDirectory())
|
||||
{
|
||||
info.initialPath = currentFileOrDirectory.getFullPathName();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFileOrDirectory.getFileName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR));
|
||||
info.initialPath = currentFileOrDirectory.getParentDirectory().getFullPathName();
|
||||
}
|
||||
|
||||
if (selectsDirectory)
|
||||
{
|
||||
BROWSEINFO bi = { 0 };
|
||||
bi.hwndOwner = (HWND) parentWindow.getWindowHandle();
|
||||
bi.pszDisplayName = files;
|
||||
bi.lpszTitle = title.toWideCharPointer();
|
||||
bi.lParam = (LPARAM) &info;
|
||||
bi.lpfn = browseCallbackProc;
|
||||
#ifdef BIF_USENEWUI
|
||||
bi.ulFlags = BIF_USENEWUI | BIF_VALIDATE;
|
||||
#else
|
||||
bi.ulFlags = 0x50;
|
||||
#endif
|
||||
|
||||
LPITEMIDLIST list = SHBrowseForFolder (&bi);
|
||||
|
||||
if (! SHGetPathFromIDListW (list, files))
|
||||
{
|
||||
files[0] = 0;
|
||||
info.returnedString.clear();
|
||||
}
|
||||
|
||||
LPMALLOC al;
|
||||
if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
|
||||
al->Free (list);
|
||||
|
||||
if (info.returnedString.isNotEmpty())
|
||||
{
|
||||
results.add (File (String (files)).getSiblingFile (info.returnedString));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;
|
||||
|
||||
if (warnAboutOverwritingExistingFiles)
|
||||
flags |= OFN_OVERWRITEPROMPT;
|
||||
|
||||
if (selectMultipleFiles)
|
||||
flags |= OFN_ALLOWMULTISELECT;
|
||||
|
||||
if (extraInfoComponent != nullptr)
|
||||
{
|
||||
flags |= OFN_ENABLEHOOK;
|
||||
|
||||
info.customComponent = new CustomComponentHolder (extraInfoComponent);
|
||||
info.customComponent->enterModalState();
|
||||
}
|
||||
|
||||
const size_t filterSpaceNumChars = 2048;
|
||||
HeapBlock<WCHAR> filters;
|
||||
filters.calloc (filterSpaceNumChars);
|
||||
const size_t bytesWritten = filter.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
|
||||
filter.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
|
||||
((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));
|
||||
|
||||
OPENFILENAMEW of = { 0 };
|
||||
String localPath (info.initialPath);
|
||||
|
||||
#ifdef OPENFILENAME_SIZE_VERSION_400W
|
||||
of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
|
||||
#else
|
||||
of.lStructSize = sizeof (of);
|
||||
#endif
|
||||
of.hwndOwner = (HWND) parentWindow.getWindowHandle();
|
||||
of.lpstrFilter = filters.getData();
|
||||
of.nFilterIndex = 1;
|
||||
of.lpstrFile = files;
|
||||
of.nMaxFile = (DWORD) charsAvailableForResult;
|
||||
of.lpstrInitialDir = localPath.toWideCharPointer();
|
||||
of.lpstrTitle = title.toWideCharPointer();
|
||||
of.Flags = flags;
|
||||
of.lCustData = (LPARAM) &info;
|
||||
|
||||
if (extraInfoComponent != nullptr)
|
||||
of.lpfnHook = &openCallback;
|
||||
|
||||
if (isSaveDialogue)
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (filter, ";,", "\"'");
|
||||
tokens.trim();
|
||||
tokens.removeEmptyStrings();
|
||||
|
||||
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
|
||||
{
|
||||
defaultExtension = tokens[0].fromFirstOccurrenceOf (".", false, false);
|
||||
of.lpstrDefExt = defaultExtension.toWideCharPointer();
|
||||
}
|
||||
|
||||
if (! GetSaveFileName (&of))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! GetOpenFileName (&of))
|
||||
return;
|
||||
}
|
||||
|
||||
filenameOffset = of.nFileOffset;
|
||||
}
|
||||
|
||||
if (selectMultipleFiles && filenameOffset > 0 && files [filenameOffset - 1] == 0)
|
||||
{
|
||||
const WCHAR* filename = files + filenameOffset;
|
||||
|
||||
while (*filename != 0)
|
||||
{
|
||||
results.add (File (String (files) + "\\" + String (filename)));
|
||||
filename += wcslen (filename) + 1;
|
||||
}
|
||||
}
|
||||
else if (files[0] != 0)
|
||||
{
|
||||
results.add (File (String (files)));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue