98 bool setAttached =
false;
99 if ([m_native.menu isKindOfClass:[QCocoaNSMenu
class]]) {
100 auto parentMenu =
static_cast<QCocoaNSMenu *>(m_native.menu);
101 setAttached = parentMenu.platformMenu && parentMenu.platformMenu->isAboutToShow();
104 if (m_menu && m_menu->menuParent() ==
this) {
105 m_menu->setMenuParent(
nullptr);
107 m_menu->propagateEnabledState(
true);
108 if (m_native && m_menu->attachedItem() == m_native)
109 m_menu->setAttachedItem(nil);
113 m_menu =
static_cast<QCocoaMenu *>(menu);
115 m_menu->setMenuParent(
this);
116 m_menu->propagateEnabledState(isEnabled());
118 m_menu->setAttachedItem(m_native);
185 QString itemCaption(captionWithPossibleMnemonic);
186 itemCaption.remove(u'&');
188 static const std::tuple<QPlatformMenuItem::MenuRole, std::vector<std::tuple<Qt::MatchFlags,
const char *>>> roleMap[] = {
189 { QPlatformMenuItem::AboutRole, {
190 { Qt::MatchStartsWith | Qt::MatchEndsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"About") }
192 { QPlatformMenuItem::PreferencesRole, {
193 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Config") },
194 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Preference") },
195 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Options") },
196 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Setting") },
197 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Setup") },
199 { QPlatformMenuItem::QuitRole, {
200 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Quit") },
201 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Exit") },
203 { QPlatformMenuItem::CutRole, {
204 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Cut") }
206 { QPlatformMenuItem::CopyRole, {
207 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Copy") }
209 { QPlatformMenuItem::PasteRole, {
210 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Paste") }
212 { QPlatformMenuItem::SelectAllRole, {
213 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Select All") }
217 auto match = [](
const QString &caption,
const QString &itemCaption, Qt::MatchFlags matchFlags) {
218 if (matchFlags.testFlag(Qt::MatchExactly))
219 return !itemCaption.compare(caption, Qt::CaseInsensitive);
220 if (matchFlags.testFlag(Qt::MatchStartsWith) && itemCaption.startsWith(caption, Qt::CaseInsensitive))
222 if (matchFlags.testFlag(Qt::MatchEndsWith) && itemCaption.endsWith(caption, Qt::CaseInsensitive))
227 QPlatformMenuItem::MenuRole detectedRole = [&]{
228 for (
const auto &[role, captions] : roleMap) {
229 for (
const auto &[matchFlags, caption] : captions) {
231 if (match(caption, itemCaption, matchFlags))
234 if (match(QCoreApplication::translate(
"QCocoaMenuItem", caption), itemCaption, matchFlags))
238 return QPlatformMenuItem::NoRole;
241 if (detectedRole == QPlatformMenuItem::AboutRole) {
242 static const QRegularExpression qtRegExp(
"qt$"_L1, QRegularExpression::CaseInsensitiveOption);
243 if (itemCaption.contains(qtRegExp))
244 detectedRole = QPlatformMenuItem::AboutQtRole;
252 if (m_isSeparator != m_native.separatorItem) {
255 m_native = [[QCocoaNSMenuItem separatorItemWithPlatformMenuItem:
this] retain];
260 if ((m_role != NoRole && !m_textSynced) || m_merged) {
262 if (m_role == TextHeuristicRole) {
264 QObject *p = menuParent();
266 while (depth < 3 && p && !(menubar = qobject_cast<
QCocoaMenuBar *>(p))) {
269 p = menuObject ? menuObject->menuParent() :
nullptr;
272 if (menubar && depth < 3)
273 m_detectedRole = detectMenuRole(m_text);
275 m_detectedRole = NoRole;
278 QCocoaMenuLoader *loader = [QCocoaMenuLoader sharedMenuLoader];
279 NSMenuItem *mergeItem = nil;
280 const auto role = effectiveRole();
283 mergeItem = [loader aboutMenuItem];
286 mergeItem = [loader aboutQtMenuItem];
288 case PreferencesRole:
289 mergeItem = [loader preferencesMenuItem];
291 case ApplicationSpecificRole:
292 mergeItem = [loader appSpecificMenuItem:
this];
295 mergeItem = [loader quitMenuItem];
302 mergeItem = menubar->itemForRole(role);
306 m_textSynced =
false;
309 if (!m_text.isEmpty())
319 m_native = mergeItem;
320 if (
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(m_native))
321 nativeItem.platformMenuItem =
this;
322 }
else if (m_merged) {
328 }
else if (!m_text.isEmpty()) {
333 m_native = [[QCocoaNSMenuItem alloc] initWithPlatformMenuItem:
this];
334 m_native.title = m_text.toNSString();
339 m_native.hidden = !m_isVisible;
340 m_native.view = m_itemView;
342 QString text = mergeText();
343#ifndef QT_NO_SHORTCUT
344 QKeySequence accel = mergeAccel();
347 if (accel.count() > 1)
348 text +=
" ("_L1 + accel.toString(QKeySequence::NativeText) +
")"_L1;
351 m_native.title = QPlatformTheme::removeMnemonics(text).toNSString();
353#ifndef QT_NO_SHORTCUT
354 if (accel.count() == 1) {
355 auto key = accel[0].key();
356 auto modifiers = accel[0].keyboardModifiers();
358 QChar cocoaKey = QAppleKeyMapper::toCocoaKey(key);
359 if (cocoaKey.isNull())
360 cocoaKey =
char16_t(QChar::toLower(key));
363 if (cocoaKey.unicode() == NSDeleteFunctionKey)
364 cocoaKey =
char16_t(NSDeleteCharacter);
366 m_native.keyEquivalent = QStringView(&cocoaKey, 1).toNSString();
367 m_native.keyEquivalentModifierMask = QAppleKeyMapper::toCocoaModifiers(modifiers);
371 m_native.keyEquivalent = @
"";
372 m_native.keyEquivalentModifierMask = NSEventModifierFlagCommand;
375 m_native.image = [NSImage imageFromQIcon:m_icon withSize:m_iconSize];
377 m_native.state = m_checked ? NSControlStateValueOn : NSControlStateValueOff;