xref: /AOO41X/main/vcl/aqua/source/window/salmenu.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 #include "rtl/ustrbuf.hxx"
29 
30 #include "vcl/cmdevt.hxx"
31 #include "vcl/floatwin.hxx"
32 #include "vcl/window.hxx"
33 #include "vcl/svapp.hxx"
34 
35 #include "aqua/saldata.hxx"
36 #include "aqua/salinst.h"
37 #include "aqua/salmenu.h"
38 #include "aqua/salnsmenu.h"
39 #include "aqua/salframe.h"
40 #include "aqua/salbmp.h"
41 #include "aqua/aqua11ywrapper.h"
42 
43 #include "svids.hrc"
44 #include "window.h"
45 
46 const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = NULL;
47 
48 @interface MainMenuSelector : NSObject
49 {
50 }
51 -(void)showDialog: (int)nDialog;
52 -(void)showPreferences: (id)sender;
53 -(void)showAbout: (id)sender;
54 @end
55 
56 @implementation MainMenuSelector
57 -(void)showDialog: (int)nDialog
58 {
59     if( AquaSalMenu::pCurrentMenuBar )
60     {
61         const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
62         if( pFrame && AquaSalFrame::isAlive( pFrame ) )
63         {
64             pFrame->CallCallback( SALEVENT_SHOWDIALOG, reinterpret_cast<void*>(nDialog) );
65         }
66     }
67     else
68     {
69         String aDialog;
70         if( nDialog == SHOWDIALOG_ID_ABOUT )
71             aDialog = String( RTL_CONSTASCII_USTRINGPARAM( "ABOUT" ) );
72         else if( nDialog == SHOWDIALOG_ID_PREFERENCES )
73             aDialog = String( RTL_CONSTASCII_USTRINGPARAM( "PREFERENCES" ) );
74         const ApplicationEvent* pAppEvent = new ApplicationEvent( String(),
75                                                                   ApplicationAddress(),
76                                                                   ByteString( "SHOWDIALOG" ),
77                                                                   aDialog );
78         AquaSalInstance::aAppEventList.push_back( pAppEvent );
79     }
80 }
81 
82 -(void)showPreferences: (id) sender
83 {
84     (void)sender;
85     YIELD_GUARD;
86 
87     [self showDialog: SHOWDIALOG_ID_PREFERENCES];
88 }
89 -(void)showAbout: (id) sender
90 {
91     (void)sender;
92     YIELD_GUARD;
93 
94     [self showDialog: SHOWDIALOG_ID_ABOUT];
95 }
96 @end
97 
98 
99 // FIXME: currently this is leaked
100 static MainMenuSelector* pMainMenuSelector = nil;
101 
102 static void initAppMenu()
103 {
104     static bool bOnce = true;
105     if( bOnce )
106     {
107         bOnce = false;
108 
109         ResMgr* pMgr = ImplGetResMgr();
110         if( pMgr )
111         {
112             // get the main menu
113             NSMenu* pMainMenu = [NSApp mainMenu];
114             if( pMainMenu != nil )
115             {
116                 // create the action selector
117                 pMainMenuSelector = [[MainMenuSelector alloc] init];
118 
119                 // get the proper submenu
120                 NSMenu* pAppMenu = [[pMainMenu itemAtIndex: 0] submenu];
121                 if( pAppMenu )
122                 {
123                     // insert about entry
124                     String aAbout( ResId( SV_STDTEXT_ABOUT, *pMgr ) );
125                     NSString* pString = CreateNSString( aAbout );
126                     NSMenuItem* pNewItem = [pAppMenu insertItemWithTitle: pString
127                                                      action: @selector(showAbout:)
128                                                      keyEquivalent: @""
129                                                      atIndex: 0];
130                     if (pString)
131                         [pString release];
132                     if( pNewItem )
133                     {
134                         [pNewItem setTarget: pMainMenuSelector];
135                         [pAppMenu insertItem: [NSMenuItem separatorItem] atIndex: 1];
136                     }
137 
138                     // insert preferences entry
139                     String aPref( ResId( SV_STDTEXT_PREFERENCES, *pMgr ) );
140                     pString = CreateNSString( aPref );
141                     pNewItem = [pAppMenu insertItemWithTitle: pString
142                                          action: @selector(showPreferences:)
143                                          keyEquivalent: @","
144                                          atIndex: 2];
145                     if (pString)
146                         [pString release];
147                     if( pNewItem )
148                     {
149                         [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask];
150                         [pNewItem setTarget: pMainMenuSelector];
151                         [pAppMenu insertItem: [NSMenuItem separatorItem] atIndex: 3];
152                     }
153 
154                     // WARNING: ultra ugly code ahead
155 
156                     // rename standard entries
157                     // rename "Services"
158                     pNewItem = [pAppMenu itemAtIndex: 4];
159                     if( pNewItem )
160                     {
161                         pString = CreateNSString( String( ResId( SV_MENU_MAC_SERVICES, *pMgr ) ) );
162                         [pNewItem  setTitle: pString];
163                         if( pString )
164                             [pString release];
165                     }
166 
167                     // rename "Hide NewApplication"
168                     pNewItem = [pAppMenu itemAtIndex: 6];
169                     if( pNewItem )
170                     {
171                         pString = CreateNSString( String( ResId( SV_MENU_MAC_HIDEAPP, *pMgr ) ) );
172                         [pNewItem  setTitle: pString];
173                         if( pString )
174                             [pString release];
175                     }
176 
177                     // rename "Hide Others"
178                     pNewItem = [pAppMenu itemAtIndex: 7];
179                     if( pNewItem )
180                     {
181                         pString = CreateNSString( String( ResId( SV_MENU_MAC_HIDEALL, *pMgr ) ) );
182                         [pNewItem  setTitle: pString];
183                         if( pString )
184                             [pString release];
185                     }
186 
187                     // rename "Show all"
188                     pNewItem = [pAppMenu itemAtIndex: 8];
189                     if( pNewItem )
190                     {
191                         pString = CreateNSString( String( ResId( SV_MENU_MAC_SHOWALL, *pMgr ) ) );
192                         [pNewItem  setTitle: pString];
193                         if( pString )
194                             [pString release];
195                     }
196 
197                     // rename "Quit NewApplication"
198                     pNewItem = [pAppMenu itemAtIndex: 10];
199                     if( pNewItem )
200                     {
201                         pString = CreateNSString( String( ResId( SV_MENU_MAC_QUITAPP, *pMgr ) ) );
202                         [pNewItem  setTitle: pString];
203                         if( pString )
204                             [pString release];
205                     }
206                 }
207             }
208         }
209     }
210 }
211 
212 // =======================================================================
213 
214 SalMenu* AquaSalInstance::CreateMenu( sal_Bool bMenuBar, Menu* pVCLMenu )
215 {
216     initAppMenu();
217 
218     AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
219     pAquaSalMenu->mpVCLMenu = pVCLMenu;
220 
221     return pAquaSalMenu;
222 }
223 
224 void AquaSalInstance::DestroyMenu( SalMenu* pSalMenu )
225 {
226     delete pSalMenu;
227 }
228 
229 SalMenuItem* AquaSalInstance::CreateMenuItem( const SalItemParams* pItemData )
230 {
231     if( !pItemData )
232         return NULL;
233 
234     AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( pItemData );
235 
236     return pSalMenuItem;
237 }
238 
239 void AquaSalInstance::DestroyMenuItem( SalMenuItem* pSalMenuItem )
240 {
241     delete pSalMenuItem;
242 }
243 
244 
245 // =======================================================================
246 
247 
248 /*
249  * AquaSalMenu
250  */
251 
252 AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
253     mbMenuBar( bMenuBar ),
254     mpMenu( nil ),
255     mpVCLMenu( NULL ),
256     mpFrame( NULL ),
257     mpParentSalMenu( NULL )
258 {
259     if( ! mbMenuBar )
260     {
261         mpMenu = [[SalNSMenu alloc] initWithMenu: this];
262         [mpMenu setDelegate: mpMenu];
263     }
264     else
265     {
266         mpMenu = [NSApp mainMenu];
267     }
268     [mpMenu setAutoenablesItems: NO];
269 }
270 
271 AquaSalMenu::~AquaSalMenu()
272 {
273     // actually someone should have done AquaSalFrame::SetMenu( NULL )
274     // on our frame, alas it is not so
275     if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
276         const_cast<AquaSalFrame*>(mpFrame)->mpMenu = NULL;
277 
278     // this should normally be empty already, but be careful...
279     for( size_t i = 0; i < maButtons.size(); i++ )
280         releaseButtonEntry( maButtons[i] );
281     maButtons.clear();
282 
283     // is this leaking in some cases ? the release often leads to a duplicate release
284     // it seems the parent item gets ownership of the menu
285     if( mpMenu )
286     {
287         if( mbMenuBar )
288         {
289             if( pCurrentMenuBar == this )
290             {
291                 // if the current menubar gets destroyed, set the default menubar
292                 setDefaultMenu();
293             }
294         }
295         else
296             // the system may still hold a reference on mpMenu
297         {
298             // so set the pointer to this AquaSalMenu to NULL
299             // to protect from calling a dead object
300 
301             // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
302             // so we can safely cast here
303             [static_cast<SalNSMenu*>(mpMenu) setSalMenu: NULL];
304             /* #i89860# FIXME:
305                using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
306                instead of [release] fixes an occasional crash. That should
307                indicate that we release menus / menu items in the wrong order
308                somewhere, but I could not find that case.
309             */
310             [mpMenu autorelease];
311         }
312     }
313 }
314 
315 sal_Int32 removeUnusedItemsRunner(NSMenu * pMenu)
316 {
317     NSArray * elements = [pMenu itemArray];
318     NSEnumerator * it = [elements objectEnumerator];
319     id elem;
320     NSMenuItem * lastDisplayedMenuItem = nil;
321     sal_Int32 drawnItems = 0;
322     bool firstEnabledItemIsNoSeparator = false;
323     while((elem=[it nextObject]) != nil) {
324         NSMenuItem * item = static_cast<NSMenuItem *>(elem);
325         if( (![item isEnabled] && ![item isSeparatorItem]) || ([item isSeparatorItem] && (lastDisplayedMenuItem != nil && [lastDisplayedMenuItem isSeparatorItem])) ) {
326             [[item menu]removeItem:item];
327         } else {
328             if( ! firstEnabledItemIsNoSeparator && [item isSeparatorItem] ) {
329                 [[item menu]removeItem:item];
330             } else {
331                 firstEnabledItemIsNoSeparator = true;
332                 lastDisplayedMenuItem = item;
333                 drawnItems++;
334                 if( [item hasSubmenu] ) {
335                     removeUnusedItemsRunner( [item submenu] );
336                 }
337             }
338         }
339     }
340     if( lastDisplayedMenuItem != nil && [lastDisplayedMenuItem isSeparatorItem]) {
341         [[lastDisplayedMenuItem menu]removeItem:lastDisplayedMenuItem];
342     }
343     return drawnItems;
344 }
345 
346 bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const Rectangle& rRect, sal_uLong nFlags)
347 {
348     // do not use native popup menu when AQUA_NATIVE_MENUS is set to sal_False
349     if( ! VisibleMenuBar() ) {
350         return false;
351     }
352 
353     // set offsets for positioning
354     const float offset = 9.0;
355 
356     // get the pointers
357     AquaSalFrame * pParentAquaSalFrame = (AquaSalFrame *) pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame();
358     NSWindow * pParentNSWindow = pParentAquaSalFrame->mpWindow;
359     NSView * pParentNSView = [pParentNSWindow contentView];
360     NSView * pPopupNSView = ((AquaSalFrame *) pWin->ImplGetWindow()->ImplGetFrame())->mpView;
361     NSRect popupFrame = [pPopupNSView frame];
362 
363     // since we manipulate the menu below (removing entries)
364     // let's rather make a copy here and work with that
365     NSMenu* pCopyMenu = [mpMenu copy];
366 
367     // filter disabled elements
368     removeUnusedItemsRunner( pCopyMenu );
369 
370     // create frame rect
371     NSRect displayPopupFrame = NSMakeRect( rRect.nLeft+(offset-1), rRect.nTop+(offset+1), popupFrame.size.width, 0 );
372     pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
373 
374     // do the same strange semantics as vcl popup windows to arrive at a frame geometry
375     // in mirrored UI case; best done by actually executing the same code
376     sal_uInt16 nArrangeIndex;
377     pWin->SetPosPixel( pWin->ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
378     displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.nX - pParentAquaSalFrame->maGeometry.nX + offset;
379     displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.nY - pParentAquaSalFrame->maGeometry.nY + offset;
380     pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
381 
382     // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
383     if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
384         [pParentNSView performSelector:@selector(clearLastEvent)];
385 
386     // open popup menu
387     NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
388     [pPopUpButtonCell setMenu: pCopyMenu];
389     [pPopUpButtonCell selectItem:nil];
390     [AquaA11yWrapper setPopupMenuOpen: YES];
391     [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
392     [pPopUpButtonCell release];
393     [AquaA11yWrapper setPopupMenuOpen: NO];
394 
395     // clean up the copy
396     [pCopyMenu release];
397     return true;
398 }
399 
400 int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
401 {
402     int nIndex = 0;
403     if( nPos == MENU_APPEND )
404         nIndex = [mpMenu numberOfItems];
405     else
406         nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
407     return nIndex;
408 }
409 
410 const AquaSalFrame* AquaSalMenu::getFrame() const
411 {
412     const AquaSalMenu* pMenu = this;
413     while( pMenu && ! pMenu->mpFrame )
414         pMenu = pMenu->mpParentSalMenu;
415     return pMenu ? pMenu->mpFrame : NULL;
416 }
417 
418 void AquaSalMenu::unsetMainMenu()
419 {
420     pCurrentMenuBar = NULL;
421 
422     // remove items from main menu
423     NSMenu* pMenu = [NSApp mainMenu];
424     for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
425         [pMenu removeItemAtIndex: 1];
426 }
427 
428 void AquaSalMenu::setMainMenu()
429 {
430     DBG_ASSERT( mbMenuBar, "setMainMenu on non menubar" );
431     if( mbMenuBar )
432     {
433         if( pCurrentMenuBar != this )
434         {
435             unsetMainMenu();
436             // insert our items
437             for( unsigned int i = 0; i < maItems.size(); i++ )
438             {
439                 NSMenuItem* pItem = maItems[i]->mpMenuItem;
440                 [mpMenu insertItem: pItem atIndex: i+1];
441             }
442             pCurrentMenuBar = this;
443 
444             // change status item
445             statusLayout();
446         }
447         enableMainMenu( true );
448     }
449 }
450 
451 void AquaSalMenu::setDefaultMenu()
452 {
453     NSMenu* pMenu = [NSApp mainMenu];
454 
455     unsetMainMenu();
456 
457     // insert default items
458     std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
459     for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
460     {
461         NSMenuItem* pItem = rFallbackMenu[i];
462         if( [pItem menu] == nil )
463             [pMenu insertItem: pItem atIndex: i+1];
464     }
465 }
466 
467 void AquaSalMenu::enableMainMenu( bool bEnable )
468 {
469     NSMenu* pMainMenu = [NSApp mainMenu];
470     if( pMainMenu )
471     {
472         // enable/disable items from main menu
473         int nItems = [pMainMenu numberOfItems];
474         for( int n = 1; n < nItems; n++ )
475         {
476             NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
477             [pItem setEnabled: bEnable ? YES : NO];
478         }
479     }
480 }
481 
482 void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
483 {
484     initAppMenu();
485 
486     std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
487 
488     // prevent duplicate insertion
489     int nItems = rFallbackMenu.size();
490     for( int i = 0; i < nItems; i++ )
491     {
492         if( rFallbackMenu[i] == pNewItem )
493             return;
494     }
495 
496     // push the item to the back and retain it
497     [pNewItem retain];
498     rFallbackMenu.push_back( pNewItem );
499 
500     if( pCurrentMenuBar == NULL )
501         setDefaultMenu();
502 }
503 
504 void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
505 {
506     std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
507 
508     // find item
509     unsigned int nItems = rFallbackMenu.size();
510     for( unsigned int i = 0; i < nItems; i++ )
511     {
512         if( rFallbackMenu[i] == pOldItem )
513         {
514             // remove item and release
515             rFallbackMenu.erase( rFallbackMenu.begin() + i );
516             [pOldItem release];
517 
518             if( pCurrentMenuBar == NULL )
519                 setDefaultMenu();
520 
521             return;
522         }
523     }
524 }
525 
526 sal_Bool AquaSalMenu::VisibleMenuBar()
527 {
528     // Enable/disable experimental native menus code?
529     //
530     // To disable native menus, set the environment variable AQUA_NATIVE_MENUS to FALSE
531 
532     static const char *pExperimental = getenv ("AQUA_NATIVE_MENUS");
533 
534     if ( ImplGetSVData()->mbIsTestTool || (pExperimental && !strcasecmp(pExperimental, "FALSE")) )
535         return sal_False;
536 
537     // End of experimental code enable/disable part
538 
539     return sal_True;
540 }
541 
542 void AquaSalMenu::SetFrame( const SalFrame *pFrame )
543 {
544     mpFrame = static_cast<const AquaSalFrame*>(pFrame);
545 }
546 
547 void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
548 {
549     AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
550 
551     pAquaSalMenuItem->mpParentMenu = this;
552     DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == NULL        ||
553                 pAquaSalMenuItem->mpVCLMenu == mpVCLMenu   ||
554                 mpVCLMenu == NULL,
555                 "resetting menu ?" );
556     if( pAquaSalMenuItem->mpVCLMenu )
557         mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
558 
559     if( nPos == MENU_APPEND || nPos == maItems.size() )
560         maItems.push_back( pAquaSalMenuItem );
561     else if( nPos < maItems.size() )
562         maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
563     else
564     {
565         DBG_ERROR( "invalid item index in insert" );
566         return;
567     }
568 
569     if( ! mbMenuBar || pCurrentMenuBar == this )
570         [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
571 }
572 
573 void AquaSalMenu::RemoveItem( unsigned nPos )
574 {
575     AquaSalMenuItem* pRemoveItem = NULL;
576     if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
577     {
578         pRemoveItem = maItems.back();
579         maItems.pop_back();
580     }
581     else if( nPos < maItems.size() )
582     {
583         pRemoveItem = maItems[ nPos ];
584         maItems.erase( maItems.begin()+nPos );
585     }
586     else
587     {
588         DBG_ERROR( "invalid item index in remove" );
589         return;
590     }
591 
592     pRemoveItem->mpParentMenu = NULL;
593 
594     if( ! mbMenuBar || pCurrentMenuBar == this )
595         [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
596 }
597 
598 void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
599 {
600     AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
601     AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
602 
603     if (subAquaSalMenu)
604     {
605         pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
606         if( subAquaSalMenu->mpParentSalMenu == NULL )
607         {
608             subAquaSalMenu->mpParentSalMenu = this;
609             [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
610 
611             // set title of submenu
612             [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
613         }
614         else if( subAquaSalMenu->mpParentSalMenu != this )
615         {
616             // cocoa doesn't allow menus to be submenus of multiple
617             // menu items, so place a copy in the menu item instead ?
618             // let's hope that NSMenu copy does the right thing
619             NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
620             [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
621 
622             // set title of submenu
623             [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
624         }
625     }
626     else
627     {
628         if( pAquaSalMenuItem->mpSubMenu )
629         {
630             if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
631                 pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = NULL;
632         }
633         pAquaSalMenuItem->mpSubMenu = NULL;
634         [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
635     }
636 }
637 
638 void AquaSalMenu::CheckItem( unsigned nPos, sal_Bool bCheck )
639 {
640     if( nPos < maItems.size() )
641     {
642         NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
643         [pItem setState: bCheck ? NSOnState : NSOffState];
644     }
645 }
646 
647 void AquaSalMenu::EnableItem( unsigned nPos, sal_Bool bEnable )
648 {
649     if( nPos < maItems.size() )
650     {
651         NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
652         [pItem setEnabled: bEnable ? YES : NO];
653     }
654 }
655 
656 void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
657 {
658     AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
659     if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
660         return;
661 
662     NSImage* pImage = CreateNSImage( rImage );
663 
664     [pSalMenuItem->mpMenuItem setImage: pImage];
665     if( pImage )
666         [pImage release];
667 }
668 
669 void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const XubString& i_rText )
670 {
671     if (!i_pSalMenuItem)
672         return;
673 
674     AquaSalMenuItem *pAquaSalMenuItem = (AquaSalMenuItem *) i_pSalMenuItem;
675 
676     String aText( i_rText );
677 
678     // Delete mnemonics
679     aText.EraseAllChars( '~' );
680 
681     /* #i90015# until there is a correct solution
682        strip out any appended (.*) in menubar entries
683     */
684     if( mbMenuBar )
685     {
686         xub_StrLen nPos = aText.SearchBackward( sal_Unicode(  '(' ) );
687         if( nPos != STRING_NOTFOUND )
688         {
689             xub_StrLen nPos2 = aText.Search( sal_Unicode( ')' ) );
690             if( nPos2 != STRING_NOTFOUND )
691                 aText.Erase( nPos, nPos2-nPos+1 );
692         }
693     }
694 
695     NSString* pString = CreateNSString( aText );
696     if (pString)
697     {
698         [pAquaSalMenuItem->mpMenuItem setTitle: pString];
699         // if the menu item has a submenu, change its title as well
700         if (pAquaSalMenuItem->mpSubMenu)
701             [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
702         [pString release];
703     }
704 }
705 
706 void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const KeyCode& rKeyCode, const XubString& /*rKeyName*/ )
707 {
708     sal_uInt16 nModifier;
709     sal_Unicode nCommandKey = 0;
710 
711     sal_uInt16 nKeyCode=rKeyCode.GetCode();
712     if( nKeyCode )
713     {
714         if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z))           // letter A..Z
715             nCommandKey = nKeyCode-KEY_A + 'a';
716         else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9))      // numbers 0..9
717             nCommandKey = nKeyCode-KEY_0 + '0';
718         else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26))   // function keys F1..F26
719             nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
720         else if( nKeyCode == KEY_REPEAT )
721             nCommandKey = NSRedoFunctionKey;
722         else if( nKeyCode == KEY_SPACE )
723             nCommandKey = ' ';
724         else
725         {
726             switch (nKeyCode)
727             {
728             case KEY_ADD:
729                 nCommandKey='+';
730                 break;
731             case KEY_SUBTRACT:
732                 nCommandKey='-';
733                 break;
734             case KEY_MULTIPLY:
735                 nCommandKey='*';
736                 break;
737             case KEY_DIVIDE:
738                 nCommandKey='/';
739                 break;
740             case KEY_POINT:
741                 nCommandKey='.';
742                 break;
743             case KEY_LESS:
744                 nCommandKey='<';
745                 break;
746             case KEY_GREATER:
747                 nCommandKey='>';
748                 break;
749             case KEY_EQUAL:
750                 nCommandKey='=';
751                 break;
752             }
753         }
754     }
755     else // not even a code ? nonsense -> ignore
756         return;
757 
758     DBG_ASSERT( nCommandKey, "unmapped accelerator key" );
759 
760     nModifier=rKeyCode.GetAllModifier();
761 
762     // should always use the command key
763     int nItemModifier = 0;
764 
765     if (nModifier & KEY_SHIFT)
766     {
767         nItemModifier |= NSShiftKeyMask;   // actually useful only for function keys
768         if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
769             nCommandKey = nKeyCode - KEY_A + 'A';
770     }
771 
772     if (nModifier & KEY_MOD1)
773         nItemModifier |= NSCommandKeyMask;
774 
775     if(nModifier & KEY_MOD2)
776         nItemModifier |= NSAlternateKeyMask;
777 
778     if(nModifier & KEY_MOD3)
779         nItemModifier |= NSControlKeyMask;
780 
781     AquaSalMenuItem *pAquaSalMenuItem = (AquaSalMenuItem *) pSalMenuItem;
782     NSString* pString = CreateNSString( rtl::OUString( &nCommandKey, 1 ) );
783     [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
784     [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
785     if (pString)
786         [pString release];
787 }
788 
789 void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
790 {
791 }
792 
793 AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
794 {
795     for( size_t i = 0; i < maButtons.size(); ++i )
796     {
797         if( maButtons[i].maButton.mnId == i_nItemId )
798             return &maButtons[i];
799     }
800     return NULL;
801 }
802 
803 void AquaSalMenu::statusLayout()
804 {
805     if( GetSalData()->mpStatusItem )
806     {
807         NSView* pView = [GetSalData()->mpStatusItem view];
808         if( [pView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
809             [(OOStatusItemView*)pView layout];
810         else
811             DBG_ERROR( "someone stole our status view" );
812     }
813 }
814 
815 void AquaSalMenu::releaseButtonEntry( MenuBarButtonEntry& i_rEntry )
816 {
817     if( i_rEntry.mpNSImage )
818     {
819         [i_rEntry.mpNSImage release];
820         i_rEntry.mpNSImage = nil;
821     }
822     if( i_rEntry.mpToolTipString )
823     {
824         [i_rEntry.mpToolTipString release];
825         i_rEntry.mpToolTipString = nil;
826     }
827 }
828 
829 bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
830 {
831     if( ! mbMenuBar || ! VisibleMenuBar() )
832         return false;
833 
834     MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
835     if( pEntry )
836     {
837         releaseButtonEntry( *pEntry );
838         pEntry->maButton = i_rNewItem;
839         pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
840         if( i_rNewItem.maToolTipText.getLength() )
841             pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
842     }
843     else
844     {
845         maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
846         maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
847         maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
848     }
849 
850     // lazy create status item
851     SalData::getStatusItem();
852 
853     if( pCurrentMenuBar == this )
854         statusLayout();
855 
856     return true;
857 }
858 
859 void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
860 {
861     MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
862     if( pEntry )
863     {
864         releaseButtonEntry( *pEntry );
865         // note: vector guarantees that its contents are in a plain array
866         maButtons.erase( maButtons.begin() + (pEntry - &maButtons[0]) );
867     }
868 
869     if( pCurrentMenuBar == this )
870         statusLayout();
871 }
872 
873 Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
874 {
875     if( GetSalData()->mnSystemVersion < VER_LEOPARD )
876         return Rectangle( Point( -1, -1 ), Size( 1, 1 ) );
877 
878     if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
879         return Rectangle();
880 
881     MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
882 
883     if( ! pEntry )
884         return Rectangle();
885 
886     NSStatusItem* pItem = SalData::getStatusItem();
887     if( ! pItem )
888         return Rectangle();
889 
890     NSView* pView = [pItem view];
891     if( ! pView )
892         return Rectangle();
893     NSWindow* pWin = [pView window];
894     if( ! pWin )
895         return Rectangle();
896 
897     NSRect aRect = [pWin frame];
898     aRect.origin = [pWin convertBaseToScreen: NSMakePoint( 0, 0 )];
899 
900     // make coordinates relative to reference frame
901     static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
902     aRect.origin.x -= i_pReferenceFrame->maGeometry.nX;
903     aRect.origin.y -= i_pReferenceFrame->maGeometry.nY + aRect.size.height;
904 
905     return Rectangle( Point(static_cast<long int>(aRect.origin.x),
906 			    static_cast<long int>(aRect.origin.y)
907 			    ),
908 		      Size( static_cast<long int>(aRect.size.width),
909 			    static_cast<long int>(aRect.size.height)
910 			  )
911 		    );
912 }
913 
914 // =======================================================================
915 
916 /*
917  * SalMenuItem
918  */
919 
920 AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
921     mnId( pItemData->nId ),
922     mpVCLMenu( pItemData->pMenu ),
923     mpParentMenu( NULL ),
924     mpSubMenu( NULL ),
925     mpMenuItem( nil )
926 {
927     String aText( pItemData->aText );
928 
929     // Delete mnemonics
930     aText.EraseAllChars( '~' );
931 
932     if (pItemData->eType == MENUITEM_SEPARATOR)
933     {
934         mpMenuItem = [NSMenuItem separatorItem];
935         // these can go occasionally go in and out of a menu, ensure their lifecycle
936         // also for the release in AquaSalMenuItem destructor
937         [mpMenuItem retain];
938     }
939     else
940     {
941         mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
942         [mpMenuItem setEnabled: YES];
943         NSString* pString = CreateNSString( aText );
944         if (pString)
945         {
946             [mpMenuItem setTitle: pString];
947             [pString release];
948         }
949         // anything but a separator should set a menu to dispatch to
950         DBG_ASSERT( mpVCLMenu, "no menu" );
951     }
952 }
953 
954 AquaSalMenuItem::~AquaSalMenuItem()
955 {
956     /* #i89860# FIXME:
957        using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
958        [release] fixes an occasional crash. That should indicate that we release
959        menus / menu items in the wrong order somewhere, but I
960        could not find that case.
961     */
962     if( mpMenuItem )
963         [mpMenuItem autorelease];
964 }
965 
966 // -------------------------------------------------------------------
967 
968