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