1
0
Fork 0
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:
Felix Faire 2014-10-29 15:55:23 +00:00
parent fefcf7aca6
commit ff6520a89a
1141 changed files with 438491 additions and 94 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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
}

View file

@ -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);
}

View file

@ -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)));
}
}