1/*
2  ==============================================================================
3
4   This file is part of the JUCE library.
5   Copyright (c) 2020 - Raw Material Software Limited
6
7   JUCE is an open source library subject to commercial or open-source
8   licensing.
9
10   By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13   End User License Agreement: www.juce.com/juce-6-licence
14   Privacy Policy: www.juce.com/juce-privacy-policy
15
16   Or: You may also use this code under the terms of the GPL v3 (see
17   www.gnu.org/licenses).
18
19   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21   DISCLAIMED.
22
23  ==============================================================================
24*/
25
26@interface NSEvent (DeviceDelta)
27- (float)deviceDeltaX;
28- (float)deviceDeltaY;
29@end
30
31//==============================================================================
32#if defined (MAC_OS_X_VERSION_10_8) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) \
33  && USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_DRAW_ASYNC
34static const juce::Identifier disableAsyncLayerBackedViewIdentifier { "disableAsyncLayerBackedView" };
35
36void setComponentAsyncLayerBackedViewDisabled (juce::Component& comp, bool shouldDisableAsyncLayerBackedView)
37{
38    comp.getProperties().set (disableAsyncLayerBackedViewIdentifier, shouldDisableAsyncLayerBackedView);
39}
40
41bool getComponentAsyncLayerBackedViewDisabled (juce::Component& comp)
42{
43    return comp.getProperties()[disableAsyncLayerBackedViewIdentifier];
44}
45#endif
46
47//==============================================================================
48namespace juce
49{
50    typedef void (*AppFocusChangeCallback)();
51    extern AppFocusChangeCallback appFocusChangeCallback;
52    typedef bool (*CheckEventBlockedByModalComps) (NSEvent*);
53    extern CheckEventBlockedByModalComps isEventBlockedByModalComps;
54}
55
56namespace juce
57{
58
59//==============================================================================
60static CGFloat getMainScreenHeight() noexcept
61{
62    if ([[NSScreen screens] count] == 0)
63        return 0.0f;
64
65    return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
66}
67
68static void flipScreenRect (NSRect& r) noexcept
69{
70    r.origin.y = getMainScreenHeight() - (r.origin.y + r.size.height);
71}
72
73static NSRect flippedScreenRect (NSRect r) noexcept
74{
75    flipScreenRect (r);
76    return r;
77}
78
79//==============================================================================
80class NSViewComponentPeer  : public ComponentPeer,
81                             private Timer
82{
83public:
84    NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo)
85        : ComponentPeer (comp, windowStyleFlags),
86          safeComponent (&comp),
87          isSharedWindow (viewToAttachTo != nil),
88          lastRepaintTime (Time::getMillisecondCounter())
89    {
90        appFocusChangeCallback = appFocusChanged;
91        isEventBlockedByModalComps = checkEventBlockedByModalComps;
92
93        auto r = makeNSRect (component.getLocalBounds());
94
95        view = [createViewInstance() initWithFrame: r];
96        setOwner (view, this);
97
98        [view registerForDraggedTypes: getSupportedDragTypes()];
99
100        notificationCenter = [NSNotificationCenter defaultCenter];
101
102        [notificationCenter  addObserver: view
103                                selector: @selector (frameChanged:)
104                                    name: NSViewFrameDidChangeNotification
105                                  object: view];
106
107        [view setPostsFrameChangedNotifications: YES];
108
109       #if defined (MAC_OS_X_VERSION_10_8) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) \
110        && USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_DRAW_ASYNC
111        if (! getComponentAsyncLayerBackedViewDisabled (component))
112        {
113            [view setWantsLayer: YES];
114            [[view layer] setDrawsAsynchronously: YES];
115        }
116       #endif
117
118        if (isSharedWindow)
119        {
120            window = [viewToAttachTo window];
121            [viewToAttachTo addSubview: view];
122        }
123        else
124        {
125            r.origin.x = (CGFloat) component.getX();
126            r.origin.y = (CGFloat) component.getY();
127            flipScreenRect (r);
128
129            window = [createWindowInstance() initWithContentRect: r
130                                                       styleMask: getNSWindowStyleMask (windowStyleFlags)
131                                                         backing: NSBackingStoreBuffered
132                                                           defer: YES];
133            setOwner (window, this);
134            [window orderOut: nil];
135            [window setDelegate: (id<NSWindowDelegate>) window];
136
137            [window setOpaque: component.isOpaque()];
138
139            if (! [window isOpaque])
140                [window setBackgroundColor: [NSColor clearColor]];
141
142           #if defined (MAC_OS_X_VERSION_10_9) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9)
143            [view setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
144           #endif
145
146            [window setHasShadow: ((windowStyleFlags & windowHasDropShadow) != 0)];
147
148            if (component.isAlwaysOnTop())
149                setAlwaysOnTop (true);
150
151            [window setContentView: view];
152            [window setAcceptsMouseMovedEvents: YES];
153
154            // We'll both retain and also release this on closing because plugin hosts can unexpectedly
155            // close the window for us, and also tend to get cause trouble if setReleasedWhenClosed is NO.
156            [window setReleasedWhenClosed: YES];
157            [window retain];
158
159            [window setExcludedFromWindowsMenu: (windowStyleFlags & windowIsTemporary) != 0];
160            [window setIgnoresMouseEvents: (windowStyleFlags & windowIgnoresMouseClicks) != 0];
161
162            if ((windowStyleFlags & (windowHasMaximiseButton | windowHasTitleBar)) == (windowHasMaximiseButton | windowHasTitleBar))
163                [window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
164
165            if ([window respondsToSelector: @selector (setRestorable:)])
166                [window setRestorable: NO];
167
168           #if defined (MAC_OS_X_VERSION_10_13) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13)
169            if ([window respondsToSelector: @selector (setTabbingMode:)])
170                [window setTabbingMode: NSWindowTabbingModeDisallowed];
171           #endif
172
173            [notificationCenter  addObserver: view
174                                    selector: @selector (frameChanged:)
175                                        name: NSWindowDidMoveNotification
176                                      object: window];
177
178            [notificationCenter  addObserver: view
179                                    selector: @selector (frameChanged:)
180                                        name: NSWindowDidMiniaturizeNotification
181                                      object: window];
182
183            [notificationCenter  addObserver: view
184                                    selector: @selector (windowWillMiniaturize:)
185                                        name: NSWindowWillMiniaturizeNotification
186                                      object: window];
187
188            [notificationCenter  addObserver: view
189                                    selector: @selector (windowDidDeminiaturize:)
190                                        name: NSWindowDidDeminiaturizeNotification
191                                      object: window];
192        }
193
194        auto alpha = component.getAlpha();
195
196        if (alpha < 1.0f)
197            setAlpha (alpha);
198
199        setTitle (component.getName());
200
201        getNativeRealtimeModifiers = []
202        {
203            if ([NSEvent respondsToSelector: @selector (modifierFlags)])
204                NSViewComponentPeer::updateModifiers ([NSEvent modifierFlags]);
205
206            return ModifierKeys::currentModifiers;
207        };
208    }
209
210    ~NSViewComponentPeer() override
211    {
212        [notificationCenter removeObserver: view];
213        setOwner (view, nullptr);
214
215        if ([view superview] != nil)
216        {
217            redirectWillMoveToWindow (nullptr);
218            [view removeFromSuperview];
219        }
220
221        if (! isSharedWindow)
222        {
223            setOwner (window, nullptr);
224            [window setContentView: nil];
225            [window close];
226            [window release];
227        }
228
229        [view release];
230    }
231
232    //==============================================================================
233    void* getNativeHandle() const override    { return view; }
234
235    void setVisible (bool shouldBeVisible) override
236    {
237        if (isSharedWindow)
238        {
239            if (shouldBeVisible)
240                [view setHidden: false];
241            else if ([window firstResponder] != view || ([window firstResponder] == view && [window makeFirstResponder: nil]))
242                [view setHidden: true];
243        }
244        else
245        {
246            if (shouldBeVisible)
247            {
248                ++insideToFrontCall;
249                [window orderFront: nil];
250                --insideToFrontCall;
251                handleBroughtToFront();
252            }
253            else
254            {
255                [window orderOut: nil];
256            }
257        }
258    }
259
260    void setTitle (const String& title) override
261    {
262        JUCE_AUTORELEASEPOOL
263        {
264            if (! isSharedWindow)
265                [window setTitle: juceStringToNS (title)];
266        }
267    }
268
269    bool setDocumentEditedStatus (bool edited) override
270    {
271        if (! hasNativeTitleBar())
272            return false;
273
274        [window setDocumentEdited: edited];
275        return true;
276    }
277
278    void setRepresentedFile (const File& file) override
279    {
280        if (! isSharedWindow)
281        {
282            [window setRepresentedFilename: juceStringToNS (file != File()
283                                                                ? file.getFullPathName()
284                                                                : String())];
285
286            windowRepresentsFile = (file != File());
287        }
288    }
289
290    void setBounds (const Rectangle<int>& newBounds, bool isNowFullScreen) override
291    {
292        fullScreen = isNowFullScreen;
293
294        auto r = makeNSRect (newBounds);
295        auto oldViewSize = [view frame].size;
296
297        if (isSharedWindow)
298        {
299            r.origin.y = [[view superview] frame].size.height - (r.origin.y + r.size.height);
300            [view setFrame: r];
301        }
302        else
303        {
304            // Repaint behaviour of setFrame seemed to change in 10.11, and the drawing became synchronous,
305            // causing performance issues. But sending an async update causes flickering in older versions,
306            // hence this version check to use the old behaviour on pre 10.11 machines
307            static bool isPre10_11 = SystemStats::getOperatingSystemType() <= SystemStats::MacOSX_10_10;
308
309            [window setFrame: [window frameRectForContentRect: flippedScreenRect (r)]
310                     display: isPre10_11];
311        }
312
313        if (oldViewSize.width != r.size.width || oldViewSize.height != r.size.height)
314            [view setNeedsDisplay: true];
315    }
316
317    Rectangle<int> getBounds (const bool global) const
318    {
319        auto r = [view frame];
320        NSWindow* viewWindow = [view window];
321
322        if (global && viewWindow != nil)
323        {
324            r = [[view superview] convertRect: r toView: nil];
325            r = [viewWindow convertRectToScreen: r];
326
327            flipScreenRect (r);
328        }
329        else
330        {
331            r.origin.y = [[view superview] frame].size.height - r.origin.y - r.size.height;
332        }
333
334        return convertToRectInt (r);
335    }
336
337    Rectangle<int> getBounds() const override
338    {
339        return getBounds (! isSharedWindow);
340    }
341
342    Point<float> localToGlobal (Point<float> relativePosition) override
343    {
344        return relativePosition + getBounds (true).getPosition().toFloat();
345    }
346
347    using ComponentPeer::localToGlobal;
348
349    Point<float> globalToLocal (Point<float> screenPosition) override
350    {
351        return screenPosition - getBounds (true).getPosition().toFloat();
352    }
353
354    using ComponentPeer::globalToLocal;
355
356    void setAlpha (float newAlpha) override
357    {
358        if (isSharedWindow)
359            [view setAlphaValue: (CGFloat) newAlpha];
360        else
361            [window setAlphaValue: (CGFloat) newAlpha];
362    }
363
364    void setMinimised (bool shouldBeMinimised) override
365    {
366        if (! isSharedWindow)
367        {
368            if (shouldBeMinimised)
369                [window miniaturize: nil];
370            else
371                [window deminiaturize: nil];
372        }
373    }
374
375    bool isMinimised() const override
376    {
377        return [window isMiniaturized];
378    }
379
380    void setFullScreen (bool shouldBeFullScreen) override
381    {
382        if (! isSharedWindow)
383        {
384            auto r = lastNonFullscreenBounds;
385
386            if (isMinimised())
387                setMinimised (false);
388
389            if (fullScreen != shouldBeFullScreen)
390            {
391                if (shouldBeFullScreen && hasNativeTitleBar())
392                {
393                    fullScreen = true;
394                    [window performZoom: nil];
395                }
396                else
397                {
398                    if (shouldBeFullScreen)
399                        r = component.getParentMonitorArea();
400
401                    // (can't call the component's setBounds method because that'll reset our fullscreen flag)
402                    if (r != component.getBounds() && ! r.isEmpty())
403                        setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen);
404                }
405            }
406        }
407    }
408
409    bool isFullScreen() const override
410    {
411        return fullScreen;
412    }
413
414    bool isKioskMode() const override
415    {
416        return isWindowInKioskMode || ComponentPeer::isKioskMode();
417    }
418
419    static bool isWindowAtPoint (NSWindow* w, NSPoint screenPoint)
420    {
421        if ([NSWindow respondsToSelector: @selector (windowNumberAtPoint:belowWindowWithWindowNumber:)])
422            return [NSWindow windowNumberAtPoint: screenPoint belowWindowWithWindowNumber: 0] == [w windowNumber];
423
424        return true;
425    }
426
427    bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
428    {
429        NSRect viewFrame = [view frame];
430
431        if (! (isPositiveAndBelow (localPos.getX(), viewFrame.size.width)
432             && isPositiveAndBelow (localPos.getY(), viewFrame.size.height)))
433            return false;
434
435        if (! SystemStats::isRunningInAppExtensionSandbox())
436        {
437            if (NSWindow* const viewWindow = [view window])
438            {
439                NSRect windowFrame = [viewWindow frame];
440                NSPoint windowPoint = [view convertPoint: NSMakePoint (localPos.x, viewFrame.size.height - localPos.y) toView: nil];
441                NSPoint screenPoint = NSMakePoint (windowFrame.origin.x + windowPoint.x,
442                                                   windowFrame.origin.y + windowPoint.y);
443
444                if (! isWindowAtPoint (viewWindow, screenPoint))
445                    return false;
446
447            }
448        }
449
450        NSView* v = [view hitTest: NSMakePoint (viewFrame.origin.x + localPos.getX(),
451                                                viewFrame.origin.y + viewFrame.size.height - localPos.getY())];
452
453        return trueIfInAChildWindow ? (v != nil)
454                                    : (v == view);
455    }
456
457    BorderSize<int> getFrameSize() const override
458    {
459        BorderSize<int> b;
460
461        if (! isSharedWindow)
462        {
463            NSRect v = [view convertRect: [view frame] toView: nil];
464            NSRect w = [window frame];
465
466            b.setTop ((int) (w.size.height - (v.origin.y + v.size.height)));
467            b.setBottom ((int) v.origin.y);
468            b.setLeft ((int) v.origin.x);
469            b.setRight ((int) (w.size.width - (v.origin.x + v.size.width)));
470        }
471
472        return b;
473    }
474
475    void updateFullscreenStatus()
476    {
477        if (hasNativeTitleBar())
478        {
479            isWindowInKioskMode = (([window styleMask] & NSWindowStyleMaskFullScreen) != 0);
480
481            auto screen = getFrameSize().subtractedFrom (component.getParentMonitorArea());
482
483            fullScreen = component.getScreenBounds().expanded (2, 2).contains (screen);
484        }
485        else
486        {
487            isWindowInKioskMode = false;
488        }
489    }
490
491    bool hasNativeTitleBar() const
492    {
493        return (getStyleFlags() & windowHasTitleBar) != 0;
494    }
495
496    bool setAlwaysOnTop (bool alwaysOnTop) override
497    {
498        if (! isSharedWindow)
499        {
500            [window setLevel: alwaysOnTop ? ((getStyleFlags() & windowIsTemporary) != 0 ? NSPopUpMenuWindowLevel
501                                                                                        : NSFloatingWindowLevel)
502                                          : NSNormalWindowLevel];
503
504            isAlwaysOnTop = alwaysOnTop;
505        }
506
507        return true;
508    }
509
510    void toFront (bool makeActiveWindow) override
511    {
512        if (isSharedWindow)
513            [[view superview] addSubview: view
514                              positioned: NSWindowAbove
515                              relativeTo: nil];
516
517        if (window != nil && component.isVisible())
518        {
519            ++insideToFrontCall;
520
521            if (makeActiveWindow)
522                [window makeKeyAndOrderFront: nil];
523            else
524                [window orderFront: nil];
525
526            if (insideToFrontCall <= 1)
527            {
528                Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
529                handleBroughtToFront();
530            }
531
532            --insideToFrontCall;
533        }
534    }
535
536    void toBehind (ComponentPeer* other) override
537    {
538        if (auto* otherPeer = dynamic_cast<NSViewComponentPeer*> (other))
539        {
540            if (isSharedWindow)
541            {
542                [[view superview] addSubview: view
543                                  positioned: NSWindowBelow
544                                  relativeTo: otherPeer->view];
545            }
546            else if (component.isVisible())
547            {
548                [window orderWindow: NSWindowBelow
549                         relativeTo: [otherPeer->window windowNumber]];
550            }
551        }
552        else
553        {
554            jassertfalse; // wrong type of window?
555        }
556    }
557
558    void setIcon (const Image& newIcon) override
559    {
560        if (! isSharedWindow)
561        {
562            // need to set a dummy represented file here to show the file icon (which we then set to the new icon)
563            if (! windowRepresentsFile)
564                [window setRepresentedFilename:juceStringToNS (" ")]; // can't just use an empty string for some reason...
565
566            [[window standardWindowButton:NSWindowDocumentIconButton] setImage:imageToNSImage (newIcon)];
567        }
568    }
569
570    StringArray getAvailableRenderingEngines() override
571    {
572        StringArray s ("Software Renderer");
573
574       #if USE_COREGRAPHICS_RENDERING
575        s.add ("CoreGraphics Renderer");
576       #endif
577
578        return s;
579    }
580
581    int getCurrentRenderingEngine() const override
582    {
583        return usingCoreGraphics ? 1 : 0;
584    }
585
586    void setCurrentRenderingEngine (int index) override
587    {
588       #if USE_COREGRAPHICS_RENDERING
589        if (usingCoreGraphics != (index > 0))
590        {
591            usingCoreGraphics = index > 0;
592            [view setNeedsDisplay: true];
593        }
594       #else
595        ignoreUnused (index);
596       #endif
597    }
598
599    void redirectMouseDown (NSEvent* ev)
600    {
601        if (! Process::isForegroundProcess())
602            Process::makeForegroundProcess();
603
604        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber]));
605        sendMouseEvent (ev);
606    }
607
608    void redirectMouseUp (NSEvent* ev)
609    {
610        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutFlags (getModifierForButtonNumber ([ev buttonNumber]));
611        sendMouseEvent (ev);
612        showArrowCursorIfNeeded();
613    }
614
615    void redirectMouseDrag (NSEvent* ev)
616    {
617        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (getModifierForButtonNumber ([ev buttonNumber]));
618        sendMouseEvent (ev);
619    }
620
621    void redirectMouseMove (NSEvent* ev)
622    {
623        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
624
625        NSPoint windowPos = [ev locationInWindow];
626        NSPoint screenPos = [[ev window] convertRectToScreen: NSMakeRect (windowPos.x, windowPos.y, 1.0f, 1.0f)].origin;
627
628        if (isWindowAtPoint ([ev window], screenPos))
629            sendMouseEvent (ev);
630        else
631            // moved into another window which overlaps this one, so trigger an exit
632            handleMouseEvent (MouseInputSource::InputSourceType::mouse, MouseInputSource::offscreenMousePos, ModifierKeys::currentModifiers,
633                              getMousePressure (ev), MouseInputSource::invalidOrientation, getMouseTime (ev));
634
635        showArrowCursorIfNeeded();
636    }
637
638    void redirectMouseEnter (NSEvent* ev)
639    {
640        Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
641        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
642        sendMouseEvent (ev);
643    }
644
645    void redirectMouseExit (NSEvent* ev)
646    {
647        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
648        sendMouseEvent (ev);
649    }
650
651    static float checkDeviceDeltaReturnValue (float v) noexcept
652    {
653        // (deviceDeltaX can fail and return NaN, so need to sanity-check the result)
654        v *= 0.5f / 256.0f;
655        return (v > -1000.0f && v < 1000.0f) ? v : 0.0f;
656    }
657
658    void redirectMouseWheel (NSEvent* ev)
659    {
660        updateModifiers (ev);
661
662        MouseWheelDetails wheel;
663        wheel.deltaX = 0;
664        wheel.deltaY = 0;
665        wheel.isReversed = false;
666        wheel.isSmooth = false;
667        wheel.isInertial = false;
668
669        @try
670        {
671            if ([ev respondsToSelector: @selector (isDirectionInvertedFromDevice)])
672                wheel.isReversed = [ev isDirectionInvertedFromDevice];
673
674            wheel.isInertial = ([ev momentumPhase] != NSEventPhaseNone);
675
676            if ([ev respondsToSelector: @selector (hasPreciseScrollingDeltas)])
677            {
678                if ([ev hasPreciseScrollingDeltas])
679                {
680                    const float scale = 0.5f / 256.0f;
681                    wheel.deltaX = scale * (float) [ev scrollingDeltaX];
682                    wheel.deltaY = scale * (float) [ev scrollingDeltaY];
683                    wheel.isSmooth = true;
684                }
685            }
686            else if ([ev respondsToSelector: @selector (deviceDeltaX)])
687            {
688                wheel.deltaX = checkDeviceDeltaReturnValue ([ev deviceDeltaX]);
689                wheel.deltaY = checkDeviceDeltaReturnValue ([ev deviceDeltaY]);
690            }
691        }
692        @catch (...)
693        {}
694
695        if (wheel.deltaX == 0.0f && wheel.deltaY == 0.0f)
696        {
697            const float scale = 10.0f / 256.0f;
698            wheel.deltaX = scale * (float) [ev deltaX];
699            wheel.deltaY = scale * (float) [ev deltaY];
700        }
701
702        handleMouseWheel (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), getMouseTime (ev), wheel);
703    }
704
705    void redirectMagnify (NSEvent* ev)
706    {
707        const float invScale = 1.0f - (float) [ev magnification];
708
709        if (invScale > 0.0f)
710            handleMagnifyGesture (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), getMouseTime (ev), 1.0f / invScale);
711    }
712
713    void redirectCopy  (NSObject*) { handleKeyPress (KeyPress ('c', ModifierKeys (ModifierKeys::commandModifier), 'c')); }
714    void redirectPaste (NSObject*) { handleKeyPress (KeyPress ('v', ModifierKeys (ModifierKeys::commandModifier), 'v')); }
715    void redirectCut   (NSObject*) { handleKeyPress (KeyPress ('x', ModifierKeys (ModifierKeys::commandModifier), 'x')); }
716
717    void redirectWillMoveToWindow (NSWindow* newWindow)
718    {
719        if (isSharedWindow && [view window] == window && newWindow == nullptr)
720        {
721            if (auto* comp = safeComponent.get())
722                comp->setVisible (false);
723        }
724    }
725
726    void sendMouseEvent (NSEvent* ev)
727    {
728        updateModifiers (ev);
729        handleMouseEvent (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), ModifierKeys::currentModifiers,
730                          getMousePressure (ev), MouseInputSource::invalidOrientation, getMouseTime (ev));
731    }
732
733    bool handleKeyEvent (NSEvent* ev, bool isKeyDown)
734    {
735        auto unicode = nsStringToJuce ([ev characters]);
736        auto keyCode = getKeyCodeFromEvent (ev);
737
738       #if JUCE_DEBUG_KEYCODES
739        DBG ("unicode: " + unicode + " " + String::toHexString ((int) unicode[0]));
740        auto unmodified = nsStringToJuce ([ev charactersIgnoringModifiers]);
741        DBG ("unmodified: " + unmodified + " " + String::toHexString ((int) unmodified[0]));
742       #endif
743
744        if (keyCode != 0 || unicode.isNotEmpty())
745        {
746            if (isKeyDown)
747            {
748                bool used = false;
749
750                for (auto u = unicode.getCharPointer(); ! u.isEmpty();)
751                {
752                    auto textCharacter = u.getAndAdvance();
753
754                    switch (keyCode)
755                    {
756                        case NSLeftArrowFunctionKey:
757                        case NSRightArrowFunctionKey:
758                        case NSUpArrowFunctionKey:
759                        case NSDownArrowFunctionKey:
760                        case NSPageUpFunctionKey:
761                        case NSPageDownFunctionKey:
762                        case NSEndFunctionKey:
763                        case NSHomeFunctionKey:
764                        case NSDeleteFunctionKey:
765                            textCharacter = 0;
766                            break; // (these all seem to generate unwanted garbage unicode strings)
767
768                        default:
769                            if (([ev modifierFlags] & NSEventModifierFlagCommand) != 0
770                                 || (keyCode >= NSF1FunctionKey && keyCode <= NSF35FunctionKey))
771                                textCharacter = 0;
772                            break;
773                    }
774
775                    used = handleKeyUpOrDown (true) || used;
776                    used = handleKeyPress (keyCode, textCharacter) || used;
777                }
778
779                return used;
780            }
781
782            if (handleKeyUpOrDown (false))
783                return true;
784        }
785
786        return false;
787    }
788
789    bool redirectKeyDown (NSEvent* ev)
790    {
791        // (need to retain this in case a modal loop runs in handleKeyEvent and
792        // our event object gets lost)
793        const std::unique_ptr<NSEvent, NSObjectDeleter> r ([ev retain]);
794
795        updateKeysDown (ev, true);
796        bool used = handleKeyEvent (ev, true);
797
798        if (([ev modifierFlags] & NSEventModifierFlagCommand) != 0)
799        {
800            // for command keys, the key-up event is thrown away, so simulate one..
801            updateKeysDown (ev, false);
802            used = (isValidPeer (this) && handleKeyEvent (ev, false)) || used;
803        }
804
805        // (If we're running modally, don't allow unused keystrokes to be passed
806        // along to other blocked views..)
807        if (Component::getCurrentlyModalComponent() != nullptr)
808            used = true;
809
810        return used;
811    }
812
813    bool redirectKeyUp (NSEvent* ev)
814    {
815        updateKeysDown (ev, false);
816        return handleKeyEvent (ev, false)
817                || Component::getCurrentlyModalComponent() != nullptr;
818    }
819
820    void redirectModKeyChange (NSEvent* ev)
821    {
822        // (need to retain this in case a modal loop runs and our event object gets lost)
823        const std::unique_ptr<NSEvent, NSObjectDeleter> r ([ev retain]);
824
825        keysCurrentlyDown.clear();
826        handleKeyUpOrDown (true);
827
828        updateModifiers (ev);
829        handleModifierKeysChange();
830    }
831
832    //==============================================================================
833    void drawRect (NSRect r)
834    {
835        if (r.size.width < 1.0f || r.size.height < 1.0f)
836            return;
837
838        auto cg = (CGContextRef) [[NSGraphicsContext currentContext]
839       #if (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10)
840                                  CGContext];
841       #else
842                                  graphicsPort];
843       #endif
844
845        if (! component.isOpaque())
846            CGContextClearRect (cg, CGContextGetClipBoundingBox (cg));
847
848        float displayScale = 1.0f;
849        NSScreen* screen = [[view window] screen];
850
851        if ([screen respondsToSelector: @selector (backingScaleFactor)])
852            displayScale = (float) screen.backingScaleFactor;
853
854       #if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
855        // This option invokes a separate paint call for each rectangle of the clip region.
856        // It's a long story, but this is a basically a workaround for a CGContext not having
857        // a way of finding whether a rectangle falls within its clip region
858        if (usingCoreGraphics)
859        {
860            const NSRect* rects = nullptr;
861            NSInteger numRects = 0;
862            [view getRectsBeingDrawn: &rects count: &numRects];
863
864            if (numRects > 1)
865            {
866                for (int i = 0; i < numRects; ++i)
867                {
868                    NSRect rect = rects[i];
869                    CGContextSaveGState (cg);
870                    CGContextClipToRect (cg, CGRectMake (rect.origin.x, rect.origin.y, rect.size.width, rect.size.height));
871                    drawRect (cg, rect, displayScale);
872                    CGContextRestoreGState (cg);
873                }
874
875                return;
876            }
877        }
878       #endif
879
880        drawRect (cg, r, displayScale);
881    }
882
883    void drawRect (CGContextRef cg, NSRect r, float displayScale)
884    {
885       #if USE_COREGRAPHICS_RENDERING
886        if (usingCoreGraphics)
887        {
888            CoreGraphicsContext context (cg, (float) [view frame].size.height);
889            invokePaint (context);
890        }
891        else
892       #endif
893        {
894            const Point<int> offset (-roundToInt (r.origin.x),
895                                     -roundToInt ([view frame].size.height - (r.origin.y + r.size.height)));
896            auto clipW = (int) (r.size.width  + 0.5f);
897            auto clipH = (int) (r.size.height + 0.5f);
898
899            RectangleList<int> clip;
900            getClipRects (clip, offset, clipW, clipH);
901
902            if (! clip.isEmpty())
903            {
904                Image temp (component.isOpaque() ? Image::RGB : Image::ARGB,
905                            roundToInt (clipW * displayScale),
906                            roundToInt (clipH * displayScale),
907                            ! component.isOpaque());
908
909                {
910                    auto intScale = roundToInt (displayScale);
911
912                    if (intScale != 1)
913                        clip.scaleAll (intScale);
914
915                    auto context = component.getLookAndFeel()
916                                     .createGraphicsContext (temp, offset * intScale, clip);
917
918                    if (intScale != 1)
919                        context->addTransform (AffineTransform::scale (displayScale));
920
921                    invokePaint (*context);
922                }
923
924                CGColorSpaceRef colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceSRGB);
925                CGImageRef image = juce_createCoreGraphicsImage (temp, colourSpace, false);
926                CGColorSpaceRelease (colourSpace);
927                CGContextDrawImage (cg, CGRectMake (r.origin.x, r.origin.y, clipW, clipH), image);
928                CGImageRelease (image);
929            }
930        }
931    }
932
933    void repaint (const Rectangle<int>& area) override
934    {
935        // In 10.11 changes were made to the way the OS handles repaint regions, and it seems that it can
936        // no longer be trusted to coalesce all the regions, or to even remember them all without losing
937        // a few when there's a lot of activity.
938        // As a work around for this, we use a RectangleList to do our own coalescing of regions before
939        // asynchronously asking the OS to repaint them.
940        deferredRepaints.add ((float) area.getX(), (float) ([view frame].size.height - area.getBottom()),
941                              (float) area.getWidth(), (float) area.getHeight());
942
943        if (isTimerRunning())
944            return;
945
946        auto now = Time::getMillisecondCounter();
947        auto msSinceLastRepaint = (lastRepaintTime >= now) ? now - lastRepaintTime
948                                                           : (std::numeric_limits<uint32>::max() - lastRepaintTime) + now;
949
950        static uint32 minimumRepaintInterval = 1000 / 30; // 30fps
951
952        // When windows are being resized, artificially throttling high-frequency repaints helps
953        // to stop the event queue getting clogged, and keeps everything working smoothly.
954        // For some reason Logic also needs this throttling to record parameter events correctly.
955        if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
956        {
957            startTimer (static_cast<int> (minimumRepaintInterval - msSinceLastRepaint));
958            return;
959        }
960
961        setNeedsDisplayRectangles();
962    }
963
964    static bool shouldThrottleRepaint()
965    {
966        return areAnyWindowsInLiveResize() || ! JUCEApplication::isStandaloneApp();
967    }
968
969    void timerCallback() override
970    {
971        setNeedsDisplayRectangles();
972        stopTimer();
973    }
974
975    void setNeedsDisplayRectangles()
976    {
977        for (auto& i : deferredRepaints)
978            [view setNeedsDisplayInRect: makeNSRect (i)];
979
980        lastRepaintTime = Time::getMillisecondCounter();
981        deferredRepaints.clear();
982    }
983
984    void invokePaint (LowLevelGraphicsContext& context)
985    {
986        handlePaint (context);
987    }
988
989    void performAnyPendingRepaintsNow() override
990    {
991        [view displayIfNeeded];
992    }
993
994    static bool areAnyWindowsInLiveResize() noexcept
995    {
996        for (NSWindow* w in [NSApp windows])
997            if ([w inLiveResize])
998                return true;
999
1000        return false;
1001    }
1002
1003    //==============================================================================
1004    bool isBlockedByModalComponent()
1005    {
1006        if (auto* modal = Component::getCurrentlyModalComponent())
1007        {
1008            if (insideToFrontCall == 0
1009                 && (! getComponent().isParentOf (modal))
1010                 && getComponent().isCurrentlyBlockedByAnotherModalComponent())
1011            {
1012                return true;
1013            }
1014        }
1015
1016        return false;
1017    }
1018
1019    void sendModalInputAttemptIfBlocked()
1020    {
1021        if (isBlockedByModalComponent())
1022            if (auto* modal = Component::getCurrentlyModalComponent())
1023                modal->inputAttemptWhenModal();
1024    }
1025
1026    bool canBecomeKeyWindow()
1027    {
1028        return component.isVisible() && (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0;
1029    }
1030
1031    bool canBecomeMainWindow()
1032    {
1033        return component.isVisible() && dynamic_cast<ResizableWindow*> (&component) != nullptr;
1034    }
1035
1036    bool worksWhenModal() const
1037    {
1038        // In plugins, the host could put our plugin window inside a modal window, so this
1039        // allows us to successfully open other popups. Feels like there could be edge-case
1040        // problems caused by this, so let us know if you spot any issues..
1041        return ! JUCEApplication::isStandaloneApp();
1042    }
1043
1044    void becomeKeyWindow()
1045    {
1046        handleBroughtToFront();
1047        grabFocus();
1048    }
1049
1050    bool windowShouldClose()
1051    {
1052        if (! isValidPeer (this))
1053            return YES;
1054
1055        handleUserClosingWindow();
1056        return NO;
1057    }
1058
1059    void redirectMovedOrResized()
1060    {
1061        updateFullscreenStatus();
1062        handleMovedOrResized();
1063    }
1064
1065    void viewMovedToWindow()
1066    {
1067        if (isSharedWindow)
1068        {
1069            auto newWindow = [view window];
1070            bool shouldSetVisible = (window == nullptr && newWindow != nullptr);
1071
1072            window = newWindow;
1073
1074            if (shouldSetVisible)
1075                getComponent().setVisible (true);
1076        }
1077    }
1078
1079    void liveResizingStart()
1080    {
1081        if (constrainer == nullptr)
1082            return;
1083
1084        constrainer->resizeStart();
1085        isFirstLiveResize = true;
1086
1087        setFullScreenSizeConstraints (*constrainer);
1088    }
1089
1090    void liveResizingEnd()
1091    {
1092        if (constrainer != nullptr)
1093            constrainer->resizeEnd();
1094    }
1095
1096    NSRect constrainRect (const NSRect r)
1097    {
1098        if (constrainer == nullptr || isKioskMode())
1099            return r;
1100
1101        const auto scale = getComponent().getDesktopScaleFactor();
1102
1103        auto pos            = ScalingHelpers::unscaledScreenPosToScaled (scale, convertToRectInt (flippedScreenRect (r)));
1104        const auto original = ScalingHelpers::unscaledScreenPosToScaled (scale, convertToRectInt (flippedScreenRect ([window frame])));
1105
1106        const auto screenBounds = Desktop::getInstance().getDisplays().getTotalBounds (true);
1107
1108        const bool inLiveResize = [window inLiveResize];
1109
1110        if (! inLiveResize || isFirstLiveResize)
1111        {
1112            isFirstLiveResize = false;
1113
1114            isStretchingTop    = (pos.getY() != original.getY() && pos.getBottom() == original.getBottom());
1115            isStretchingLeft   = (pos.getX() != original.getX() && pos.getRight()  == original.getRight());
1116            isStretchingBottom = (pos.getY() == original.getY() && pos.getBottom() != original.getBottom());
1117            isStretchingRight  = (pos.getX() == original.getX() && pos.getRight()  != original.getRight());
1118        }
1119
1120        constrainer->checkBounds (pos, original, screenBounds,
1121                                  isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
1122
1123        return flippedScreenRect (makeNSRect (ScalingHelpers::scaledScreenPosToUnscaled (scale, pos)));
1124    }
1125
1126    static void showArrowCursorIfNeeded()
1127    {
1128        auto& desktop = Desktop::getInstance();
1129        auto mouse = desktop.getMainMouseSource();
1130
1131        if (mouse.getComponentUnderMouse() == nullptr
1132             && desktop.findComponentAt (mouse.getScreenPosition().roundToInt()) == nullptr)
1133        {
1134            [[NSCursor arrowCursor] set];
1135        }
1136    }
1137
1138    static void updateModifiers (NSEvent* e)
1139    {
1140        updateModifiers ([e modifierFlags]);
1141    }
1142
1143    static void updateModifiers (const NSUInteger flags)
1144    {
1145        int m = 0;
1146
1147        if ((flags & NSEventModifierFlagShift) != 0)        m |= ModifierKeys::shiftModifier;
1148        if ((flags & NSEventModifierFlagControl) != 0)      m |= ModifierKeys::ctrlModifier;
1149        if ((flags & NSEventModifierFlagOption) != 0)       m |= ModifierKeys::altModifier;
1150        if ((flags & NSEventModifierFlagCommand) != 0)      m |= ModifierKeys::commandModifier;
1151
1152        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons().withFlags (m);
1153    }
1154
1155    static void updateKeysDown (NSEvent* ev, bool isKeyDown)
1156    {
1157        updateModifiers (ev);
1158
1159        if (auto keyCode = getKeyCodeFromEvent (ev))
1160        {
1161            if (isKeyDown)
1162                keysCurrentlyDown.addIfNotAlreadyThere (keyCode);
1163            else
1164                keysCurrentlyDown.removeFirstMatchingValue (keyCode);
1165        }
1166    }
1167
1168    static int getKeyCodeFromEvent (NSEvent* ev)
1169    {
1170        // Unfortunately, charactersIgnoringModifiers does not ignore the shift key.
1171        // Using [ev keyCode] is not a solution either as this will,
1172        // for example, return VK_KEY_Y if the key is pressed which
1173        // is typically located at the Y key position on a QWERTY
1174        // keyboard. However, on international keyboards this might not
1175        // be the key labeled Y (for example, on German keyboards this key
1176        // has a Z label). Therefore, we need to query the current keyboard
1177        // layout to figure out what character the key would have produced
1178        // if the shift key was not pressed
1179        String unmodified;
1180
1181       #if JUCE_SUPPORT_CARBON
1182        if (TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource())
1183        {
1184            if (auto layoutData = (CFDataRef) TISGetInputSourceProperty (currentKeyboard,
1185                                                                         kTISPropertyUnicodeKeyLayoutData))
1186            {
1187                if (auto* layoutPtr = (const UCKeyboardLayout*) CFDataGetBytePtr (layoutData))
1188                {
1189                    UInt32 keysDown = 0;
1190                    UniChar buffer[4];
1191                    UniCharCount actual;
1192
1193                    if (UCKeyTranslate (layoutPtr, [ev keyCode], kUCKeyActionDown, 0, LMGetKbdType(),
1194                                        kUCKeyTranslateNoDeadKeysBit, &keysDown, sizeof (buffer) / sizeof (UniChar),
1195                                        &actual, buffer) == 0)
1196                        unmodified = String (CharPointer_UTF16 (reinterpret_cast<CharPointer_UTF16::CharType*> (buffer)), 4);
1197                }
1198            }
1199
1200            CFRelease (currentKeyboard);
1201        }
1202
1203        // did the above layout conversion fail
1204        if (unmodified.isEmpty())
1205       #endif
1206        {
1207            unmodified = nsStringToJuce ([ev charactersIgnoringModifiers]);
1208        }
1209
1210        auto keyCode = (int) unmodified[0];
1211
1212        if (keyCode == 0x19) // (backwards-tab)
1213            keyCode = '\t';
1214        else if (keyCode == 0x03) // (enter)
1215            keyCode = '\r';
1216        else
1217            keyCode = (int) CharacterFunctions::toUpperCase ((juce_wchar) keyCode);
1218
1219        if (([ev modifierFlags] & NSEventModifierFlagNumericPad) != 0)
1220        {
1221            const int numPadConversions[] = { '0', KeyPress::numberPad0, '1', KeyPress::numberPad1,
1222                                              '2', KeyPress::numberPad2, '3', KeyPress::numberPad3,
1223                                              '4', KeyPress::numberPad4, '5', KeyPress::numberPad5,
1224                                              '6', KeyPress::numberPad6, '7', KeyPress::numberPad7,
1225                                              '8', KeyPress::numberPad8, '9', KeyPress::numberPad9,
1226                                              '+', KeyPress::numberPadAdd, '-', KeyPress::numberPadSubtract,
1227                                              '*', KeyPress::numberPadMultiply, '/', KeyPress::numberPadDivide,
1228                                              '.', KeyPress::numberPadDecimalPoint,
1229                                              ',', KeyPress::numberPadDecimalPoint, // (to deal with non-english kbds)
1230                                              '=', KeyPress::numberPadEquals };
1231
1232            for (int i = 0; i < numElementsInArray (numPadConversions); i += 2)
1233                if (keyCode == numPadConversions [i])
1234                    keyCode = numPadConversions [i + 1];
1235        }
1236
1237        return keyCode;
1238    }
1239
1240    static int64 getMouseTime (NSEvent* e) noexcept
1241    {
1242        return (Time::currentTimeMillis() - Time::getMillisecondCounter())
1243                 + (int64) ([e timestamp] * 1000.0);
1244    }
1245
1246    static float getMousePressure (NSEvent* e) noexcept
1247    {
1248        @try
1249        {
1250            if (e.type != NSEventTypeMouseEntered && e.type != NSEventTypeMouseExited)
1251                return (float) e.pressure;
1252        }
1253        @catch (NSException* e) {}
1254        @finally {}
1255
1256        return 0.0f;
1257    }
1258
1259    static Point<float> getMousePos (NSEvent* e, NSView* view)
1260    {
1261        NSPoint p = [view convertPoint: [e locationInWindow] fromView: nil];
1262        return { (float) p.x, (float) ([view frame].size.height - p.y) };
1263    }
1264
1265    static int getModifierForButtonNumber (const NSInteger num)
1266    {
1267        return num == 0 ? ModifierKeys::leftButtonModifier
1268                        : (num == 1 ? ModifierKeys::rightButtonModifier
1269                                    : (num == 2 ? ModifierKeys::middleButtonModifier : 0));
1270    }
1271
1272    static unsigned int getNSWindowStyleMask (const int flags) noexcept
1273    {
1274        unsigned int style = (flags & windowHasTitleBar) != 0 ? NSWindowStyleMaskTitled
1275                                                              : NSWindowStyleMaskBorderless;
1276
1277        if ((flags & windowHasMinimiseButton) != 0)  style |= NSWindowStyleMaskMiniaturizable;
1278        if ((flags & windowHasCloseButton) != 0)     style |= NSWindowStyleMaskClosable;
1279        if ((flags & windowIsResizable) != 0)        style |= NSWindowStyleMaskResizable;
1280        return style;
1281    }
1282
1283    static NSArray* getSupportedDragTypes()
1284    {
1285        return [NSArray arrayWithObjects: (NSString*) kUTTypeFileURL, (NSString*) kPasteboardTypeFileURLPromise, NSPasteboardTypeString, nil];
1286    }
1287
1288    BOOL sendDragCallback (const int type, id <NSDraggingInfo> sender)
1289    {
1290        NSPasteboard* pasteboard = [sender draggingPasteboard];
1291        NSString* contentType = [pasteboard availableTypeFromArray: getSupportedDragTypes()];
1292
1293        if (contentType == nil)
1294            return false;
1295
1296        NSPoint p = [view convertPoint: [sender draggingLocation] fromView: nil];
1297        ComponentPeer::DragInfo dragInfo;
1298        dragInfo.position.setXY ((int) p.x, (int) ([view frame].size.height - p.y));
1299
1300        if (contentType == NSPasteboardTypeString)
1301            dragInfo.text = nsStringToJuce ([pasteboard stringForType: NSPasteboardTypeString]);
1302        else
1303            dragInfo.files = getDroppedFiles (pasteboard, contentType);
1304
1305        if (! dragInfo.isEmpty())
1306        {
1307            switch (type)
1308            {
1309                case 0:   return handleDragMove (dragInfo);
1310                case 1:   return handleDragExit (dragInfo);
1311                case 2:   return handleDragDrop (dragInfo);
1312                default:  jassertfalse; break;
1313            }
1314        }
1315
1316        return false;
1317    }
1318
1319    StringArray getDroppedFiles (NSPasteboard* pasteboard, NSString* contentType)
1320    {
1321        StringArray files;
1322        NSString* iTunesPasteboardType = nsStringLiteral ("CorePasteboardFlavorType 0x6974756E"); // 'itun'
1323
1324        if ([contentType isEqualToString: (NSString*) kPasteboardTypeFileURLPromise]
1325             && [[pasteboard types] containsObject: iTunesPasteboardType])
1326        {
1327            id list = [pasteboard propertyListForType: iTunesPasteboardType];
1328
1329            if ([list isKindOfClass: [NSDictionary class]])
1330            {
1331                NSDictionary* iTunesDictionary = (NSDictionary*) list;
1332                NSArray* tracks = [iTunesDictionary valueForKey: nsStringLiteral ("Tracks")];
1333                NSEnumerator* enumerator = [tracks objectEnumerator];
1334                NSDictionary* track;
1335
1336                while ((track = [enumerator nextObject]) != nil)
1337                {
1338                    if (id value = [track valueForKey: nsStringLiteral ("Location")])
1339                    {
1340                        NSURL* url = [NSURL URLWithString: value];
1341
1342                        if ([url isFileURL])
1343                            files.add (nsStringToJuce ([url path]));
1344                    }
1345                }
1346            }
1347        }
1348        else
1349        {
1350            NSArray* items = [pasteboard readObjectsForClasses:@[[NSURL class]] options: nil];
1351
1352            for (unsigned int i = 0; i < [items count]; ++i)
1353            {
1354                NSURL* url = [items objectAtIndex: i];
1355
1356                if ([url isFileURL])
1357                    files.add (nsStringToJuce ([url path]));
1358            }
1359        }
1360
1361        return files;
1362    }
1363
1364    //==============================================================================
1365    void viewFocusGain()
1366    {
1367        if (currentlyFocusedPeer != this)
1368        {
1369            if (ComponentPeer::isValidPeer (currentlyFocusedPeer))
1370                currentlyFocusedPeer->handleFocusLoss();
1371
1372            currentlyFocusedPeer = this;
1373            handleFocusGain();
1374        }
1375    }
1376
1377    void viewFocusLoss()
1378    {
1379        if (currentlyFocusedPeer == this)
1380        {
1381            currentlyFocusedPeer = nullptr;
1382            handleFocusLoss();
1383        }
1384    }
1385
1386    bool isFocused() const override
1387    {
1388        return (isSharedWindow || ! JUCEApplication::isStandaloneApp())
1389                    ? this == currentlyFocusedPeer
1390                    : [window isKeyWindow];
1391    }
1392
1393    void grabFocus() override
1394    {
1395        if (window != nil)
1396        {
1397            [window makeKeyWindow];
1398            [window makeFirstResponder: view];
1399
1400            viewFocusGain();
1401        }
1402    }
1403
1404    void textInputRequired (Point<int>, TextInputTarget&) override {}
1405
1406    void resetWindowPresentation()
1407    {
1408        if (hasNativeTitleBar())
1409        {
1410            [window setStyleMask: (NSViewComponentPeer::getNSWindowStyleMask (getStyleFlags()))];
1411            setTitle (getComponent().getName()); // required to force the OS to update the title
1412        }
1413
1414        [NSApp setPresentationOptions: NSApplicationPresentationDefault];
1415    }
1416
1417    //==============================================================================
1418    NSWindow* window = nil;
1419    NSView* view = nil;
1420    WeakReference<Component> safeComponent;
1421    bool isSharedWindow = false, fullScreen = false;
1422    bool isWindowInKioskMode = false;
1423   #if USE_COREGRAPHICS_RENDERING
1424    bool usingCoreGraphics = true;
1425   #else
1426    bool usingCoreGraphics = false;
1427   #endif
1428    bool isZooming = false, isFirstLiveResize = false, textWasInserted = false;
1429    bool isStretchingTop = false, isStretchingLeft = false, isStretchingBottom = false, isStretchingRight = false;
1430    bool windowRepresentsFile = false;
1431    bool isAlwaysOnTop = false, wasAlwaysOnTop = false;
1432    String stringBeingComposed;
1433    NSNotificationCenter* notificationCenter = nil;
1434
1435    RectangleList<float> deferredRepaints;
1436    uint32 lastRepaintTime;
1437
1438    static ComponentPeer* currentlyFocusedPeer;
1439    static Array<int> keysCurrentlyDown;
1440    static int insideToFrontCall;
1441
1442private:
1443    static NSView* createViewInstance();
1444    static NSWindow* createWindowInstance();
1445
1446    static void setOwner (id viewOrWindow, NSViewComponentPeer* newOwner)
1447    {
1448        object_setInstanceVariable (viewOrWindow, "owner", newOwner);
1449    }
1450
1451    void getClipRects (RectangleList<int>& clip, Point<int> offset, int clipW, int clipH)
1452    {
1453        const NSRect* rects = nullptr;
1454        NSInteger numRects = 0;
1455        [view getRectsBeingDrawn: &rects count: &numRects];
1456
1457        const Rectangle<int> clipBounds (clipW, clipH);
1458        auto viewH = [view frame].size.height;
1459
1460        clip.ensureStorageAllocated ((int) numRects);
1461
1462        for (int i = 0; i < numRects; ++i)
1463            clip.addWithoutMerging (clipBounds.getIntersection (Rectangle<int> (roundToInt (rects[i].origin.x) + offset.x,
1464                                                                                roundToInt (viewH - (rects[i].origin.y + rects[i].size.height)) + offset.y,
1465                                                                                roundToInt (rects[i].size.width),
1466                                                                                roundToInt (rects[i].size.height))));
1467    }
1468
1469    static void appFocusChanged()
1470    {
1471        keysCurrentlyDown.clear();
1472
1473        if (isValidPeer (currentlyFocusedPeer))
1474        {
1475            if (Process::isForegroundProcess())
1476            {
1477                currentlyFocusedPeer->handleFocusGain();
1478                ModalComponentManager::getInstance()->bringModalComponentsToFront();
1479            }
1480            else
1481            {
1482                currentlyFocusedPeer->handleFocusLoss();
1483            }
1484        }
1485    }
1486
1487    static bool checkEventBlockedByModalComps (NSEvent* e)
1488    {
1489        if (Component::getNumCurrentlyModalComponents() == 0)
1490            return false;
1491
1492        NSWindow* const w = [e window];
1493
1494        if (w == nil || [w worksWhenModal])
1495            return false;
1496
1497        bool isKey = false, isInputAttempt = false;
1498
1499        switch ([e type])
1500        {
1501            case NSEventTypeKeyDown:
1502            case NSEventTypeKeyUp:
1503                isKey = isInputAttempt = true;
1504                break;
1505
1506            case NSEventTypeLeftMouseDown:
1507            case NSEventTypeRightMouseDown:
1508            case NSEventTypeOtherMouseDown:
1509                isInputAttempt = true;
1510                break;
1511
1512            case NSEventTypeLeftMouseDragged:
1513            case NSEventTypeRightMouseDragged:
1514            case NSEventTypeLeftMouseUp:
1515            case NSEventTypeRightMouseUp:
1516            case NSEventTypeOtherMouseUp:
1517            case NSEventTypeOtherMouseDragged:
1518                if (Desktop::getInstance().getDraggingMouseSource(0) != nullptr)
1519                    return false;
1520                break;
1521
1522            case NSEventTypeMouseMoved:
1523            case NSEventTypeMouseEntered:
1524            case NSEventTypeMouseExited:
1525            case NSEventTypeCursorUpdate:
1526            case NSEventTypeScrollWheel:
1527            case NSEventTypeTabletPoint:
1528            case NSEventTypeTabletProximity:
1529                break;
1530
1531            case NSEventTypeFlagsChanged:
1532            case NSEventTypeAppKitDefined:
1533            case NSEventTypeSystemDefined:
1534            case NSEventTypeApplicationDefined:
1535            case NSEventTypePeriodic:
1536           #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8
1537            case NSEventTypeGesture:
1538           #endif
1539            case NSEventTypeMagnify:
1540            case NSEventTypeSwipe:
1541            case NSEventTypeRotate:
1542            case NSEventTypeBeginGesture:
1543            case NSEventTypeEndGesture:
1544            case NSEventTypeQuickLook:
1545          #if JUCE_64BIT
1546            case NSEventTypeSmartMagnify:
1547           #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8
1548            case NSEventTypePressure:
1549           #endif
1550          #endif
1551          #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
1552           #if JUCE_64BIT
1553            case NSEventTypeDirectTouch:
1554           #endif
1555           #if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
1556            case NSEventTypeChangeMode:
1557           #endif
1558          #endif
1559            default:
1560                return false;
1561        }
1562
1563        for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
1564        {
1565            if (auto* peer = dynamic_cast<NSViewComponentPeer*> (ComponentPeer::getPeer (i)))
1566            {
1567                if ([peer->view window] == w)
1568                {
1569                    if (isKey)
1570                    {
1571                        if (peer->view == [w firstResponder])
1572                            return false;
1573                    }
1574                    else
1575                    {
1576                        if (peer->isSharedWindow
1577                               ? NSPointInRect ([peer->view convertPoint: [e locationInWindow] fromView: nil], [peer->view bounds])
1578                               : NSPointInRect ([e locationInWindow], NSMakeRect (0, 0, [w frame].size.width, [w frame].size.height)))
1579                            return false;
1580                    }
1581                }
1582            }
1583        }
1584
1585        if (isInputAttempt)
1586        {
1587            if (! [NSApp isActive])
1588                [NSApp activateIgnoringOtherApps: YES];
1589
1590            if (auto* modal = Component::getCurrentlyModalComponent())
1591                modal->inputAttemptWhenModal();
1592        }
1593
1594        return true;
1595    }
1596
1597    void setFullScreenSizeConstraints (const ComponentBoundsConstrainer& c)
1598    {
1599        const auto minSize = NSMakeSize (static_cast<float> (c.getMinimumWidth()),
1600                                         0.0f);
1601        [window setMinFullScreenContentSize: minSize];
1602    }
1603
1604    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer)
1605};
1606
1607int NSViewComponentPeer::insideToFrontCall = 0;
1608
1609//==============================================================================
1610struct JuceNSViewClass   : public ObjCClass<NSView>
1611{
1612    JuceNSViewClass()  : ObjCClass<NSView> ("JUCEView_")
1613    {
1614        addIvar<NSViewComponentPeer*> ("owner");
1615
1616        addMethod (@selector (isOpaque),                      isOpaque,                   "c@:");
1617        addMethod (@selector (drawRect:),                     drawRect,                   "v@:", @encode (NSRect));
1618        addMethod (@selector (mouseDown:),                    mouseDown,                  "v@:@");
1619        addMethod (@selector (asyncMouseDown:),               asyncMouseDown,             "v@:@");
1620        addMethod (@selector (mouseUp:),                      mouseUp,                    "v@:@");
1621        addMethod (@selector (asyncMouseUp:),                 asyncMouseUp,               "v@:@");
1622        addMethod (@selector (mouseDragged:),                 mouseDragged,               "v@:@");
1623        addMethod (@selector (mouseMoved:),                   mouseMoved,                 "v@:@");
1624        addMethod (@selector (mouseEntered:),                 mouseEntered,               "v@:@");
1625        addMethod (@selector (mouseExited:),                  mouseExited,                "v@:@");
1626        addMethod (@selector (rightMouseDown:),               mouseDown,                  "v@:@");
1627        addMethod (@selector (rightMouseDragged:),            mouseDragged,               "v@:@");
1628        addMethod (@selector (rightMouseUp:),                 mouseUp,                    "v@:@");
1629        addMethod (@selector (otherMouseDown:),               mouseDown,                  "v@:@");
1630        addMethod (@selector (otherMouseDragged:),            mouseDragged,               "v@:@");
1631        addMethod (@selector (otherMouseUp:),                 mouseUp,                    "v@:@");
1632        addMethod (@selector (scrollWheel:),                  scrollWheel,                "v@:@");
1633        addMethod (@selector (magnifyWithEvent:),             magnify,                    "v@:@");
1634        addMethod (@selector (acceptsFirstMouse:),            acceptsFirstMouse,          "c@:@");
1635        addMethod (@selector (frameChanged:),                 frameChanged,               "v@:@");
1636        addMethod (@selector (windowWillMiniaturize:),        windowWillMiniaturize,      "v@:@");
1637        addMethod (@selector (windowDidDeminiaturize:),       windowDidDeminiaturize,     "v@:@");
1638        addMethod (@selector (wantsDefaultClipping:),         wantsDefaultClipping,       "c@:");
1639        addMethod (@selector (worksWhenModal),                worksWhenModal,             "c@:");
1640        addMethod (@selector (viewDidMoveToWindow),           viewDidMoveToWindow,        "v@:");
1641        addMethod (@selector (keyDown:),                      keyDown,                    "v@:@");
1642        addMethod (@selector (keyUp:),                        keyUp,                      "v@:@");
1643        addMethod (@selector (insertText:),                   insertText,                 "v@:@");
1644        addMethod (@selector (doCommandBySelector:),          doCommandBySelector,        "v@::");
1645        addMethod (@selector (setMarkedText:selectedRange:),  setMarkedText,              "v@:@", @encode (NSRange));
1646        addMethod (@selector (unmarkText),                    unmarkText,                 "v@:");
1647        addMethod (@selector (hasMarkedText),                 hasMarkedText,              "c@:");
1648        addMethod (@selector (conversationIdentifier),        conversationIdentifier,     "l@:");
1649        addMethod (@selector (attributedSubstringFromRange:), attributedSubstringFromRange, "@@:", @encode (NSRange));
1650        addMethod (@selector (markedRange),                   markedRange,                @encode (NSRange), "@:");
1651        addMethod (@selector (selectedRange),                 selectedRange,              @encode (NSRange), "@:");
1652        addMethod (@selector (firstRectForCharacterRange:),   firstRectForCharacterRange, @encode (NSRect), "@:", @encode (NSRange));
1653        addMethod (@selector (characterIndexForPoint:),       characterIndexForPoint,     "L@:", @encode (NSPoint));
1654        addMethod (@selector (validAttributesForMarkedText),  validAttributesForMarkedText, "@@:");
1655        addMethod (@selector (flagsChanged:),                 flagsChanged,               "v@:@");
1656
1657        addMethod (@selector (becomeFirstResponder),          becomeFirstResponder,       "c@:");
1658        addMethod (@selector (resignFirstResponder),          resignFirstResponder,       "c@:");
1659        addMethod (@selector (acceptsFirstResponder),         acceptsFirstResponder,      "c@:");
1660
1661        addMethod (@selector (draggingEntered:),              draggingEntered,            @encode (NSDragOperation), "@:@");
1662        addMethod (@selector (draggingUpdated:),              draggingUpdated,            @encode (NSDragOperation), "@:@");
1663        addMethod (@selector (draggingEnded:),                draggingEnded,              "v@:@");
1664        addMethod (@selector (draggingExited:),               draggingExited,             "v@:@");
1665        addMethod (@selector (prepareForDragOperation:),      prepareForDragOperation,    "c@:@");
1666        addMethod (@selector (performDragOperation:),         performDragOperation,       "c@:@");
1667        addMethod (@selector (concludeDragOperation:),        concludeDragOperation,      "v@:@");
1668
1669        addMethod (@selector (paste:),                        paste,                      "v@:@");
1670        addMethod (@selector (copy:),                         copy,                       "v@:@");
1671        addMethod (@selector (cut:),                          cut,                        "v@:@");
1672
1673        addMethod (@selector (viewWillMoveToWindow:),         willMoveToWindow,           "v@:@");
1674
1675        addProtocol (@protocol (NSTextInput));
1676
1677        registerClass();
1678    }
1679
1680private:
1681    static NSViewComponentPeer* getOwner (id self)
1682    {
1683        return getIvar<NSViewComponentPeer*> (self, "owner");
1684    }
1685
1686    static void mouseDown (id self, SEL s, NSEvent* ev)
1687    {
1688        if (JUCEApplicationBase::isStandaloneApp())
1689            asyncMouseDown (self, s, ev);
1690        else
1691            // In some host situations, the host will stop modal loops from working
1692            // correctly if they're called from a mouse event, so we'll trigger
1693            // the event asynchronously..
1694            [self performSelectorOnMainThread: @selector (asyncMouseDown:)
1695                                   withObject: ev
1696                                waitUntilDone: NO];
1697    }
1698
1699    static void mouseUp (id self, SEL s, NSEvent* ev)
1700    {
1701        if (JUCEApplicationBase::isStandaloneApp())
1702            asyncMouseUp (self, s, ev);
1703        else
1704            // In some host situations, the host will stop modal loops from working
1705            // correctly if they're called from a mouse event, so we'll trigger
1706            // the event asynchronously..
1707            [self performSelectorOnMainThread: @selector (asyncMouseUp:)
1708                                   withObject: ev
1709                                waitUntilDone: NO];
1710    }
1711
1712    static void asyncMouseDown   (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseDown  (ev); }
1713    static void asyncMouseUp     (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseUp    (ev); }
1714    static void mouseDragged     (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseDrag  (ev); }
1715    static void mouseMoved       (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseMove  (ev); }
1716    static void mouseEntered     (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseEnter (ev); }
1717    static void mouseExited      (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseExit  (ev); }
1718    static void scrollWheel      (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMouseWheel (ev); }
1719    static void magnify          (id self, SEL, NSEvent* ev)   { if (auto* p = getOwner (self)) p->redirectMagnify    (ev); }
1720    static void copy             (id self, SEL, NSObject* s)   { if (auto* p = getOwner (self)) p->redirectCopy       (s);  }
1721    static void paste            (id self, SEL, NSObject* s)   { if (auto* p = getOwner (self)) p->redirectPaste      (s);  }
1722    static void cut              (id self, SEL, NSObject* s)   { if (auto* p = getOwner (self)) p->redirectCut        (s);  }
1723    static void willMoveToWindow (id self, SEL, NSWindow* w)   { if (auto* p = getOwner (self)) p->redirectWillMoveToWindow (w); }
1724
1725    static BOOL acceptsFirstMouse (id, SEL, NSEvent*)          { return YES; }
1726    static BOOL wantsDefaultClipping (id, SEL)                 { return YES; } // (this is the default, but may want to customise it in future)
1727    static BOOL worksWhenModal (id self, SEL)                  { if (auto* p = getOwner (self)) return p->worksWhenModal(); return NO; }
1728
1729    static void drawRect (id self, SEL, NSRect r)              { if (auto* p = getOwner (self)) p->drawRect (r); }
1730    static void frameChanged (id self, SEL, NSNotification*)   { if (auto* p = getOwner (self)) p->redirectMovedOrResized(); }
1731    static void viewDidMoveToWindow (id self, SEL)             { if (auto* p = getOwner (self)) p->viewMovedToWindow(); }
1732
1733    static void windowWillMiniaturize (id self, SEL, NSNotification*)
1734    {
1735        if (auto* p = getOwner (self))
1736        {
1737            if (p->isAlwaysOnTop)
1738            {
1739                // there is a bug when restoring minimised always on top windows so we need
1740                // to remove this behaviour before minimising and restore it afterwards
1741                p->setAlwaysOnTop (false);
1742                p->wasAlwaysOnTop = true;
1743            }
1744        }
1745    }
1746
1747    static void windowDidDeminiaturize (id self, SEL, NSNotification*)
1748    {
1749        if (auto* p = getOwner (self))
1750        {
1751            if (p->wasAlwaysOnTop)
1752                p->setAlwaysOnTop (true);
1753
1754            p->redirectMovedOrResized();
1755        }
1756    }
1757
1758    static BOOL isOpaque (id self, SEL)
1759    {
1760        auto* owner = getOwner (self);
1761        return owner == nullptr || owner->getComponent().isOpaque();
1762    }
1763
1764    //==============================================================================
1765    static void keyDown (id self, SEL, NSEvent* ev)
1766    {
1767        if (auto* owner = getOwner (self))
1768        {
1769            auto* target = owner->findCurrentTextInputTarget();
1770            owner->textWasInserted = false;
1771
1772            if (target != nullptr)
1773                [(NSView*) self interpretKeyEvents: [NSArray arrayWithObject: ev]];
1774            else
1775                owner->stringBeingComposed.clear();
1776
1777            if (! (owner->textWasInserted || owner->redirectKeyDown (ev)))
1778                sendSuperclassMessage<void> (self, @selector (keyDown:), ev);
1779        }
1780    }
1781
1782    static void keyUp (id self, SEL, NSEvent* ev)
1783    {
1784        auto* owner = getOwner (self);
1785
1786        if (! owner->redirectKeyUp (ev))
1787            sendSuperclassMessage<void> (self, @selector (keyUp:), ev);
1788    }
1789
1790    //==============================================================================
1791    static void insertText (id self, SEL, id aString)
1792    {
1793        // This commits multi-byte text when return is pressed, or after every keypress for western keyboards
1794        if (auto* owner = getOwner (self))
1795        {
1796            NSString* newText = [aString isKindOfClass: [NSAttributedString class]] ? [aString string] : aString;
1797
1798            if ([newText length] > 0)
1799            {
1800                if (auto* target = owner->findCurrentTextInputTarget())
1801                {
1802                    target->insertTextAtCaret (nsStringToJuce (newText));
1803                    owner->textWasInserted = true;
1804                }
1805            }
1806
1807            owner->stringBeingComposed.clear();
1808        }
1809    }
1810
1811    static void doCommandBySelector (id, SEL, SEL) {}
1812
1813    static void setMarkedText (id self, SEL, id aString, NSRange)
1814    {
1815        if (auto* owner = getOwner (self))
1816        {
1817            owner->stringBeingComposed = nsStringToJuce ([aString isKindOfClass: [NSAttributedString class]]
1818                                                           ? [aString string] : aString);
1819
1820            if (auto* target = owner->findCurrentTextInputTarget())
1821            {
1822                auto currentHighlight = target->getHighlightedRegion();
1823                target->insertTextAtCaret (owner->stringBeingComposed);
1824                target->setHighlightedRegion (currentHighlight.withLength (owner->stringBeingComposed.length()));
1825                owner->textWasInserted = true;
1826            }
1827        }
1828    }
1829
1830    static void unmarkText (id self, SEL)
1831    {
1832        if (auto* owner = getOwner (self))
1833        {
1834            if (owner->stringBeingComposed.isNotEmpty())
1835            {
1836                if (auto* target = owner->findCurrentTextInputTarget())
1837                {
1838                    target->insertTextAtCaret (owner->stringBeingComposed);
1839                    owner->textWasInserted = true;
1840                }
1841
1842                owner->stringBeingComposed.clear();
1843            }
1844        }
1845    }
1846
1847    static BOOL hasMarkedText (id self, SEL)
1848    {
1849        auto* owner = getOwner (self);
1850        return owner != nullptr && owner->stringBeingComposed.isNotEmpty();
1851    }
1852
1853    static long conversationIdentifier (id self, SEL)
1854    {
1855        return (long) (pointer_sized_int) self;
1856    }
1857
1858    static NSAttributedString* attributedSubstringFromRange (id self, SEL, NSRange theRange)
1859    {
1860        if (auto* owner = getOwner (self))
1861        {
1862            if (auto* target = owner->findCurrentTextInputTarget())
1863            {
1864                Range<int> r ((int) theRange.location,
1865                              (int) (theRange.location + theRange.length));
1866
1867                return [[[NSAttributedString alloc] initWithString: juceStringToNS (target->getTextInRange (r))] autorelease];
1868            }
1869        }
1870
1871        return nil;
1872    }
1873
1874    static NSRange markedRange (id self, SEL)
1875    {
1876        if (auto* owner = getOwner (self))
1877            if (owner->stringBeingComposed.isNotEmpty())
1878                return NSMakeRange (0, (NSUInteger) owner->stringBeingComposed.length());
1879
1880        return NSMakeRange (NSNotFound, 0);
1881    }
1882
1883    static NSRange selectedRange (id self, SEL)
1884    {
1885        if (auto* owner = getOwner (self))
1886        {
1887            if (auto* target = owner->findCurrentTextInputTarget())
1888            {
1889                auto highlight = target->getHighlightedRegion();
1890
1891                if (! highlight.isEmpty())
1892                    return NSMakeRange ((NSUInteger) highlight.getStart(),
1893                                        (NSUInteger) highlight.getLength());
1894            }
1895        }
1896
1897        return NSMakeRange (NSNotFound, 0);
1898    }
1899
1900    static NSRect firstRectForCharacterRange (id self, SEL, NSRange)
1901    {
1902        if (auto* owner = getOwner (self))
1903            if (auto* comp = dynamic_cast<Component*> (owner->findCurrentTextInputTarget()))
1904                return flippedScreenRect (makeNSRect (comp->getScreenBounds()));
1905
1906        return NSZeroRect;
1907    }
1908
1909    static NSUInteger characterIndexForPoint (id, SEL, NSPoint)     { return NSNotFound; }
1910    static NSArray* validAttributesForMarkedText (id, SEL)          { return [NSArray array]; }
1911
1912    //==============================================================================
1913    static void flagsChanged (id self, SEL, NSEvent* ev)
1914    {
1915        if (auto* owner = getOwner (self))
1916            owner->redirectModKeyChange (ev);
1917    }
1918
1919    static BOOL becomeFirstResponder (id self, SEL)
1920    {
1921        if (auto* owner = getOwner (self))
1922            owner->viewFocusGain();
1923
1924        return YES;
1925    }
1926
1927    static BOOL resignFirstResponder (id self, SEL)
1928    {
1929        if (auto* owner = getOwner (self))
1930            owner->viewFocusLoss();
1931
1932        return YES;
1933    }
1934
1935    static BOOL acceptsFirstResponder (id self, SEL)
1936    {
1937        auto* owner = getOwner (self);
1938        return owner != nullptr && owner->canBecomeKeyWindow();
1939    }
1940
1941    //==============================================================================
1942    static NSDragOperation draggingEntered (id self, SEL s, id<NSDraggingInfo> sender)
1943    {
1944        return draggingUpdated (self, s, sender);
1945    }
1946
1947    static NSDragOperation draggingUpdated (id self, SEL, id<NSDraggingInfo> sender)
1948    {
1949        if (auto* owner = getOwner (self))
1950            if (owner->sendDragCallback (0, sender))
1951                return NSDragOperationGeneric;
1952
1953        return NSDragOperationNone;
1954    }
1955
1956    static void draggingEnded (id self, SEL s, id<NSDraggingInfo> sender)
1957    {
1958        draggingExited (self, s, sender);
1959    }
1960
1961    static void draggingExited (id self, SEL, id<NSDraggingInfo> sender)
1962    {
1963        if (auto* owner = getOwner (self))
1964            owner->sendDragCallback (1, sender);
1965    }
1966
1967    static BOOL prepareForDragOperation (id, SEL, id<NSDraggingInfo>)
1968    {
1969        return YES;
1970    }
1971
1972    static BOOL performDragOperation (id self, SEL, id<NSDraggingInfo> sender)
1973    {
1974        auto* owner = getOwner (self);
1975        return owner != nullptr && owner->sendDragCallback (2, sender);
1976    }
1977
1978    static void concludeDragOperation (id, SEL, id<NSDraggingInfo>) {}
1979};
1980
1981//==============================================================================
1982struct JuceNSWindowClass   : public ObjCClass<NSWindow>
1983{
1984    JuceNSWindowClass()  : ObjCClass<NSWindow> ("JUCEWindow_")
1985    {
1986        addIvar<NSViewComponentPeer*> ("owner");
1987
1988        addMethod (@selector (canBecomeKeyWindow),                  canBecomeKeyWindow,        "c@:");
1989        addMethod (@selector (canBecomeMainWindow),                 canBecomeMainWindow,       "c@:");
1990        addMethod (@selector (becomeKeyWindow),                     becomeKeyWindow,           "v@:");
1991        addMethod (@selector (windowShouldClose:),                  windowShouldClose,         "c@:@");
1992        addMethod (@selector (constrainFrameRect:toScreen:),        constrainFrameRect,        @encode (NSRect), "@:",  @encode (NSRect), "@");
1993        addMethod (@selector (windowWillResize:toSize:),            windowWillResize,          @encode (NSSize), "@:@", @encode (NSSize));
1994        addMethod (@selector (windowDidExitFullScreen:),            windowDidExitFullScreen,   "v@:@");
1995        addMethod (@selector (windowWillEnterFullScreen:),          windowWillEnterFullScreen, "v@:@");
1996        addMethod (@selector (zoom:),                               zoom,                      "v@:@");
1997        addMethod (@selector (windowWillMove:),                     windowWillMove,            "v@:@");
1998        addMethod (@selector (windowWillStartLiveResize:),          windowWillStartLiveResize, "v@:@");
1999        addMethod (@selector (windowDidEndLiveResize:),             windowDidEndLiveResize,    "v@:@");
2000        addMethod (@selector (window:shouldPopUpDocumentPathMenu:), shouldPopUpPathMenu, "B@:@", @encode (NSMenu*));
2001
2002        addMethod (@selector (window:shouldDragDocumentWithEvent:from:withPasteboard:),
2003                   shouldAllowIconDrag, "B@:@", @encode (NSEvent*), @encode (NSPoint), @encode (NSPasteboard*));
2004
2005        addProtocol (@protocol (NSWindowDelegate));
2006
2007        registerClass();
2008    }
2009
2010private:
2011    static NSViewComponentPeer* getOwner (id self)
2012    {
2013        return getIvar<NSViewComponentPeer*> (self, "owner");
2014    }
2015
2016    //==============================================================================
2017    static BOOL canBecomeKeyWindow (id self, SEL)
2018    {
2019        auto* owner = getOwner (self);
2020
2021        return owner != nullptr
2022                && owner->canBecomeKeyWindow()
2023                && ! owner->isBlockedByModalComponent();
2024    }
2025
2026    static BOOL canBecomeMainWindow (id self, SEL)
2027    {
2028        auto* owner = getOwner (self);
2029
2030        return owner != nullptr
2031                && owner->canBecomeMainWindow()
2032                && ! owner->isBlockedByModalComponent();
2033    }
2034
2035    static void becomeKeyWindow (id self, SEL)
2036    {
2037        sendSuperclassMessage<void> (self, @selector (becomeKeyWindow));
2038
2039        if (auto* owner = getOwner (self))
2040        {
2041            if (owner->canBecomeKeyWindow())
2042            {
2043                owner->becomeKeyWindow();
2044                return;
2045            }
2046
2047            // this fixes a bug causing hidden windows to sometimes become visible when the app regains focus
2048            if (! owner->getComponent().isVisible())
2049                [(NSWindow*) self orderOut: nil];
2050        }
2051    }
2052
2053    static BOOL windowShouldClose (id self, SEL, id /*window*/)
2054    {
2055        auto* owner = getOwner (self);
2056        return owner == nullptr || owner->windowShouldClose();
2057    }
2058
2059    static NSRect constrainFrameRect (id self, SEL, NSRect frameRect, NSScreen* screen)
2060    {
2061        if (auto* owner = getOwner (self))
2062        {
2063            frameRect = sendSuperclassMessage<NSRect, NSRect, NSScreen*> (self, @selector (constrainFrameRect:toScreen:),
2064                                                                          frameRect, screen);
2065
2066            frameRect = owner->constrainRect (frameRect);
2067        }
2068
2069        return frameRect;
2070    }
2071
2072    static NSSize windowWillResize (id self, SEL, NSWindow*, NSSize proposedFrameSize)
2073    {
2074        auto* owner = getOwner (self);
2075
2076        if (owner == nullptr || owner->isZooming)
2077            return proposedFrameSize;
2078
2079        NSRect frameRect = [(NSWindow*) self frame];
2080        frameRect.origin.y -= proposedFrameSize.height - frameRect.size.height;
2081        frameRect.size = proposedFrameSize;
2082
2083        frameRect = owner->constrainRect (frameRect);
2084
2085        if (owner->hasNativeTitleBar())
2086            owner->sendModalInputAttemptIfBlocked();
2087
2088        return frameRect.size;
2089    }
2090
2091    static void windowDidExitFullScreen (id self, SEL, NSNotification*)
2092    {
2093        if (auto* owner = getOwner (self))
2094            owner->resetWindowPresentation();
2095    }
2096
2097    static void windowWillEnterFullScreen (id self, SEL, NSNotification*)
2098    {
2099        if (SystemStats::getOperatingSystemType() <= SystemStats::MacOSX_10_9)
2100            return;
2101
2102        if (auto* owner = getOwner (self))
2103            if (owner->hasNativeTitleBar() && (owner->getStyleFlags() & ComponentPeer::windowIsResizable) == 0)
2104                [owner->window setStyleMask: NSWindowStyleMaskBorderless];
2105    }
2106
2107    static void zoom (id self, SEL, id sender)
2108    {
2109        if (auto* owner = getOwner (self))
2110        {
2111            {
2112                const ScopedValueSetter<bool> svs (owner->isZooming, true);
2113                sendSuperclassMessage<void> (self, @selector (zoom:), sender);
2114            }
2115
2116            owner->redirectMovedOrResized();
2117        }
2118    }
2119
2120    static void windowWillMove (id self, SEL, NSNotification*)
2121    {
2122        if (auto* owner = getOwner (self))
2123            if (owner->hasNativeTitleBar())
2124                owner->sendModalInputAttemptIfBlocked();
2125    }
2126
2127    static void windowWillStartLiveResize (id self, SEL, NSNotification*)
2128    {
2129        if (auto* owner = getOwner (self))
2130            owner->liveResizingStart();
2131    }
2132
2133    static void windowDidEndLiveResize (id self, SEL, NSNotification*)
2134    {
2135        if (auto* owner = getOwner (self))
2136            owner->liveResizingEnd();
2137    }
2138
2139    static bool shouldPopUpPathMenu (id self, SEL, id /*window*/, NSMenu*)
2140    {
2141        if (auto* owner = getOwner (self))
2142            return owner->windowRepresentsFile;
2143
2144        return false;
2145    }
2146
2147    static bool shouldAllowIconDrag (id self, SEL, id /*window*/, NSEvent*, NSPoint, NSPasteboard*)
2148    {
2149        if (auto* owner = getOwner (self))
2150            return owner->windowRepresentsFile;
2151
2152        return false;
2153    }
2154};
2155
2156NSView* NSViewComponentPeer::createViewInstance()
2157{
2158    static JuceNSViewClass cls;
2159    return cls.createInstance();
2160}
2161
2162NSWindow* NSViewComponentPeer::createWindowInstance()
2163{
2164    static JuceNSWindowClass cls;
2165    return cls.createInstance();
2166}
2167
2168
2169//==============================================================================
2170ComponentPeer* NSViewComponentPeer::currentlyFocusedPeer = nullptr;
2171Array<int> NSViewComponentPeer::keysCurrentlyDown;
2172
2173//==============================================================================
2174bool KeyPress::isKeyCurrentlyDown (int keyCode)
2175{
2176    if (NSViewComponentPeer::keysCurrentlyDown.contains (keyCode))
2177        return true;
2178
2179    if (keyCode >= 'A' && keyCode <= 'Z'
2180         && NSViewComponentPeer::keysCurrentlyDown.contains ((int) CharacterFunctions::toLowerCase ((juce_wchar) keyCode)))
2181        return true;
2182
2183    if (keyCode >= 'a' && keyCode <= 'z'
2184         && NSViewComponentPeer::keysCurrentlyDown.contains ((int) CharacterFunctions::toUpperCase ((juce_wchar) keyCode)))
2185        return true;
2186
2187    return false;
2188}
2189
2190//==============================================================================
2191bool MouseInputSource::SourceList::addSource()
2192{
2193    if (sources.size() == 0)
2194    {
2195        addSource (0, MouseInputSource::InputSourceType::mouse);
2196        return true;
2197    }
2198
2199    return false;
2200}
2201
2202bool MouseInputSource::SourceList::canUseTouch()
2203{
2204    return false;
2205}
2206
2207//==============================================================================
2208void Desktop::setKioskComponent (Component* kioskComp, bool shouldBeEnabled, bool allowMenusAndBars)
2209{
2210    auto* peer = dynamic_cast<NSViewComponentPeer*> (kioskComp->getPeer());
2211    jassert (peer != nullptr); // (this should have been checked by the caller)
2212
2213    if (peer->hasNativeTitleBar()
2214          && [peer->window respondsToSelector: @selector (toggleFullScreen:)])
2215    {
2216        if (shouldBeEnabled && ! allowMenusAndBars)
2217            [NSApp setPresentationOptions: NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar];
2218        else if (! shouldBeEnabled)
2219            [NSApp setPresentationOptions: NSApplicationPresentationDefault];
2220
2221        [peer->window performSelector: @selector (toggleFullScreen:) withObject: nil];
2222    }
2223    else
2224    {
2225        if (shouldBeEnabled)
2226        {
2227            if (peer->hasNativeTitleBar())
2228                [peer->window setStyleMask: NSWindowStyleMaskBorderless];
2229
2230            [NSApp setPresentationOptions: (allowMenusAndBars ? (NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)
2231                                                              : (NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar))];
2232
2233            kioskComp->setBounds (getDisplays().findDisplayForRect (kioskComp->getScreenBounds()).totalArea);
2234            peer->becomeKeyWindow();
2235        }
2236        else
2237        {
2238            peer->resetWindowPresentation();
2239        }
2240    }
2241}
2242
2243void Desktop::allowedOrientationsChanged() {}
2244
2245//==============================================================================
2246ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo)
2247{
2248    return new NSViewComponentPeer (*this, styleFlags, (NSView*) windowToAttachTo);
2249}
2250
2251//==============================================================================
2252const int KeyPress::spaceKey        = ' ';
2253const int KeyPress::returnKey       = 0x0d;
2254const int KeyPress::escapeKey       = 0x1b;
2255const int KeyPress::backspaceKey    = 0x7f;
2256const int KeyPress::leftKey         = NSLeftArrowFunctionKey;
2257const int KeyPress::rightKey        = NSRightArrowFunctionKey;
2258const int KeyPress::upKey           = NSUpArrowFunctionKey;
2259const int KeyPress::downKey         = NSDownArrowFunctionKey;
2260const int KeyPress::pageUpKey       = NSPageUpFunctionKey;
2261const int KeyPress::pageDownKey     = NSPageDownFunctionKey;
2262const int KeyPress::endKey          = NSEndFunctionKey;
2263const int KeyPress::homeKey         = NSHomeFunctionKey;
2264const int KeyPress::deleteKey       = NSDeleteFunctionKey;
2265const int KeyPress::insertKey       = -1;
2266const int KeyPress::tabKey          = 9;
2267const int KeyPress::F1Key           = NSF1FunctionKey;
2268const int KeyPress::F2Key           = NSF2FunctionKey;
2269const int KeyPress::F3Key           = NSF3FunctionKey;
2270const int KeyPress::F4Key           = NSF4FunctionKey;
2271const int KeyPress::F5Key           = NSF5FunctionKey;
2272const int KeyPress::F6Key           = NSF6FunctionKey;
2273const int KeyPress::F7Key           = NSF7FunctionKey;
2274const int KeyPress::F8Key           = NSF8FunctionKey;
2275const int KeyPress::F9Key           = NSF9FunctionKey;
2276const int KeyPress::F10Key          = NSF10FunctionKey;
2277const int KeyPress::F11Key          = NSF11FunctionKey;
2278const int KeyPress::F12Key          = NSF12FunctionKey;
2279const int KeyPress::F13Key          = NSF13FunctionKey;
2280const int KeyPress::F14Key          = NSF14FunctionKey;
2281const int KeyPress::F15Key          = NSF15FunctionKey;
2282const int KeyPress::F16Key          = NSF16FunctionKey;
2283const int KeyPress::F17Key          = NSF17FunctionKey;
2284const int KeyPress::F18Key          = NSF18FunctionKey;
2285const int KeyPress::F19Key          = NSF19FunctionKey;
2286const int KeyPress::F20Key          = NSF20FunctionKey;
2287const int KeyPress::F21Key          = NSF21FunctionKey;
2288const int KeyPress::F22Key          = NSF22FunctionKey;
2289const int KeyPress::F23Key          = NSF23FunctionKey;
2290const int KeyPress::F24Key          = NSF24FunctionKey;
2291const int KeyPress::F25Key          = NSF25FunctionKey;
2292const int KeyPress::F26Key          = NSF26FunctionKey;
2293const int KeyPress::F27Key          = NSF27FunctionKey;
2294const int KeyPress::F28Key          = NSF28FunctionKey;
2295const int KeyPress::F29Key          = NSF29FunctionKey;
2296const int KeyPress::F30Key          = NSF30FunctionKey;
2297const int KeyPress::F31Key          = NSF31FunctionKey;
2298const int KeyPress::F32Key          = NSF32FunctionKey;
2299const int KeyPress::F33Key          = NSF33FunctionKey;
2300const int KeyPress::F34Key          = NSF34FunctionKey;
2301const int KeyPress::F35Key          = NSF35FunctionKey;
2302
2303const int KeyPress::numberPad0      = 0x30020;
2304const int KeyPress::numberPad1      = 0x30021;
2305const int KeyPress::numberPad2      = 0x30022;
2306const int KeyPress::numberPad3      = 0x30023;
2307const int KeyPress::numberPad4      = 0x30024;
2308const int KeyPress::numberPad5      = 0x30025;
2309const int KeyPress::numberPad6      = 0x30026;
2310const int KeyPress::numberPad7      = 0x30027;
2311const int KeyPress::numberPad8      = 0x30028;
2312const int KeyPress::numberPad9      = 0x30029;
2313const int KeyPress::numberPadAdd            = 0x3002a;
2314const int KeyPress::numberPadSubtract       = 0x3002b;
2315const int KeyPress::numberPadMultiply       = 0x3002c;
2316const int KeyPress::numberPadDivide         = 0x3002d;
2317const int KeyPress::numberPadSeparator      = 0x3002e;
2318const int KeyPress::numberPadDecimalPoint   = 0x3002f;
2319const int KeyPress::numberPadEquals         = 0x30030;
2320const int KeyPress::numberPadDelete         = 0x30031;
2321const int KeyPress::playKey         = 0x30000;
2322const int KeyPress::stopKey         = 0x30001;
2323const int KeyPress::fastForwardKey  = 0x30002;
2324const int KeyPress::rewindKey       = 0x30003;
2325
2326} // namespace juce
2327