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