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
initAppMenu()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
CreateMenu(sal_Bool bMenuBar,Menu * pVCLMenu)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
DestroyMenu(SalMenu * pSalMenu)220 void AquaSalInstance::DestroyMenu( SalMenu* pSalMenu )
221 {
222 delete pSalMenu;
223 }
224
CreateMenuItem(const SalItemParams * pItemData)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
DestroyMenuItem(SalMenuItem * pSalMenuItem)235 void AquaSalInstance::DestroyMenuItem( SalMenuItem* pSalMenuItem )
236 {
237 delete pSalMenuItem;
238 }
239
240
241 // =======================================================================
242
243
244 /*
245 * AquaSalMenu
246 */
247
AquaSalMenu(bool bMenuBar)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: (id<NSMenuDelegate>)mpMenu];
259 }
260 else
261 {
262 mpMenu = [NSApp mainMenu];
263 }
264 [mpMenu setAutoenablesItems: NO];
265 }
266
~AquaSalMenu()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
removeUnusedItemsRunner(NSMenu * pMenu)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
ShowNativePopupMenu(FloatingWindow * pWin,const Rectangle & rRect,sal_uLong nFlags)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->mpNSWindow;
355 NSView* pParentNSView = [pParentNSWindow contentView];
356 NSView* pPopupNSView = ((AquaSalFrame *) pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
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
getItemIndexByPos(sal_uInt16 nPos) const396 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
getFrame() const406 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
unsetMainMenu()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
setMainMenu()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
setDefaultMenu()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
enableMainMenu(bool bEnable)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
addFallbackMenuItem(NSMenuItem * pNewItem)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
removeFallbackMenuItem(NSMenuItem * pOldItem)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
VisibleMenuBar()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
SetFrame(const SalFrame * pFrame)538 void AquaSalMenu::SetFrame( const SalFrame *pFrame )
539 {
540 mpFrame = static_cast<const AquaSalFrame*>(pFrame);
541 }
542
InsertItem(SalMenuItem * pSalMenuItem,unsigned nPos)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
RemoveItem(unsigned nPos)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
SetSubMenu(SalMenuItem * pSalMenuItem,SalMenu * pSubMenu,unsigned)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
CheckItem(unsigned nPos,sal_Bool bCheck)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
EnableItem(unsigned nPos,sal_Bool bEnable)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
SetItemImage(unsigned,SalMenuItem * pSMI,const Image & rImage)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
SetItemText(unsigned,SalMenuItem * i_pSalMenuItem,const XubString & i_rText)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
SetAccelerator(unsigned,SalMenuItem * pSalMenuItem,const KeyCode & rKeyCode,const XubString &)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
GetSystemMenuData(SystemMenuData *)785 void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
786 {
787 }
788
findButtonItem(sal_uInt16 i_nItemId)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
statusLayout()799 void AquaSalMenu::statusLayout()
800 {
801 if( GetSalData()->mpStatusItem )
802 {
803 NSView* pNSView = [GetSalData()->mpStatusItem view];
804 if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
805 [(OOStatusItemView*)pNSView layout];
806 else
807 DBG_ERROR( "someone stole our status view" );
808 }
809 }
810
releaseButtonEntry(MenuBarButtonEntry & i_rEntry)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
AddMenuBarButton(const SalMenuButtonItem & i_rNewItem)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
RemoveMenuBarButton(sal_uInt16 i_nId)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
GetMenuBarButtonRectPixel(sal_uInt16 i_nItemId,SalFrame * i_pReferenceFrame)869 Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
870 {
871 if( GetSalData()->mnSystemVersion < OSX_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* pNSView = [pItem view];
887 if( ! pNSView )
888 return Rectangle();
889 NSWindow* pNSWin = [pNSView window];
890 if( ! pNSWin )
891 return Rectangle();
892
893 NSRect aRect = [pNSWin frame];
894 aRect.origin = [pNSWin 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
AquaSalMenuItem(const SalItemParams * pItemData)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
~AquaSalMenuItem()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