From 76cafd10d01700198d7b316bf5c028e1e8339581 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Tue, 5 Nov 2019 12:34:12 +0000 Subject: [PATCH] macOS: Fixed some issues targeting older operating systems --- .../native/juce_mac_Windowing.mm | 6 + .../native/juce_mac_SystemTrayIcon.cpp | 323 ++++++++++++++---- 2 files changed, 264 insertions(+), 65 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm index b40684f5df..ddc082a903 100644 --- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -518,8 +518,14 @@ bool juce_areThereAnyAlwaysOnTopWindows() static void selectImageForDrawing (const Image& image) { [NSGraphicsContext saveGraphicsState]; + + #if (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithCGContext: juce_getImageContext (image) flipped: false]]; + #else + [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image) + flipped: false]]; + #endif } static void releaseImageAfterDrawing() diff --git a/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp b/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp index 1a0feedd3f..3086162b0a 100644 --- a/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp +++ b/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp @@ -27,20 +27,82 @@ namespace juce { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +#if JUCE_CLANG && defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14 + #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate); -class SystemTrayIconComponent::Pimpl : private Timer +//============================================================================== +struct StatusItemContainer : public Timer { -public: //============================================================================== - Pimpl (SystemTrayIconComponent& iconComp, const Image& im) + StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im) : owner (iconComp), statusIcon (imageToNSImage (im)) + { + } + + virtual void configureIcon() = 0; + virtual void setHighlighted (bool shouldHighlight) = 0; + + //============================================================================== + void setIconSize() + { + [statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)]; + } + + void updateIcon (const Image& newImage) + { + statusIcon.reset (imageToNSImage (newImage)); + setIconSize(); + configureIcon(); + } + + void showMenu (const PopupMenu& menu) + { + if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true)) + { + setHighlighted (true); + stopTimer(); + + // There's currently no good alternative to this. + [statusItem.get() popUpStatusItemMenu: m]; + + startTimer (1); + } + } + + //============================================================================== + void timerCallback() override + { + stopTimer(); + setHighlighted (false); + } + + //============================================================================== + SystemTrayIconComponent& owner; + + std::unique_ptr statusItem; + std::unique_ptr statusIcon; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer) +}; + +//============================================================================== +struct ButtonBasedStatusItem : public StatusItemContainer +{ + //============================================================================== + ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im) + : StatusItemContainer (iconComp, im) { static ButtonEventForwarderClass cls; eventForwarder.reset ([cls.createInstance() init]); ButtonEventForwarderClass::setOwner (eventForwarder.get(), this); + setIconSize(); configureIcon(); statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]); @@ -55,50 +117,17 @@ public: #endif } - //============================================================================== - void updateIcon (const Image& newImage) + void configureIcon() override { - statusIcon.reset (imageToNSImage (newImage)); - configureIcon(); + [statusIcon.get() setTemplate: true]; [statusItem.get() button].image = statusIcon.get(); } - void setHighlighted (bool shouldHighlight) + void setHighlighted (bool shouldHighlight) override { [[statusItem.get() button] setHighlighted: shouldHighlight]; } - void showMenu (const PopupMenu& menu) - { - if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true)) - { - setHighlighted (true); - stopTimer(); - - // There's currently no good alternative to this... - #if JUCE_CLANG && ! (defined (MAC_OS_X_VERSION_10_16) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_16) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - #define JUCE_DEPRECATION_IGNORED 1 - #endif - - [statusItem.get() popUpStatusItemMenu: m]; - - #if JUCE_DEPRECATION_IGNORED - #pragma clang diagnostic pop - #undef JUCE_DEPRECATION_IGNORED - #endif - - startTimer (1); - } - } - - //============================================================================== - NSStatusItem* getStatusItem() - { - return statusItem.get(); - } - //============================================================================== void handleEvent() { @@ -152,35 +181,21 @@ public: } } -private: - //============================================================================== - void configureIcon() - { - [statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)]; - [statusIcon.get() setTemplate: true]; - } - - void timerCallback() override - { - stopTimer(); - setHighlighted (false); - } - //============================================================================== class ButtonEventForwarderClass : public ObjCClass { public: ButtonEventForwarderClass() : ObjCClass ("JUCEButtonEventForwarderClass_") { - addIvar ("owner"); + addIvar ("owner"); addMethod (@selector (handleEvent:), handleEvent, "v@:@"); registerClass(); } - static Pimpl* getOwner (id self) { return getIvar (self, "owner"); } - static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); } + static ButtonBasedStatusItem* getOwner (id self) { return getIvar (self, "owner"); } + static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); } private: static void handleEvent (id self, SEL, id) @@ -191,15 +206,191 @@ private: }; //============================================================================== - SystemTrayIconComponent& owner; - std::unique_ptr statusItem; std::unique_ptr eventForwarder; - std::unique_ptr statusIcon; +}; + +//============================================================================== +struct ViewBasedStatusItem : public StatusItemContainer +{ + //============================================================================== + ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im) + : StatusItemContainer (iconComp, im) + { + static SystemTrayViewClass cls; + view.reset ([cls.createInstance() init]); + SystemTrayViewClass::setOwner (view.get(), this); + SystemTrayViewClass::setImage (view.get(), statusIcon.get()); + + setIconSize(); + + statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]); + [statusItem.get() setView: view.get()]; + + SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr); + + [[NSNotificationCenter defaultCenter] addObserver: view.get() + selector: @selector (frameChanged:) + name: NSWindowDidMoveNotification + object: nil]; + } + + ~ViewBasedStatusItem() override + { + [[NSNotificationCenter defaultCenter] removeObserver: view.get()]; + [[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()]; + SystemTrayViewClass::setOwner (view.get(), nullptr); + SystemTrayViewClass::setImage (view.get(), nil); + } + + void configureIcon() override + { + SystemTrayViewClass::setImage (view.get(), statusIcon.get()); + [statusItem.get() setView: view.get()]; + } + + void setHighlighted (bool shouldHighlight) override + { + isHighlighted = shouldHighlight; + [view.get() setNeedsDisplay: true]; + } + + //============================================================================== + void handleStatusItemAction (NSEvent* e) + { + NSEventType type = [e type]; + + const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp); + const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp); + + if (owner.isCurrentlyBlockedByAnotherModalComponent()) + { + if (isLeft || isRight) + if (auto* current = Component::getCurrentlyModalComponent()) + current->inputAttemptWhenModal(); + } + else + { + auto eventMods = ComponentPeer::getCurrentModifiersRealtime(); + + if (([e modifierFlags] & NSEventModifierFlagCommand) != 0) + eventMods = eventMods.withFlags (ModifierKeys::commandModifier); + + auto now = Time::getCurrentTime(); + auto mouseSource = Desktop::getInstance().getMainMouseSource(); + auto pressure = (float) e.pressure; + + if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up + { + setHighlighted (true); + startTimer (150); + + owner.mouseDown (MouseEvent (mouseSource, {}, + eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier + : ModifierKeys::rightButtonModifier), + pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + &owner, &owner, now, {}, now, 1, false)); + + owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + &owner, &owner, now, {}, now, 1, false)); + } + else if (type == NSEventTypeMouseMoved) + { + owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure, + MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation, + MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY, + &owner, &owner, now, {}, now, 1, false)); + } + } + } + + //============================================================================== + struct SystemTrayViewClass : public ObjCClass + { + SystemTrayViewClass() : ObjCClass ("JUCESystemTrayView_") + { + addIvar ("owner"); + addIvar ("image"); + + addMethod (@selector (mouseDown:), handleEventDown, "v@:@"); + addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@"); + addMethod (@selector (drawRect:), drawRect, "v@:@"); + addMethod (@selector (frameChanged:), frameChanged, "v@:@"); + + registerClass(); + } + + static ViewBasedStatusItem* getOwner (id self) { return getIvar (self, "owner"); } + static NSImage* getImage (id self) { return getIvar (self, "image"); } + static void setOwner (id self, ViewBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); } + static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); } + + static void frameChanged (id self, SEL, NSNotification*) + { + if (auto* owner = getOwner (self)) + { + NSRect r = [[[owner->statusItem.get() view] window] frame]; + NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame]; + r.origin.y = sr.size.height - r.origin.y - r.size.height; + owner->owner.setBounds (convertToRectInt (r)); + } + } + + private: + static void handleEventDown (id self, SEL, NSEvent* e) + { + if (auto* owner = getOwner (self)) + owner->handleStatusItemAction (e); + } + + static void drawRect (id self, SEL, NSRect) + { + NSRect bounds = [self bounds]; + + if (auto* owner = getOwner (self)) + [owner->statusItem.get() drawStatusBarBackgroundInRect: bounds + withHighlight: owner->isHighlighted]; + + if (NSImage* const im = getImage (self)) + { + NSSize imageSize = [im size]; + + [im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f), + bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f), + imageSize.width, imageSize.height) + fromRect: NSZeroRect + operation: NSCompositingOperationSourceOver + fraction: 1.0f]; + } + } + }; + + //============================================================================== + std::unique_ptr view; + bool isHighlighted = false; +}; + +//============================================================================== +class SystemTrayIconComponent::Pimpl +{ +public: + //============================================================================== + Pimpl (SystemTrayIconComponent& iconComp, const Image& im) + { + if (std::floor (NSFoundationVersionNumber) > NSFoundationVersionNumber10_10) + statusItemHolder = std::make_unique (iconComp, im); + else + statusItemHolder = std::make_unique (iconComp, im); + } + + //============================================================================== + std::unique_ptr statusItemHolder; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; - //============================================================================== void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage) { @@ -208,7 +399,7 @@ void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateI if (pimpl == nullptr) pimpl.reset (new Pimpl (*this, templateImage)); else - pimpl->updateIcon (templateImage); + pimpl->statusItemHolder->updateIcon (templateImage); } else { @@ -221,10 +412,10 @@ void SystemTrayIconComponent::setIconTooltip (const String&) // xxx not yet implemented! } -void SystemTrayIconComponent::setHighlighted (bool highlight) +void SystemTrayIconComponent::setHighlighted (bool shouldHighlight) { if (pimpl != nullptr) - pimpl->setHighlighted (highlight); + pimpl->statusItemHolder->setHighlighted (shouldHighlight); } void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/) @@ -239,13 +430,15 @@ void SystemTrayIconComponent::hideInfoBubble() void* SystemTrayIconComponent::getNativeHandle() const { - return pimpl != nullptr ? pimpl->getStatusItem() : nullptr; + return pimpl != nullptr ? pimpl->statusItemHolder->statusItem.get() : nullptr; } void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu) { if (pimpl != nullptr) - pimpl->showMenu (menu); + pimpl->statusItemHolder->showMenu (menu); } +#pragma clang diagnostic pop + } // namespace juce