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