99 bool setAttached =
false;
100 if ([m_native.menu isKindOfClass:[QCocoaNSMenu
class]]) {
101 auto parentMenu =
static_cast<QCocoaNSMenu *>(m_native.menu);
102 setAttached = parentMenu.platformMenu && parentMenu.platformMenu->isAboutToShow();
105 if (m_menu && m_menu->menuParent() ==
this) {
106 m_menu->setMenuParent(
nullptr);
108 m_menu->propagateEnabledState(
true);
109 if (m_native && m_menu->attachedItem() == m_native)
110 m_menu->setAttachedItem(nil);
114 m_menu =
static_cast<QCocoaMenu *>(menu);
116 m_menu->setMenuParent(
this);
117 m_menu->propagateEnabledState(isEnabled());
119 m_menu->setAttachedItem(m_native);
186 QString itemCaption(captionWithPossibleMnemonic);
187 itemCaption.remove(u'&');
189 static const std::tuple<QPlatformMenuItem::MenuRole, std::vector<std::tuple<Qt::MatchFlags,
const char *>>> roleMap[] = {
190 { QPlatformMenuItem::AboutRole, {
191 { Qt::MatchStartsWith | Qt::MatchEndsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"About") }
193 { QPlatformMenuItem::PreferencesRole, {
194 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Config") },
195 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Preference") },
196 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Options") },
197 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Setting") },
198 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Setup") },
200 { QPlatformMenuItem::QuitRole, {
201 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Quit") },
202 { Qt::MatchStartsWith, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Exit") },
204 { QPlatformMenuItem::CutRole, {
205 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Cut") }
207 { QPlatformMenuItem::CopyRole, {
208 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Copy") }
210 { QPlatformMenuItem::PasteRole, {
211 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Paste") }
213 { QPlatformMenuItem::SelectAllRole, {
214 { Qt::MatchExactly, QT_TRANSLATE_NOOP(
"QCocoaMenuItem",
"Select All") }
218 auto match = [](
const QString &caption,
const QString &itemCaption, Qt::MatchFlags matchFlags) {
219 if (matchFlags.testFlag(Qt::MatchExactly))
220 return !itemCaption.compare(caption, Qt::CaseInsensitive);
221 if (matchFlags.testFlag(Qt::MatchStartsWith) && itemCaption.startsWith(caption, Qt::CaseInsensitive))
223 if (matchFlags.testFlag(Qt::MatchEndsWith) && itemCaption.endsWith(caption, Qt::CaseInsensitive))
228 QPlatformMenuItem::MenuRole detectedRole = [&]{
229 for (
const auto &[role, captions] : roleMap) {
230 for (
const auto &[matchFlags, caption] : captions) {
232 if (match(caption, itemCaption, matchFlags))
235 if (match(QCoreApplication::translate(
"QCocoaMenuItem", caption), itemCaption, matchFlags))
239 return QPlatformMenuItem::NoRole;
242 if (detectedRole == QPlatformMenuItem::AboutRole) {
243 static const QRegularExpression qtRegExp(
"qt$"_L1, QRegularExpression::CaseInsensitiveOption);
244 if (itemCaption.contains(qtRegExp))
245 detectedRole = QPlatformMenuItem::AboutQtRole;
253 if (m_isSeparator != m_native.separatorItem) {
256 m_native = [[QCocoaNSMenuItem separatorItemWithPlatformMenuItem:
this] retain];
261 if ((m_role != NoRole && !m_textSynced) || m_merged) {
263 if (m_role == TextHeuristicRole) {
265 QObject *p = menuParent();
267 while (depth < 3 && p && !(menubar = qobject_cast<
QCocoaMenuBar *>(p))) {
270 p = menuObject ? menuObject->menuParent() :
nullptr;
273 if (menubar && depth < 3)
274 m_detectedRole = detectMenuRole(m_text);
276 m_detectedRole = NoRole;
279 QCocoaMenuLoader *loader = [QCocoaMenuLoader sharedMenuLoader];
280 NSMenuItem *mergeItem = nil;
281 const auto role = effectiveRole();
284 mergeItem = [loader aboutMenuItem];
287 mergeItem = [loader aboutQtMenuItem];
289 case PreferencesRole:
290 mergeItem = [loader preferencesMenuItem];
292 case ApplicationSpecificRole:
293 mergeItem = [loader appSpecificMenuItem:
this];
296 mergeItem = [loader quitMenuItem];
303 mergeItem = menubar->itemForRole(role);
307 m_textSynced =
false;
310 if (!m_text.isEmpty())
320 m_native = mergeItem;
321 if (
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(m_native))
322 nativeItem.platformMenuItem =
this;
323 }
else if (m_merged) {
329 }
else if (!m_text.isEmpty()) {
334 m_native = [[QCocoaNSMenuItem alloc] initWithPlatformMenuItem:
this];
335 m_native.title = m_text.toNSString();
340 m_native.hidden = !m_isVisible;
341 m_native.view = m_itemView;
343 QString text = mergeText();
344#ifndef QT_NO_SHORTCUT
345 QKeySequence accel = mergeAccel();
348 if (accel.count() > 1)
349 text +=
" ("_L1 + accel.toString(QKeySequence::NativeText) +
")"_L1;
352 m_native.title = QPlatformTheme::removeMnemonics(text).toNSString();
354#ifndef QT_NO_SHORTCUT
355 if (accel.count() == 1) {
356 auto key = accel[0].key();
357 auto modifiers = accel[0].keyboardModifiers();
359 QChar cocoaKey = QAppleKeyMapper::toCocoaKey(key);
360 if (cocoaKey.isNull())
361 cocoaKey =
char16_t(QChar::toLower(key));
364 if (cocoaKey.unicode() == NSDeleteFunctionKey)
365 cocoaKey =
char16_t(NSDeleteCharacter);
367 m_native.keyEquivalent = QStringView(&cocoaKey, 1).toNSString();
368 m_native.keyEquivalentModifierMask = QAppleKeyMapper::toCocoaModifiers(modifiers);
372 m_native.keyEquivalent = @
"";
373 m_native.keyEquivalentModifierMask = NSEventModifierFlagCommand;
376 const QIcon::Mode mode = m_enabled ? QIcon::Normal : QIcon::Disabled;
377 const QIcon::State state = m_checked ? QIcon::On : QIcon::Off;
378 m_native.image = [NSImage imageFromQIcon:m_icon withSize:QSize(m_iconSize, m_iconSize)
382 m_native.state = m_checked ? NSControlStateValueOn : NSControlStateValueOff;