Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnsview_menus.mm
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5// This file is included from qnsview.mm, and only used to organize the code
6
8#include "qcocoansmenu.h"
10#include "qcocoamenu.h"
11#include "qcocoamenubar.h"
12
13@implementation QNSView (Menus)
14
15// Qt does not (yet) have a mechanism for propagating generic actions,
16// so we can only support actions that originate from a QCocoaNSMenuItem,
17// where we can forward the action by emitting QPlatformMenuItem::activated().
18// But waiting for forwardInvocation to check that the sender is a
19// QCocoaNSMenuItem is too late, as AppKit has at that point chosen
20// our view as the target for the action, and if we can't handle it
21// the action will not propagate up the responder chain as it should.
22// Instead, we hook in early in the process of determining the target
23// via the supplementalTargetForAction API, and if we can support the
24// action we forward it to a helper. The helper must be tied to the
25// view, as the menu validation logic depends on the view's state.
26
27- (id)supplementalTargetForAction:(SEL)action sender:(id)sender
28{
29 qCDebug(lcQpaMenus) << "Resolving action target for" << action << "from" << sender << "via" << self;
30
31 if (qt_objc_cast<QCocoaNSMenuItem *>(sender)) {
32 // The supplemental target must support the selector, but we
33 // determine so dynamically, so check here before continuing.
34 if ([self.menuHelper respondsToSelector:action])
35 return self.menuHelper;
36 } else {
37 qCDebug(lcQpaMenus) << "Ignoring action for menu item we didn't create";
38 }
39
40 return [super supplementalTargetForAction:action sender:sender];
41}
42
43#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(150000)
44- (void)showContextMenuForSelection:(id)sender
45{
46 QPointF windowPoint;
47 QPointF screenPoint;
48 [self convertFromScreen:[NSEvent mouseLocation]
49 toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
50
51 auto *keyMapper = QCocoaIntegration::instance()->keyMapper();
52 auto keyboardModifiers = keyMapper->queryKeyboardModifiers();
53
54 // We currently propagate this as QContextMenuEvent::Reason::Keyboard,
55 // but we can also get here via an accessibility ShowMenu action, in
56 // which case QContextMenuEvent::Reason::Other reason might fit better.
57 // Unfortunately QWSI/QGuiApplication/QWidgetWindow doesn't handle that
58 // yet. FIXME: Teach other parts of Qt about QContextMenuEvent::Other.
59 const bool mouseTriggered = false;
60
61 qCDebug(lcQpaMenus) << "Initiating context menu at"
62 << windowPoint.toPoint() << "in" << m_platformWindow
63 << "with" << keyboardModifiers;
64
65 bool accepted = QWindowSystemInterface::handleContextMenuEvent<
66 QWindowSystemInterface::SynchronousDelivery>(
67 m_platformWindow->window(), mouseTriggered,
68 windowPoint.toPoint(), screenPoint.toPoint(),
69 keyboardModifiers);
70
71 qCDebug(lcQpaMenus) << "Context menu event accepted =" << accepted;
72
73 // If the view does not support a context menu we should pass
74 // the request up the responder chain.
75 if (!accepted)
76 [[self nextResponder] tryToPerform:_cmd with:sender];
77}
78#endif
79
80@end
81
82@interface QNSViewMenuHelper ()
83@property (assign) QNSView* view;
84@end
85
86@implementation QNSViewMenuHelper
87
88- (instancetype)initWithView:(QNSView *)theView
89{
90 if ((self = [super init]))
91 self.view = theView;
92
93 return self;
94}
95
96- (BOOL)validateMenuItem:(NSMenuItem*)item
97{
98 qCDebug(lcQpaMenus) << "Validating" << item << "for" << self.view;
99
100 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
101 if (!nativeItem)
102 return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
103
104 auto *platformItem = nativeItem.platformMenuItem;
105 if (!platformItem)
106 return NO;
107
108 // Menu-holding items are always enabled, as it's conventional in Cocoa
109 if (platformItem->menu())
110 return YES;
111
112 // Check if a modal dialog is active. If so, enable only menu
113 // items explicitly belonging to this window's own menu bar, or to the window.
114 if (QGuiApplication::modalWindow() && QGuiApplication::modalWindow()->isActive()) {
115 QCocoaMenuBar *menubar = nullptr;
116 QCocoaWindow *menuWindow = nullptr;
117
118 QObject *menuParent = platformItem->menuParent();
119 while (menuParent && !(menubar = qobject_cast<QCocoaMenuBar *>(menuParent))) {
120 menuWindow = qobject_cast<QCocoaWindow *>(menuParent);
121 auto *menuObject = dynamic_cast<QCocoaMenuObject *>(menuParent);
122 menuParent = menuObject ? menuObject->menuParent() : nullptr;
123 }
124
125 if ((!menuWindow || menuWindow->window() != QGuiApplication::modalWindow())
126 && (!menubar || menubar->cocoaWindow() != self.view.platformWindow))
127 return NO;
128 }
129
130 return platformItem->isEnabled();
131}
132
133- (BOOL)respondsToSelector:(SEL)selector
134{
135 // See QCocoaMenuItem::resolveTargetAction()
136
137 if (selector == @selector(cut:)
138 || selector == @selector(copy:)
139 || selector == @selector(paste:)
140 || selector == @selector(selectAll:)) {
141 // Not exactly true. Both copy: and selectAll: can work on non key views.
142 return NSApp.keyWindow == self.view.window
143 && self.view.window.firstResponder == self.view;
144 }
145
146 if (selector == @selector(qt_itemFired:))
147 return YES;
148
149 return [super respondsToSelector:selector];
150}
151
152- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
153{
154 // Double check, in case something has cached that we respond
155 // to the selector, but the result has changed since then.
156 if (![self respondsToSelector:selector])
157 return nil;
158
159 auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate];
160 return [appDelegate methodSignatureForSelector:@selector(qt_itemFired:)];
161}
162
163- (void)forwardInvocation:(NSInvocation *)invocation
164{
165 NSObject *sender;
166 [invocation getArgument:&sender atIndex:2];
167 qCDebug(lcQpaMenus) << "Forwarding" << invocation.selector << "from" << sender;
168 Q_ASSERT(qt_objc_cast<QCocoaNSMenuItem *>(sender));
169 invocation.selector = @selector(qt_itemFired:);
170 [invocation invokeWithTarget:[QCocoaApplicationDelegate sharedDelegate]];
171}
172
173@end