macOS: add default Edit menu items, if not added by AppKit

AppKit automatically appends "Start Dictation..." and "Emoji & Symbols"
menu items, after a separator, to a menu in the menu bar that has the
title "Edit" in the operating system language.

Qt applications might however be translated to some other language, in
which case the "Edit" menu is not recognized by AppKit, and the menu
items won't be added. This is bad for accessibility and for users
wanting to type emojis.

If we have a menu that has the title "Edit" as translated in Qt's i18n
system, then create those items manually. To prevent a duplication of
the system- provided menu items, don't add the items if there already
is one with the action being set to the relevant selector. Otherwise,
perform the selector or call the NSApplication method ourselves. This
then results in the relevant keyboard input through regular code paths.

Fixes: QTBUG-79565
Change-Id: Ifd06036211756277550d398034689aca8e770133
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Volker Hilsheimer 2021-10-19 19:22:32 +02:00
parent e2fc3246d2
commit d42cfeb84f
2 changed files with 49 additions and 0 deletions

View File

@ -80,12 +80,14 @@ private:
bool needsImmediateUpdate();
bool shouldDisable(QCocoaWindow *active) const;
void insertDefaultEditItems(QCocoaMenu *menu);
NSMenuItem *nativeItemForMenu(QCocoaMenu *menu) const;
QList<QPointer<QCocoaMenu> > m_menus;
NSMenu *m_nativeMenu;
QPointer<QCocoaWindow> m_window;
QList<QPointer<QCocoaMenuItem>> m_defaultEditMenuItems;
};
QT_END_NAMESPACE

View File

@ -194,6 +194,11 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate)
for (QCocoaMenuItem *item : cocoaMenu->items())
cocoaMenu->syncMenuItem_helper(item, menubarUpdate);
const QString captionNoAmpersand = QString::fromNSString(cocoaMenu->nsMenu().title)
.remove(QLatin1Char('&'));
if (captionNoAmpersand == QCoreApplication::translate("QCocoaMenu", "Edit"))
insertDefaultEditItems(cocoaMenu);
BOOL shouldHide = YES;
if (cocoaMenu->isVisible()) {
// If the NSMenu has no visible items, or only separators, we should hide it
@ -400,6 +405,48 @@ QCocoaWindow *QCocoaMenuBar::cocoaWindow() const
return m_window.data();
}
void QCocoaMenuBar::insertDefaultEditItems(QCocoaMenu *menu)
{
if (menu->items().isEmpty())
return;
NSMenu *nsEditMenu = menu->nsMenu();
if ([nsEditMenu itemAtIndex:nsEditMenu.numberOfItems - 1].action
== @selector(orderFrontCharacterPalette:)) {
for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) {
if (menu->items().contains(defaultEditMenuItem))
menu->removeMenuItem(defaultEditMenuItem);
}
qDeleteAll(m_defaultEditMenuItems);
m_defaultEditMenuItems.clear();
} else {
if (m_defaultEditMenuItems.isEmpty()) {
QCocoaMenuItem *separator = new QCocoaMenuItem;
separator->setIsSeparator(true);
QCocoaMenuItem *dictationItem = new QCocoaMenuItem;
dictationItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Start Dictation..."));
QObject::connect(dictationItem, &QPlatformMenuItem::activated, this, []{
[NSApplication.sharedApplication performSelector:@selector(startDictation:)];
});
QCocoaMenuItem *emojiItem = new QCocoaMenuItem;
emojiItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Emoji && Symbols"));
emojiItem->setShortcut(QKeyCombination(Qt::MetaModifier|Qt::ControlModifier, Qt::Key_Space));
QObject::connect(emojiItem, &QPlatformMenuItem::activated, this, []{
[NSApplication.sharedApplication orderFrontCharacterPalette:nil];
});
m_defaultEditMenuItems << separator << dictationItem << emojiItem;
}
for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) {
if (menu->items().contains(defaultEditMenuItem))
menu->removeMenuItem(defaultEditMenuItem);
menu->insertMenuItem(defaultEditMenuItem, nullptr);
}
}
}
QT_END_NAMESPACE
#include "moc_qcocoamenubar.cpp"