From 85226c33d47b4cc472dca99c1ae45facd59e5714 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 12 Jul 2021 19:42:06 +0100 Subject: [PATCH] Mac MainMenu: Allow commands without modifiers to be passed to peer Ideally, we want to pass shortcut keys to the component to handle, and only fall back to invoking a menu item if the component was unable to handle the keyboard event, or if the action was triggered by clicking/selecting an item in the menu itself. The old implementation tried to work out whether the action was triggered by a shortcut by checking the event's characters and modifiers. This method was inaccurate, because some shortcuts (such as arrow keys) may add unexpected numpad/function modifier flags. We now try handling shortcut keys directly in the peer, and pass events up to the superclass (which will forward them to the main menu) if the event could not be handled. This commit also adjusts some Objective-C method signatures to use the correct string encoding for the BOOL type. --- .../AU/juce_AUv3_Wrapper.mm | 4 +-- .../native/juce_mac_MessageManager.mm | 4 +-- .../native/juce_mac_MainMenu.mm | 35 +------------------ .../native/juce_mac_NSViewComponentPeer.mm | 34 ++++++++++++++++-- .../native/juce_mac_PushNotifications.cpp | 2 +- 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index 014c907bf1..d955d499c4 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -245,7 +245,7 @@ private: addMethod (@selector (inputBusses), getInputBusses, "@@:"); addMethod (@selector (outputBusses), getOutputBusses, "@@:"); addMethod (@selector (channelCapabilities), getChannelCapabilities, "@@:"); - addMethod (@selector (shouldChangeToFormat:forBus:), shouldChangeToFormat, "B@:@@"); + addMethod (@selector (shouldChangeToFormat:forBus:), shouldChangeToFormat, "c@:@@"); //============================================================================== addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger), "@:"); @@ -262,7 +262,7 @@ private: addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL)); addMethod (@selector (shouldBypassEffect), getShouldBypassEffect, @encode (BOOL), "@:"); addMethod (@selector (setShouldBypassEffect:), setShouldBypassEffect, "v@:", @encode (BOOL)); - addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); + addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "c@:^@"); addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); //============================================================================== diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm index 8bc3767bf4..8db6c39ae7 100644 --- a/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/modules/juce_events/native/juce_mac_MessageManager.mm @@ -157,13 +157,13 @@ private: static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) { if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (true); + menuTrackingChangedCallback (true); } static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) { if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (false); + menuTrackingChangedCallback (false); } static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) diff --git a/modules/juce_gui_basics/native/juce_mac_MainMenu.mm b/modules/juce_gui_basics/native/juce_mac_MainMenu.mm index 8d6c1bbc67..f081a7b298 100644 --- a/modules/juce_gui_basics/native/juce_mac_MainMenu.mm +++ b/modules/juce_gui_basics/native/juce_mac_MainMenu.mm @@ -516,7 +516,7 @@ private: //============================================================================== struct JuceMenuCallbackClass : public ObjCClass { - JuceMenuCallbackClass() : ObjCClass ("JUCEMainMenu_") + JuceMenuCallbackClass() : ObjCClass ("JUCEMainMenu_") { addIvar ("owner"); @@ -537,41 +537,8 @@ private: } private: - /* Returns true if and only if the peer handles the event. */ - static bool tryPassingKeyEventToPeer (NSMenuItem* item) - { - auto* e = [NSApp currentEvent]; - - if ([e type] != NSEventTypeKeyDown && [e type] != NSEventTypeKeyUp) - return false; - - const auto triggeredByShortcut = [[e charactersIgnoringModifiers] isEqualToString: [item keyEquivalent]] - && ([e modifierFlags] & ~(NSUInteger) 0xFFFF) == [item keyEquivalentModifierMask]; - - if (! triggeredByShortcut) - return false; - - if (auto* focused = juce::Component::getCurrentlyFocusedComponent()) - { - if (auto* peer = dynamic_cast (focused->getPeer())) - { - if ([e type] == NSEventTypeKeyDown) - peer->redirectKeyDown (e); - else - peer->redirectKeyUp (e); - - return true; - } - } - - return false; - } - static void menuItemInvoked (id self, SEL, NSMenuItem* item) { - if (tryPassingKeyEventToPeer (item)) - return; - if (auto* juceItem = getJuceClassFromNSObject ([item representedObject])) getIvar (self, "owner")->invoke (*juceItem, static_cast ([item tag])); } diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index e1c3473c98..839584d187 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -1795,6 +1795,8 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper> addMethod (NSViewComponentPeer::frameChangedSelector, frameChanged, "v@:@"); addMethod (NSViewComponentPeer::becomeKeySelector, becomeKey, "v@:@"); + addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, "c@:@"); + addProtocol (@protocol (NSTextInput)); registerClass(); @@ -2156,6 +2158,34 @@ private: return sendSuperclassMessage (self, @selector (accessibilityAttributeValue:), attribute); } + + static bool tryPassingKeyEventToPeer (NSEvent* e) + { + if ([e type] != NSEventTypeKeyDown && [e type] != NSEventTypeKeyUp) + return false; + + if (auto* focused = Component::getCurrentlyFocusedComponent()) + { + if (auto* peer = dynamic_cast (focused->getPeer())) + { + return [e type] == NSEventTypeKeyDown ? peer->redirectKeyDown (e) + : peer->redirectKeyUp (e); + } + } + + return false; + } + + static BOOL performKeyEquivalent (id self, SEL s, NSEvent* event) + { + // We try passing shortcut keys to the currently focused component first. + // If the component doesn't want the event, we'll fall back to the superclass + // implementation, which will pass the event to the main menu. + if (tryPassingKeyEventToPeer (event)) + return YES; + + return sendSuperclassMessage (self, s, event); + } }; //============================================================================== @@ -2175,7 +2205,7 @@ struct JuceNSWindowClass : public NSViewComponentPeerWrapper