Related: tdf#137748 "Update" should have use-underline
[LibreOffice.git] / vcl / osx / salmenu.cxx
blob92b1d44abdd4be9e8d02a64e96494cbcb960c93b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <osl/diagnose.h>
24 #include <objc/objc-runtime.h>
26 #include <rtl/ustrbuf.hxx>
27 #include <tools/debug.hxx>
28 #include <tools/long.hxx>
29 #include <vcl/commandevent.hxx>
30 #include <vcl/toolkit/floatwin.hxx>
31 #include <vcl/window.hxx>
32 #include <vcl/svapp.hxx>
34 #include <osx/runinmain.hxx>
35 #include <osx/saldata.hxx>
36 #include <osx/salinst.h>
37 #include <osx/salmenu.h>
38 #include <osx/salnsmenu.h>
39 #include <osx/salframe.h>
40 #include <osx/a11ywrapper.h>
41 #include <quartz/utils.h>
42 #include <strings.hrc>
43 #include <window.h>
44 #include <vcl/mnemonic.hxx>
46 namespace {
48 void releaseButtonEntry( AquaSalMenu::MenuBarButtonEntry& i_rEntry )
50 if( i_rEntry.mpNSImage )
52 [i_rEntry.mpNSImage release];
53 i_rEntry.mpNSImage = nil;
55 if( i_rEntry.mpToolTipString )
57 [i_rEntry.mpToolTipString release];
58 i_rEntry.mpToolTipString = nil;
64 const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = nullptr;
66 @interface MainMenuSelector : NSObject
69 -(void)showDialog: (ShowDialogId)nDialog;
70 -(void)showPreferences: (id)sender;
71 -(void)showAbout: (id)sender;
72 @end
74 @implementation MainMenuSelector
75 -(void)showDialog: (ShowDialogId)nDialog
77 if( AquaSalMenu::pCurrentMenuBar )
79 const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
80 if( pFrame && AquaSalFrame::isAlive( pFrame ) )
82 pFrame->CallCallback( SalEvent::ShowDialog, reinterpret_cast<void*>(nDialog) );
85 else
87 OUString aDialog;
88 if( nDialog == ShowDialogId::About )
89 aDialog = "ABOUT";
90 else if( nDialog == ShowDialogId::Preferences )
91 aDialog = "PREFERENCES";
92 const ApplicationEvent* pAppEvent = new ApplicationEvent(
93 ApplicationEvent::Type::ShowDialog, aDialog);
94 AquaSalInstance::aAppEventList.push_back( pAppEvent );
98 -(void)showPreferences: (id) sender
100 (void)sender;
101 SolarMutexGuard aGuard;
103 [self showDialog: ShowDialogId::Preferences];
105 -(void)showAbout: (id) sender
107 (void)sender;
108 SolarMutexGuard aGuard;
110 [self showDialog: ShowDialogId::About];
112 @end
114 // FIXME: currently this is leaked
115 static MainMenuSelector* pMainMenuSelector = nil;
117 static void initAppMenu()
119 static bool bInitialized = false;
120 if (bInitialized)
121 return;
122 OSX_SALDATA_RUNINMAIN(initAppMenu())
123 bInitialized = true;
125 NSMenu* pAppMenu = nil;
126 NSMenuItem* pNewItem = nil;
128 NSMenu* pMainMenu = [[[NSMenu alloc] initWithTitle: @"Main Menu"] autorelease];
129 pNewItem = [pMainMenu addItemWithTitle: @"Application"
130 action: nil
131 keyEquivalent: @""];
132 pAppMenu = [[[NSMenu alloc] initWithTitle: @"Application"] autorelease];
133 [pNewItem setSubmenu: pAppMenu];
134 [NSApp setMainMenu: pMainMenu];
136 pMainMenuSelector = [[MainMenuSelector alloc] init];
138 // about
139 NSString* pString = CreateNSString(VclResId(SV_STDTEXT_ABOUT));
140 pNewItem = [pAppMenu addItemWithTitle: pString
141 action: @selector(showAbout:)
142 keyEquivalent: @""];
143 [pString release];
144 [pNewItem setTarget: pMainMenuSelector];
146 [pAppMenu addItem:[NSMenuItem separatorItem]];
148 // preferences
149 pString = CreateNSString(VclResId(SV_STDTEXT_PREFERENCES));
150 pNewItem = [pAppMenu addItemWithTitle: pString
151 action: @selector(showPreferences:)
152 keyEquivalent: @","];
153 [pString release];
154 [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand];
155 [pNewItem setTarget: pMainMenuSelector];
157 [pAppMenu addItem:[NSMenuItem separatorItem]];
159 // Services item and menu
160 pString = CreateNSString(VclResId(SV_MENU_MAC_SERVICES));
161 pNewItem = [pAppMenu addItemWithTitle: pString
162 action: nil
163 keyEquivalent: @""];
164 NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
165 [pNewItem setSubmenu: servicesMenu];
166 [NSApp setServicesMenu: servicesMenu];
168 [pAppMenu addItem:[NSMenuItem separatorItem]];
170 // Hide Application
171 pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEAPP));
172 [pAppMenu addItemWithTitle: pString
173 action:@selector(hide:)
174 keyEquivalent:@"h"];
175 [pString release];
177 // Hide Others
178 pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEALL));
179 [pAppMenu addItemWithTitle: pString
180 action:@selector(hideOtherApplications:)
181 keyEquivalent:@"h"];
182 [pString release];
183 [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
185 // Show All
186 pString = CreateNSString(VclResId(SV_MENU_MAC_SHOWALL));
187 [pAppMenu addItemWithTitle: pString
188 action:@selector(unhideAllApplications:)
189 keyEquivalent:@""];
190 [pString release];
192 [pAppMenu addItem:[NSMenuItem separatorItem]];
194 // Quit
195 pString = CreateNSString(VclResId(SV_MENU_MAC_QUITAPP));
196 [pAppMenu addItemWithTitle: pString
197 action:@selector(terminate:)
198 keyEquivalent:@"q"];
199 [pString release];
202 std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
204 initAppMenu();
206 AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
207 pAquaSalMenu->mpVCLMenu = pVCLMenu;
209 return std::unique_ptr<SalMenu>(pAquaSalMenu);
212 std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
214 AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
216 return std::unique_ptr<SalMenuItem>(pSalMenuItem);
220 * AquaSalMenu
223 AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
224 mbMenuBar( bMenuBar ),
225 mpMenu( nil ),
226 mpFrame( nullptr ),
227 mpParentSalMenu( nullptr )
229 if( ! mbMenuBar )
231 mpMenu = [[SalNSMenu alloc] initWithMenu: this];
232 [mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
234 else
236 mpMenu = [NSApp mainMenu];
238 [mpMenu setAutoenablesItems: NO];
241 AquaSalMenu::~AquaSalMenu()
243 // actually someone should have done AquaSalFrame::SetMenu( NULL )
244 // on our frame, alas it is not so
245 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
246 const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
248 // this should normally be empty already, but be careful...
249 for( size_t i = 0; i < maButtons.size(); i++ )
250 releaseButtonEntry( maButtons[i] );
251 maButtons.clear();
253 // is this leaking in some cases ? the release often leads to a duplicate release
254 // it seems the parent item gets ownership of the menu
255 if( mpMenu )
257 if( mbMenuBar )
259 if( pCurrentMenuBar == this )
261 // if the current menubar gets destroyed, set the default menubar
262 setDefaultMenu();
265 else
266 // the system may still hold a reference on mpMenu
268 // so set the pointer to this AquaSalMenu to NULL
269 // to protect from calling a dead object
271 // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
272 // so we can safely cast here
273 [static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
274 /* #i89860# FIXME:
275 using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
276 instead of [release] fixes an occasional crash. That should
277 indicate that we release menus / menu items in the wrong order
278 somewhere, but I could not find that case.
280 [mpMenu autorelease];
285 bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
287 // set offsets for positioning
288 const float offset = 9.0;
290 // get the pointers
291 AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
292 NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
293 NSView* pParentNSView = [pParentNSWindow contentView];
294 NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
295 NSRect popupFrame = [pPopupNSView frame];
297 // create frame rect
298 NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
299 pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
301 // do the same strange semantics as vcl popup windows to arrive at a frame geometry
302 // in mirrored UI case; best done by actually executing the same code
303 sal_uInt16 nArrangeIndex;
304 pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
305 displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.nX - pParentAquaSalFrame->maGeometry.nX + offset;
306 displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.nY - pParentAquaSalFrame->maGeometry.nY + offset;
307 pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
309 // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
310 if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
311 [pParentNSView performSelector:@selector(clearLastEvent)];
313 // open popup menu
314 NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
315 [pPopUpButtonCell setMenu: mpMenu];
316 [pPopUpButtonCell selectItem:nil];
317 [AquaA11yWrapper setPopupMenuOpen: YES];
318 [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
319 [pPopUpButtonCell release];
320 [AquaA11yWrapper setPopupMenuOpen: NO];
322 return true;
325 int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
327 int nIndex = 0;
328 if( nPos == MENU_APPEND )
329 nIndex = [mpMenu numberOfItems];
330 else
331 nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
332 return nIndex;
335 const AquaSalFrame* AquaSalMenu::getFrame() const
337 const AquaSalMenu* pMenu = this;
338 while( pMenu && ! pMenu->mpFrame )
339 pMenu = pMenu->mpParentSalMenu;
340 return pMenu ? pMenu->mpFrame : nullptr;
343 void AquaSalMenu::unsetMainMenu()
345 pCurrentMenuBar = nullptr;
347 // remove items from main menu
348 NSMenu* pMenu = [NSApp mainMenu];
349 for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
350 [pMenu removeItemAtIndex: 1];
353 void AquaSalMenu::setMainMenu()
355 SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
356 if( mbMenuBar )
358 if( pCurrentMenuBar != this )
360 unsetMainMenu();
361 // insert our items
362 for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
364 NSMenuItem* pItem = maItems[i]->mpMenuItem;
365 [mpMenu insertItem: pItem atIndex: i+1];
367 pCurrentMenuBar = this;
369 // change status item
370 statusLayout();
372 enableMainMenu( true );
376 void AquaSalMenu::setDefaultMenu()
378 NSMenu* pMenu = [NSApp mainMenu];
380 unsetMainMenu();
382 // insert default items
383 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
384 for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
386 NSMenuItem* pItem = rFallbackMenu[i];
387 if( [pItem menu] == nil )
388 [pMenu insertItem: pItem atIndex: i+1];
392 void AquaSalMenu::enableMainMenu( bool bEnable )
394 NSMenu* pMainMenu = [NSApp mainMenu];
395 if( pMainMenu )
397 // enable/disable items from main menu
398 int nItems = [pMainMenu numberOfItems];
399 for( int n = 1; n < nItems; n++ )
401 NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
402 [pItem setEnabled: bEnable ? YES : NO];
407 void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
409 initAppMenu();
411 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
413 // prevent duplicate insertion
414 int nItems = rFallbackMenu.size();
415 for( int i = 0; i < nItems; i++ )
417 if( rFallbackMenu[i] == pNewItem )
418 return;
421 // push the item to the back and retain it
422 [pNewItem retain];
423 rFallbackMenu.push_back( pNewItem );
425 if( pCurrentMenuBar == nullptr )
426 setDefaultMenu();
429 void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
431 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
433 // find item
434 unsigned int nItems = rFallbackMenu.size();
435 for( unsigned int i = 0; i < nItems; i++ )
437 if( rFallbackMenu[i] == pOldItem )
439 // remove item and release
440 rFallbackMenu.erase( rFallbackMenu.begin() + i );
441 [pOldItem release];
443 if( pCurrentMenuBar == nullptr )
444 setDefaultMenu();
446 return;
451 bool AquaSalMenu::VisibleMenuBar()
453 return true;
456 void AquaSalMenu::SetFrame( const SalFrame *pFrame )
458 mpFrame = static_cast<const AquaSalFrame*>(pFrame);
461 void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
463 OSX_SALDATA_RUNINMAIN(InsertItem(pSalMenuItem, nPos))
465 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
467 pAquaSalMenuItem->mpParentMenu = this;
468 DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr ||
469 pAquaSalMenuItem->mpVCLMenu == mpVCLMenu ||
470 mpVCLMenu == nullptr,
471 "resetting menu ?" );
472 if( pAquaSalMenuItem->mpVCLMenu )
473 mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
475 if( nPos == MENU_APPEND || nPos == maItems.size() )
476 maItems.push_back( pAquaSalMenuItem );
477 else if( nPos < maItems.size() )
478 maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
479 else
481 OSL_FAIL( "invalid item index in insert" );
482 return;
485 if( ! mbMenuBar || pCurrentMenuBar == this )
486 [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
489 void AquaSalMenu::RemoveItem( unsigned nPos )
491 AquaSalMenuItem* pRemoveItem = nullptr;
492 if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
494 pRemoveItem = maItems.back();
495 maItems.pop_back();
497 else if( nPos < maItems.size() )
499 pRemoveItem = maItems[ nPos ];
500 maItems.erase( maItems.begin()+nPos );
502 else
504 OSL_FAIL( "invalid item index in remove" );
505 return;
508 pRemoveItem->mpParentMenu = nullptr;
510 if( ! mbMenuBar || pCurrentMenuBar == this )
511 [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
514 void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
516 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
517 AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
519 if (subAquaSalMenu)
521 pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
522 if( subAquaSalMenu->mpParentSalMenu == nullptr )
524 subAquaSalMenu->mpParentSalMenu = this;
525 [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
527 // set title of submenu
528 [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
530 else if( subAquaSalMenu->mpParentSalMenu != this )
532 // cocoa doesn't allow menus to be submenus of multiple
533 // menu items, so place a copy in the menu item instead ?
534 // let's hope that NSMenu copy does the right thing
535 NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
536 [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
538 // set title of submenu
539 [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
542 else
544 if( pAquaSalMenuItem->mpSubMenu )
546 if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
547 pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
549 pAquaSalMenuItem->mpSubMenu = nullptr;
550 [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
554 void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
556 if( nPos < maItems.size() )
558 NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
559 [pItem setState: bCheck ? NSControlStateValueOn : NSControlStateValueOff];
563 void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
565 if( nPos < maItems.size() )
567 NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
568 [pItem setEnabled: bEnable ? YES : NO];
572 void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
574 AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
575 if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
576 return;
578 NSImage* pImage = CreateNSImage( rImage );
580 [pSalMenuItem->mpMenuItem setImage: pImage];
581 if( pImage )
582 [pImage release];
585 void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
587 if (!i_pSalMenuItem)
588 return;
590 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
592 // Delete all mnemonics of mbMenuBar and CJK-style mnemonic
593 OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
595 if (aText.endsWith("...", &aText))
596 aText += u"\u2026";
598 NSString* pString = CreateNSString( aText );
599 if (pString)
601 [pAquaSalMenuItem->mpMenuItem setTitle: pString];
602 // if the menu item has a submenu, change its title as well
603 if (pAquaSalMenuItem->mpSubMenu)
604 [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
605 [pString release];
609 void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
611 sal_uInt16 nModifier;
612 sal_Unicode nCommandKey = 0;
614 sal_uInt16 nKeyCode=rKeyCode.GetCode();
615 if( nKeyCode )
617 if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z)) // letter A..Z
618 nCommandKey = nKeyCode-KEY_A + 'a';
619 else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9)) // numbers 0..9
620 nCommandKey = nKeyCode-KEY_0 + '0';
621 else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26)) // function keys F1..F26
622 nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
623 else if( nKeyCode == KEY_REPEAT )
624 nCommandKey = NSRedoFunctionKey;
625 else if( nKeyCode == KEY_SPACE )
626 nCommandKey = ' ';
627 else
629 switch (nKeyCode)
631 case KEY_ADD:
632 nCommandKey='+';
633 break;
634 case KEY_SUBTRACT:
635 nCommandKey='-';
636 break;
637 case KEY_MULTIPLY:
638 nCommandKey='*';
639 break;
640 case KEY_DIVIDE:
641 nCommandKey='/';
642 break;
643 case KEY_POINT:
644 nCommandKey='.';
645 break;
646 case KEY_LESS:
647 nCommandKey='<';
648 break;
649 case KEY_GREATER:
650 nCommandKey='>';
651 break;
652 case KEY_EQUAL:
653 nCommandKey='=';
654 break;
655 case KEY_SEMICOLON:
656 nCommandKey=';';
657 break;
658 case KEY_BACKSPACE:
659 nCommandKey=u'\x232b';
660 break;
661 case KEY_PAGEUP:
662 nCommandKey=u'\x21de';
663 break;
664 case KEY_PAGEDOWN:
665 nCommandKey=u'\x21df';
666 break;
667 case KEY_UP:
668 nCommandKey=u'\x21e1';
669 break;
670 case KEY_DOWN:
671 nCommandKey=u'\x21e3';
672 break;
673 case KEY_RETURN:
674 nCommandKey=u'\x21a9';
675 break;
676 case KEY_BRACKETLEFT:
677 nCommandKey='[';
678 break;
679 case KEY_BRACKETRIGHT:
680 nCommandKey=']';
681 break;
685 else // not even a code ? nonsense -> ignore
686 return;
688 SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
690 nModifier=rKeyCode.GetModifier();
692 // should always use the command key
693 int nItemModifier = 0;
695 if (nModifier & KEY_SHIFT)
697 nItemModifier |= NSEventModifierFlagShift; // actually useful only for function keys
698 if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
699 nCommandKey = nKeyCode - KEY_A + 'A';
702 if (nModifier & KEY_MOD1)
703 nItemModifier |= NSEventModifierFlagCommand;
705 if(nModifier & KEY_MOD2)
706 nItemModifier |= NSEventModifierFlagOption;
708 if(nModifier & KEY_MOD3)
709 nItemModifier |= NSEventModifierFlagControl;
711 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
712 NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
713 [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
714 [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
715 if (pString)
716 [pString release];
719 void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
723 AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
725 for( size_t i = 0; i < maButtons.size(); ++i )
727 if( maButtons[i].maButton.mnId == i_nItemId )
728 return &maButtons[i];
730 return nullptr;
733 void AquaSalMenu::statusLayout()
735 if( GetSalData()->mpStatusItem )
737 SAL_WNODEPRECATED_DECLARATIONS_PUSH
738 // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button
739 // property instead"
740 NSView* pNSView = [GetSalData()->mpStatusItem view];
741 SAL_WNODEPRECATED_DECLARATIONS_POP
742 if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
743 [static_cast<OOStatusItemView*>(pNSView) layout];
744 else
745 OSL_FAIL( "someone stole our status view" );
749 bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
751 if( ! mbMenuBar )
752 return false;
754 MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
755 if( pEntry )
757 releaseButtonEntry( *pEntry );
758 pEntry->maButton = i_rNewItem;
759 pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
760 if( i_rNewItem.maToolTipText.getLength() )
761 pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
763 else
765 maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
766 maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
767 maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
770 // lazy create status item
771 SalData::getStatusItem();
773 if( pCurrentMenuBar == this )
774 statusLayout();
776 return true;
779 void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
781 MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
782 if( pEntry )
784 releaseButtonEntry( *pEntry );
785 // note: vector guarantees that its contents are in a plain array
786 maButtons.erase( maButtons.begin() + (pEntry - maButtons.data()) );
789 if( pCurrentMenuBar == this )
790 statusLayout();
793 tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
795 if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
796 return tools::Rectangle();
798 MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
800 if( ! pEntry )
801 return tools::Rectangle();
803 NSStatusItem* pItem = SalData::getStatusItem();
804 if( ! pItem )
805 return tools::Rectangle();
807 SAL_WNODEPRECATED_DECLARATIONS_PUSH
808 // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button property
809 // instead"
810 NSView* pNSView = [pItem view];
811 SAL_WNODEPRECATED_DECLARATIONS_POP
812 if( ! pNSView )
813 return tools::Rectangle();
814 NSWindow* pNSWin = [pNSView window];
815 if( ! pNSWin )
816 return tools::Rectangle();
818 NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
820 // make coordinates relative to reference frame
821 static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
822 aRect.origin.x -= i_pReferenceFrame->maGeometry.nX;
823 aRect.origin.y -= i_pReferenceFrame->maGeometry.nY + aRect.size.height;
825 return tools::Rectangle( Point(static_cast<tools::Long>(aRect.origin.x),
826 static_cast<tools::Long>(aRect.origin.y)
828 Size( static_cast<tools::Long>(aRect.size.width),
829 static_cast<tools::Long>(aRect.size.height)
835 * SalMenuItem
838 AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
839 mnId( pItemData->nId ),
840 mpVCLMenu( pItemData->pMenu ),
841 mpParentMenu( nullptr ),
842 mpSubMenu( nullptr ),
843 mpMenuItem( nil )
845 if (pItemData->eType == MenuItemType::SEPARATOR)
847 mpMenuItem = [NSMenuItem separatorItem];
848 // these can go occasionally go in and out of a menu, ensure their lifecycle
849 // also for the release in AquaSalMenuItem destructor
850 [mpMenuItem retain];
852 else
854 mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
855 [mpMenuItem setEnabled: YES];
857 // peel mnemonics because on mac there are no such things for menu items
858 // Delete CJK-style mnemonics for the dropdown menu of the 'New button' and lower menu of 'File > New'
859 NSString* pString = CreateNSString(MnemonicGenerator::EraseAllMnemonicChars((pItemData->aText)));
860 if (pString)
862 [mpMenuItem setTitle: pString];
863 [pString release];
865 // anything but a separator should set a menu to dispatch to
866 SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
870 AquaSalMenuItem::~AquaSalMenuItem()
872 /* #i89860# FIXME:
873 using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
874 [release] fixes an occasional crash. That should indicate that we release
875 menus / menu items in the wrong order somewhere, but I
876 could not find that case.
878 if( mpMenuItem )
879 [mpMenuItem autorelease];
882 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */