Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / osx / salmenu.cxx
blobd943c82064bcb7f1b1f750c948c82e8b4fcb9ae1
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>
22 #include <objc/objc-runtime.h>
24 #include <comphelper/string.hxx>
25 #include <rtl/ustrbuf.hxx>
27 #include <vcl/commandevent.hxx>
28 #include <vcl/floatwin.hxx>
29 #include <vcl/window.hxx>
30 #include <vcl/svapp.hxx>
32 #include "osx/saldata.hxx"
33 #include "osx/salinst.h"
34 #include "osx/salmenu.h"
35 #include "osx/salnsmenu.h"
36 #include "osx/salframe.h"
37 #include "osx/a11ywrapper.h"
38 #include "quartz/utils.h"
39 #include "svids.hrc"
40 #include "window.h"
42 namespace {
44 void releaseButtonEntry( AquaSalMenu::MenuBarButtonEntry& i_rEntry )
46 if( i_rEntry.mpNSImage )
48 [i_rEntry.mpNSImage release];
49 i_rEntry.mpNSImage = nil;
51 if( i_rEntry.mpToolTipString )
53 [i_rEntry.mpToolTipString release];
54 i_rEntry.mpToolTipString = nil;
60 const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = nullptr;
62 @interface MainMenuSelector : NSObject
65 -(void)showDialog: (ShowDialogId)nDialog;
66 -(void)showPreferences: (id)sender;
67 -(void)showAbout: (id)sender;
68 @end
70 @implementation MainMenuSelector
71 -(void)showDialog: (ShowDialogId)nDialog
73 if( AquaSalMenu::pCurrentMenuBar )
75 const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
76 if( pFrame && AquaSalFrame::isAlive( pFrame ) )
78 pFrame->CallCallback( SalEvent::ShowDialog, reinterpret_cast<void*>(nDialog) );
81 else
83 OUString aDialog;
84 if( nDialog == ShowDialogId::About )
85 aDialog = "ABOUT";
86 else if( nDialog == ShowDialogId::Preferences )
87 aDialog = "PREFERENCES";
88 const ApplicationEvent* pAppEvent = new ApplicationEvent(
89 ApplicationEvent::Type::ShowDialog, aDialog);
90 AquaSalInstance::aAppEventList.push_back( pAppEvent );
94 -(void)showPreferences: (id) sender
96 (void)sender;
97 SolarMutexGuard aGuard;
99 [self showDialog: ShowDialogId::Preferences];
101 -(void)showAbout: (id) sender
103 (void)sender;
104 SolarMutexGuard aGuard;
106 [self showDialog: ShowDialogId::About];
108 @end
110 // FIXME: currently this is leaked
111 static MainMenuSelector* pMainMenuSelector = nil;
113 static void initAppMenu()
115 static bool bOnce = true;
116 if( bOnce )
118 bOnce = false;
120 ResMgr* pMgr = ImplGetResMgr();
121 if( pMgr )
123 // get the main menu
124 NSMenu* pMainMenu = [NSApp mainMenu];
125 if( pMainMenu != nil )
127 // create the action selector
128 pMainMenuSelector = [[MainMenuSelector alloc] init];
130 // get the proper submenu
131 NSMenu* pAppMenu = [[pMainMenu itemAtIndex: 0] submenu];
132 if( pAppMenu )
134 // insert about entry
135 OUString aAbout( ResId( SV_STDTEXT_ABOUT, *pMgr ) );
136 NSString* pString = CreateNSString( aAbout );
137 NSMenuItem* pNewItem = [pAppMenu insertItemWithTitle: pString
138 action: @selector(showAbout:)
139 keyEquivalent: @""
140 atIndex: 0];
141 if (pString)
142 [pString release];
143 if( pNewItem )
145 [pNewItem setTarget: pMainMenuSelector];
146 [pAppMenu insertItem: [NSMenuItem separatorItem] atIndex: 1];
149 // insert preferences entry
150 OUString aPref( ResId( SV_STDTEXT_PREFERENCES, *pMgr ) );
151 pString = CreateNSString( aPref );
152 pNewItem = [pAppMenu insertItemWithTitle: pString
153 action: @selector(showPreferences:)
154 keyEquivalent: @","
155 atIndex: 2];
156 if (pString)
157 [pString release];
158 if( pNewItem )
160 SAL_WNODEPRECATED_DECLARATIONS_PUSH
161 // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
162 [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask];
163 SAL_WNODEPRECATED_DECLARATIONS_POP
164 [pNewItem setTarget: pMainMenuSelector];
165 [pAppMenu insertItem: [NSMenuItem separatorItem] atIndex: 3];
168 // WARNING: ultra ugly code ahead
170 // rename standard entries
171 // rename "Services"
172 pNewItem = [pAppMenu itemAtIndex: 4];
173 if( pNewItem )
175 pString = CreateNSString( OUString( ResId( SV_MENU_MAC_SERVICES, *pMgr ) ) );
176 [pNewItem setTitle: pString];
177 if( pString )
178 [pString release];
181 // rename "Hide NewApplication"
182 pNewItem = [pAppMenu itemAtIndex: 6];
183 if( pNewItem )
185 pString = CreateNSString( OUString( ResId( SV_MENU_MAC_HIDEAPP, *pMgr ) ) );
186 [pNewItem setTitle: pString];
187 if( pString )
188 [pString release];
191 // rename "Hide Others"
192 pNewItem = [pAppMenu itemAtIndex: 7];
193 if( pNewItem )
195 pString = CreateNSString( OUString( ResId( SV_MENU_MAC_HIDEALL, *pMgr ) ) );
196 [pNewItem setTitle: pString];
197 if( pString )
198 [pString release];
201 // rename "Show all"
202 pNewItem = [pAppMenu itemAtIndex: 8];
203 if( pNewItem )
205 pString = CreateNSString( OUString( ResId( SV_MENU_MAC_SHOWALL, *pMgr ) ) );
206 [pNewItem setTitle: pString];
207 if( pString )
208 [pString release];
211 // rename "Quit NewApplication"
212 pNewItem = [pAppMenu itemAtIndex: 10];
213 if( pNewItem )
215 pString = CreateNSString( OUString( ResId( SV_MENU_MAC_QUITAPP, *pMgr ) ) );
216 [pNewItem setTitle: pString];
217 if( pString )
218 [pString release];
226 SalMenu* AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
228 initAppMenu();
230 AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
231 pAquaSalMenu->mpVCLMenu = pVCLMenu;
233 return pAquaSalMenu;
236 void AquaSalInstance::DestroyMenu( SalMenu* pSalMenu )
238 delete pSalMenu;
241 SalMenuItem* AquaSalInstance::CreateMenuItem( const SalItemParams* pItemData )
243 if( !pItemData )
244 return nullptr;
246 AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( pItemData );
248 return pSalMenuItem;
251 void AquaSalInstance::DestroyMenuItem( SalMenuItem* pSalMenuItem )
253 delete pSalMenuItem;
257 * AquaSalMenu
260 AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
261 mbMenuBar( bMenuBar ),
262 mpMenu( nil ),
263 mpFrame( nullptr ),
264 mpParentSalMenu( nullptr )
266 if( ! mbMenuBar )
268 mpMenu = [[SalNSMenu alloc] initWithMenu: this];
269 [mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
271 else
273 mpMenu = [NSApp mainMenu];
275 [mpMenu setAutoenablesItems: NO];
278 AquaSalMenu::~AquaSalMenu()
280 // actually someone should have done AquaSalFrame::SetMenu( NULL )
281 // on our frame, alas it is not so
282 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
283 const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
285 // this should normally be empty already, but be careful...
286 for( size_t i = 0; i < maButtons.size(); i++ )
287 releaseButtonEntry( maButtons[i] );
288 maButtons.clear();
290 // is this leaking in some cases ? the release often leads to a duplicate release
291 // it seems the parent item gets ownership of the menu
292 if( mpMenu )
294 if( mbMenuBar )
296 if( pCurrentMenuBar == this )
298 // if the current menubar gets destroyed, set the default menubar
299 setDefaultMenu();
302 else
303 // the system may still hold a reference on mpMenu
305 // so set the pointer to this AquaSalMenu to NULL
306 // to protect from calling a dead object
308 // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
309 // so we can safely cast here
310 [static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
311 /* #i89860# FIXME:
312 using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
313 instead of [release] fixes an occasional crash. That should
314 indicate that we release menus / menu items in the wrong order
315 somewhere, but I could not find that case.
317 [mpMenu autorelease];
322 bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
324 // do not use native popup menu when AQUA_NATIVE_MENUS is set to false
325 if( ! VisibleMenuBar() ) {
326 return false;
329 // set offsets for positioning
330 const float offset = 9.0;
332 // get the pointers
333 AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
334 NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
335 NSView* pParentNSView = [pParentNSWindow contentView];
336 NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
337 NSRect popupFrame = [pPopupNSView frame];
339 // create frame rect
340 NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
341 pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
343 // do the same strange semantics as vcl popup windows to arrive at a frame geometry
344 // in mirrored UI case; best done by actually executing the same code
345 sal_uInt16 nArrangeIndex;
346 pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
347 displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.nX - pParentAquaSalFrame->maGeometry.nX + offset;
348 displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.nY - pParentAquaSalFrame->maGeometry.nY + offset;
349 pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
351 // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
352 if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
353 [pParentNSView performSelector:@selector(clearLastEvent)];
355 // open popup menu
356 NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
357 [pPopUpButtonCell setMenu: mpMenu];
358 [pPopUpButtonCell selectItem:nil];
359 [AquaA11yWrapper setPopupMenuOpen: YES];
360 [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
361 [pPopUpButtonCell release];
362 [AquaA11yWrapper setPopupMenuOpen: NO];
364 return true;
367 int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
369 int nIndex = 0;
370 if( nPos == MENU_APPEND )
371 nIndex = [mpMenu numberOfItems];
372 else
373 nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
374 return nIndex;
377 const AquaSalFrame* AquaSalMenu::getFrame() const
379 const AquaSalMenu* pMenu = this;
380 while( pMenu && ! pMenu->mpFrame )
381 pMenu = pMenu->mpParentSalMenu;
382 return pMenu ? pMenu->mpFrame : nullptr;
385 void AquaSalMenu::unsetMainMenu()
387 pCurrentMenuBar = nullptr;
389 // remove items from main menu
390 NSMenu* pMenu = [NSApp mainMenu];
391 for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
392 [pMenu removeItemAtIndex: 1];
395 void AquaSalMenu::setMainMenu()
397 SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
398 if( mbMenuBar )
400 if( pCurrentMenuBar != this )
402 unsetMainMenu();
403 // insert our items
404 for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
406 NSMenuItem* pItem = maItems[i]->mpMenuItem;
407 [mpMenu insertItem: pItem atIndex: i+1];
409 pCurrentMenuBar = this;
411 // change status item
412 statusLayout();
414 enableMainMenu( true );
418 void AquaSalMenu::setDefaultMenu()
420 NSMenu* pMenu = [NSApp mainMenu];
422 unsetMainMenu();
424 // insert default items
425 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
426 for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
428 NSMenuItem* pItem = rFallbackMenu[i];
429 if( [pItem menu] == nil )
430 [pMenu insertItem: pItem atIndex: i+1];
434 void AquaSalMenu::enableMainMenu( bool bEnable )
436 NSMenu* pMainMenu = [NSApp mainMenu];
437 if( pMainMenu )
439 // enable/disable items from main menu
440 int nItems = [pMainMenu numberOfItems];
441 for( int n = 1; n < nItems; n++ )
443 NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
444 [pItem setEnabled: bEnable ? YES : NO];
449 void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
451 initAppMenu();
453 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
455 // prevent duplicate insertion
456 int nItems = rFallbackMenu.size();
457 for( int i = 0; i < nItems; i++ )
459 if( rFallbackMenu[i] == pNewItem )
460 return;
463 // push the item to the back and retain it
464 [pNewItem retain];
465 rFallbackMenu.push_back( pNewItem );
467 if( pCurrentMenuBar == nullptr )
468 setDefaultMenu();
471 void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
473 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
475 // find item
476 unsigned int nItems = rFallbackMenu.size();
477 for( unsigned int i = 0; i < nItems; i++ )
479 if( rFallbackMenu[i] == pOldItem )
481 // remove item and release
482 rFallbackMenu.erase( rFallbackMenu.begin() + i );
483 [pOldItem release];
485 if( pCurrentMenuBar == nullptr )
486 setDefaultMenu();
488 return;
493 bool AquaSalMenu::VisibleMenuBar()
495 // Enable/disable experimental native menus code?
497 // To disable native menus, set the environment variable AQUA_NATIVE_MENUS to FALSE
499 static const char *pExperimental = getenv ("AQUA_NATIVE_MENUS");
501 if ( pExperimental && !strcasecmp(pExperimental, "FALSE") )
502 return false;
504 // End of experimental code enable/disable part
506 return true;
509 void AquaSalMenu::SetFrame( const SalFrame *pFrame )
511 mpFrame = static_cast<const AquaSalFrame*>(pFrame);
514 void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
516 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
518 pAquaSalMenuItem->mpParentMenu = this;
519 DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr ||
520 pAquaSalMenuItem->mpVCLMenu == mpVCLMenu ||
521 mpVCLMenu == nullptr,
522 "resetting menu ?" );
523 if( pAquaSalMenuItem->mpVCLMenu )
524 mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
526 if( nPos == MENU_APPEND || nPos == maItems.size() )
527 maItems.push_back( pAquaSalMenuItem );
528 else if( nPos < maItems.size() )
529 maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
530 else
532 OSL_FAIL( "invalid item index in insert" );
533 return;
536 if( ! mbMenuBar || pCurrentMenuBar == this )
537 [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
540 void AquaSalMenu::RemoveItem( unsigned nPos )
542 AquaSalMenuItem* pRemoveItem = nullptr;
543 if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
545 pRemoveItem = maItems.back();
546 maItems.pop_back();
548 else if( nPos < maItems.size() )
550 pRemoveItem = maItems[ nPos ];
551 maItems.erase( maItems.begin()+nPos );
553 else
555 OSL_FAIL( "invalid item index in remove" );
556 return;
559 pRemoveItem->mpParentMenu = nullptr;
561 if( ! mbMenuBar || pCurrentMenuBar == this )
562 [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
565 void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
567 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
568 AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
570 if (subAquaSalMenu)
572 pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
573 if( subAquaSalMenu->mpParentSalMenu == nullptr )
575 subAquaSalMenu->mpParentSalMenu = this;
576 [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
578 // set title of submenu
579 [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
581 else if( subAquaSalMenu->mpParentSalMenu != this )
583 // cocoa doesn't allow menus to be submenus of multiple
584 // menu items, so place a copy in the menu item instead ?
585 // let's hope that NSMenu copy does the right thing
586 NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
587 [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
589 // set title of submenu
590 [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
593 else
595 if( pAquaSalMenuItem->mpSubMenu )
597 if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
598 pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
600 pAquaSalMenuItem->mpSubMenu = nullptr;
601 [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
605 void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
607 if( nPos < maItems.size() )
609 NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
610 [pItem setState: bCheck ? NSOnState : NSOffState];
614 void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
616 if( nPos < maItems.size() )
618 NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
619 [pItem setEnabled: bEnable ? YES : NO];
623 void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
625 AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
626 if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
627 return;
629 NSImage* pImage = CreateNSImage( rImage );
631 [pSalMenuItem->mpMenuItem setImage: pImage];
632 if( pImage )
633 [pImage release];
636 void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
638 if (!i_pSalMenuItem)
639 return;
641 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
643 // Delete mnemonics
644 OUString aText = i_rText.replaceAll("~", "");
646 /* #i90015# until there is a correct solution
647 strip out any appended (.*) in menubar entries
649 if( mbMenuBar )
651 sal_Int32 nPos = aText.lastIndexOf( '(' );
652 if( nPos != -1 )
654 sal_Int32 nPos2 = aText.indexOf( ')' );
655 if( nPos2 != -1 )
656 aText = aText.replaceAt( nPos, nPos2-nPos+1, "" );
660 NSString* pString = CreateNSString( aText );
661 if (pString)
663 [pAquaSalMenuItem->mpMenuItem setTitle: pString];
664 // if the menu item has a submenu, change its title as well
665 if (pAquaSalMenuItem->mpSubMenu)
666 [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
667 [pString release];
671 void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
673 sal_uInt16 nModifier;
674 sal_Unicode nCommandKey = 0;
676 sal_uInt16 nKeyCode=rKeyCode.GetCode();
677 if( nKeyCode )
679 if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z)) // letter A..Z
680 nCommandKey = nKeyCode-KEY_A + 'a';
681 else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9)) // numbers 0..9
682 nCommandKey = nKeyCode-KEY_0 + '0';
683 else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26)) // function keys F1..F26
684 nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
685 else if( nKeyCode == KEY_REPEAT )
686 nCommandKey = NSRedoFunctionKey;
687 else if( nKeyCode == KEY_SPACE )
688 nCommandKey = ' ';
689 else
691 switch (nKeyCode)
693 case KEY_ADD:
694 nCommandKey='+';
695 break;
696 case KEY_SUBTRACT:
697 nCommandKey='-';
698 break;
699 case KEY_MULTIPLY:
700 nCommandKey='*';
701 break;
702 case KEY_DIVIDE:
703 nCommandKey='/';
704 break;
705 case KEY_POINT:
706 nCommandKey='.';
707 break;
708 case KEY_LESS:
709 nCommandKey='<';
710 break;
711 case KEY_GREATER:
712 nCommandKey='>';
713 break;
714 case KEY_EQUAL:
715 nCommandKey='=';
716 break;
717 case KEY_SEMICOLON:
718 nCommandKey=';';
719 break;
720 case KEY_BACKSPACE:
721 nCommandKey=u'\x232b';
722 break;
723 case KEY_PAGEUP:
724 nCommandKey=u'\x21de';
725 break;
726 case KEY_PAGEDOWN:
727 nCommandKey=u'\x21df';
728 break;
729 case KEY_UP:
730 nCommandKey=u'\x21e1';
731 break;
732 case KEY_DOWN:
733 nCommandKey=u'\x21e3';
734 break;
735 case KEY_RETURN:
736 nCommandKey=u'\x21a9';
737 break;
738 case KEY_BRACKETLEFT:
739 nCommandKey='[';
740 break;
741 case KEY_BRACKETRIGHT:
742 nCommandKey=']';
743 break;
747 else // not even a code ? nonsense -> ignore
748 return;
750 SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
752 nModifier=rKeyCode.GetModifier();
754 // should always use the command key
755 int nItemModifier = 0;
757 SAL_WNODEPRECATED_DECLARATIONS_PUSH
758 // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
759 // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
760 // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
761 // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
762 if (nModifier & KEY_SHIFT)
764 nItemModifier |= NSShiftKeyMask; // actually useful only for function keys
765 if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
766 nCommandKey = nKeyCode - KEY_A + 'A';
769 if (nModifier & KEY_MOD1)
770 nItemModifier |= NSCommandKeyMask;
772 if(nModifier & KEY_MOD2)
773 nItemModifier |= NSAlternateKeyMask;
775 if(nModifier & KEY_MOD3)
776 nItemModifier |= NSControlKeyMask;
777 SAL_WNODEPRECATED_DECLARATIONS_POP
779 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
780 NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
781 [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
782 [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
783 if (pString)
784 [pString release];
787 void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
791 AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
793 for( size_t i = 0; i < maButtons.size(); ++i )
795 if( maButtons[i].maButton.mnId == i_nItemId )
796 return &maButtons[i];
798 return nullptr;
801 void AquaSalMenu::statusLayout()
803 if( GetSalData()->mpStatusItem )
805 NSView* pNSView = [GetSalData()->mpStatusItem view];
806 if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
807 [(OOStatusItemView*)pNSView layout];
808 else
809 OSL_FAIL( "someone stole our status view" );
813 bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
815 if( ! mbMenuBar || ! VisibleMenuBar() )
816 return false;
818 MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
819 if( pEntry )
821 releaseButtonEntry( *pEntry );
822 pEntry->maButton = i_rNewItem;
823 pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
824 if( i_rNewItem.maToolTipText.getLength() )
825 pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
827 else
829 maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
830 maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
831 maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
834 // lazy create status item
835 SalData::getStatusItem();
837 if( pCurrentMenuBar == this )
838 statusLayout();
840 return true;
843 void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
845 MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
846 if( pEntry )
848 releaseButtonEntry( *pEntry );
849 // note: vector guarantees that its contents are in a plain array
850 maButtons.erase( maButtons.begin() + (pEntry - &maButtons[0]) );
853 if( pCurrentMenuBar == this )
854 statusLayout();
857 tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
859 if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
860 return tools::Rectangle();
862 MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
864 if( ! pEntry )
865 return tools::Rectangle();
867 NSStatusItem* pItem = SalData::getStatusItem();
868 if( ! pItem )
869 return tools::Rectangle();
871 NSView* pNSView = [pItem view];
872 if( ! pNSView )
873 return tools::Rectangle();
874 NSWindow* pNSWin = [pNSView window];
875 if( ! pNSWin )
876 return tools::Rectangle();
878 NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
880 // make coordinates relative to reference frame
881 static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
882 aRect.origin.x -= i_pReferenceFrame->maGeometry.nX;
883 aRect.origin.y -= i_pReferenceFrame->maGeometry.nY + aRect.size.height;
885 return tools::Rectangle( Point(static_cast<long int>(aRect.origin.x),
886 static_cast<long int>(aRect.origin.y)
888 Size( static_cast<long int>(aRect.size.width),
889 static_cast<long int>(aRect.size.height)
895 * SalMenuItem
898 AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
899 mnId( pItemData->nId ),
900 mpVCLMenu( pItemData->pMenu ),
901 mpParentMenu( nullptr ),
902 mpSubMenu( nullptr ),
903 mpMenuItem( nil )
905 if (pItemData->eType == MenuItemType::SEPARATOR)
907 mpMenuItem = [NSMenuItem separatorItem];
908 // these can go occasionally go in and out of a menu, ensure their lifecycle
909 // also for the release in AquaSalMenuItem destructor
910 [mpMenuItem retain];
912 else
914 mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
915 [mpMenuItem setEnabled: YES];
917 // peel mnemonics because on mac there are no such things for menu items
918 NSString* pString = CreateNSString( pItemData->aText.replaceAll( "~", "" ) );
919 if (pString)
921 [mpMenuItem setTitle: pString];
922 [pString release];
924 // anything but a separator should set a menu to dispatch to
925 SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
929 AquaSalMenuItem::~AquaSalMenuItem()
931 /* #i89860# FIXME:
932 using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
933 [release] fixes an occasional crash. That should indicate that we release
934 menus / menu items in the wrong order somewhere, but I
935 could not find that case.
937 if( mpMenuItem )
938 [mpMenuItem autorelease];
941 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */