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