1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <osl/diagnose.h>
23 
24 #include <objc/objc-runtime.h>
25 
26 #include <rtl/ustrbuf.hxx>
27 #include <tools/debug.hxx>
28 
29 #include <vcl/commandevent.hxx>
30 #include <vcl/floatwin.hxx>
31 #include <vcl/window.hxx>
32 #include <vcl/svapp.hxx>
33 
34 #include <osx/saldata.hxx>
35 #include <osx/salinst.h>
36 #include <osx/salmenu.h>
37 #include <osx/salnsmenu.h>
38 #include <osx/salframe.h>
39 #include <osx/a11ywrapper.h>
40 #include <quartz/utils.h>
41 #include <strings.hrc>
42 #include <window.h>
43 
44 namespace {
45 
releaseButtonEntry(AquaSalMenu::MenuBarButtonEntry & i_rEntry)46 void releaseButtonEntry( AquaSalMenu::MenuBarButtonEntry& i_rEntry )
47 {
48     if( i_rEntry.mpNSImage )
49     {
50         [i_rEntry.mpNSImage release];
51         i_rEntry.mpNSImage = nil;
52     }
53     if( i_rEntry.mpToolTipString )
54     {
55         [i_rEntry.mpToolTipString release];
56         i_rEntry.mpToolTipString = nil;
57     }
58 }
59 
60 }
61 
62 const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = nullptr;
63 
64 @interface MainMenuSelector : NSObject
65 {
66 }
67 -(void)showDialog: (ShowDialogId)nDialog;
68 -(void)showPreferences: (id)sender;
69 -(void)showAbout: (id)sender;
70 @end
71 
72 @implementation MainMenuSelector
73 -(void)showDialog: (ShowDialogId)nDialog
74 {
75     if( AquaSalMenu::pCurrentMenuBar )
76     {
77         const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
78         if( pFrame && AquaSalFrame::isAlive( pFrame ) )
79         {
80             pFrame->CallCallback( SalEvent::ShowDialog, reinterpret_cast<void*>(nDialog) );
81         }
82     }
83     else
84     {
85         OUString aDialog;
86         if( nDialog == ShowDialogId::About )
87             aDialog = "ABOUT";
88         else if( nDialog == ShowDialogId::Preferences )
89             aDialog = "PREFERENCES";
90         const ApplicationEvent* pAppEvent = new ApplicationEvent(
91             ApplicationEvent::Type::ShowDialog, aDialog);
92         AquaSalInstance::aAppEventList.push_back( pAppEvent );
93     }
94 }
95 
96 -(void)showPreferences: (id) sender
97 {
98     (void)sender;
99     SolarMutexGuard aGuard;
100 
101     [self showDialog: ShowDialogId::Preferences];
102 }
103 -(void)showAbout: (id) sender
104 {
105     (void)sender;
106     SolarMutexGuard aGuard;
107 
108     [self showDialog: ShowDialogId::About];
109 }
110 @end
111 
112 // FIXME: currently this is leaked
113 static MainMenuSelector* pMainMenuSelector = nil;
114 
initAppMenu()115 static void initAppMenu()
116 {
117     static bool bInitialized = false;
118     if (bInitialized)
119         return;
120     bInitialized = true;
121 
122     NSMenu* pAppMenu = nil;
123     NSMenuItem* pNewItem = nil;
124 
125     NSMenu* pMainMenu = [[[NSMenu alloc] initWithTitle: @"Main Menu"] autorelease];
126     pNewItem = [pMainMenu addItemWithTitle: @"Application"
127         action: nil
128         keyEquivalent: @""];
129     pAppMenu = [[[NSMenu alloc] initWithTitle: @"Application"] autorelease];
130     [pNewItem setSubmenu: pAppMenu];
131     [NSApp setMainMenu: pMainMenu];
132 
133     pMainMenuSelector = [[MainMenuSelector alloc] init];
134 
135     // about
136     NSString* pString = CreateNSString(VclResId(SV_STDTEXT_ABOUT));
137     pNewItem = [pAppMenu addItemWithTitle: pString
138         action: @selector(showAbout:)
139         keyEquivalent: @""];
140     [pString release];
141     [pNewItem setTarget: pMainMenuSelector];
142 
143     [pAppMenu addItem:[NSMenuItem separatorItem]];
144 
145     // preferences
146     pString = CreateNSString(VclResId(SV_STDTEXT_PREFERENCES));
147     pNewItem = [pAppMenu addItemWithTitle: pString
148         action: @selector(showPreferences:)
149         keyEquivalent: @","];
150     [pString release];
151 SAL_WNODEPRECATED_DECLARATIONS_PUSH
152     // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
153     [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask];
154 SAL_WNODEPRECATED_DECLARATIONS_POP
155     [pNewItem setTarget: pMainMenuSelector];
156 
157     [pAppMenu addItem:[NSMenuItem separatorItem]];
158 
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];
167 
168     [pAppMenu addItem:[NSMenuItem separatorItem]];
169 
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];
176 
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 SAL_WNODEPRECATED_DECLARATIONS_PUSH
184     // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
185     [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
186 SAL_WNODEPRECATED_DECLARATIONS_POP
187 
188     // Show All
189     pString = CreateNSString(VclResId(SV_MENU_MAC_SHOWALL));
190     [pAppMenu addItemWithTitle: pString
191         action:@selector(unhideAllApplications:)
192         keyEquivalent:@""];
193     [pString release];
194 
195     [pAppMenu addItem:[NSMenuItem separatorItem]];
196 
197     // Quit
198     pString = CreateNSString(VclResId(SV_MENU_MAC_QUITAPP));
199     [pAppMenu addItemWithTitle: pString
200         action:@selector(terminate:)
201         keyEquivalent:@"q"];
202     [pString release];
203 }
204 
CreateMenu(bool bMenuBar,Menu * pVCLMenu)205 std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
206 {
207     initAppMenu();
208 
209     AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
210     pAquaSalMenu->mpVCLMenu = pVCLMenu;
211 
212     return std::unique_ptr<SalMenu>(pAquaSalMenu);
213 }
214 
CreateMenuItem(const SalItemParams & rItemData)215 std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
216 {
217     AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
218 
219     return std::unique_ptr<SalMenuItem>(pSalMenuItem);
220 }
221 
222 /*
223  * AquaSalMenu
224  */
225 
AquaSalMenu(bool bMenuBar)226 AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
227     mbMenuBar( bMenuBar ),
228     mpMenu( nil ),
229     mpFrame( nullptr ),
230     mpParentSalMenu( nullptr )
231 {
232     if( ! mbMenuBar )
233     {
234         mpMenu = [[SalNSMenu alloc] initWithMenu: this];
235         [mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
236     }
237     else
238     {
239         mpMenu = [NSApp mainMenu];
240     }
241     [mpMenu setAutoenablesItems: NO];
242 }
243 
~AquaSalMenu()244 AquaSalMenu::~AquaSalMenu()
245 {
246     // actually someone should have done AquaSalFrame::SetMenu( NULL )
247     // on our frame, alas it is not so
248     if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
249         const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
250 
251     // this should normally be empty already, but be careful...
252     for( size_t i = 0; i < maButtons.size(); i++ )
253         releaseButtonEntry( maButtons[i] );
254     maButtons.clear();
255 
256     // is this leaking in some cases ? the release often leads to a duplicate release
257     // it seems the parent item gets ownership of the menu
258     if( mpMenu )
259     {
260         if( mbMenuBar )
261         {
262             if( pCurrentMenuBar == this )
263             {
264                 // if the current menubar gets destroyed, set the default menubar
265                 setDefaultMenu();
266             }
267         }
268         else
269             // the system may still hold a reference on mpMenu
270         {
271             // so set the pointer to this AquaSalMenu to NULL
272             // to protect from calling a dead object
273 
274             // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
275             // so we can safely cast here
276             [static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
277             /* #i89860# FIXME:
278                using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
279                instead of [release] fixes an occasional crash. That should
280                indicate that we release menus / menu items in the wrong order
281                somewhere, but I could not find that case.
282             */
283             [mpMenu autorelease];
284         }
285     }
286 }
287 
ShowNativePopupMenu(FloatingWindow * pWin,const tools::Rectangle & rRect,FloatWinPopupFlags nFlags)288 bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
289 {
290     // set offsets for positioning
291     const float offset = 9.0;
292 
293     // get the pointers
294     AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
295     NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
296     NSView* pParentNSView = [pParentNSWindow contentView];
297     NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
298     NSRect popupFrame = [pPopupNSView frame];
299 
300     // create frame rect
301     NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
302     pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
303 
304     // do the same strange semantics as vcl popup windows to arrive at a frame geometry
305     // in mirrored UI case; best done by actually executing the same code
306     sal_uInt16 nArrangeIndex;
307     pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
308     displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.nX - pParentAquaSalFrame->maGeometry.nX + offset;
309     displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.nY - pParentAquaSalFrame->maGeometry.nY + offset;
310     pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
311 
312     // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
313     if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
314         [pParentNSView performSelector:@selector(clearLastEvent)];
315 
316     // open popup menu
317     NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
318     [pPopUpButtonCell setMenu: mpMenu];
319     [pPopUpButtonCell selectItem:nil];
320     [AquaA11yWrapper setPopupMenuOpen: YES];
321     [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
322     [pPopUpButtonCell release];
323     [AquaA11yWrapper setPopupMenuOpen: NO];
324 
325     return true;
326 }
327 
getItemIndexByPos(sal_uInt16 nPos) const328 int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
329 {
330     int nIndex = 0;
331     if( nPos == MENU_APPEND )
332         nIndex = [mpMenu numberOfItems];
333     else
334         nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
335     return nIndex;
336 }
337 
getFrame() const338 const AquaSalFrame* AquaSalMenu::getFrame() const
339 {
340     const AquaSalMenu* pMenu = this;
341     while( pMenu && ! pMenu->mpFrame )
342         pMenu = pMenu->mpParentSalMenu;
343     return pMenu ? pMenu->mpFrame : nullptr;
344 }
345 
unsetMainMenu()346 void AquaSalMenu::unsetMainMenu()
347 {
348     pCurrentMenuBar = nullptr;
349 
350     // remove items from main menu
351     NSMenu* pMenu = [NSApp mainMenu];
352     for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
353         [pMenu removeItemAtIndex: 1];
354 }
355 
setMainMenu()356 void AquaSalMenu::setMainMenu()
357 {
358     SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
359     if( mbMenuBar )
360     {
361         if( pCurrentMenuBar != this )
362         {
363             unsetMainMenu();
364             // insert our items
365             for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
366             {
367                 NSMenuItem* pItem = maItems[i]->mpMenuItem;
368                 [mpMenu insertItem: pItem atIndex: i+1];
369             }
370             pCurrentMenuBar = this;
371 
372             // change status item
373             statusLayout();
374         }
375         enableMainMenu( true );
376     }
377 }
378 
setDefaultMenu()379 void AquaSalMenu::setDefaultMenu()
380 {
381     NSMenu* pMenu = [NSApp mainMenu];
382 
383     unsetMainMenu();
384 
385     // insert default items
386     std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
387     for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
388     {
389         NSMenuItem* pItem = rFallbackMenu[i];
390         if( [pItem menu] == nil )
391             [pMenu insertItem: pItem atIndex: i+1];
392     }
393 }
394 
enableMainMenu(bool bEnable)395 void AquaSalMenu::enableMainMenu( bool bEnable )
396 {
397     NSMenu* pMainMenu = [NSApp mainMenu];
398     if( pMainMenu )
399     {
400         // enable/disable items from main menu
401         int nItems = [pMainMenu numberOfItems];
402         for( int n = 1; n < nItems; n++ )
403         {
404             NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
405             [pItem setEnabled: bEnable ? YES : NO];
406         }
407     }
408 }
409 
addFallbackMenuItem(NSMenuItem * pNewItem)410 void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
411 {
412     initAppMenu();
413 
414     std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
415 
416     // prevent duplicate insertion
417     int nItems = rFallbackMenu.size();
418     for( int i = 0; i < nItems; i++ )
419     {
420         if( rFallbackMenu[i] == pNewItem )
421             return;
422     }
423 
424     // push the item to the back and retain it
425     [pNewItem retain];
426     rFallbackMenu.push_back( pNewItem );
427 
428     if( pCurrentMenuBar == nullptr )
429         setDefaultMenu();
430 }
431 
removeFallbackMenuItem(NSMenuItem * pOldItem)432 void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
433 {
434     std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
435 
436     // find item
437     unsigned int nItems = rFallbackMenu.size();
438     for( unsigned int i = 0; i < nItems; i++ )
439     {
440         if( rFallbackMenu[i] == pOldItem )
441         {
442             // remove item and release
443             rFallbackMenu.erase( rFallbackMenu.begin() + i );
444             [pOldItem release];
445 
446             if( pCurrentMenuBar == nullptr )
447                 setDefaultMenu();
448 
449             return;
450         }
451     }
452 }
453 
VisibleMenuBar()454 bool AquaSalMenu::VisibleMenuBar()
455 {
456     return true;
457 }
458 
SetFrame(const SalFrame * pFrame)459 void AquaSalMenu::SetFrame( const SalFrame *pFrame )
460 {
461     mpFrame = static_cast<const AquaSalFrame*>(pFrame);
462 }
463 
InsertItem(SalMenuItem * pSalMenuItem,unsigned nPos)464 void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
465 {
466     AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
467 
468     pAquaSalMenuItem->mpParentMenu = this;
469     DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr        ||
470                 pAquaSalMenuItem->mpVCLMenu == mpVCLMenu   ||
471                 mpVCLMenu == nullptr,
472                 "resetting menu ?" );
473     if( pAquaSalMenuItem->mpVCLMenu )
474         mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
475 
476     if( nPos == MENU_APPEND || nPos == maItems.size() )
477         maItems.push_back( pAquaSalMenuItem );
478     else if( nPos < maItems.size() )
479         maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
480     else
481     {
482         OSL_FAIL( "invalid item index in insert" );
483         return;
484     }
485 
486     if( ! mbMenuBar || pCurrentMenuBar == this )
487         [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
488 }
489 
RemoveItem(unsigned nPos)490 void AquaSalMenu::RemoveItem( unsigned nPos )
491 {
492     AquaSalMenuItem* pRemoveItem = nullptr;
493     if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
494     {
495         pRemoveItem = maItems.back();
496         maItems.pop_back();
497     }
498     else if( nPos < maItems.size() )
499     {
500         pRemoveItem = maItems[ nPos ];
501         maItems.erase( maItems.begin()+nPos );
502     }
503     else
504     {
505         OSL_FAIL( "invalid item index in remove" );
506         return;
507     }
508 
509     pRemoveItem->mpParentMenu = nullptr;
510 
511     if( ! mbMenuBar || pCurrentMenuBar == this )
512         [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
513 }
514 
SetSubMenu(SalMenuItem * pSalMenuItem,SalMenu * pSubMenu,unsigned)515 void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
516 {
517     AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
518     AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
519 
520     if (subAquaSalMenu)
521     {
522         pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
523         if( subAquaSalMenu->mpParentSalMenu == nullptr )
524         {
525             subAquaSalMenu->mpParentSalMenu = this;
526             [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
527 
528             // set title of submenu
529             [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
530         }
531         else if( subAquaSalMenu->mpParentSalMenu != this )
532         {
533             // cocoa doesn't allow menus to be submenus of multiple
534             // menu items, so place a copy in the menu item instead ?
535             // let's hope that NSMenu copy does the right thing
536             NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
537             [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
538 
539             // set title of submenu
540             [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
541         }
542     }
543     else
544     {
545         if( pAquaSalMenuItem->mpSubMenu )
546         {
547             if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
548                 pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
549         }
550         pAquaSalMenuItem->mpSubMenu = nullptr;
551         [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
552     }
553 }
554 
CheckItem(unsigned nPos,bool bCheck)555 void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
556 {
557     if( nPos < maItems.size() )
558     {
559         NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
560         [pItem setState: bCheck ? NSControlStateValueOn : NSControlStateValueOff];
561     }
562 }
563 
EnableItem(unsigned nPos,bool bEnable)564 void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
565 {
566     if( nPos < maItems.size() )
567     {
568         NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
569         [pItem setEnabled: bEnable ? YES : NO];
570     }
571 }
572 
SetItemImage(unsigned,SalMenuItem * pSMI,const Image & rImage)573 void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
574 {
575     AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
576     if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
577         return;
578 
579     NSImage* pImage = CreateNSImage( rImage );
580 
581     [pSalMenuItem->mpMenuItem setImage: pImage];
582     if( pImage )
583         [pImage release];
584 }
585 
SetItemText(unsigned,SalMenuItem * i_pSalMenuItem,const OUString & i_rText)586 void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
587 {
588     if (!i_pSalMenuItem)
589         return;
590 
591     AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
592 
593     // Delete mnemonics
594     OUString aText = i_rText.replaceAll("~", "");
595 
596     /* #i90015# until there is a correct solution
597        strip out any appended (.*) in menubar entries
598     */
599     if( mbMenuBar )
600     {
601         sal_Int32 nPos = aText.lastIndexOf( '(' );
602         if( nPos != -1 )
603         {
604             sal_Int32 nPos2 = aText.indexOf( ')' );
605             if( nPos2 != -1 )
606                 aText = aText.replaceAt( nPos, nPos2-nPos+1, "" );
607         }
608     }
609 
610     if (aText.endsWith("...", &aText))
611         aText += u"\u2026";
612 
613     NSString* pString = CreateNSString( aText );
614     if (pString)
615     {
616         [pAquaSalMenuItem->mpMenuItem setTitle: pString];
617         // if the menu item has a submenu, change its title as well
618         if (pAquaSalMenuItem->mpSubMenu)
619             [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
620         [pString release];
621     }
622 }
623 
SetAccelerator(unsigned,SalMenuItem * pSalMenuItem,const vcl::KeyCode & rKeyCode,const OUString &)624 void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
625 {
626     sal_uInt16 nModifier;
627     sal_Unicode nCommandKey = 0;
628 
629     sal_uInt16 nKeyCode=rKeyCode.GetCode();
630     if( nKeyCode )
631     {
632         if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z))           // letter A..Z
633             nCommandKey = nKeyCode-KEY_A + 'a';
634         else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9))      // numbers 0..9
635             nCommandKey = nKeyCode-KEY_0 + '0';
636         else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26))   // function keys F1..F26
637             nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
638         else if( nKeyCode == KEY_REPEAT )
639             nCommandKey = NSRedoFunctionKey;
640         else if( nKeyCode == KEY_SPACE )
641             nCommandKey = ' ';
642         else
643         {
644             switch (nKeyCode)
645             {
646             case KEY_ADD:
647                 nCommandKey='+';
648                 break;
649             case KEY_SUBTRACT:
650                 nCommandKey='-';
651                 break;
652             case KEY_MULTIPLY:
653                 nCommandKey='*';
654                 break;
655             case KEY_DIVIDE:
656                 nCommandKey='/';
657                 break;
658             case KEY_POINT:
659                 nCommandKey='.';
660                 break;
661             case KEY_LESS:
662                 nCommandKey='<';
663                 break;
664             case KEY_GREATER:
665                 nCommandKey='>';
666                 break;
667             case KEY_EQUAL:
668                 nCommandKey='=';
669                 break;
670             case KEY_SEMICOLON:
671                 nCommandKey=';';
672                 break;
673             case KEY_BACKSPACE:
674                 nCommandKey=u'\x232b';
675                 break;
676             case KEY_PAGEUP:
677                 nCommandKey=u'\x21de';
678                 break;
679             case KEY_PAGEDOWN:
680                 nCommandKey=u'\x21df';
681                 break;
682             case KEY_UP:
683                 nCommandKey=u'\x21e1';
684                 break;
685             case KEY_DOWN:
686                 nCommandKey=u'\x21e3';
687                 break;
688             case KEY_RETURN:
689                 nCommandKey=u'\x21a9';
690                 break;
691             case KEY_BRACKETLEFT:
692                 nCommandKey='[';
693                 break;
694             case KEY_BRACKETRIGHT:
695                 nCommandKey=']';
696                 break;
697             }
698         }
699     }
700     else // not even a code ? nonsense -> ignore
701         return;
702 
703     SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
704 
705     nModifier=rKeyCode.GetModifier();
706 
707     // should always use the command key
708     int nItemModifier = 0;
709 
710 SAL_WNODEPRECATED_DECLARATIONS_PUSH
711         // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
712         // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
713         // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
714         // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
715     if (nModifier & KEY_SHIFT)
716     {
717         nItemModifier |= NSShiftKeyMask;   // actually useful only for function keys
718         if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
719             nCommandKey = nKeyCode - KEY_A + 'A';
720     }
721 
722     if (nModifier & KEY_MOD1)
723         nItemModifier |= NSCommandKeyMask;
724 
725     if(nModifier & KEY_MOD2)
726         nItemModifier |= NSAlternateKeyMask;
727 
728     if(nModifier & KEY_MOD3)
729         nItemModifier |= NSControlKeyMask;
730 SAL_WNODEPRECATED_DECLARATIONS_POP
731 
732     AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
733     NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
734     [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
735     [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
736     if (pString)
737         [pString release];
738 }
739 
GetSystemMenuData(SystemMenuData *)740 void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
741 {
742 }
743 
findButtonItem(sal_uInt16 i_nItemId)744 AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
745 {
746     for( size_t i = 0; i < maButtons.size(); ++i )
747     {
748         if( maButtons[i].maButton.mnId == i_nItemId )
749             return &maButtons[i];
750     }
751     return nullptr;
752 }
753 
statusLayout()754 void AquaSalMenu::statusLayout()
755 {
756     if( GetSalData()->mpStatusItem )
757     {
758 SAL_WNODEPRECATED_DECLARATIONS_PUSH
759             // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button
760             // property instead"
761         NSView* pNSView = [GetSalData()->mpStatusItem view];
762 SAL_WNODEPRECATED_DECLARATIONS_POP
763         if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
764             [static_cast<OOStatusItemView*>(pNSView) layout];
765         else
766             OSL_FAIL( "someone stole our status view" );
767     }
768 }
769 
AddMenuBarButton(const SalMenuButtonItem & i_rNewItem)770 bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
771 {
772     if( ! mbMenuBar  )
773         return false;
774 
775     MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
776     if( pEntry )
777     {
778         releaseButtonEntry( *pEntry );
779         pEntry->maButton = i_rNewItem;
780         pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
781         if( i_rNewItem.maToolTipText.getLength() )
782             pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
783     }
784     else
785     {
786         maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
787         maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
788         maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
789     }
790 
791     // lazy create status item
792     SalData::getStatusItem();
793 
794     if( pCurrentMenuBar == this )
795         statusLayout();
796 
797     return true;
798 }
799 
RemoveMenuBarButton(sal_uInt16 i_nId)800 void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
801 {
802     MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
803     if( pEntry )
804     {
805         releaseButtonEntry( *pEntry );
806         // note: vector guarantees that its contents are in a plain array
807         maButtons.erase( maButtons.begin() + (pEntry - maButtons.data()) );
808     }
809 
810     if( pCurrentMenuBar == this )
811         statusLayout();
812 }
813 
GetMenuBarButtonRectPixel(sal_uInt16 i_nItemId,SalFrame * i_pReferenceFrame)814 tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
815 {
816     if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
817         return tools::Rectangle();
818 
819     MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
820 
821     if( ! pEntry )
822         return tools::Rectangle();
823 
824     NSStatusItem* pItem = SalData::getStatusItem();
825     if( ! pItem )
826         return tools::Rectangle();
827 
828 SAL_WNODEPRECATED_DECLARATIONS_PUSH
829         // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button property
830         // instead"
831     NSView* pNSView = [pItem view];
832 SAL_WNODEPRECATED_DECLARATIONS_POP
833     if( ! pNSView )
834         return tools::Rectangle();
835     NSWindow* pNSWin = [pNSView window];
836     if( ! pNSWin )
837         return tools::Rectangle();
838 
839     NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
840 
841     // make coordinates relative to reference frame
842     static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
843     aRect.origin.x -= i_pReferenceFrame->maGeometry.nX;
844     aRect.origin.y -= i_pReferenceFrame->maGeometry.nY + aRect.size.height;
845 
846     return tools::Rectangle( Point(static_cast<long int>(aRect.origin.x),
847                 static_cast<long int>(aRect.origin.y)
848                 ),
849               Size( static_cast<long int>(aRect.size.width),
850                 static_cast<long int>(aRect.size.height)
851               )
852             );
853 }
854 
855 /*
856  * SalMenuItem
857  */
858 
AquaSalMenuItem(const SalItemParams * pItemData)859 AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
860     mnId( pItemData->nId ),
861     mpVCLMenu( pItemData->pMenu ),
862     mpParentMenu( nullptr ),
863     mpSubMenu( nullptr ),
864     mpMenuItem( nil )
865 {
866     if (pItemData->eType == MenuItemType::SEPARATOR)
867     {
868         mpMenuItem = [NSMenuItem separatorItem];
869         // these can go occasionally go in and out of a menu, ensure their lifecycle
870         // also for the release in AquaSalMenuItem destructor
871         [mpMenuItem retain];
872     }
873     else
874     {
875         mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
876         [mpMenuItem setEnabled: YES];
877 
878         // peel mnemonics because on mac there are no such things for menu items
879         NSString* pString = CreateNSString( pItemData->aText.replaceAll( "~", "" ) );
880         if (pString)
881         {
882             [mpMenuItem setTitle: pString];
883             [pString release];
884         }
885         // anything but a separator should set a menu to dispatch to
886         SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
887     }
888 }
889 
~AquaSalMenuItem()890 AquaSalMenuItem::~AquaSalMenuItem()
891 {
892     /* #i89860# FIXME:
893        using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
894        [release] fixes an occasional crash. That should indicate that we release
895        menus / menu items in the wrong order somewhere, but I
896        could not find that case.
897     */
898     if( mpMenuItem )
899         [mpMenuItem autorelease];
900 }
901 
902 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
903