tdf#94251: enable Emoji & Symbols, Dictation, and AutoFill menu items

These are added automatically by macOS whenever the main menu is set with
[NSApp setMainMenu:] and an Edit menu exists. Previously LO would set the
main menu before adding all the actual menus, which prevented macOS from
doing the automatic additions. Instead, re-set the main menu whenever LO
changes the menu layout.

However, some Edit menu translations in LO do not match macOS standard
Edit menu names (eg Español, where LO uses Editar but macOS uses Edición)
and in these cases macOS won't add the standard items. To handle this case
cache the macOS standard menu name inside the AquaSalMenuItem, use the
macOS standard name when calling [NSApp setMainMenu:] and flip back to
the LO name immediately after.

Change-Id: I38d33299f05f99bb084175f6004a4f50068acc74
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201933
Reviewed-by: Dan Williams <dcbw@libreoffice.org>
Reviewed-by: Patrick Luby <guibomacdev@gmail.com>
Tested-by: Jenkins
Reviewed-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
This commit is contained in:
Dan Williams
2026-04-14 21:04:23 +02:00
parent 9a140ee85d
commit c0ff12fc3a
6 changed files with 214 additions and 40 deletions
+8 -4
View File
@@ -6,20 +6,24 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
nib_WRKDIR := $(gb_CustomTarget_workdir)/vcl/osx/res
res_WRKDIR := $(gb_CustomTarget_workdir)/vcl/osx/res
$(nib_WRKDIR)/.dir:
$(res_WRKDIR)/.dir:
mkdir -p $(@D) && touch $@
$(nib_WRKDIR)/MainMenu.nib: $(SRCDIR)/vcl/osx/res/MainMenu.xib | $(nib_WRKDIR)/.dir
$(res_WRKDIR)/MainMenu.nib: $(SRCDIR)/vcl/osx/res/MainMenu.xib | $(res_WRKDIR)/.dir
$(call gb_Output_announce,$(@F),$(true),NIB,2)
ibtool --compile "$@" "$<"
$(eval $(call gb_Package_Package,vcl_osxres,$(nib_WRKDIR)))
$(res_WRKDIR)/MenuTranslations.plist: $(SRCDIR)/vcl/osx/res/MenuTranslations.plist | $(res_WRKDIR)/.dir
cp "$<" "$@"
$(eval $(call gb_Package_Package,vcl_osxres,$(res_WRKDIR)))
$(call gb_Package_get_clean_target,vcl_osxres): $(call gb_CustomTarget_get_clean_target,vcl/osx/res)
$(eval $(call gb_Package_add_files_with_dir,vcl_osxres,Resources,\
MainMenu.nib \
MenuTranslations.plist \
))
# vim:set noet sw=4 ts=4:
+3
View File
@@ -165,6 +165,9 @@ public:
static const short DispatchTimerEvent = 30;
static NSMenu* GetDynamicDockMenu();
private:
NSDictionary *mpMenuTranslations;
};
CGImageRef CreateCGImage( const Image& );
+14 -2
View File
@@ -47,11 +47,15 @@ public: // for OOStatusView
};
private:
std::vector< MenuBarButtonEntry > maButtons;
NSString * mpAltTitle;
NSDictionary * mpMenuTranslations;
MenuBarButtonEntry* findButtonItem( sal_uInt16 i_nItemId );
static void statusLayout();
void useAltTitle(bool bAltTitle);
public:
AquaSalMenu( bool bMenuBar );
AquaSalMenu( bool bMenuBar, NSDictionary *pTranslations );
virtual ~AquaSalMenu() override;
virtual bool VisibleMenuBar() override;
@@ -95,14 +99,22 @@ public:
class AquaSalMenuItem : public SalMenuItem
{
public:
AquaSalMenuItem( const SalItemParams* );
AquaSalMenuItem( const SalItemParams*, NSDictionary* );
virtual ~AquaSalMenuItem() override;
OUString getItemCommand();
void setTitle(const OUString& i_rText);
void useAltTitle(bool bUseAlt);
sal_uInt16 mnId; // Item ID
VclPtr<Menu> mpVCLMenu; // VCL Menu into which this MenuItem is inserted
AquaSalMenu* mpParentMenu; // The menu in which this menu item is inserted
AquaSalMenu* mpSubMenu; // Sub menu of this item (if defined)
NSMenuItem* mpMenuItem; // The NSMenuItem
private:
NSString * mpOrigTitle;
NSString * mpAltTitle;
NSDictionary * mpMenuTranslations;
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>.uno:EditMenu</key>
<dict>
<key>ar</key>
<string>تحرير</string>
<key>ca</key>
<string>Editar</string>
<key>cs</key>
<string>Úpravy</string>
<key>da</key>
<string>Rediger</string>
<key>de</key>
<string>Bearbeiten</string>
<key>el</key>
<string>Επεξεργασία</string>
<key>es-419</key>
<string>Edición</string>
<key>es</key>
<string>Edición</string>
<key>fi</key>
<string>Muokkaa</string>
<key>fr</key>
<string>Édition</string>
<key>he</key>
<string>עריכה</string>
<key>hr</key>
<string>Uredi</string>
<key>hu</key>
<string>Szerkesztés</string>
<key>id</key>
<string>Edit</string>
<key>it</key>
<string>Modifica</string>
<key>ja</key>
<string>編集</string>
<key>ko</key>
<string>편집</string>
<key>ms</key>
<string>Edit</string>
<key>nb</key>
<string>Rediger</string>
<key>nl</key>
<string>Wijzig</string>
<key>pl</key>
<string>Edycja</string>
<key>pt-BR</key>
<string>Editar</string>
<key>pt-PT</key>
<string>Edição</string>
<key>ro</key>
<string>Editare</string>
<key>ru</key>
<string>Правка</string>
<key>sk</key>
<string>Upraviť</string>
<key>sv</key>
<string>Redigera</string>
<key>th</key>
<string>แก้ไข</string>
<key>tr</key>
<string>Düzen</string>
<key>uk</key>
<string>Редагування</string>
<key>vi</key>
<string>Sửa</string>
<key>zh-Hans</key>
<string>编辑</string>
<key>zh-Hant</key>
<string>編輯</string>
</dict>
</dict>
</plist>
+5
View File
@@ -352,6 +352,9 @@ AquaSalInstance::AquaSalInstance()
mpStepperCell = [[NSStepperCell alloc] init];
mpListNodeCell = [[NSButtonCell alloc] init];
NSString *path = [[[NSBundle mainBundle] pathForResource:@"MenuTranslations" ofType:@"plist"] autorelease];
mpMenuTranslations = [NSDictionary dictionaryWithContentsOfFile:path];
#if HAVE_FEATURE_SKIA
AquaSkiaSalGraphicsImpl::prepareSkia();
#endif
@@ -375,6 +378,8 @@ AquaSalInstance::~AquaSalInstance()
[mpCheckCell release];
[mpButtonCell release];
[mpMenuTranslations release];
#if HAVE_FEATURE_SKIA
SkiaHelper::cleanup();
#endif
+109 -34
View File
@@ -23,7 +23,9 @@
#include <objc/objc-runtime.h>
#include <osl/process.h>
#include <rtl/ustrbuf.hxx>
#include <rtl/ustring.hxx>
#include <tools/debug.hxx>
#include <tools/long.hxx>
#include <vcl/commandevent.hxx>
@@ -207,7 +209,7 @@ std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLM
{
initAppMenu();
AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar, mpMenuTranslations );
pAquaSalMenu->mpVCLMenu = pVCLMenu;
return std::unique_ptr<SalMenu>(pAquaSalMenu);
@@ -215,7 +217,7 @@ std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLM
std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
{
AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData, mpMenuTranslations );
return std::unique_ptr<SalMenuItem>(pSalMenuItem);
}
@@ -224,7 +226,8 @@ std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParam
* AquaSalMenu
*/
AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
AquaSalMenu::AquaSalMenu( bool bMenuBar, NSDictionary *pMenuTranslations ) :
mpMenuTranslations( [pMenuTranslations retain] ),
mbMenuBar( bMenuBar ),
mpMenu( nil ),
mpFrame( nullptr ),
@@ -291,6 +294,10 @@ AquaSalMenu::~AquaSalMenu()
[mpMenu autorelease];
}
}
[mpMenuTranslations autorelease];
if( mpAltTitle )
[mpAltTitle autorelease];
}
bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
@@ -395,12 +402,19 @@ void AquaSalMenu::unsetMainMenu()
{
pCurrentMenuBar = nullptr;
// remove items from main menu
// remove all menus except the app menu
NSMenu* pMenu = [NSApp mainMenu];
[NSApp setMainMenu:nil];
for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
[pMenu removeItemAtIndex: 1];
}
void AquaSalMenu::useAltTitle(bool bAltTitle)
{
for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
maItems[i]->useAltTitle(bAltTitle);
}
void AquaSalMenu::setMainMenu()
{
SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
@@ -418,7 +432,7 @@ void AquaSalMenu::setMainMenu()
// tdf#165448 Allow macOS to add menu items in LibreOffice windows menu
// macOS will automatically insert menu items in NSApp's
// windows menu so set that menu to LibreOffice's windows menu.
if( maItems[i]->mpVCLMenu && maItems[i]->mpVCLMenu->GetItemCommand( maItems[i]->mnId ) == u".uno:WindowList"_ustr )
if ( maItems[i]->getItemCommand() == u".uno:WindowList"_ustr )
{
// Avoid macOS inserting duplicate menu items in the
// windows menu
@@ -429,6 +443,15 @@ void AquaSalMenu::setMainMenu()
}
pCurrentMenuBar = this;
// Flip menu/item names to alt ones so macOS will automatically add standard
// menu items like Dictation and Emojis & Symbols
useAltTitle(true);
[NSApp setMainMenu:mpMenu];
// Flip alt menu/item names back to LO ones
useAltTitle(false);
// change status item
statusLayout();
}
@@ -453,6 +476,7 @@ void AquaSalMenu::setDefaultMenu()
if( [pItem menu] == nil )
[pMenu insertItem: pItem atIndex: i+1];
}
[NSApp setMainMenu:pMenu];
// Related: tdf#128186 force key window to a native full screen window
// AquaSalMenu::setDefaultMenu() is generally called when the key
@@ -672,25 +696,9 @@ void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Imag
void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
{
if (!i_pSalMenuItem)
return;
AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
// Delete all mnemonics of mbMenuBar and CJK-style mnemonic
OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
if (aText.endsWith("...", &aText))
aText += u"\u2026";
NSString* pString = CreateNSString( aText );
if (pString)
{
[pAquaSalMenuItem->mpMenuItem setTitle: pString];
// if the menu item has a submenu, change its title as well
if (pAquaSalMenuItem->mpSubMenu)
[pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
[pString release];
if (i_pSalMenuItem) {
AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
pAquaSalMenuItem->setTitle( i_rText );
}
}
@@ -927,16 +935,46 @@ SAL_WNODEPRECATED_DECLARATIONS_POP
);
}
static NSString *getMenuTranslation( NSDictionary *pDict, OUString rUnoCommand )
{
NSDictionary *pLangs = pDict[ [CreateNSString(rUnoCommand) autorelease] ];
if ( !pLangs )
return NULL;
rtl_Locale *pLocale = NULL;
osl_getProcessLocale(&pLocale);
NSString *pLang = [CreateNSString(pLocale->Language) autorelease];
NSString *pRegion = [CreateNSString(pLocale->Country) autorelease];
NSString *pLangAndRegion = NULL;
// HACK: Español (Latinoamérica) is reported as es-419 but osl_getProcessLocale()
// can't deal with 3-character regions; convert back for translation lookup
if ([pLang isEqualToString:@"es"] && [pRegion isEqualToString:@"41"])
pLangAndRegion = @"es_419";
if (!pLangAndRegion)
pLangAndRegion = [[[NSString alloc] initWithFormat:@"%@_%@", pLang, pRegion] autorelease];
NSString *pAltTitle = pLangs[pLangAndRegion];
if ( !pAltTitle )
pAltTitle = pLangs[pLang];
return pAltTitle;
}
/*
* SalMenuItem
*/
AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData, NSDictionary *pMenuTranslations ) :
mnId( pItemData->nId ),
mpVCLMenu( pItemData->pMenu ),
mpParentMenu( nullptr ),
mpSubMenu( nullptr ),
mpMenuItem( nil )
mpMenuItem( nil ),
mpMenuTranslations( [pMenuTranslations retain] )
{
if (pItemData->eType == MenuItemType::SEPARATOR)
{
@@ -950,14 +988,8 @@ AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
[static_cast<SalNSMenuItem*>(mpMenuItem) setReallyEnabled: YES];
// peel mnemonics because on mac there are no such things for menu items
// Delete CJK-style mnemonics for the dropdown menu of the 'New button' and lower menu of 'File > New'
NSString* pString = CreateNSString(MnemonicGenerator::EraseAllMnemonicChars(pItemData->aText));
if (pString)
{
[mpMenuItem setTitle: pString];
[pString release];
}
setTitle(pItemData->aText);
// anything but a separator should set a menu to dispatch to
SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
}
@@ -973,6 +1005,49 @@ AquaSalMenuItem::~AquaSalMenuItem()
*/
if( mpMenuItem )
[mpMenuItem autorelease];
if( mpAltTitle )
[mpAltTitle autorelease];
[mpOrigTitle autorelease];
[mpMenuTranslations autorelease];
}
// getItemCommand() returns the UNO command ID of the menu item,
// eg like ".uno:WindowList" or ".uno:EditMenu"
OUString AquaSalMenuItem::getItemCommand()
{
return mpVCLMenu ? mpVCLMenu->GetItemCommand( mnId ) : OUString();
}
void AquaSalMenuItem::useAltTitle(bool bUseAlt)
{
NSString *pTitle = mpOrigTitle;
if (bUseAlt && mpAltTitle)
pTitle = mpAltTitle;
[mpMenuItem setTitle: pTitle];
// if the menu item has a submenu, change its title too
if (mpSubMenu)
[mpSubMenu->mpMenu setTitle: pTitle];
}
void AquaSalMenuItem::setTitle(const OUString& i_rText)
{
// Delete all normal and CJK-style mnemonic
OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
if (aText.endsWith("...", &aText))
aText += u"\u2026";
mpOrigTitle = CreateNSString( aText );
if ( !mpOrigTitle )
return;
[mpAltTitle autorelease];
mpAltTitle = getMenuTranslation( mpMenuTranslations, getItemCommand() );
useAltTitle(false);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */