text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

void LookAndFeel::playAlertSound()
{
    NSBeep();
}

//==============================================================================
class OSXMessageBox  : private AsyncUpdater
{
public:
    OSXMessageBox (AlertWindow::AlertIconType type, const String& t, const String& m,
                   const char* b1, const char* b2, const char* b3,
                   ModalComponentManager::Callback* c, const bool runAsync)
        : iconType (type), title (t), message (m), callback (c),
          button1 (b1), button2 (b2), button3 (b3)
    {
        if (runAsync)
            triggerAsyncUpdate();
    }

    int getResult() const
    {
        switch (getRawResult())
        {
            case NSAlertFirstButtonReturn:  return 1;
            case NSAlertThirdButtonReturn:  return 2;
            default:                        return 0;
        }
    }

    static int show (AlertWindow::AlertIconType iconType, const String& title, const String& message,
                     ModalComponentManager::Callback* callback, const char* b1, const char* b2, const char* b3,
                     bool runAsync)
    {
        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
    {
        auto result = getResult();

        if (callback != nullptr)
            callback->modalStateFinished (result);

        delete this;
    }

    NSInteger getRawResult() const
    {
        NSAlert* alert = [[[NSAlert alloc] init] autorelease];

        [alert setMessageText:     juceStringToNS (title)];
        [alert setInformativeText: juceStringToNS (message)];

        [alert setAlertStyle: iconType == AlertWindow::WarningIcon ? NSAlertStyleCritical
                                                                   : NSAlertStyleInformational];
        addButton (alert, button1);
        addButton (alert, button2);
        addButton (alert, button3);

        return [alert runModal];
    }

    static void addButton (NSAlert* alert, const char* button)
    {
        if (button != nullptr)
            [alert addButtonWithTitle: juceStringToNS (TRANS (button))];
    }
};

#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType,
                                                     const String& title, const String& message,
                                                     Component* /*associatedComponent*/)
{
    OSXMessageBox::show (iconType, title, message, nullptr, "OK", nullptr, nullptr, false);
}
#endif

void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
                                                          const String& title, const String& message,
                                                          Component* /*associatedComponent*/,
                                                          ModalComponentManager::Callback* callback)
{
    OSXMessageBox::show (iconType, title, message, callback, "OK", nullptr, nullptr, true);
}

bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
                                                      const String& title, const String& message,
                                                      Component* /*associatedComponent*/,
                                                      ModalComponentManager::Callback* callback)
{
    return OSXMessageBox::show (iconType, title, message, callback,
                                "OK", "Cancel", nullptr, callback != nullptr) == 1;
}

int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
                                                        const String& title, const String& message,
                                                        Component* /*associatedComponent*/,
                                                        ModalComponentManager::Callback* callback)
{
    return OSXMessageBox::show (iconType, title, message, callback,
                                "Yes", "Cancel", "No", callback != nullptr);
}

int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType iconType,
                                                  const String& title, const String& message,
                                                  Component* /*associatedComponent*/,
                                                  ModalComponentManager::Callback* callback)
{
    return OSXMessageBox::show (iconType, title, message, callback,
                                "Yes", "No", nullptr, callback != nullptr);
}

//==============================================================================
static NSRect getDragRect (NSView* view, NSEvent* event)
{
    auto eventPos = [event locationInWindow];

    return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
                    fromView: nil];
}

static NSView* getNSViewForDragEvent (Component* sourceComp)
{
    if (sourceComp == nullptr)
        if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource(0))
            sourceComp = draggingSource->getComponentUnderMouse();

    if (sourceComp != nullptr)
        return (NSView*) sourceComp->getWindowHandle();

    jassertfalse;  // This method must be called in response to a component's mouseDown or mouseDrag event!
    return nil;
}

struct TextDragDataProviderClass   : public ObjCClass<NSObject>
{
    TextDragDataProviderClass()  : ObjCClass<NSObject> ("JUCE_NSTextDragDataProvider_")
    {
        addIvar<String*> ("text");
        addMethod (@selector (dealloc), dealloc, "v@:");
        addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
        addProtocol (@protocol (NSPasteboardItemDataProvider));
        registerClass();
    }

    static void setText (id self, const String& text)
    {
        object_setInstanceVariable (self, "text", new String (text));
    }

private:
    static void dealloc (id self, SEL)
    {
        delete getIvar<String*> (self, "text");
        sendSuperclassMessage (self, @selector (dealloc));
    }

    static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
    {
        if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
            if (auto* text = getIvar<String*> (self, "text"))
                [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
                        forType: NSPasteboardTypeString];
    }
};

bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent)
{
    if (text.isEmpty())
        return false;

    if (auto* view = getNSViewForDragEvent (sourceComponent))
    {
        JUCE_AUTORELEASEPOOL
        {
            if (auto* event = [[view window] currentEvent])
            {
                static TextDragDataProviderClass dataProviderClass;
                id delegate = [dataProviderClass.createInstance() init];
                TextDragDataProviderClass::setText (delegate, text);

                auto* pasteboardItem = [[NSPasteboardItem new] autorelease];
                [pasteboardItem setDataProvider: delegate
                                       forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];

                auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];

                NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
                [dragItem setDraggingFrame: getDragRect (view, event) contents: image];

                auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
                                                                      event: event
                                                                     source: delegate];

                draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
                draggingSession.draggingFormation = NSDraggingFormationNone;
                return true;
            }
        }
    }

    return false;
}

struct NSDraggingSourceHelper   : public ObjCClass<NSObject<NSDraggingSource>>
{
    NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
    {
        addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
        registerClass();
    }

    static NSDragOperation sourceOperationMaskForDraggingContext (id, SEL, NSDraggingSession*, NSDraggingContext)
    {
        return NSDragOperationCopy;
    }
};

static NSDraggingSourceHelper draggingSourceHelper;

bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/,
                                                           Component* sourceComponent)
{
    if (files.isEmpty())
        return false;

    if (auto* view = getNSViewForDragEvent (sourceComponent))
    {
        JUCE_AUTORELEASEPOOL
        {
            if (auto* event = [[view window] currentEvent])
            {
                auto* dragItems = [[[NSMutableArray alloc] init] autorelease];

                for (auto& filename : files)
                {
                    auto* nsFilename = juceStringToNS (filename);
                    auto* fileURL = [NSURL fileURLWithPath: nsFilename];
                    auto* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];

                    auto eventPos = [event locationInWindow];
                    auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
                                             fromView: nil];
                    auto *dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
                    [dragItem setDraggingFrame: dragRect
                                      contents: dragImage];

                    [dragItems addObject: dragItem];
                    [dragItem release];
                }

                auto* helper = [draggingSourceHelper.createInstance() autorelease];

                return [view beginDraggingSessionWithItems: dragItems
                                                     event: event
                                                    source: helper];
            }
        }
    }

    return false;
}

//==============================================================================
bool Desktop::canUseSemiTransparentWindows() noexcept
{
    return true;
}

Point<float> MouseInputSource::getCurrentRawMousePosition()
{
    JUCE_AUTORELEASEPOOL
    {
        auto p = [NSEvent mouseLocation];
        return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
    }
}

void MouseInputSource::setRawMousePosition (Point<float> newPosition)
{
    // this rubbish needs to be done around the warp call, to avoid causing a
    // bizarre glitch..
    CGAssociateMouseAndMouseCursorPosition (false);
    CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
    CGAssociateMouseAndMouseCursorPosition (true);
}

double Desktop::getDefaultMasterScale()
{
    return 1.0;
}

Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
{
    return upright;
}

//==============================================================================
#if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)
 #define JUCE_USE_IOPM_SCREENSAVER_DEFEAT 1
#endif

#if ! (defined (JUCE_USE_IOPM_SCREENSAVER_DEFEAT) || defined (__POWER__))
 extern "C"  { extern OSErr UpdateSystemActivity (UInt8); } // Some versions of the SDK omit this function..
#endif

class ScreenSaverDefeater   : public Timer
{
public:
   #if JUCE_USE_IOPM_SCREENSAVER_DEFEAT
    ScreenSaverDefeater()
    {
        startTimer (5000);
        timerCallback();
    }

    void timerCallback() override
    {
        if (Process::isForegroundProcess())
        {
            if (assertion == nullptr)
                assertion = new PMAssertion();
        }
        else
        {
            assertion = nullptr;
        }
    }

    struct PMAssertion
    {
        PMAssertion()  : assertionID (kIOPMNullAssertionID)
        {
            IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
                                                        kIOPMAssertionLevelOn,
                                                        CFSTR ("JUCE Playback"),
                                                        &assertionID);
            jassert (res == kIOReturnSuccess); ignoreUnused (res);
        }

        ~PMAssertion()
        {
            if (assertionID != kIOPMNullAssertionID)
                IOPMAssertionRelease (assertionID);
        }

        IOPMAssertionID assertionID;
    };

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

//==============================================================================
struct DisplaySettingsChangeCallback  : private DeletedAtShutdown
{
    DisplaySettingsChangeCallback()
    {
        CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, 0);
    }

    ~DisplaySettingsChangeCallback()
    {
        CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, 0);
        clearSingletonInstance();
    }

    static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*)
    {
        const_cast<Desktop::Displays&> (Desktop::getInstance().getDisplays()).refresh();
    }

    juce_DeclareSingleton_SingleThreaded_Minimal (DisplaySettingsChangeCallback)

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

//==============================================================================
static void selectImageForDrawing (const Image& image)
{
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
                                                                                flipped: false]];
}

static void releaseImageAfterDrawing()
{
    [[NSGraphicsContext currentContext] flushGraphics];
    [NSGraphicsContext restoreGraphicsState];
}

Image juce_createIconForFile (const File& file)
{
    JUCE_AUTORELEASEPOOL
    {
        NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];

        Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);

        selectImageForDrawing (result);
        [image drawAtPoint: NSMakePoint (0, 0)
                  fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
                 operation: NSCompositingOperationSourceOver fraction: 1.0f];
        releaseImageAfterDrawing();

        return result;
    }
}

static Image createNSWindowSnapshot (NSWindow* nsWindow)
{
    JUCE_AUTORELEASEPOOL
    {
        CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
                                                         kCGWindowListOptionIncludingWindow,
                                                         (CGWindowID) [nsWindow windowNumber],
                                                         kCGWindowImageBoundsIgnoreFraming);

        NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];

        Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);

        selectImageForDrawing (result);
        [bitmapRep drawAtPoint: NSMakePoint (0, 0)];
        releaseImageAfterDrawing();

        [bitmapRep release];
        CGImageRelease (screenShot);

        return result;
    }
}

Image createSnapshotOfNativeWindow (void*);
Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
{
    if (id windowOrView = (id) nativeWindowHandle)
    {
        if ([windowOrView isKindOfClass: [NSWindow class]])
            return createNSWindowSnapshot ((NSWindow*) windowOrView);

        if ([windowOrView isKindOfClass: [NSView class]])
            return createNSWindowSnapshot ([(NSView*) windowOrView window]);
    }

    return {};
}

//==============================================================================
void SystemClipboard::copyTextToClipboard (const String& text)
{
    NSPasteboard* pb = [NSPasteboard generalPasteboard];

    [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType]
               owner: nil];

    [pb setString: juceStringToNS (text)
          forType: NSStringPboardType];
}

String SystemClipboard::getTextFromClipboard()
{
    return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]);
}

void Process::setDockIconVisible (bool isVisible)
{
    ProcessSerialNumber psn { 0, kCurrentProcess };

    OSStatus err = TransformProcessType (&psn, isVisible ? kProcessTransformToForegroundApplication
                                                         : kProcessTransformToUIElementApplication);
    jassert (err == 0);
    ignoreUnused (err);
}

bool Desktop::isOSXDarkModeActive()
{
    return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
                isEqualToString: nsStringLiteral ("Dark")];
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

extern juce::JUCEApplicationBase* juce_CreateApplication(); // (from START_JUCE_APPLICATION)

namespace juce
{

//==============================================================================
#if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra
 // Returns true if the intent was handled.
 extern bool juce_handleNotificationIntent (void*);
 extern void juce_firebaseDeviceNotificationsTokenRefreshed (void*);
 extern void juce_firebaseRemoteNotificationReceived (void*);
 extern void juce_firebaseRemoteMessagesDeleted();
 extern void juce_firebaseRemoteMessageSent(void*);
 extern void juce_firebaseRemoteMessageSendError (void*, void*);
#endif

#if JUCE_IN_APP_PURCHASES && JUCE_MODULE_AVAILABLE_juce_product_unlocking
 extern void juce_inAppPurchaseCompleted (void*);
#endif

//==============================================================================
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity,
                                                                      jstring appFile, jstring appDataDir))
{
    setEnv (env);

    android.initialise (env, activity, appFile, appDataDir);

    DBG (SystemStats::getJUCEVersion());

    JUCEApplicationBase::createInstance = &juce_CreateApplication;

    initialiseJuce_GUI();

    if (JUCEApplicationBase* app = JUCEApplicationBase::createInstance())
    {
        if (! app->initialiseApp())
            exit (app->shutdownApp());
    }
    else
    {
        jassertfalse; // you must supply an application object for an android app!
    }

    jassert (MessageManager::getInstance()->isThisTheMessageThread());
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, suspendApp, void, (JNIEnv* env, jobject))
{
    setEnv (env);

    if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
        app->suspended();
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject))
{
    setEnv (env);

    if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
        app->resumed();
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject))
{
    setEnv (env);

    JUCEApplicationBase::appWillTerminateByForce();

    android.shutdown (env);
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appActivityResult, void, (JNIEnv* env, jobject, jint requestCode, jint /*resultCode*/, jobject intentData))
{
    setEnv (env);

   #if JUCE_IN_APP_PURCHASES && JUCE_MODULE_AVAILABLE_juce_product_unlocking
    if (requestCode == 1001)
        juce_inAppPurchaseCompleted (intentData);
   #else
    ignoreUnused (intentData, requestCode);
   #endif
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appNewIntent, void, (JNIEnv* env, jobject, jobject intentData))
{
    setEnv (env);

  #if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra
    if (juce_handleNotificationIntent ((void *)intentData))
        return;

    // Add other functions processing intents here as needed.
  #else
    ignoreUnused (intentData);
  #endif
}

#if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
JUCE_JNI_CALLBACK (JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME, firebaseInstanceIdTokenRefreshed, void, (JNIEnv* env, jobject /*activity*/, jstring token))
{
    setEnv (env);

  #if JUCE_MODULE_AVAILABLE_juce_gui_extra
    juce_firebaseDeviceNotificationsTokenRefreshed (token);
  #else
    ignoreUnused (token);
  #endif
}

JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessageReceived, void, (JNIEnv* env, jobject /*activity*/, jobject remoteMessage))
{
    setEnv (env);

  #if JUCE_MODULE_AVAILABLE_juce_gui_extra
    juce_firebaseRemoteNotificationReceived (remoteMessage);
  #else
    ignoreUnused (remoteMessage);
  #endif
}

JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessagesDeleted, void, (JNIEnv* env, jobject /*activity*/))
{
    setEnv (env);

  #if JUCE_MODULE_AVAILABLE_juce_gui_extra
    juce_firebaseRemoteMessagesDeleted();
  #endif
}

JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessageSent, void, (JNIEnv* env, jobject /*activity*/, jstring messageId))
{
    setEnv (env);

  #if JUCE_MODULE_AVAILABLE_juce_gui_extra
    juce_firebaseRemoteMessageSent (messageId);
  #else
    ignoreUnused (messageId);
  #endif
}

JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessageSendError, void, (JNIEnv* env, jobject /*activity*/, jstring messageId, jstring error))
{
    setEnv (env);

  #if JUCE_MODULE_AVAILABLE_juce_gui_extra
    juce_firebaseRemoteMessageSendError (messageId, error);
  #else
    ignoreUnused (messageId, error);
  #endif
}
#endif

//==============================================================================
#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 (setSystemUiVisibility, "setSystemUiVisibilityCompat", "(I)V") \

DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView");
#undef JNI_CLASS_MEMBERS

//==============================================================================
class AndroidComponentPeer  : public ComponentPeer,
                              private Timer
{
public:
    AndroidComponentPeer (Component& comp, const int windowStyleFlags)
        : ComponentPeer (comp, windowStyleFlags),
          fullScreen (false),
          navBarsHidden (false),
          sizeAllocated (0),
          scale ((float) Desktop::getInstance().getDisplays().getMainDisplay().scale)
    {
        // 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,
                                                             (jboolean) component.isOpaque(),
                                                             (jlong) this));

        if (isFocused())
            handleFocusGain();
    }

    ~AndroidComponentPeer()
    {
        if (MessageManager::getInstance()->isThisTheMessageThread())
        {
            frontWindow = nullptr;
            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);
                }

                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>& userRect, bool isNowFullScreen) override
    {
        Rectangle<int> r = (userRect.toFloat() * scale).toNearestInt();

        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>& boundsToUse)  : view (v), bounds (boundsToUse) {}

                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<float> (view.callIntMethod (ComponentPeerView.getLeft),
                                  view.callIntMethod (ComponentPeerView.getTop),
                                  view.callIntMethod (ComponentPeerView.getWidth),
                                  view.callIntMethod (ComponentPeerView.getHeight)) / scale).toNearestInt();
    }

    void handleScreenSizeChange() override
    {
        ComponentPeer::handleScreenSizeChange();

        if (isFullScreen())
            setFullScreen (true);
    }

    Point<float> getScreenPosition() const
    {
        return Point<float> (view.callIntMethod (ComponentPeerView.getLeft),
                             view.callIntMethod (ComponentPeerView.getTop)) / scale;
    }

    Point<float> localToGlobal (Point<float> relativePosition) override
    {
        return relativePosition + getScreenPosition();
    }

    Point<float> globalToLocal (Point<float> screenPosition) override
    {
        return screenPosition - getScreenPosition();
    }

    void setMinimised (bool /*shouldBeMinimised*/) override
    {
        // n/a
    }

    bool isMinimised() const override
    {
        return false;
    }

    bool shouldNavBarsBeHidden (bool shouldBeFullScreen) const
    {
        if (shouldBeFullScreen)
            if (Component* kiosk = Desktop::getInstance().getKioskModeComponent())
                if (kiosk->getPeer() == this)
                    return true;

        return false;
    }

    void setNavBarsHidden (bool hidden)
    {
        enum
        {
            SYSTEM_UI_FLAG_VISIBLE                  = 0,
            SYSTEM_UI_FLAG_LOW_PROFILE              = 1,
            SYSTEM_UI_FLAG_HIDE_NAVIGATION          = 2,
            SYSTEM_UI_FLAG_FULLSCREEN               = 4,
            SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION   = 512,
            SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN        = 1024,
            SYSTEM_UI_FLAG_IMMERSIVE                = 2048,
            SYSTEM_UI_FLAG_IMMERSIVE_STICKY         = 4096
        };

        view.callVoidMethod (ComponentPeerView.setSystemUiVisibility,
                             hidden ? (jint) (SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
                                    : (jint) (SYSTEM_UI_FLAG_VISIBLE));

        navBarsHidden = hidden;
    }

    void setFullScreen (bool shouldBeFullScreen) override
    {
        // updating the nav bar visibility is a bit odd on Android - need to wait for
        if (shouldNavBarsBeHidden (shouldBeFullScreen))
        {
            if (! navBarsHidden && ! isTimerRunning())
            {
                startTimer (500);
            }
        }
        else
        {
            setNavBarsHidden (false);
        }

        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 timerCallback() override
    {
        setNavBarsHidden (shouldNavBarsBeHidden (fullScreen));
        setFullScreen (fullScreen);
        stopTimer();
    }

    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 * scale,
                                                                    localPos.y * scale));
    }

    BorderSize<int> getFrameSize() const override
    {
        // TODO
        return BorderSize<int>();
    }

    bool setAlwaysOnTop (bool /*alwaysOnTop*/) override
    {
        // TODO
        return false;
    }

    void toFront (bool makeActive) override
    {
        // Avoid calling bringToFront excessively: it's very slow
        if (frontWindow != this)
        {
            view.callVoidMethod (ComponentPeerView.bringToFront);

            frontWindow = this;
        }

        if (makeActive)
            grabFocus();

        handleBroughtToFront();
    }

    void toBehind (ComponentPeer*) override
    {
        // TODO
    }

    //==============================================================================
    void handleMouseDownCallback (int index, Point<float> sysPos, int64 time)
    {
        Point<float> pos = sysPos / scale;
        lastMousePos = localToGlobal (pos);

        // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
        handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, currentModifiers.withoutMouseButtons(),
                          MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, index);

        if (isValidPeer (this))
            handleMouseDragCallback (index, sysPos, time);
    }

    void handleMouseDragCallback (int index, Point<float> pos, int64 time)
    {
        pos /= scale;
        lastMousePos = localToGlobal (pos);

        jassert (index < 64);
        touchesDown = (touchesDown | (1 << (index & 63)));
        currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
        handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier),
                          MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, index);
    }

    void handleMouseUpCallback (int index, Point<float> pos, int64 time)
    {
        pos /= scale;
        lastMousePos = localToGlobal (pos);

        jassert (index < 64);
        touchesDown = (touchesDown & ~(1 << (index & 63)));

        if (touchesDown == 0)
            currentModifiers = currentModifiers.withoutMouseButtons();

        handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, currentModifiers.withoutMouseButtons(), MouseInputSource::invalidPressure,
                          MouseInputSource::invalidOrientation, time, {}, index);
    }

    void handleKeyDownCallback (int k, int kc)
    {
        handleKeyPress (k, static_cast<juce_wchar> (kc));
    }

    void handleKeyUpCallback (int /*k*/, int /*kc*/)
    {
    }

    void handleBackButtonCallback()
    {
        if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
            app->backButtonPressed();
    }

    //==============================================================================
    bool isFocused() const override
    {
        if (view != nullptr)
            return view.callBooleanMethod (ComponentPeerView.hasFocus);

        return false;
    }

    void grabFocus() override
    {
        if (view != nullptr)
            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::decimalKeyboard:       return "numberDecimal";
            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 paint)
    {
        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));
        }
        else if (sizeNeeded == 0)
        {
            return;
        }

        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());
                    g.addTransform (AffineTransform::scale (scale));
                    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, paint);
        }
    }

    void repaint (const Rectangle<int>& userArea) override
    {
        Rectangle<int> area = userArea * scale;

        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 fullScreen;
    bool navBarsHidden;
    int sizeAllocated;
    float scale;
    static AndroidComponentPeer* frontWindow;

    struct PreallocatedImage  : public ImagePixelData
    {
        PreallocatedImage (const int width_, const int height_, jint* data_, bool hasAlpha_)
            : ImagePixelData (Image::ARGB, width_, height_), data (data_), hasAlpha (hasAlpha_)
        {
            if (hasAlpha_)
                zeromem (data_, static_cast<size_t> (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*/) override
        {
            bm.lineStride = width * static_cast<int> (sizeof (jint));
            bm.pixelStride = static_cast<int> (sizeof (jint));
            bm.pixelFormat = Image::ARGB;
            bm.data = (uint8*) (data + x + y * width);
        }

        ImagePixelData::Ptr clone() override
        {
            PreallocatedImage* s = new PreallocatedImage (width, height, 0, hasAlpha);
            s->allocatedData.malloc (sizeof (jint) * static_cast<size_t> (width * height));
            s->data = s->allocatedData;
            memcpy (s->data, data, sizeof (jint) * static_cast<size_t> (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;
AndroidComponentPeer* AndroidComponentPeer::frontWindow = nullptr;

//==============================================================================
#define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \
  JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \
  { \
      setEnv (env); \
      if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \
          peer->juceMethodInvocation; \
  }

JUCE_VIEW_CALLBACK (void, handlePaint,      (JNIEnv* env, jobject /*view*/, jlong host, jobject canvas, jobject paint),                          handlePaintCallback (env, canvas, paint))
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))
JUCE_VIEW_CALLBACK (void, handleBackButton, (JNIEnv* env, jobject /*view*/, jlong host),                                          handleBackButtonCallback())

//==============================================================================
ComponentPeer* Component::createNewPeer (int styleFlags, void*)
{
    return new AndroidComponentPeer (*this, styleFlags);
}

//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (getRotation, "getRotation", "()I")

DECLARE_JNI_CLASS (Display, "android/view/Display");
#undef JNI_CLASS_MEMBERS

#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (getDefaultDisplay, "getDefaultDisplay", "()Landroid/view/Display;")

DECLARE_JNI_CLASS (WindowManager, "android/view/WindowManager");
#undef JNI_CLASS_MEMBERS

bool Desktop::canUseSemiTransparentWindows() noexcept
{
    return true;
}

double Desktop::getDefaultMasterScale()
{
    return 1.0;
}

Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
{
    enum
    {
        ROTATION_0   = 0,
        ROTATION_90  = 1,
        ROTATION_180 = 2,
        ROTATION_270 = 3
    };

    JNIEnv* env = getEnv();
    LocalRef<jstring> windowServiceString (javaString ("window"));
    LocalRef<jobject> windowManager = LocalRef<jobject> (env->CallObjectMethod (android.activity, JuceAppActivity.getSystemService, windowServiceString.get()));

    if (windowManager.get() != 0)
    {
        LocalRef<jobject> display = LocalRef<jobject> (env->CallObjectMethod (windowManager, WindowManager.getDefaultDisplay));

        if (display.get() != 0)
        {
            int rotation = env->CallIntMethod (display, Display.getRotation);

            switch (rotation)
            {
                case ROTATION_0:   return upright;
                case ROTATION_90:  return rotatedAntiClockwise;
                case ROTATION_180: return upsideDown;
                case ROTATION_270: return rotatedClockwise;
            }
        }
    }

    jassertfalse;
    return upright;
}

bool MouseInputSource::SourceList::addSource()
{
    addSource (sources.size(), MouseInputSource::InputSourceType::touch);
    return true;
}

bool MouseInputSource::SourceList::canUseTouch()
{
    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;
}

JUCE_API void JUCE_CALLTYPE Process::hide()
{
    if (android.activity.callBooleanMethod (JuceAppActivity.moveTaskToBack, true) == 0)
    {
        auto* env = getEnv();

        GlobalRef intent (env->NewObject (Intent, Intent.constructor));
        env->CallObjectMethod (intent, Intent.setAction,   javaString ("android.intent.action.MAIN")  .get());
        env->CallObjectMethod (intent, Intent.addCategory, javaString ("android.intent.category.HOME").get());

        android.activity.callVoidMethod (JuceAppActivity.startActivity, intent.get());
    }
}

//==============================================================================
// TODO
JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return true; }
JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() {}

//==============================================================================
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,
                                     javaString (TRANS ("OK")).get(), javaString (TRANS ("Cancel")).get());
    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;
}

int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (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,
                                     javaString (TRANS ("Yes")).get(), javaString (TRANS ("No")).get());
    return 0;
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject /*activity*/,
                                                                           jlong callbackAsLong, jint result))
{
    setEnv (env);

    if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong)
    {
        callback->modalStateFinished (result);
        delete callback;
    }
}

//==============================================================================
void Desktop::setScreenSaverEnabled (const bool isEnabled)
{
    android.activity.callVoidMethod (JuceAppActivity.setScreenSaver, isEnabled);
}

bool Desktop::isScreenSaverEnabled()
{
    return android.activity.callBooleanMethod (JuceAppActivity.getScreenSaver);
}

//==============================================================================
void Desktop::setKioskComponent (Component* kioskComp, bool enableOrDisable, bool allowMenusAndBars)
{
    ignoreUnused (allowMenusAndBars);

    if (AndroidComponentPeer* peer = dynamic_cast<AndroidComponentPeer*> (kioskComp->getPeer()))
        peer->setFullScreen (enableOrDisable);
    else
        jassertfalse; // (this should have been checked by the caller)
}

//==============================================================================
static jint getAndroidOrientationFlag (int orientations) noexcept
{
    enum
    {
        SCREEN_ORIENTATION_LANDSCAPE          = 0,
        SCREEN_ORIENTATION_PORTRAIT           = 1,
        SCREEN_ORIENTATION_USER               = 2,
        SCREEN_ORIENTATION_REVERSE_LANDSCAPE  = 8,
        SCREEN_ORIENTATION_REVERSE_PORTRAIT   = 9,
        SCREEN_ORIENTATION_USER_LANDSCAPE     = 11,
        SCREEN_ORIENTATION_USER_PORTRAIT      = 12,
    };

    switch (orientations)
    {
        case Desktop::upright:                                          return (jint) SCREEN_ORIENTATION_PORTRAIT;
        case Desktop::upsideDown:                                       return (jint) SCREEN_ORIENTATION_REVERSE_PORTRAIT;
        case Desktop::upright + Desktop::upsideDown:                    return (jint) SCREEN_ORIENTATION_USER_PORTRAIT;
        case Desktop::rotatedAntiClockwise:                             return (jint) SCREEN_ORIENTATION_LANDSCAPE;
        case Desktop::rotatedClockwise:                                 return (jint) SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
        case Desktop::rotatedClockwise + Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_USER_LANDSCAPE;
        default:                                                        return (jint) SCREEN_ORIENTATION_USER;
    }
}

void Desktop::allowedOrientationsChanged()
{
    android.activity.callVoidMethod (JuceAppActivity.setRequestedOrientation,
                                     getAndroidOrientationFlag (allowedOrientations));
}

//==============================================================================
bool juce_areThereAnyAlwaysOnTopWindows()
{
    return false;
}

//==============================================================================
void Desktop::Displays::findDisplays (float masterScale)
{
    Display d;

    d.isMain = true;
    d.dpi = android.dpi;
    d.scale = masterScale * (d.dpi / 150.);
    d.userArea = d.totalArea = Rectangle<int> (android.screenWidth,
                                               android.screenHeight) / d.scale;

    displays.add (d);
}

JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv* env, jobject /*activity*/,
                                                                          jint screenWidth, jint screenHeight,
                                                                          jint dpi))
{
    setEnv (env);

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

//==============================================================================
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*/,
                                                           Component* /*srcComp*/)
{
    return false;
}

bool DragAndDropContainer::performExternalDragDropOfText (const String& /*text*/, Component* /*srcComp*/)
{
    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::F17Key          = extendedKeyModifier + 50;
const int KeyPress::F18Key          = extendedKeyModifier + 51;
const int KeyPress::F19Key          = extendedKeyModifier + 52;
const int KeyPress::F20Key          = extendedKeyModifier + 53;
const int KeyPress::F21Key          = extendedKeyModifier + 54;
const int KeyPress::F22Key          = extendedKeyModifier + 55;
const int KeyPress::F23Key          = extendedKeyModifier + 56;
const int KeyPress::F24Key          = extendedKeyModifier + 57;
const int KeyPress::F25Key          = extendedKeyModifier + 58;
const int KeyPress::F26Key          = extendedKeyModifier + 59;
const int KeyPress::F27Key          = extendedKeyModifier + 60;
const int KeyPress::F28Key          = extendedKeyModifier + 61;
const int KeyPress::F29Key          = extendedKeyModifier + 62;
const int KeyPress::F30Key          = extendedKeyModifier + 63;
const int KeyPress::F31Key          = extendedKeyModifier + 64;
const int KeyPress::F32Key          = extendedKeyModifier + 65;
const int KeyPress::F33Key          = extendedKeyModifier + 66;
const int KeyPress::F34Key          = extendedKeyModifier + 67;
const int KeyPress::F35Key          = extendedKeyModifier + 68;
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;

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
struct JuceMainMenuBarHolder : private DeletedAtShutdown
{
    JuceMainMenuBarHolder()
        : mainMenuBar ([[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")])
    {
        auto* item = [mainMenuBar addItemWithTitle: nsStringLiteral ("Apple")
                                            action: nil
                                     keyEquivalent: nsEmptyString()];

        auto* appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")];

        [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
        [mainMenuBar setSubmenu: appMenu forItem: item];
        [appMenu release];

        [NSApp setMainMenu: mainMenuBar];
    }

    ~JuceMainMenuBarHolder()
    {
        clearSingletonInstance();

        [NSApp setMainMenu: nil];
        [mainMenuBar release];
    }

    NSMenu* mainMenuBar = nil;

    juce_DeclareSingleton_SingleThreaded (JuceMainMenuBarHolder, true)
};

juce_ImplementSingleton_SingleThreaded (JuceMainMenuBarHolder)

//==============================================================================
class JuceMainMenuHandler   : private MenuBarModel::Listener,
                              private DeletedAtShutdown
{
public:
    JuceMainMenuHandler()
    {
        static JuceMenuCallbackClass cls;
        callback = [cls.createInstance() init];
        JuceMenuCallbackClass::setOwner (callback, this);
    }

    ~JuceMainMenuHandler()
    {
        setMenu (nullptr, nullptr, String());

        jassert (instance == this);
        instance = nullptr;

        [callback release];
    }

    void setMenu (MenuBarModel* const newMenuBarModel,
                  const PopupMenu* newExtraAppleMenuItems,
                  const String& recentItemsName)
    {
        recentItemsMenuName = recentItemsName;

        if (currentModel != newMenuBarModel)
        {
            if (currentModel != nullptr)
                currentModel->removeListener (this);

            currentModel = newMenuBarModel;

            if (currentModel != nullptr)
                currentModel->addListener (this);

            menuBarItemsChanged (nullptr);
        }

        extraAppleMenuItems = createCopyIfNotNull (newExtraAppleMenuItems);
    }

    void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex)
    {
        NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
                                             action: nil
                                      keyEquivalent: nsEmptyString()];

        NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true);

        [parent setSubmenu: sub forItem: item];
        [sub setAutoenablesItems: false];
        [sub release];
    }

    void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex)
    {
        // Note: This method used to update the contents of the existing menu in-place, but that caused
        // weird side-effects which messed-up keyboard focus when switching between windows. By creating
        // a new menu and replacing the old one with it, that problem seems to be avoided..
        NSMenu* menu = [[NSMenu alloc] initWithTitle: juceStringToNS (name)];

        for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();)
            addMenuItem (iter, menu, menuId, topLevelIndex);

        [menu setAutoenablesItems: false];
        [menu update];

        removeItemRecursive ([parentItem submenu]);
        [parentItem setSubmenu: menu];

        [menu release];
    }

    void updateTopLevelMenu (NSMenu* menu)
    {
        NSMenu* superMenu = [menu supermenu];
        auto menuNames = currentModel->getMenuBarNames();
        auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1;

        removeItemRecursive (menu);

        auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu, menuNames[indexOfMenu]);

        for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();)
            addMenuItem (iter, menu, 1, indexOfMenu);

        [menu update];
    }

    void menuBarItemsChanged (MenuBarModel*) override
    {
        if (isOpen)
        {
            defferedUpdateRequested = true;
            return;
        }

        lastUpdateTime = Time::getMillisecondCounter();

        StringArray menuNames;
        if (currentModel != nullptr)
            menuNames = currentModel->getMenuBarNames();

        auto* menuBar = getMainMenuBar();

        while ([menuBar numberOfItems] > 1 + menuNames.size())
            removeItemRecursive (menuBar, static_cast<int> ([menuBar numberOfItems] - 1));

        int menuId = 1;

        for (int i = 0; i < menuNames.size(); ++i)
        {
            const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames[i]));

            if (i >= [menuBar numberOfItems] - 1)
                addTopLevelMenu (menuBar, menu, menuNames[i], menuId, i);
            else
                updateTopLevelMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
        }
    }

    void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) override
    {
        if ((info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0
              && info.invocationMethod != ApplicationCommandTarget::InvocationInfo::fromKeyPress)
            if (auto* item = findMenuItemWithCommandID (getMainMenuBar(), info.commandID))
                flashMenuBar ([item menu]);
    }

    void invoke (const PopupMenu::Item& item, int topLevelIndex) const
    {
        if (currentModel != nullptr)
        {
            if (item.customCallback != nullptr)
                if (! item.customCallback->menuItemTriggered())
                    return;

            if (item.commandManager != nullptr)
            {
                ApplicationCommandTarget::InvocationInfo info (item.itemID);
                info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;

                item.commandManager->invoke (info, true);
            }

            MessageManager::callAsync ([=]()
            {
                if (instance != nullptr)
                    instance->invokeDirectly (item.itemID, topLevelIndex);
            });
        }
    }

    void invokeDirectly (int commandId, int topLevelIndex)
    {
        if (currentModel != nullptr)
            currentModel->menuItemSelected (commandId, topLevelIndex);
    }

    void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
                      const int topLevelMenuId, const int topLevelIndex)
    {
        const PopupMenu::Item& i = iter.getItem();
        NSString* text = juceStringToNS (i.text);

        if (text == nil)
            text = nsEmptyString();

        if (i.isSeparator)
        {
            [menuToAddTo addItem: [NSMenuItem separatorItem]];
        }
        else if (i.isSectionHeader)
        {
            NSMenuItem* item = [menuToAddTo addItemWithTitle: text
                                                      action: nil
                                               keyEquivalent: nsEmptyString()];

            [item setEnabled: false];
        }
        else if (i.subMenu != nullptr)
        {
            if (i.text == recentItemsMenuName)
            {
                if (recent == nullptr)
                    recent = new RecentFilesMenuItem();

                if (recent->recentItem != nil)
                {
                    if (NSMenu* parent = [recent->recentItem menu])
                        [parent removeItem: recent->recentItem];

                    [menuToAddTo addItem: recent->recentItem];
                    return;
                }
            }

            NSMenuItem* item = [menuToAddTo addItemWithTitle: text
                                                      action: nil
                                               keyEquivalent: nsEmptyString()];

            [item setTag: i.itemID];
            [item setEnabled: i.isEnabled];

            NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false);
            [menuToAddTo setSubmenu: sub forItem: item];
            [sub release];
        }
        else
        {
            auto* item = [[NSMenuItem alloc] initWithTitle: text
                                                    action: @selector (menuItemInvoked:)
                                             keyEquivalent: nsEmptyString()];

            [item setTag: topLevelIndex];
            [item setEnabled: i.isEnabled];
            [item setState: i.isTicked ? NSOnState : NSOffState];
            [item setTarget: (id) callback];

            auto* juceItem = new PopupMenu::Item (i);
            juceItem->customComponent = nullptr;

            [item setRepresentedObject: [createNSObjectFromJuceClass (juceItem) autorelease]];

            if (i.commandManager != nullptr)
            {
                for (auto& kp : i.commandManager->getKeyMappings()->getKeyPressesAssignedToCommand (i.itemID))
                {
                    if (kp != KeyPress::backspaceKey   // (adding these is annoying because it flashes the menu bar
                         && kp != KeyPress::deleteKey) // every time you press the key while editing text)
                    {
                        juce_wchar key = kp.getTextCharacter();

                        if (key == 0)
                            key = (juce_wchar) kp.getKeyCode();

                        [item setKeyEquivalent: juceStringToNS (String::charToString (key).toLowerCase())];
                        [item setKeyEquivalentModifierMask: juceModsToNSMods (kp.getModifiers())];
                    }

                    break;
                }
            }

            [menuToAddTo addItem: item];
            [item release];
        }
    }

    NSMenu* createMenu (const PopupMenu menu,
                        const String& menuName,
                        const int topLevelMenuId,
                        const int topLevelIndex,
                        const bool addDelegate)
    {
        NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];

        [m setAutoenablesItems: false];

        if (addDelegate)
        {
           #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
            [m setDelegate: (id<NSMenuDelegate>) callback];
           #else
            [m setDelegate: callback];
           #endif
        }

        for (PopupMenu::MenuItemIterator iter (menu); iter.next();)
            addMenuItem (iter, m, topLevelMenuId, topLevelIndex);

        [m update];
        return m;
    }

    static JuceMainMenuHandler* instance;

    MenuBarModel* currentModel = nullptr;
    ScopedPointer<PopupMenu> extraAppleMenuItems;
    uint32 lastUpdateTime = 0;
    NSObject* callback = nil;
    String recentItemsMenuName;
    bool isOpen = false, defferedUpdateRequested = false;

private:
    struct RecentFilesMenuItem
    {
        RecentFilesMenuItem() : recentItem (nil)
        {
            if (NSNib* menuNib = [[[NSNib alloc] initWithNibNamed: @"RecentFilesMenuTemplate" bundle: nil] autorelease])
            {
                NSArray* array = nil;

               #if (! defined (MAC_OS_X_VERSION_10_8)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
                [menuNib instantiateNibWithOwner: NSApp  topLevelObjects: &array];
               #else
                [menuNib instantiateWithOwner: NSApp  topLevelObjects: &array];
               #endif

                for (id object in array)
                {
                    if ([object isKindOfClass: [NSMenu class]])
                    {
                        if (NSArray* items = [object itemArray])
                        {
                            if (NSMenuItem* item = findRecentFilesItem (items))
                            {
                                recentItem = [item retain];
                                break;
                            }
                        }
                    }
                }
            }
        }

        ~RecentFilesMenuItem()
        {
            [recentItem release];
        }

        static NSMenuItem* findRecentFilesItem (NSArray* const items)
        {
            for (id object in items)
                if (NSArray* subMenuItems = [[object submenu] itemArray])
                    for (id subObject in subMenuItems)
                        if ([subObject isKindOfClass: [NSMenuItem class]])
                            return subObject;
            return nil;
        }

        NSMenuItem* recentItem;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecentFilesMenuItem)
    };

    ScopedPointer<RecentFilesMenuItem> recent;

    //==============================================================================
    static NSMenuItem* findMenuItemWithCommandID (NSMenu* const menu, int commandID)
    {
        for (NSInteger i = [menu numberOfItems]; --i >= 0;)
        {
            NSMenuItem* m = [menu itemAtIndex: i];
            if (auto* menuItem = getJuceClassFromNSObject<PopupMenu::Item> ([m representedObject]))
                if (menuItem->itemID == commandID)
                    return m;

            if (NSMenu* sub = [m submenu])
                if (NSMenuItem* found = findMenuItemWithCommandID (sub, commandID))
                    return found;
        }

        return nil;
    }

    static void flashMenuBar (NSMenu* menu)
    {
        if ([[menu title] isEqualToString: nsStringLiteral ("Apple")])
            return;

        [menu retain];

        const unichar f35Key = NSF35FunctionKey;
        NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];

        NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: nsStringLiteral ("x")
                                                      action: nil
                                               keyEquivalent: f35String];
        [item setTarget: nil];
        [menu insertItem: item atIndex: [menu numberOfItems]];
        [item release];

        if ([menu indexOfItem: item] >= 0)
        {
            NSEvent* f35Event = [NSEvent keyEventWithType: NSEventTypeKeyDown
                                                 location: NSZeroPoint
                                            modifierFlags: NSEventModifierFlagCommand
                                                timestamp: 0
                                             windowNumber: 0
                                                  context: [NSGraphicsContext currentContext]
                                               characters: f35String
                              charactersIgnoringModifiers: f35String
                                                isARepeat: NO
                                                  keyCode: 0];

            [menu performKeyEquivalent: f35Event];

            if ([menu indexOfItem: item] >= 0)
                [menu removeItem: item]; // (this throws if the item isn't actually in the menu)
        }

        [menu release];
    }

    static unsigned int juceModsToNSMods (const ModifierKeys mods)
    {
        unsigned int m = 0;
        if (mods.isShiftDown())    m |= NSEventModifierFlagShift;
        if (mods.isCtrlDown())     m |= NSEventModifierFlagControl;
        if (mods.isAltDown())      m |= NSEventModifierFlagOption;
        if (mods.isCommandDown())  m |= NSEventModifierFlagCommand;
        return m;
    }

    // Apple Bug: For some reason [NSMenu removeAllItems] seems to leak it's objects
    // on shutdown, so we need this method to release the items one-by-one manually
    static void removeItemRecursive (NSMenu* parentMenu, int menuItemIndex)
    {
        if (isPositiveAndBelow (menuItemIndex, (int) [parentMenu numberOfItems]))
        {
            auto* menuItem = [parentMenu itemAtIndex:menuItemIndex];

            if (auto* submenu = [menuItem submenu])
                removeItemRecursive (submenu);

            [parentMenu removeItem:menuItem];
        }
        else
            jassertfalse;
    }

    static void removeItemRecursive (NSMenu* menu)
    {
        if (menu != nullptr)
        {
            auto n = static_cast<int> ([menu numberOfItems]);

            for (auto i = n; --i >= 0;)
                removeItemRecursive (menu, i);
        }
    }

    static NSMenu* getMainMenuBar()
    {
        return JuceMainMenuBarHolder::getInstance()->mainMenuBar;
    }

    //==============================================================================
    struct JuceMenuCallbackClass   : public ObjCClass<NSObject>
    {
        JuceMenuCallbackClass()  : ObjCClass<NSObject> ("JUCEMainMenu_")
        {
            addIvar<JuceMainMenuHandler*> ("owner");

            addMethod (@selector (menuItemInvoked:),  menuItemInvoked, "v@:@");
            addMethod (@selector (menuNeedsUpdate:),  menuNeedsUpdate, "v@:@");

           #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
            addProtocol (@protocol (NSMenuDelegate));
           #endif

            registerClass();
        }

        static void setOwner (id self, JuceMainMenuHandler* owner)
        {
            object_setInstanceVariable (self, "owner", owner);
        }

    private:
        static void menuItemInvoked (id self, SEL, NSMenuItem* item)
        {
            auto owner = getIvar<JuceMainMenuHandler*> (self, "owner");

            if (auto* juceItem = getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject]))
            {
                // If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to
                // our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back
                // into the focused component and let it trigger the menu item indirectly.
                NSEvent* e = [NSApp currentEvent];

                if ([e type] == NSEventTypeKeyDown || [e type] == NSEventTypeKeyUp)
                {
                    if (auto* focused = juce::Component::getCurrentlyFocusedComponent())
                    {
                        if (auto peer = dynamic_cast<juce::NSViewComponentPeer*> (focused->getPeer()))
                        {
                            if ([e type] == NSEventTypeKeyDown)
                                peer->redirectKeyDown (e);
                            else
                                peer->redirectKeyUp (e);

                            return;
                        }
                    }
                }

                owner->invoke (*juceItem, static_cast<int> ([item tag]));
            }
        }

        static void menuNeedsUpdate (id self, SEL, NSMenu* menu)
        {
            getIvar<JuceMainMenuHandler*> (self, "owner")->updateTopLevelMenu (menu);
        }
    };
};

JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;

//==============================================================================
class TemporaryMainMenuWithStandardCommands
{
public:
    TemporaryMainMenuWithStandardCommands()
        : oldMenu (MenuBarModel::getMacMainMenu())
    {
        if (auto* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu())
            oldAppleMenu = new PopupMenu (*appleMenu);

        if (auto* handler = JuceMainMenuHandler::instance)
            oldRecentItems = handler->recentItemsMenuName;

        MenuBarModel::setMacMainMenu (nullptr);

        if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
        {
            NSMenu* menu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Edit")];
            NSMenuItem* item;

            item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Cut"), nil)
                                              action: @selector (cut:)  keyEquivalent: nsStringLiteral ("x")];
            [menu addItem: item];
            [item release];

            item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Copy"), nil)
                                              action: @selector (copy:)  keyEquivalent: nsStringLiteral ("c")];
            [menu addItem: item];
            [item release];

            item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Paste"), nil)
                                              action: @selector (paste:)  keyEquivalent: nsStringLiteral ("v")];
            [menu addItem: item];
            [item release];

            item = [mainMenu addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil)
                                       action: nil  keyEquivalent: nsEmptyString()];
            [mainMenu setSubmenu: menu forItem: item];
            [menu release];
        }

        // use a dummy modal component so that apps can tell that something is currently modal.
        dummyModalComponent.enterModalState (false);
    }

    ~TemporaryMainMenuWithStandardCommands()
    {
        MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu, oldRecentItems);
    }

private:
    MenuBarModel* const 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
    struct SilentDummyModalComp  : public Component
    {
        SilentDummyModalComp() {}
        void inputAttemptWhenModal() override {}
    };

    SilentDummyModalComp dummyModalComponent;
};

//==============================================================================
namespace MainMenuHelpers
{
    static NSString* translateMenuName (const String& name)
    {
        return NSLocalizedString (juceStringToNS (TRANS (name)), nil);
    }

    static NSMenuItem* createMenuItem (NSMenu* menu, const String& name, SEL sel, NSString* key)
    {
        NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle: translateMenuName (name)
                                                       action: sel
                                                keyEquivalent: key] autorelease];
        [item setTarget: NSApp];
        [menu addItem: item];
        return item;
    }

    static void createStandardAppMenu (NSMenu* menu, const String& appName, const PopupMenu* extraItems)
    {
        if (extraItems != nullptr && JuceMainMenuHandler::instance != nullptr && extraItems->getNumItems() > 0)
        {
            for (PopupMenu::MenuItemIterator iter (*extraItems); iter.next();)
                JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);

            [menu addItem: [NSMenuItem separatorItem]];
        }

        // Services...
        NSMenuItem* services = [[[NSMenuItem alloc] initWithTitle: translateMenuName ("Services")
                                                           action: nil  keyEquivalent: nsEmptyString()] autorelease];
        [menu addItem: services];

        NSMenu* servicesMenu = [[[NSMenu alloc] initWithTitle: translateMenuName ("Services")] autorelease];
        [menu setSubmenu: servicesMenu forItem: services];
        [NSApp setServicesMenu: servicesMenu];
        [menu addItem: [NSMenuItem separatorItem]];

        createMenuItem (menu, TRANS("Hide") + String (" ") + appName, @selector (hide:), nsStringLiteral ("h"));

        [createMenuItem (menu, TRANS("Hide Others"), @selector (hideOtherApplications:), nsStringLiteral ("h"))
            setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];

        createMenuItem (menu, TRANS("Show All"), @selector (unhideAllApplications:), nsEmptyString());

        [menu addItem: [NSMenuItem separatorItem]];

        createMenuItem (menu, TRANS("Quit") + String (" ") + appName, @selector (terminate:), nsStringLiteral ("q"));
    }

    // Since our app has no NIB, this initialises a standard app menu...
    static void rebuildMainMenu (const PopupMenu* extraItems)
    {
        // this can't be used in a plugin!
        jassert (JUCEApplicationBase::isStandaloneApp());

        if (auto* app = JUCEApplicationBase::getInstance())
        {
            if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
            {
                if ([mainMenu numberOfItems] > 0)
                {
                    if (auto* appMenu = [[mainMenu itemAtIndex:0] submenu])
                    {
                        [appMenu removeAllItems];
                        MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);
                    }
                }
            }
        }
    }
}

void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
                                   const PopupMenu* extraAppleMenuItems,
                                   const String& recentItemsMenuName)
{
    if (getMacMainMenu() != newMenuBarModel)
    {
        JUCE_AUTORELEASEPOOL
        {
            if (newMenuBarModel == nullptr)
            {
                delete JuceMainMenuHandler::instance;
                jassert (JuceMainMenuHandler::instance == nullptr); // should be zeroed in the destructor
                jassert (extraAppleMenuItems == nullptr); // you can't specify some extra items without also supplying a model

                extraAppleMenuItems = nullptr;
            }
            else
            {
                if (JuceMainMenuHandler::instance == nullptr)
                    JuceMainMenuHandler::instance = new JuceMainMenuHandler();

                JuceMainMenuHandler::instance->setMenu (newMenuBarModel, extraAppleMenuItems, recentItemsMenuName);
            }
        }
    }

    MainMenuHelpers::rebuildMainMenu (extraAppleMenuItems);

    if (newMenuBarModel != nullptr)
        newMenuBarModel->menuItemsChanged();
}

MenuBarModel* MenuBarModel::getMacMainMenu()
{
    if (auto* mm = JuceMainMenuHandler::instance)
        return mm->currentModel;

    return nullptr;
}

const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()
{
    if (auto* mm = JuceMainMenuHandler::instance)
        return mm->extraAppleMenuItems.get();

    return nullptr;
}

typedef void (*MenuTrackingChangedCallback) (bool);
extern MenuTrackingChangedCallback menuTrackingChangedCallback;

static void mainMenuTrackingChanged (bool isTracking)
{
    PopupMenu::dismissAllActiveMenus();

    if (auto* menuHandler = JuceMainMenuHandler::instance)
    {
        menuHandler->isOpen = isTracking;

        if (auto* model = menuHandler->currentModel)
            model->handleMenuBarActivate (isTracking);

        if (menuHandler->defferedUpdateRequested && ! isTracking)
        {
            menuHandler->defferedUpdateRequested = false;
            menuHandler->menuBarItemsChanged (menuHandler->currentModel);
        }
    }
}

void juce_initialiseMacMainMenu()
{
    menuTrackingChangedCallback = mainMenuTrackingChanged;

    if (JuceMainMenuHandler::instance == nullptr)
        MainMenuHelpers::rebuildMainMenu (nullptr);
}

// (used from other modules that need to create an NSMenu)
NSMenu* createNSMenu (const PopupMenu&, const String&, int, int, bool);
NSMenu* createNSMenu (const PopupMenu& menu, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate)
{
    juce_initialiseMacMainMenu();

    if (auto* mm = JuceMainMenuHandler::instance)
        return mm->createMenu (menu, name, topLevelMenuId, topLevelIndex, addDelegate);

    jassertfalse; // calling this before making sure the OSX main menu stuff was initialised?
    return nil;
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

void FileChooser::showPlatformDialog (Array<File>& /*results*/,
                                      const String& /*title*/,
                                      const File& /*currentFileOrDirectory*/,
                                      const String& /*filter*/,
                                      bool /*selectsDirectory*/,
                                      bool /*selectsFiles*/,
                                      bool /*isSaveDialogue*/,
                                      bool /*warnAboutOverwritingExistingFiles*/,
                                      bool /*selectMultipleFiles*/,
                                      bool /*treatFilePackagesAsDirs*/,
                                      FilePreviewComponent* /*extraInfoComponent*/)
{
    // TODO

}

bool FileChooser::isPlatformDialogAvailable()
{
    return false;
}

} // namespace juce