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