summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qnsview_menus.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qnsview_menus.mm')
-rw-r--r--src/plugins/platforms/cocoa/qnsview_menus.mm101
1 files changed, 68 insertions, 33 deletions
diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm
index 7f2b3d387c..2840936975 100644
--- a/src/plugins/platforms/cocoa/qnsview_menus.mm
+++ b/src/plugins/platforms/cocoa/qnsview_menus.mm
@@ -9,22 +9,56 @@
#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.view;
+
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
if (!nativeItem)
return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
@@ -51,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;
}
@@ -60,41 +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];
- if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(sender)) {
- [self qt_itemFired:nativeItem];
- 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