diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qnsview_menus.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview_menus.mm | 114 |
1 files changed, 67 insertions, 47 deletions
diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm index 644f2139de..2840936975 100644 --- a/src/plugins/platforms/cocoa/qnsview_menus.mm +++ b/src/plugins/platforms/cocoa/qnsview_menus.mm @@ -9,23 +9,55 @@ #include "qcocoamenu.h" #include "qcocoamenubar.h" -static bool selectorIsCutCopyPaste(SEL selector) +@implementation QNSView (Menus) + +// Qt does not (yet) have a mechanism for propagating generic actions, +// so we can only support actions that originate from a QCocoaNSMenuItem, +// where we can forward the action by emitting QPlatformMenuItem::activated(). +// But waiting for forwardInvocation to check that the sender is a +// QCocoaNSMenuItem is too late, as AppKit has at that point chosen +// our view as the target for the action, and if we can't handle it +// the action will not propagate up the responder chain as it should. +// Instead, we hook in early in the process of determining the target +// via the supplementalTargetForAction API, and if we can support the +// action we forward it to a helper. The helper must be tied to the +// view, as the menu validation logic depends on the view's state. + +- (id)supplementalTargetForAction:(SEL)action sender:(id)sender { - return (selector == @selector(cut:) - || selector == @selector(copy:) - || selector == @selector(paste:) - || selector == @selector(selectAll:)); + qCDebug(lcQpaMenus) << "Resolving action target for" << action << "from" << sender << "via" << self; + + if (qt_objc_cast<QCocoaNSMenuItem *>(sender)) { + // The supplemental target must support the selector, but we + // determine so dynamically, so check here before continuing. + if ([self.menuHelper respondsToSelector:action]) + return self.menuHelper; + } else { + qCDebug(lcQpaMenus) << "Ignoring action for menu item we didn't create"; + } + + return [super supplementalTargetForAction:action sender:sender]; } -@interface QNSView (Menus) -- (void)qt_itemFired:(QCocoaNSMenuItem *)item; @end -@implementation QNSView (Menus) +@interface QNSViewMenuHelper () +@property (assign) QNSView* view; +@end + +@implementation QNSViewMenuHelper + +- (instancetype)initWithView:(QNSView *)theView +{ + if ((self = [super init])) + self.view = theView; + + return self; +} - (BOOL)validateMenuItem:(NSMenuItem*)item { - qCDebug(lcQpaMenus) << "Validating" << item << "for" << self; + qCDebug(lcQpaMenus) << "Validating" << item << "for" << self.view; auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); if (!nativeItem) @@ -53,7 +85,7 @@ static bool selectorIsCutCopyPaste(SEL selector) } if ((!menuWindow || menuWindow->window() != QGuiApplication::modalWindow()) - && (!menubar || menubar->cocoaWindow() != self.platformWindow)) + && (!menubar || menubar->cocoaWindow() != self.view.platformWindow)) return NO; } @@ -62,54 +94,42 @@ static bool selectorIsCutCopyPaste(SEL selector) - (BOOL)respondsToSelector:(SEL)selector { - // Not exactly true. Both copy: and selectAll: can work on non key views. - if (selectorIsCutCopyPaste(selector)) - return ([NSApp keyWindow] == self.window) && (self.window.firstResponder == self); + // See QCocoaMenuItem::resolveTargetAction() + + if (selector == @selector(cut:) + || selector == @selector(copy:) + || selector == @selector(paste:) + || selector == @selector(selectAll:)) { + // Not exactly true. Both copy: and selectAll: can work on non key views. + return NSApp.keyWindow == self.view.window + && self.view.window.firstResponder == self.view; + } - return [super respondsToSelector:selector]; -} + if (selector == @selector(qt_itemFired:)) + return YES; -- (void)qt_itemFired:(QCocoaNSMenuItem *)item -{ - auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate]; - [appDelegate qt_itemFired:item]; + return [super respondsToSelector:selector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - if (selectorIsCutCopyPaste(selector)) { - NSMethodSignature *itemFiredSign = [super methodSignatureForSelector:@selector(qt_itemFired:)]; - return itemFiredSign; - } + // Double check, in case something has cached that we respond + // to the selector, but the result has changed since then. + if (![self respondsToSelector:selector]) + return nil; - return [super methodSignatureForSelector:selector]; + auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate]; + return [appDelegate methodSignatureForSelector:@selector(qt_itemFired:)]; } - (void)forwardInvocation:(NSInvocation *)invocation { - if (selectorIsCutCopyPaste(invocation.selector)) { - NSObject *sender; - [invocation getArgument:&sender atIndex:2]; - qCDebug(lcQpaMenus) << "Forwarding" << invocation.selector << "from" << sender; - - // We claim to respond to standard edit actions such as cut/copy/paste, - // but these might not be exclusively coming from menu items that we - // control. For example, when embedded into a native UI (as a plugin), - // the menu items might be part of the host application, and if we're - // the first responder, we'll be the target of these actions. As we - // don't have a mechanism in Qt to trigger generic actions, we have - // to bail out if we don't have a QCocoaNSMenuItem we can activate(). - // Note that we skip the call to super as well, as that would just - // try to invoke the current action on ourselves again. - if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(sender)) - [self qt_itemFired:nativeItem]; - else - qCDebug(lcQpaMenus) << "Ignoring action for menu item we didn't create"; - - return; - } - - [super forwardInvocation:invocation]; + NSObject *sender; + [invocation getArgument:&sender atIndex:2]; + qCDebug(lcQpaMenus) << "Forwarding" << invocation.selector << "from" << sender; + Q_ASSERT(qt_objc_cast<QCocoaNSMenuItem *>(sender)); + invocation.selector = @selector(qt_itemFired:); + [invocation invokeWithTarget:[QCocoaApplicationDelegate sharedDelegate]]; } @end |