tdf#130857 qt weld: Support mail merge "Server Auth" dialog
[LibreOffice.git] / vcl / osx / salmenu.cxx
bloba14f1874c83eb1411c25c961cd0695f45bf0784b
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 // Related: tdf#126638 use NSMenu subclass to catch and redirect key
129 // shortcuts when a modal window is displayed
130 SalNSMainMenu* pMainMenu = [[[SalNSMainMenu alloc] initWithTitle: @"Main Menu"] autorelease];
131 pNewItem = [pMainMenu addItemWithTitle: @"Application"
132 action: nil
133 keyEquivalent: @""];
134 pAppMenu = [[[NSMenu alloc] initWithTitle: @"Application"] autorelease];
135 [pNewItem setSubmenu: pAppMenu];
136 [NSApp setMainMenu: pMainMenu];
138 pMainMenuSelector = [[MainMenuSelector alloc] init];
140 // about
141 NSString* pString = CreateNSString(VclResId(SV_STDTEXT_ABOUT));
142 pNewItem = [pAppMenu addItemWithTitle: pString
143 action: @selector(showAbout:)
144 keyEquivalent: @""];
145 [pString release];
146 [pNewItem setTarget: pMainMenuSelector];
148 [pAppMenu addItem:[NSMenuItem separatorItem]];
150 // preferences
151 pString = CreateNSString(VclResId(SV_STDTEXT_PREFERENCES));
152 pNewItem = [pAppMenu addItemWithTitle: pString
153 action: @selector(showPreferences:)
154 keyEquivalent: @","];
155 [pString release];
156 [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand];
157 [pNewItem setTarget: pMainMenuSelector];
159 [pAppMenu addItem:[NSMenuItem separatorItem]];
161 // Services item and menu
162 pString = CreateNSString(VclResId(SV_MENU_MAC_SERVICES));
163 pNewItem = [pAppMenu addItemWithTitle: pString
164 action: nil
165 keyEquivalent: @""];
166 [pString release];
167 NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
168 [pNewItem setSubmenu: servicesMenu];
169 [NSApp setServicesMenu: servicesMenu];
171 [pAppMenu addItem:[NSMenuItem separatorItem]];
173 // Hide Application
174 pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEAPP));
175 [pAppMenu addItemWithTitle: pString
176 action:@selector(hide:)
177 keyEquivalent:@"h"];
178 [pString release];
180 // Hide Others
181 pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEALL));
182 [pAppMenu addItemWithTitle: pString
183 action:@selector(hideOtherApplications:)
184 keyEquivalent:@"h"];
185 [pString release];
186 [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
188 // Show All
189 pString = CreateNSString(VclResId(SV_MENU_MAC_SHOWALL));
190 [pAppMenu addItemWithTitle: pString
191 action:@selector(unhideAllApplications:)
192 keyEquivalent:@""];
193 [pString release];
195 [pAppMenu addItem:[NSMenuItem separatorItem]];
197 // Quit
198 pString = CreateNSString(VclResId(SV_MENU_MAC_QUITAPP));
199 [pAppMenu addItemWithTitle: pString
200 action:@selector(terminate:)
201 keyEquivalent:@"q"];
202 [pString release];
205 std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
207 initAppMenu();
209 AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
210 pAquaSalMenu->mpVCLMenu = pVCLMenu;
212 return std::unique_ptr<SalMenu>(pAquaSalMenu);
215 std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
217 AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
219 return std::unique_ptr<SalMenuItem>(pSalMenuItem);
223 * AquaSalMenu
226 AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
227 mbMenuBar( bMenuBar ),
228 mpMenu( nil ),
229 mpFrame( nullptr ),
230 mpParentSalMenu( nullptr )
232 if( ! mbMenuBar )
234 mpMenu = [[SalNSMenu alloc] initWithMenu: this];
235 [mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
237 // Related: tdf#126638 enable the menu's "autoenabledItems" property
238 // Enable the menu's "autoenabledItems" property so that
239 // -[SalNSMenuItem validateMenuItem:] will be called before handling
240 // a key shortcut and the menu item can be temporarily disabled if a
241 // modal window is displayed.
242 [mpMenu setAutoenablesItems: YES];
244 else
246 mpMenu = [NSApp mainMenu];
247 [mpMenu setAutoenablesItems: NO];
251 AquaSalMenu::~AquaSalMenu()
253 // actually someone should have done AquaSalFrame::SetMenu( NULL )
254 // on our frame, alas it is not so
255 if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
256 const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
258 // this should normally be empty already, but be careful...
259 for( size_t i = 0; i < maButtons.size(); i++ )
260 releaseButtonEntry( maButtons[i] );
261 maButtons.clear();
263 // is this leaking in some cases ? the release often leads to a duplicate release
264 // it seems the parent item gets ownership of the menu
265 if( mpMenu )
267 if( mbMenuBar )
269 if( pCurrentMenuBar == this )
271 // if the current menubar gets destroyed, set the default menubar
272 setDefaultMenu();
275 else
276 // the system may still hold a reference on mpMenu
278 // so set the pointer to this AquaSalMenu to NULL
279 // to protect from calling a dead object
281 // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
282 // so we can safely cast here
283 [static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
284 /* #i89860# FIXME:
285 using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
286 instead of [release] fixes an occasional crash. That should
287 indicate that we release menus / menu items in the wrong order
288 somewhere, but I could not find that case.
290 [mpMenu autorelease];
295 bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
297 // set offsets for positioning
298 const float offset = 9.0;
300 // get the pointers
301 AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
302 NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
303 NSView* pParentNSView = [pParentNSWindow contentView];
304 NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
305 NSRect popupFrame = [pPopupNSView frame];
307 // create frame rect
308 NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
309 pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
311 // do the same strange semantics as vcl popup windows to arrive at a frame geometry
312 // in mirrored UI case; best done by actually executing the same code
313 sal_uInt16 nArrangeIndex;
314 pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
315 displayPopupFrame.origin.x = pWin->ImplGetFrame()->GetUnmirroredGeometry().x() - pParentAquaSalFrame->GetUnmirroredGeometry().x() + offset;
316 displayPopupFrame.origin.y = pWin->ImplGetFrame()->GetUnmirroredGeometry().y() - pParentAquaSalFrame->GetUnmirroredGeometry().y() + offset;
317 pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
319 // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
320 if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
321 [pParentNSView performSelector:@selector(clearLastEvent)];
323 // open popup menu
324 NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
325 [pPopUpButtonCell setMenu: mpMenu];
326 [pPopUpButtonCell selectItem:nil];
327 [AquaA11yWrapper setPopupMenuOpen: YES];
328 [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
329 [pPopUpButtonCell release];
330 [AquaA11yWrapper setPopupMenuOpen: NO];
332 return true;
335 int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
337 int nIndex = 0;
338 if( nPos == MENU_APPEND )
339 nIndex = [mpMenu numberOfItems];
340 else
341 nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
342 return nIndex;
345 const AquaSalFrame* AquaSalMenu::getFrame() const
347 const AquaSalMenu* pMenu = this;
348 while( pMenu && ! pMenu->mpFrame )
349 pMenu = pMenu->mpParentSalMenu;
350 return pMenu ? pMenu->mpFrame : nullptr;
353 void AquaSalMenu::unsetMainMenu()
355 pCurrentMenuBar = nullptr;
357 // remove items from main menu
358 NSMenu* pMenu = [NSApp mainMenu];
359 for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
360 [pMenu removeItemAtIndex: 1];
363 void AquaSalMenu::setMainMenu()
365 SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
366 if( mbMenuBar )
368 if( pCurrentMenuBar != this )
370 unsetMainMenu();
371 // insert our items
372 for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
374 NSMenuItem* pItem = maItems[i]->mpMenuItem;
375 [mpMenu insertItem: pItem atIndex: i+1];
377 pCurrentMenuBar = this;
379 // change status item
380 statusLayout();
382 enableMainMenu( true );
386 void AquaSalMenu::setDefaultMenu()
388 // tdf#160427 native menu changes can only be done on the main thread
389 OSX_SALDATA_RUNINMAIN(AquaSalMenu::setDefaultMenu())
391 NSMenu* pMenu = [NSApp mainMenu];
393 unsetMainMenu();
395 // insert default items
396 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
397 for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
399 NSMenuItem* pItem = rFallbackMenu[i];
400 if( [pItem menu] == nil )
401 [pMenu insertItem: pItem atIndex: i+1];
405 void AquaSalMenu::enableMainMenu( bool bEnable )
407 NSMenu* pMainMenu = [NSApp mainMenu];
408 if( pMainMenu )
410 // enable/disable items from main menu
411 int nItems = [pMainMenu numberOfItems];
412 for( int n = 1; n < nItems; n++ )
414 NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
415 if( [pItem isKindOfClass: [SalNSMenuItem class]])
416 [static_cast<SalNSMenuItem*>(pItem) setReallyEnabled: bEnable];
417 else
418 [pItem setEnabled: bEnable];
423 void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
425 initAppMenu();
427 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
429 // prevent duplicate insertion
430 int nItems = rFallbackMenu.size();
431 for( int i = 0; i < nItems; i++ )
433 if( rFallbackMenu[i] == pNewItem )
434 return;
437 // push the item to the back and retain it
438 [pNewItem retain];
439 rFallbackMenu.push_back( pNewItem );
441 if( pCurrentMenuBar == nullptr )
442 setDefaultMenu();
445 void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
447 std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
449 // find item
450 unsigned int nItems = rFallbackMenu.size();
451 for( unsigned int i = 0; i < nItems; i++ )
453 if( rFallbackMenu[i] == pOldItem )
455 // remove item and release
456 rFallbackMenu.erase( rFallbackMenu.begin() + i );
457 [pOldItem release];
459 if( pCurrentMenuBar == nullptr )
460 setDefaultMenu();
462 return;
467 bool AquaSalMenu::VisibleMenuBar()
469 return true;
472 void AquaSalMenu::SetFrame( const SalFrame *pFrame )
474 mpFrame = static_cast<const AquaSalFrame*>(pFrame);
477 void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
479 OSX_SALDATA_RUNINMAIN(InsertItem(pSalMenuItem, nPos))
481 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
483 pAquaSalMenuItem->mpParentMenu = this;
484 DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr ||
485 pAquaSalMenuItem->mpVCLMenu == mpVCLMenu ||
486 mpVCLMenu == nullptr,
487 "resetting menu ?" );
488 if( pAquaSalMenuItem->mpVCLMenu )
489 mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
491 if( nPos == MENU_APPEND || nPos == maItems.size() )
492 maItems.push_back( pAquaSalMenuItem );
493 else if( nPos < maItems.size() )
494 maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
495 else
497 OSL_FAIL( "invalid item index in insert" );
498 return;
501 if( ! mbMenuBar || pCurrentMenuBar == this )
502 [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
505 void AquaSalMenu::RemoveItem( unsigned nPos )
507 // tdf#160427 native menu changes can only be done on the main thread
508 OSX_SALDATA_RUNINMAIN(RemoveItem(nPos))
510 AquaSalMenuItem* pRemoveItem = nullptr;
511 if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
513 pRemoveItem = maItems.back();
514 maItems.pop_back();
516 else if( nPos < maItems.size() )
518 pRemoveItem = maItems[ nPos ];
519 maItems.erase( maItems.begin()+nPos );
521 else
523 OSL_FAIL( "invalid item index in remove" );
524 return;
527 pRemoveItem->mpParentMenu = nullptr;
529 if( ! mbMenuBar || pCurrentMenuBar == this )
530 [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
533 void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
535 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
536 AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
538 if (subAquaSalMenu)
540 pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
541 if( subAquaSalMenu->mpParentSalMenu == nullptr )
543 subAquaSalMenu->mpParentSalMenu = this;
544 [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
546 // set title of submenu
547 [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
549 else if( subAquaSalMenu->mpParentSalMenu != this )
551 // cocoa doesn't allow menus to be submenus of multiple
552 // menu items, so place a copy in the menu item instead ?
553 // let's hope that NSMenu copy does the right thing
554 NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
555 [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
557 // set title of submenu
558 [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
561 else
563 if( pAquaSalMenuItem->mpSubMenu )
565 if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
566 pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
568 pAquaSalMenuItem->mpSubMenu = nullptr;
569 [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
573 void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
575 if( nPos < maItems.size() )
577 NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
578 [pItem setState: bCheck ? NSControlStateValueOn : NSControlStateValueOff];
582 void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
584 if( nPos < maItems.size() )
586 NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
587 if( [pItem isKindOfClass: [SalNSMenuItem class]])
588 [static_cast<SalNSMenuItem*>(pItem) setReallyEnabled: bEnable];
589 else
590 [pItem setEnabled: bEnable];
594 void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
596 AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
597 if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
598 return;
600 NSImage* pImage = CreateNSImage( rImage );
602 [pSalMenuItem->mpMenuItem setImage: pImage];
603 if( pImage )
604 [pImage release];
607 void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
609 if (!i_pSalMenuItem)
610 return;
612 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
614 // Delete all mnemonics of mbMenuBar and CJK-style mnemonic
615 OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
617 if (aText.endsWith("...", &aText))
618 aText += u"\u2026";
620 NSString* pString = CreateNSString( aText );
621 if (pString)
623 [pAquaSalMenuItem->mpMenuItem setTitle: pString];
624 // if the menu item has a submenu, change its title as well
625 if (pAquaSalMenuItem->mpSubMenu)
626 [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
627 [pString release];
631 void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
633 sal_uInt16 nModifier;
634 sal_Unicode nCommandKey = 0;
636 sal_uInt16 nKeyCode=rKeyCode.GetCode();
637 if( nKeyCode )
639 if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z)) // letter A..Z
640 nCommandKey = nKeyCode-KEY_A + 'a';
641 else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9)) // numbers 0..9
642 nCommandKey = nKeyCode-KEY_0 + '0';
643 else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26)) // function keys F1..F26
644 nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
645 else if( nKeyCode == KEY_REPEAT )
646 nCommandKey = NSRedoFunctionKey;
647 else if( nKeyCode == KEY_SPACE )
648 nCommandKey = ' ';
649 else
651 switch (nKeyCode)
653 case KEY_ADD:
654 nCommandKey='+';
655 break;
656 case KEY_SUBTRACT:
657 nCommandKey='-';
658 break;
659 case KEY_MULTIPLY:
660 nCommandKey='*';
661 break;
662 case KEY_DIVIDE:
663 nCommandKey='/';
664 break;
665 case KEY_POINT:
666 nCommandKey='.';
667 break;
668 case KEY_LESS:
669 nCommandKey='<';
670 break;
671 case KEY_GREATER:
672 nCommandKey='>';
673 break;
674 case KEY_EQUAL:
675 nCommandKey='=';
676 break;
677 case KEY_COLON:
678 nCommandKey=':';
679 break;
680 case KEY_NUMBERSIGN:
681 nCommandKey='#';
682 break;
683 case KEY_SEMICOLON:
684 nCommandKey=';';
685 break;
686 case KEY_BACKSPACE:
687 nCommandKey=u'\x232b';
688 break;
689 case KEY_PAGEUP:
690 nCommandKey=u'\x21de';
691 break;
692 case KEY_PAGEDOWN:
693 nCommandKey=u'\x21df';
694 break;
695 case KEY_UP:
696 nCommandKey=u'\x21e1';
697 break;
698 case KEY_DOWN:
699 nCommandKey=u'\x21e3';
700 break;
701 case KEY_RETURN:
702 nCommandKey=u'\x21a9';
703 break;
704 case KEY_BRACKETLEFT:
705 nCommandKey='[';
706 break;
707 case KEY_BRACKETRIGHT:
708 nCommandKey=']';
709 break;
713 else // not even a code ? nonsense -> ignore
714 return;
716 SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
718 nModifier=rKeyCode.GetModifier();
720 // should always use the command key
721 int nItemModifier = 0;
723 if (nModifier & KEY_SHIFT)
725 nItemModifier |= NSEventModifierFlagShift; // actually useful only for function keys
726 if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
727 nCommandKey = nKeyCode - KEY_A + 'A';
730 if (nModifier & KEY_MOD1)
731 nItemModifier |= NSEventModifierFlagCommand;
733 if(nModifier & KEY_MOD2)
734 nItemModifier |= NSEventModifierFlagOption;
736 if(nModifier & KEY_MOD3)
737 nItemModifier |= NSEventModifierFlagControl;
739 AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
740 NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
741 [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
742 [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
743 if (pString)
744 [pString release];
747 AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
749 for( size_t i = 0; i < maButtons.size(); ++i )
751 if( maButtons[i].maButton.mnId == i_nItemId )
752 return &maButtons[i];
754 return nullptr;
757 void AquaSalMenu::statusLayout()
759 if( GetSalData()->mpStatusItem )
761 SAL_WNODEPRECATED_DECLARATIONS_PUSH
762 // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button
763 // property instead"
764 NSView* pNSView = [GetSalData()->mpStatusItem view];
765 SAL_WNODEPRECATED_DECLARATIONS_POP
766 if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
767 [static_cast<OOStatusItemView*>(pNSView) layout];
768 else
769 OSL_FAIL( "someone stole our status view" );
773 bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
775 if( ! mbMenuBar )
776 return false;
778 MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
779 if( pEntry )
781 releaseButtonEntry( *pEntry );
782 pEntry->maButton = i_rNewItem;
783 pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
784 if( i_rNewItem.maToolTipText.getLength() )
785 pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
787 else
789 maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
790 maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
791 maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
794 // lazy create status item
795 SalData::getStatusItem();
797 if( pCurrentMenuBar == this )
798 statusLayout();
800 return true;
803 void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
805 MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
806 if( pEntry )
808 releaseButtonEntry( *pEntry );
809 // note: vector guarantees that its contents are in a plain array
810 maButtons.erase( maButtons.begin() + (pEntry - maButtons.data()) );
813 if( pCurrentMenuBar == this )
814 statusLayout();
817 tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
819 if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
820 return tools::Rectangle();
822 MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
824 if( ! pEntry )
825 return tools::Rectangle();
827 NSStatusItem* pItem = SalData::getStatusItem();
828 if( ! pItem )
829 return tools::Rectangle();
831 SAL_WNODEPRECATED_DECLARATIONS_PUSH
832 // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button property
833 // instead"
834 NSView* pNSView = [pItem view];
835 SAL_WNODEPRECATED_DECLARATIONS_POP
836 if( ! pNSView )
837 return tools::Rectangle();
838 NSWindow* pNSWin = [pNSView window];
839 if( ! pNSWin )
840 return tools::Rectangle();
842 NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
844 // make coordinates relative to reference frame
845 static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
846 aRect.origin.x -= i_pReferenceFrame->GetUnmirroredGeometry().x();
847 aRect.origin.y -= i_pReferenceFrame->GetUnmirroredGeometry().y() + aRect.size.height;
849 return tools::Rectangle( Point(static_cast<tools::Long>(aRect.origin.x),
850 static_cast<tools::Long>(aRect.origin.y)
852 Size( static_cast<tools::Long>(aRect.size.width),
853 static_cast<tools::Long>(aRect.size.height)
859 * SalMenuItem
862 AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
863 mnId( pItemData->nId ),
864 mpVCLMenu( pItemData->pMenu ),
865 mpParentMenu( nullptr ),
866 mpSubMenu( nullptr ),
867 mpMenuItem( nil )
869 if (pItemData->eType == MenuItemType::SEPARATOR)
871 mpMenuItem = [NSMenuItem separatorItem];
872 // these can go occasionally go in and out of a menu, ensure their lifecycle
873 // also for the release in AquaSalMenuItem destructor
874 [mpMenuItem retain];
876 else
878 mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
879 [static_cast<SalNSMenuItem*>(mpMenuItem) setReallyEnabled: YES];
881 // peel mnemonics because on mac there are no such things for menu items
882 // Delete CJK-style mnemonics for the dropdown menu of the 'New button' and lower menu of 'File > New'
883 NSString* pString = CreateNSString(MnemonicGenerator::EraseAllMnemonicChars(pItemData->aText));
884 if (pString)
886 [mpMenuItem setTitle: pString];
887 [pString release];
889 // anything but a separator should set a menu to dispatch to
890 SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
894 AquaSalMenuItem::~AquaSalMenuItem()
896 /* #i89860# FIXME:
897 using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
898 [release] fixes an occasional crash. That should indicate that we release
899 menus / menu items in the wrong order somewhere, but I
900 could not find that case.
902 if( mpMenuItem )
903 [mpMenuItem autorelease];
906 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */