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
qcocoamenuloader.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#include <AppKit/AppKit.h>
6
8
10#include "qcocoansmenu.h"
11#include "qcocoamenubar.h"
12#include "qcocoamenuitem.h"
14
15#include <QtCore/private/qcore_mac_p.h>
16#include <QtCore/private/qthread_p.h>
17#include <QtCore/qcoreapplication.h>
18#include <QtGui/private/qguiapplication_p.h>
19
20@implementation QCocoaMenuLoader {
21 NSMenu *theMenu;
22 NSMenu *appMenu;
23 NSMenuItem *quitItem;
24 NSMenuItem *preferencesItem;
25 NSMenuItem *aboutItem;
26 NSMenuItem *aboutQtItem;
27 NSMenuItem *hideItem;
28 NSMenuItem *servicesItem;
29 NSMenuItem *hideAllOthersItem;
30 NSMenuItem *showAllItem;
31}
32
33+ (instancetype)sharedMenuLoader
34{
35 static QCocoaMenuLoader *shared = nil;
36 static dispatch_once_t onceToken;
37 dispatch_once(&onceToken, ^{
38 shared = [[self alloc] init];
39 atexit_b(^{
40 [shared release];
41 shared = nil;
42 });
43 });
44 return shared;
45}
46
47- (instancetype)init
48{
49 if ((self = [super init])) {
50 NSString *appName = qt_mac_applicationName().toNSString();
51
52 // Menubar as menu. Title as set in the NIB file
53 theMenu = [[NSMenu alloc] initWithTitle:@"Main Menu"];
54
55 // Application menu. Since 10.6, the first menu
56 // is always identified as the application menu.
57 NSMenuItem *appItem = [[[NSMenuItem alloc] init] autorelease];
58 appItem.title = appName;
59 [theMenu addItem:appItem];
60 appMenu = [[QCocoaNSMenu alloc] initWithoutPlatformMenu:appName];
61 appItem.submenu = appMenu;
62
63 // About Application
64 aboutItem = [[QCocoaNSMenuItem alloc] init];
65 aboutItem.title = [@"About " stringByAppendingString:appName];
66 // FIXME This seems useless since barely adding a QAction
67 // with AboutRole role will reset the target/action
68 aboutItem.target = self;
69 aboutItem.action = @selector(orderFrontStandardAboutPanel:);
70 // Disable until a QAction is associated
71 aboutItem.enabled = NO;
72 aboutItem.hidden = YES;
73 [appMenu addItem:aboutItem];
74
75 // About Qt (shameless self-promotion)
76 aboutQtItem = [[QCocoaNSMenuItem alloc] init];
77 aboutQtItem.title = @"About Qt";
78 // Disable until a QAction is associated
79 aboutQtItem.enabled = NO;
80 aboutQtItem.hidden = YES;
81 [appMenu addItem:aboutQtItem];
82
83 [appMenu addItem:[NSMenuItem separatorItem]];
84
85 // Preferences
86 // We'll be adding app specific items after this. The macOS HIG state that,
87 // "In general, a Preferences menu item should be the first app-specific menu item."
88 // https://developer.apple.com/macos/human-interface-guidelines/menus/menu-bar-menus/
89 preferencesItem = [[QCocoaNSMenuItem alloc] init];
90 preferencesItem.title = @"Preferences…";
91 preferencesItem.keyEquivalent = @",";
92 // Disable until a QAction is associated
93 preferencesItem.enabled = NO;
94 preferencesItem.hidden = YES;
95 [appMenu addItem:preferencesItem];
96
97 [appMenu addItem:[NSMenuItem separatorItem]];
98
99 // Services item and menu
100 servicesItem = [[NSMenuItem alloc] init];
101 servicesItem.title = @"Services";
102 NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
103 servicesItem.submenu = servicesMenu;
104 [NSApplication sharedApplication].servicesMenu = servicesMenu;
105 [appMenu addItem:servicesItem];
106
107 [appMenu addItem:[NSMenuItem separatorItem]];
108
109 // Hide Application
110 hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName]
111 action:@selector(hide:)
112 keyEquivalent:@"h"];
113 hideItem.target = self;
114 [appMenu addItem:hideItem];
115
116 // Hide Others
117 hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
118 action:@selector(hideOtherApplications:)
119 keyEquivalent:@"h"];
120 hideAllOthersItem.target = self;
121 hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
122 [appMenu addItem:hideAllOthersItem];
123
124 // Show All
125 showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
126 action:@selector(unhideAllApplications:)
127 keyEquivalent:@""];
128 showAllItem.target = self;
129 [appMenu addItem:showAllItem];
130
131 [appMenu addItem:[NSMenuItem separatorItem]];
132
133 // Quit Application
134 quitItem = [[QCocoaNSMenuItem alloc] init];
135 quitItem.title = [@"Quit " stringByAppendingString:appName];
136 quitItem.keyEquivalent = @"q";
137 // This will remain true until synced with a QCocoaMenuItem.
138 // This way, we will always have a functional Quit menu item
139 // even if no QAction is added.
140 quitItem.action = @selector(terminate:);
141 [appMenu addItem:quitItem];
142 }
143
144 return self;
145}
146
147- (void)dealloc
148{
149 [theMenu release];
150 [appMenu release];
151 [aboutItem release];
152 [aboutQtItem release];
153 [preferencesItem release];
154 [servicesItem release];
155 [hideItem release];
156 [hideAllOthersItem release];
157 [showAllItem release];
158 [quitItem release];
159
160 [super dealloc];
161}
162
163- (void)ensureAppMenuInMenu:(NSMenu *)menu
164{
165 // The application menu is the menu in the menu bar that contains the
166 // 'Quit' item. When changing menu bar (e.g when switching between
167 // windows with different menu bars), we never recreate this menu, but
168 // instead pull it out the current menu bar and place into the new one:
169 NSMenu *mainMenu = [NSApp mainMenu];
170 if (mainMenu == menu)
171 return; // nothing to do (menu is the current menu bar)!
172
173#ifndef QT_NAMESPACE
174 Q_ASSERT(mainMenu);
175#endif
176 // Grab the app menu out of the current menu.
177 auto unparentAppMenu = ^bool (NSMenu *supermenu) {
178 auto index = [supermenu indexOfItemWithSubmenu:appMenu];
179 if (index != -1) {
180 [supermenu removeItemAtIndex:index];
181 return true;
182 }
183 return false;
184 };
185
186 if (!mainMenu || !unparentAppMenu(mainMenu))
187 if (appMenu.supermenu)
188 unparentAppMenu(appMenu.supermenu);
189
190 NSMenuItem *appMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Apple"
191 action:nil keyEquivalent:@""] autorelease];
192 appMenuItem.submenu = appMenu;
193 [menu insertItem:appMenuItem atIndex:0];
194}
195
196- (NSMenu *)menu
197{
198 return [[theMenu retain] autorelease];
199}
200
201- (NSMenu *)applicationMenu
202{
203 return [[appMenu retain] autorelease];
204}
205
206- (NSMenuItem *)quitMenuItem
207{
208 return [[quitItem retain] autorelease];
209}
210
211- (NSMenuItem *)preferencesMenuItem
212{
213 return [[preferencesItem retain] autorelease];
214}
215
216- (NSMenuItem *)aboutMenuItem
217{
218 return [[aboutItem retain] autorelease];
219}
220
221- (NSMenuItem *)aboutQtMenuItem
222{
223 return [[aboutQtItem retain] autorelease];
224}
225
226- (NSMenuItem *)hideMenuItem
227{
228 return [[hideItem retain] autorelease];
229}
230
231- (NSMenuItem *)appSpecificMenuItem:(QCocoaMenuItem *)platformItem
232{
233 // No reason to create the item if it already exists.
234 for (NSMenuItem *item in appMenu.itemArray)
235 if (qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem == platformItem)
236 return item;
237
238 // Create an App-Specific menu item, insert it into the menu and return
239 // it as an autorelease item.
240 QCocoaNSMenuItem *item;
241 if (platformItem->isSeparator())
242 item = [QCocoaNSMenuItem separatorItemWithPlatformMenuItem:platformItem];
243 else
244 item = [[[QCocoaNSMenuItem alloc] initWithPlatformMenuItem:platformItem] autorelease];
245
246 const auto location = [self indexOfLastAppSpecificMenuItem];
247 [appMenu insertItem:item atIndex:NSInteger(location) + 1];
248
249 return item;
250}
251
252- (void)orderFrontStandardAboutPanel:(id)sender
253{
254 [NSApp orderFrontStandardAboutPanel:sender];
255}
256
257- (void)hideOtherApplications:(id)sender
258{
259 [NSApp hideOtherApplications:sender];
260}
261
262- (void)unhideAllApplications:(id)sender
263{
264 [NSApp unhideAllApplications:sender];
265}
266
267- (void)hide:(id)sender
268{
269 [NSApp hide:sender];
270}
271
272- (void)qtTranslateApplicationMenu
273{
274#ifndef QT_NO_TRANSLATION
275 aboutItem.title = qt_mac_applicationmenu_string(AboutAppMenuItem).arg(qt_mac_applicationName()).toNSString();
276 preferencesItem.title = qt_mac_applicationmenu_string(PreferencesAppMenuItem).toNSString();
277 servicesItem.title = qt_mac_applicationmenu_string(ServicesAppMenuItem).toNSString();
278 hideItem.title = qt_mac_applicationmenu_string(HideAppMenuItem).arg(qt_mac_applicationName()).toNSString();
279 hideAllOthersItem.title = qt_mac_applicationmenu_string(HideOthersAppMenuItem).toNSString();
280 showAllItem.title = qt_mac_applicationmenu_string(ShowAllAppMenuItem).toNSString();
281 quitItem.title = qt_mac_applicationmenu_string(QuitAppMenuItem).arg(qt_mac_applicationName()).toNSString();
282#endif
283}
284
285- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
286{
287 if (menuItem.action == @selector(hideOtherApplications:)
288 || menuItem.action == @selector(unhideAllApplications:)
289 || menuItem.action == @selector(hide:)) {
290 return [NSApp validateMenuItem:menuItem];
291 }
292
293 return menuItem.enabled;
294}
295
296- (NSArray<NSMenuItem *> *)mergeable
297{
298 // Don't include the quitItem here, since we want it always visible and enabled regardless
299 auto items = [NSArray arrayWithObjects:preferencesItem, aboutItem, aboutQtItem,
300 appMenu.itemArray[[self indexOfLastAppSpecificMenuItem]], nil];
301 return items;
302}
303
304- (NSUInteger)indexOfLastAppSpecificMenuItem
305{
306 // Either the 'Preferences', which is the first app specific menu item, or something
307 // else we appended later (thus the reverse order):
308 const auto location = [appMenu.itemArray indexOfObjectWithOptions:NSEnumerationReverse
309 passingTest:^BOOL(NSMenuItem *item, NSUInteger, BOOL *) {
310 if (auto qtItem = qt_objc_cast<QCocoaNSMenuItem*>(item))
311 return qtItem != quitItem;
312 return NO;
313 }];
314 Q_ASSERT(location != NSNotFound);
315 return location;
316}
317
318
319@end
Q_FORWARD_DECLARE_OBJC_CLASS(NSMenuItem)
Q_FORWARD_DECLARE_OBJC_CLASS(NSMenu)
unsigned long NSUInteger