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