Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcocoansmenu.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
4#include <AppKit/AppKit.h>
5
6#include "qcocoansmenu.h"
7#include "qcocoamenu.h"
8#include "qcocoamenuitem.h"
9#include "qcocoamenubar.h"
10#include "qcocoawindow.h"
11#include "qnsview.h"
12#include "qcocoahelpers.h"
13
14#include <QtCore/qcoreapplication.h>
15#include <QtCore/qcoreevent.h>
16#include <QtCore/qvarlengtharray.h>
17#include <QtGui/private/qapplekeymapper_p.h>
18
19#include <QtCore/qpointer.h>
20
21static NSString *qt_mac_removePrivateUnicode(NSString *string)
22{
23 if (const int len = string.length) {
24 QVarLengthArray<unichar, 10> characters(len);
25 bool changed = false;
26 for (int i = 0; i < len; i++) {
27 characters[i] = [string characterAtIndex:i];
28 // check if they belong to key codes in private unicode range
29 // currently we need to handle only the NSDeleteFunctionKey
30 if (characters[i] == NSDeleteFunctionKey) {
31 characters[i] = NSDeleteCharacter;
32 changed = true;
33 }
34 }
35 if (changed)
36 return [NSString stringWithCharacters:characters.data() length:len];
37 }
38 return string;
39}
40
41@implementation QCocoaNSMenu
42{
43 QPointer<QCocoaMenu> _platformMenu;
44}
45
46- (instancetype)initWithPlatformMenu:(QCocoaMenu *)menu
47{
48 if ((self = [super initWithTitle:@"Untitled"])) {
49 _platformMenu = menu;
50 self.autoenablesItems = YES;
51 self.delegate = [QCocoaNSMenuDelegate sharedMenuDelegate];
52 }
53
54 return self;
55}
56
57- (instancetype)initWithoutPlatformMenu:(NSString *)title
58{
59 if (self = [super initWithTitle:title])
60 self.delegate = [QCocoaNSMenuDelegate sharedMenuDelegate];
61 return self;
62}
63
65{
66 return _platformMenu.data();
67}
68
69@end
70
71@implementation QCocoaNSMenuItem
72{
73 QPointer<QCocoaMenuItem> _platformMenuItem;
74}
75
76+ (instancetype)separatorItemWithPlatformMenuItem:(QCocoaMenuItem *)menuItem
77{
78 // Safe because +[NSMenuItem separatorItem] invokes [[self alloc] init]
79 auto *item = qt_objc_cast<QCocoaNSMenuItem *>([self separatorItem]);
80 Q_ASSERT_X(item, qPrintable(__FUNCTION__),
81 "Did +[NSMenuItem separatorItem] not invoke [[self alloc] init]?");
82 if (item)
83 item.platformMenuItem = menuItem;
84
85 return item;
86}
87
88- (instancetype)initWithPlatformMenuItem:(QCocoaMenuItem *)menuItem
89{
90 if ((self = [super initWithTitle:@"" action:nil keyEquivalent:@""])) {
91 _platformMenuItem = menuItem;
92 }
93
94 return self;
95}
96
97- (instancetype)init
98{
99 return [self initWithPlatformMenuItem:nullptr];
100}
101
102- (QCocoaMenuItem *)platformMenuItem
103{
104 return _platformMenuItem.data();
105}
106
107- (void)setPlatformMenuItem:(QCocoaMenuItem *)menuItem
108{
109 _platformMenuItem = menuItem;
110}
111
112@end
113
114#define CHECK_MENU_CLASS(menu) Q_ASSERT_X([menu isMemberOfClass:[QCocoaNSMenu class]], \
115 __FUNCTION__, "Menu is not a QCocoaNSMenu")
116
117@implementation QCocoaNSMenuDelegate
118
119+ (instancetype)sharedMenuDelegate
120{
121 static QCocoaNSMenuDelegate *shared = nil;
122 static dispatch_once_t onceToken;
123 dispatch_once(&onceToken, ^{
124 shared = [[self alloc] init];
125 atexit_b(^{
126 [shared release];
127 shared = nil;
128 });
129 });
130 return shared;
131}
132
133- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu
134{
136 return menu.numberOfItems;
137}
138
139- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
140{
143
144 if (shouldCancel)
145 return NO;
146
147 const auto &platformMenu = static_cast<QCocoaNSMenu *>(menu).platformMenu;
148 if (!platformMenu)
149 return YES;
150
151 if (auto *platformItem = qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem) {
152 if (platformMenu->items().contains(platformItem)) {
153 if (auto *itemSubmenu = platformItem->menu())
154 itemSubmenu->setAttachedItem(item);
155 }
156 }
157
158 return YES;
159}
160
161- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
162{
164 if (auto *platformItem = qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem)
165 emit platformItem->hovered();
166}
167
168- (void)menuWillOpen:(NSMenu *)menu
169{
171 auto *platformMenu = static_cast<QCocoaNSMenu *>(menu).platformMenu;
172 if (!platformMenu)
173 return;
174
175 platformMenu->setIsOpen(true);
176 platformMenu->setIsAboutToShow(true);
177 emit platformMenu->aboutToShow();
178 platformMenu->setIsAboutToShow(false);
179}
180
181- (void)menuDidClose:(NSMenu *)menu
182{
184 auto *platformMenu = static_cast<QCocoaNSMenu *>(menu).platformMenu;
185 if (!platformMenu)
186 return;
187
188 platformMenu->setIsOpen(false);
189 // wrong, but it's the best we can do
190 emit platformMenu->aboutToHide();
191}
192
193- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action
194{
195 /*
196 Check if the menu actually has a keysequence defined for this key event.
197 If it does, then we will first send the key sequence to the QWidget that has focus
198 since (in Qt's eyes) it needs to a chance at the key event first (QEvent::ShortcutOverride).
199 If the widget accepts the key event, we then return YES, but set the target and action to be nil,
200 which means that the action should not be triggered, and instead dispatch the event ourselves.
201 In every other case we return NO, which means that Cocoa can do as it pleases
202 (i.e., fire the menu action).
203 */
204
206
207 // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ...
208 static const NSUInteger mask = NSEventModifierFlagShift | NSEventModifierFlagControl
209 | NSEventModifierFlagCommand | NSEventModifierFlagOption;
210
211 // Change the private unicode keys to the ones used in setting the "Key Equivalents"
212 NSString *characters = qt_mac_removePrivateUnicode(event.charactersIgnoringModifiers);
213 const auto modifiers = event.modifierFlags & mask;
214 NSMenuItem *keyEquivalentItem = [self findItemInMenu:menu
215 forKey:characters
216 modifiers:modifiers];
217 if (!keyEquivalentItem) {
218 // Maybe the modified character is what we're looking for after all
220 keyEquivalentItem = [self findItemInMenu:menu
221 forKey:characters
222 modifiers:modifiers];
223 }
224
225 if (keyEquivalentItem) {
226 QObject *object = qApp->focusObject();
227 if (object) {
228 QChar ch;
229 int keyCode;
230 ulong nativeModifiers = event.modifierFlags;
231 Qt::KeyboardModifiers modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers);
232 NSString *charactersIgnoringModifiers = event.charactersIgnoringModifiers;
233 NSString *characters = event.characters;
234
235 if (charactersIgnoringModifiers.length > 0) { // convert the first character into a key code
236 if ((modifiers & Qt::ControlModifier) && characters.length > 0) {
237 ch = QChar([characters characterAtIndex:0]);
238 } else {
239 ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
240 }
241 keyCode = QAppleKeyMapper::fromCocoaKey(ch);
242 } else {
243 // might be a dead key
244 ch = QChar::ReplacementCharacter;
245 keyCode = Qt::Key_unknown;
246 }
247
249 Qt::KeyboardModifiers(modifiers & Qt::KeyboardModifierMask));
250 accel_ev.ignore();
251 QCoreApplication::sendEvent(object, &accel_ev);
252 if (accel_ev.isAccepted()) {
253 [[NSApp keyWindow] sendEvent:event];
254 *target = nil;
255 *action = nil;
256 return YES;
257 }
258 }
259 }
260
261 return NO;
262}
263
264- (NSMenuItem *)findItemInMenu:(NSMenu *)menu
265 forKey:(NSString *)key
266 modifiers:(NSUInteger)modifiers
267{
268 // Find an item in 'menu' that has the same key equivalent as specified by
269 // 'key' and 'modifiers'. We ignore disabled, hidden and separator items.
270 // In a similar fashion, we don't need to recurse into submenus because their
271 // delegate will have [menuHasKeyEquivalent:...] invoked at some point.
272
273 for (NSMenuItem *item in menu.itemArray) {
274 if (!item.enabled || item.hidden || item.separatorItem)
275 continue;
276
277 if (item.hasSubmenu)
278 continue;
279
280 NSString *menuKey = item.keyEquivalent;
281 if (menuKey && NSOrderedSame == [menuKey compare:key]
282 && modifiers == item.keyEquivalentModifierMask)
283 return item;
284 }
285
286 return nil;
287}
288
289@end
290
291#undef CHECK_MENU_CLASS
\inmodule QtCore
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
@ ShortcutOverride
Definition qcoreevent.h:158
The QKeyEvent class describes a key event.
Definition qevent.h:424
\inmodule QtCore
Definition qobject.h:103
EGLImageKHR int int EGLuint64KHR * modifiers
@ Key_unknown
@ ControlModifier
@ KeyboardModifierMask
QString self
Definition language.cpp:58
long NSInteger
instancetype initWithoutPlatformMenu
instancetype initWithPlatformMenu
NSMenu QCocoaMenu * platformMenu
static NSString * qt_mac_removePrivateUnicode(NSString *string)
#define CHECK_MENU_CLASS(menu)
unsigned long NSUInteger
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
GLuint64 key
GLuint index
[2]
GLenum GLuint GLenum GLsizei length
GLenum target
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
struct _cl_event * event
GLuint in
GLenum GLsizei len
GLsizei const GLchar *const * string
[0]
Definition qopenglext.h:694
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
#define qPrintable(string)
Definition qstring.h:1531
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
static int compare(quint64 a, quint64 b)
unsigned long ulong
Definition qtypes.h:35
QList< QChar > characters
QString title
[35]
QGraphicsItem * item
QMenu menu
[5]