0735a4306d
Found the following memory leaks using Xcode's Instruments application: 1. Posting an NSAccessibilityUIElementDestroyedNotification notification causes [ AquaA11yWrapper isAccessibilityElement ] to be called on the object so mark the object as disposed before posting the destroyed notification and test for disposed in all of the standard NSAccessibility selectors to prevent any calls to likely disposed C++ accessibility objects. 2. In [ AquaA11yWrapper accessibilityHitTest: ], [ AquaA11yFactory wrapperForAccessibleContext: ] already retains the returned object so retaining it until the next call to this selector can lead to a memory leak when dragging selected cells in Calc to a new location. So autorelease the object so that transient objects stay alive but not past the next clearing of the autorelease pool. 3. [ AquaA11ySelectionWrapper selectedChildrenAttributeForElement: ] is expected to return an autoreleased object. 4. [ AquaA11yFactory wrapperForAccessible: ] is not a getter. It expects the caller to release the returned object. 5. CreateNSString() is not a getter. It expects the caller to release the returned string. Change-Id: I824740d7e3851b0c3e31e2c009860aa822c94222 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168034 Reviewed-by: Patrick Luby <guibomacdev@gmail.com> Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk> Tested-by: Jenkins
898 lines
28 KiB
C++
898 lines
28 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
#include <sal/log.hxx>
|
|
#include <osl/diagnose.h>
|
|
|
|
#include <objc/objc-runtime.h>
|
|
|
|
#include <rtl/ustrbuf.hxx>
|
|
#include <tools/debug.hxx>
|
|
#include <tools/long.hxx>
|
|
#include <vcl/commandevent.hxx>
|
|
#include <vcl/toolkit/floatwin.hxx>
|
|
#include <vcl/window.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
|
|
#include <osx/runinmain.hxx>
|
|
#include <osx/saldata.hxx>
|
|
#include <osx/salinst.h>
|
|
#include <osx/salmenu.h>
|
|
#include <osx/salnsmenu.h>
|
|
#include <osx/salframe.h>
|
|
#include <osx/a11ywrapper.h>
|
|
#include <quartz/utils.h>
|
|
#include <strings.hrc>
|
|
#include <window.h>
|
|
#include <vcl/mnemonic.hxx>
|
|
|
|
namespace {
|
|
|
|
void releaseButtonEntry( AquaSalMenu::MenuBarButtonEntry& i_rEntry )
|
|
{
|
|
if( i_rEntry.mpNSImage )
|
|
{
|
|
[i_rEntry.mpNSImage release];
|
|
i_rEntry.mpNSImage = nil;
|
|
}
|
|
if( i_rEntry.mpToolTipString )
|
|
{
|
|
[i_rEntry.mpToolTipString release];
|
|
i_rEntry.mpToolTipString = nil;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = nullptr;
|
|
|
|
@interface MainMenuSelector : NSObject
|
|
{
|
|
}
|
|
-(void)showDialog: (ShowDialogId)nDialog;
|
|
-(void)showPreferences: (id)sender;
|
|
-(void)showAbout: (id)sender;
|
|
@end
|
|
|
|
@implementation MainMenuSelector
|
|
-(void)showDialog: (ShowDialogId)nDialog
|
|
{
|
|
if( AquaSalMenu::pCurrentMenuBar )
|
|
{
|
|
const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
|
|
if( pFrame && AquaSalFrame::isAlive( pFrame ) )
|
|
{
|
|
pFrame->CallCallback( SalEvent::ShowDialog, reinterpret_cast<void*>(nDialog) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OUString aDialog;
|
|
if( nDialog == ShowDialogId::About )
|
|
aDialog = "ABOUT";
|
|
else if( nDialog == ShowDialogId::Preferences )
|
|
aDialog = "PREFERENCES";
|
|
const ApplicationEvent* pAppEvent = new ApplicationEvent(
|
|
ApplicationEvent::Type::ShowDialog, aDialog);
|
|
AquaSalInstance::aAppEventList.push_back( pAppEvent );
|
|
}
|
|
}
|
|
|
|
-(void)showPreferences: (id) sender
|
|
{
|
|
(void)sender;
|
|
SolarMutexGuard aGuard;
|
|
|
|
[self showDialog: ShowDialogId::Preferences];
|
|
}
|
|
-(void)showAbout: (id) sender
|
|
{
|
|
(void)sender;
|
|
SolarMutexGuard aGuard;
|
|
|
|
[self showDialog: ShowDialogId::About];
|
|
}
|
|
@end
|
|
|
|
// FIXME: currently this is leaked
|
|
static MainMenuSelector* pMainMenuSelector = nil;
|
|
|
|
static void initAppMenu()
|
|
{
|
|
static bool bInitialized = false;
|
|
if (bInitialized)
|
|
return;
|
|
OSX_SALDATA_RUNINMAIN(initAppMenu())
|
|
bInitialized = true;
|
|
|
|
NSMenu* pAppMenu = nil;
|
|
NSMenuItem* pNewItem = nil;
|
|
|
|
// Related: tdf#126638 use NSMenu subclass to catch and redirect key
|
|
// shortcuts when a modal window is displayed
|
|
SalNSMainMenu* pMainMenu = [[[SalNSMainMenu alloc] initWithTitle: @"Main Menu"] autorelease];
|
|
pNewItem = [pMainMenu addItemWithTitle: @"Application"
|
|
action: nil
|
|
keyEquivalent: @""];
|
|
pAppMenu = [[[NSMenu alloc] initWithTitle: @"Application"] autorelease];
|
|
[pNewItem setSubmenu: pAppMenu];
|
|
[NSApp setMainMenu: pMainMenu];
|
|
|
|
pMainMenuSelector = [[MainMenuSelector alloc] init];
|
|
|
|
// about
|
|
NSString* pString = CreateNSString(VclResId(SV_STDTEXT_ABOUT));
|
|
pNewItem = [pAppMenu addItemWithTitle: pString
|
|
action: @selector(showAbout:)
|
|
keyEquivalent: @""];
|
|
[pString release];
|
|
[pNewItem setTarget: pMainMenuSelector];
|
|
|
|
[pAppMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
// preferences
|
|
pString = CreateNSString(VclResId(SV_STDTEXT_PREFERENCES));
|
|
pNewItem = [pAppMenu addItemWithTitle: pString
|
|
action: @selector(showPreferences:)
|
|
keyEquivalent: @","];
|
|
[pString release];
|
|
[pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand];
|
|
[pNewItem setTarget: pMainMenuSelector];
|
|
|
|
[pAppMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
// Services item and menu
|
|
pString = CreateNSString(VclResId(SV_MENU_MAC_SERVICES));
|
|
pNewItem = [pAppMenu addItemWithTitle: pString
|
|
action: nil
|
|
keyEquivalent: @""];
|
|
[pString release];
|
|
NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
|
|
[pNewItem setSubmenu: servicesMenu];
|
|
[NSApp setServicesMenu: servicesMenu];
|
|
|
|
[pAppMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
// Hide Application
|
|
pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEAPP));
|
|
[pAppMenu addItemWithTitle: pString
|
|
action:@selector(hide:)
|
|
keyEquivalent:@"h"];
|
|
[pString release];
|
|
|
|
// Hide Others
|
|
pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEALL));
|
|
[pAppMenu addItemWithTitle: pString
|
|
action:@selector(hideOtherApplications:)
|
|
keyEquivalent:@"h"];
|
|
[pString release];
|
|
[pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
|
|
|
|
// Show All
|
|
pString = CreateNSString(VclResId(SV_MENU_MAC_SHOWALL));
|
|
[pAppMenu addItemWithTitle: pString
|
|
action:@selector(unhideAllApplications:)
|
|
keyEquivalent:@""];
|
|
[pString release];
|
|
|
|
[pAppMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
// Quit
|
|
pString = CreateNSString(VclResId(SV_MENU_MAC_QUITAPP));
|
|
[pAppMenu addItemWithTitle: pString
|
|
action:@selector(terminate:)
|
|
keyEquivalent:@"q"];
|
|
[pString release];
|
|
}
|
|
|
|
std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
|
|
{
|
|
initAppMenu();
|
|
|
|
AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
|
|
pAquaSalMenu->mpVCLMenu = pVCLMenu;
|
|
|
|
return std::unique_ptr<SalMenu>(pAquaSalMenu);
|
|
}
|
|
|
|
std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
|
|
{
|
|
AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
|
|
|
|
return std::unique_ptr<SalMenuItem>(pSalMenuItem);
|
|
}
|
|
|
|
/*
|
|
* AquaSalMenu
|
|
*/
|
|
|
|
AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
|
|
mbMenuBar( bMenuBar ),
|
|
mpMenu( nil ),
|
|
mpFrame( nullptr ),
|
|
mpParentSalMenu( nullptr )
|
|
{
|
|
if( ! mbMenuBar )
|
|
{
|
|
mpMenu = [[SalNSMenu alloc] initWithMenu: this];
|
|
[mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
|
|
|
|
// Related: tdf#126638 enable the menu's "autoenabledItems" property
|
|
// Enable the menu's "autoenabledItems" property so that
|
|
// -[SalNSMenuItem validateMenuItem:] will be called before handling
|
|
// a key shortcut and the menu item can be temporarily disabled if a
|
|
// modal window is displayed.
|
|
[mpMenu setAutoenablesItems: YES];
|
|
}
|
|
else
|
|
{
|
|
mpMenu = [NSApp mainMenu];
|
|
[mpMenu setAutoenablesItems: NO];
|
|
}
|
|
}
|
|
|
|
AquaSalMenu::~AquaSalMenu()
|
|
{
|
|
// actually someone should have done AquaSalFrame::SetMenu( NULL )
|
|
// on our frame, alas it is not so
|
|
if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
|
|
const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
|
|
|
|
// this should normally be empty already, but be careful...
|
|
for( size_t i = 0; i < maButtons.size(); i++ )
|
|
releaseButtonEntry( maButtons[i] );
|
|
maButtons.clear();
|
|
|
|
// is this leaking in some cases ? the release often leads to a duplicate release
|
|
// it seems the parent item gets ownership of the menu
|
|
if( mpMenu )
|
|
{
|
|
if( mbMenuBar )
|
|
{
|
|
if( pCurrentMenuBar == this )
|
|
{
|
|
// if the current menubar gets destroyed, set the default menubar
|
|
setDefaultMenu();
|
|
}
|
|
}
|
|
else
|
|
// the system may still hold a reference on mpMenu
|
|
{
|
|
// so set the pointer to this AquaSalMenu to NULL
|
|
// to protect from calling a dead object
|
|
|
|
// in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
|
|
// so we can safely cast here
|
|
[static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
|
|
/* #i89860# FIXME:
|
|
using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
|
|
instead of [release] fixes an occasional crash. That should
|
|
indicate that we release menus / menu items in the wrong order
|
|
somewhere, but I could not find that case.
|
|
*/
|
|
[mpMenu autorelease];
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
|
|
{
|
|
// set offsets for positioning
|
|
const float offset = 9.0;
|
|
|
|
// get the pointers
|
|
AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
|
|
NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
|
|
NSView* pParentNSView = [pParentNSWindow contentView];
|
|
NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
|
|
NSRect popupFrame = [pPopupNSView frame];
|
|
|
|
// create frame rect
|
|
NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
|
|
pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
|
|
|
|
// do the same strange semantics as vcl popup windows to arrive at a frame geometry
|
|
// in mirrored UI case; best done by actually executing the same code
|
|
sal_uInt16 nArrangeIndex;
|
|
pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
|
|
displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.x() - pParentAquaSalFrame->maGeometry.x() + offset;
|
|
displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.y() - pParentAquaSalFrame->maGeometry.y() + offset;
|
|
pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
|
|
|
|
// #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
|
|
if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
|
|
[pParentNSView performSelector:@selector(clearLastEvent)];
|
|
|
|
// open popup menu
|
|
NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
|
|
[pPopUpButtonCell setMenu: mpMenu];
|
|
[pPopUpButtonCell selectItem:nil];
|
|
[AquaA11yWrapper setPopupMenuOpen: YES];
|
|
[pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
|
|
[pPopUpButtonCell release];
|
|
[AquaA11yWrapper setPopupMenuOpen: NO];
|
|
|
|
return true;
|
|
}
|
|
|
|
int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
|
|
{
|
|
int nIndex = 0;
|
|
if( nPos == MENU_APPEND )
|
|
nIndex = [mpMenu numberOfItems];
|
|
else
|
|
nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
|
|
return nIndex;
|
|
}
|
|
|
|
const AquaSalFrame* AquaSalMenu::getFrame() const
|
|
{
|
|
const AquaSalMenu* pMenu = this;
|
|
while( pMenu && ! pMenu->mpFrame )
|
|
pMenu = pMenu->mpParentSalMenu;
|
|
return pMenu ? pMenu->mpFrame : nullptr;
|
|
}
|
|
|
|
void AquaSalMenu::unsetMainMenu()
|
|
{
|
|
pCurrentMenuBar = nullptr;
|
|
|
|
// remove items from main menu
|
|
NSMenu* pMenu = [NSApp mainMenu];
|
|
for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
|
|
[pMenu removeItemAtIndex: 1];
|
|
}
|
|
|
|
void AquaSalMenu::setMainMenu()
|
|
{
|
|
SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
|
|
if( mbMenuBar )
|
|
{
|
|
if( pCurrentMenuBar != this )
|
|
{
|
|
unsetMainMenu();
|
|
// insert our items
|
|
for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
|
|
{
|
|
NSMenuItem* pItem = maItems[i]->mpMenuItem;
|
|
[mpMenu insertItem: pItem atIndex: i+1];
|
|
}
|
|
pCurrentMenuBar = this;
|
|
|
|
// change status item
|
|
statusLayout();
|
|
}
|
|
enableMainMenu( true );
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::setDefaultMenu()
|
|
{
|
|
NSMenu* pMenu = [NSApp mainMenu];
|
|
|
|
unsetMainMenu();
|
|
|
|
// insert default items
|
|
std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
|
|
for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
|
|
{
|
|
NSMenuItem* pItem = rFallbackMenu[i];
|
|
if( [pItem menu] == nil )
|
|
[pMenu insertItem: pItem atIndex: i+1];
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::enableMainMenu( bool bEnable )
|
|
{
|
|
NSMenu* pMainMenu = [NSApp mainMenu];
|
|
if( pMainMenu )
|
|
{
|
|
// enable/disable items from main menu
|
|
int nItems = [pMainMenu numberOfItems];
|
|
for( int n = 1; n < nItems; n++ )
|
|
{
|
|
NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
|
|
[pItem setEnabled: bEnable ? YES : NO];
|
|
}
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
|
|
{
|
|
initAppMenu();
|
|
|
|
std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
|
|
|
|
// prevent duplicate insertion
|
|
int nItems = rFallbackMenu.size();
|
|
for( int i = 0; i < nItems; i++ )
|
|
{
|
|
if( rFallbackMenu[i] == pNewItem )
|
|
return;
|
|
}
|
|
|
|
// push the item to the back and retain it
|
|
[pNewItem retain];
|
|
rFallbackMenu.push_back( pNewItem );
|
|
|
|
if( pCurrentMenuBar == nullptr )
|
|
setDefaultMenu();
|
|
}
|
|
|
|
void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
|
|
{
|
|
std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
|
|
|
|
// find item
|
|
unsigned int nItems = rFallbackMenu.size();
|
|
for( unsigned int i = 0; i < nItems; i++ )
|
|
{
|
|
if( rFallbackMenu[i] == pOldItem )
|
|
{
|
|
// remove item and release
|
|
rFallbackMenu.erase( rFallbackMenu.begin() + i );
|
|
[pOldItem release];
|
|
|
|
if( pCurrentMenuBar == nullptr )
|
|
setDefaultMenu();
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AquaSalMenu::VisibleMenuBar()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void AquaSalMenu::SetFrame( const SalFrame *pFrame )
|
|
{
|
|
mpFrame = static_cast<const AquaSalFrame*>(pFrame);
|
|
}
|
|
|
|
void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
|
|
{
|
|
OSX_SALDATA_RUNINMAIN(InsertItem(pSalMenuItem, nPos))
|
|
|
|
AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
|
|
|
|
pAquaSalMenuItem->mpParentMenu = this;
|
|
DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr ||
|
|
pAquaSalMenuItem->mpVCLMenu == mpVCLMenu ||
|
|
mpVCLMenu == nullptr,
|
|
"resetting menu ?" );
|
|
if( pAquaSalMenuItem->mpVCLMenu )
|
|
mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
|
|
|
|
if( nPos == MENU_APPEND || nPos == maItems.size() )
|
|
maItems.push_back( pAquaSalMenuItem );
|
|
else if( nPos < maItems.size() )
|
|
maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
|
|
else
|
|
{
|
|
OSL_FAIL( "invalid item index in insert" );
|
|
return;
|
|
}
|
|
|
|
if( ! mbMenuBar || pCurrentMenuBar == this )
|
|
[mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
|
|
}
|
|
|
|
void AquaSalMenu::RemoveItem( unsigned nPos )
|
|
{
|
|
AquaSalMenuItem* pRemoveItem = nullptr;
|
|
if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
|
|
{
|
|
pRemoveItem = maItems.back();
|
|
maItems.pop_back();
|
|
}
|
|
else if( nPos < maItems.size() )
|
|
{
|
|
pRemoveItem = maItems[ nPos ];
|
|
maItems.erase( maItems.begin()+nPos );
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL( "invalid item index in remove" );
|
|
return;
|
|
}
|
|
|
|
pRemoveItem->mpParentMenu = nullptr;
|
|
|
|
if( ! mbMenuBar || pCurrentMenuBar == this )
|
|
[mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
|
|
}
|
|
|
|
void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
|
|
{
|
|
AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
|
|
AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
|
|
|
|
if (subAquaSalMenu)
|
|
{
|
|
pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
|
|
if( subAquaSalMenu->mpParentSalMenu == nullptr )
|
|
{
|
|
subAquaSalMenu->mpParentSalMenu = this;
|
|
[pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
|
|
|
|
// set title of submenu
|
|
[subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
|
|
}
|
|
else if( subAquaSalMenu->mpParentSalMenu != this )
|
|
{
|
|
// cocoa doesn't allow menus to be submenus of multiple
|
|
// menu items, so place a copy in the menu item instead ?
|
|
// let's hope that NSMenu copy does the right thing
|
|
NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
|
|
[pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
|
|
|
|
// set title of submenu
|
|
[pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( pAquaSalMenuItem->mpSubMenu )
|
|
{
|
|
if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
|
|
pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
|
|
}
|
|
pAquaSalMenuItem->mpSubMenu = nullptr;
|
|
[pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
|
|
{
|
|
if( nPos < maItems.size() )
|
|
{
|
|
NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
|
|
[pItem setState: bCheck ? NSControlStateValueOn : NSControlStateValueOff];
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
|
|
{
|
|
if( nPos < maItems.size() )
|
|
{
|
|
NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
|
|
[pItem setEnabled: bEnable ? YES : NO];
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
|
|
{
|
|
AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
|
|
if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
|
|
return;
|
|
|
|
NSImage* pImage = CreateNSImage( rImage );
|
|
|
|
[pSalMenuItem->mpMenuItem setImage: pImage];
|
|
if( pImage )
|
|
[pImage release];
|
|
}
|
|
|
|
void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
|
|
{
|
|
if (!i_pSalMenuItem)
|
|
return;
|
|
|
|
AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
|
|
|
|
// Delete all mnemonics of mbMenuBar and CJK-style mnemonic
|
|
OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
|
|
|
|
if (aText.endsWith("...", &aText))
|
|
aText += u"\u2026";
|
|
|
|
NSString* pString = CreateNSString( aText );
|
|
if (pString)
|
|
{
|
|
[pAquaSalMenuItem->mpMenuItem setTitle: pString];
|
|
// if the menu item has a submenu, change its title as well
|
|
if (pAquaSalMenuItem->mpSubMenu)
|
|
[pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
|
|
[pString release];
|
|
}
|
|
}
|
|
|
|
void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
|
|
{
|
|
sal_uInt16 nModifier;
|
|
sal_Unicode nCommandKey = 0;
|
|
|
|
sal_uInt16 nKeyCode=rKeyCode.GetCode();
|
|
if( nKeyCode )
|
|
{
|
|
if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z)) // letter A..Z
|
|
nCommandKey = nKeyCode-KEY_A + 'a';
|
|
else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9)) // numbers 0..9
|
|
nCommandKey = nKeyCode-KEY_0 + '0';
|
|
else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26)) // function keys F1..F26
|
|
nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
|
|
else if( nKeyCode == KEY_REPEAT )
|
|
nCommandKey = NSRedoFunctionKey;
|
|
else if( nKeyCode == KEY_SPACE )
|
|
nCommandKey = ' ';
|
|
else
|
|
{
|
|
switch (nKeyCode)
|
|
{
|
|
case KEY_ADD:
|
|
nCommandKey='+';
|
|
break;
|
|
case KEY_SUBTRACT:
|
|
nCommandKey='-';
|
|
break;
|
|
case KEY_MULTIPLY:
|
|
nCommandKey='*';
|
|
break;
|
|
case KEY_DIVIDE:
|
|
nCommandKey='/';
|
|
break;
|
|
case KEY_POINT:
|
|
nCommandKey='.';
|
|
break;
|
|
case KEY_LESS:
|
|
nCommandKey='<';
|
|
break;
|
|
case KEY_GREATER:
|
|
nCommandKey='>';
|
|
break;
|
|
case KEY_EQUAL:
|
|
nCommandKey='=';
|
|
break;
|
|
case KEY_COLON:
|
|
nCommandKey=':';
|
|
break;
|
|
case KEY_NUMBERSIGN:
|
|
nCommandKey='#';
|
|
break;
|
|
case KEY_SEMICOLON:
|
|
nCommandKey=';';
|
|
break;
|
|
case KEY_BACKSPACE:
|
|
nCommandKey=u'\x232b';
|
|
break;
|
|
case KEY_PAGEUP:
|
|
nCommandKey=u'\x21de';
|
|
break;
|
|
case KEY_PAGEDOWN:
|
|
nCommandKey=u'\x21df';
|
|
break;
|
|
case KEY_UP:
|
|
nCommandKey=u'\x21e1';
|
|
break;
|
|
case KEY_DOWN:
|
|
nCommandKey=u'\x21e3';
|
|
break;
|
|
case KEY_RETURN:
|
|
nCommandKey=u'\x21a9';
|
|
break;
|
|
case KEY_BRACKETLEFT:
|
|
nCommandKey='[';
|
|
break;
|
|
case KEY_BRACKETRIGHT:
|
|
nCommandKey=']';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else // not even a code ? nonsense -> ignore
|
|
return;
|
|
|
|
SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
|
|
|
|
nModifier=rKeyCode.GetModifier();
|
|
|
|
// should always use the command key
|
|
int nItemModifier = 0;
|
|
|
|
if (nModifier & KEY_SHIFT)
|
|
{
|
|
nItemModifier |= NSEventModifierFlagShift; // actually useful only for function keys
|
|
if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
|
|
nCommandKey = nKeyCode - KEY_A + 'A';
|
|
}
|
|
|
|
if (nModifier & KEY_MOD1)
|
|
nItemModifier |= NSEventModifierFlagCommand;
|
|
|
|
if(nModifier & KEY_MOD2)
|
|
nItemModifier |= NSEventModifierFlagOption;
|
|
|
|
if(nModifier & KEY_MOD3)
|
|
nItemModifier |= NSEventModifierFlagControl;
|
|
|
|
AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
|
|
NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
|
|
[pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
|
|
[pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
|
|
if (pString)
|
|
[pString release];
|
|
}
|
|
|
|
void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
|
|
{
|
|
}
|
|
|
|
AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
|
|
{
|
|
for( size_t i = 0; i < maButtons.size(); ++i )
|
|
{
|
|
if( maButtons[i].maButton.mnId == i_nItemId )
|
|
return &maButtons[i];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void AquaSalMenu::statusLayout()
|
|
{
|
|
if( GetSalData()->mpStatusItem )
|
|
{
|
|
SAL_WNODEPRECATED_DECLARATIONS_PUSH
|
|
// "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button
|
|
// property instead"
|
|
NSView* pNSView = [GetSalData()->mpStatusItem view];
|
|
SAL_WNODEPRECATED_DECLARATIONS_POP
|
|
if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
|
|
[static_cast<OOStatusItemView*>(pNSView) layout];
|
|
else
|
|
OSL_FAIL( "someone stole our status view" );
|
|
}
|
|
}
|
|
|
|
bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
|
|
{
|
|
if( ! mbMenuBar )
|
|
return false;
|
|
|
|
MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
|
|
if( pEntry )
|
|
{
|
|
releaseButtonEntry( *pEntry );
|
|
pEntry->maButton = i_rNewItem;
|
|
pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
|
|
if( i_rNewItem.maToolTipText.getLength() )
|
|
pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
|
|
}
|
|
else
|
|
{
|
|
maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
|
|
maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
|
|
maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
|
|
}
|
|
|
|
// lazy create status item
|
|
SalData::getStatusItem();
|
|
|
|
if( pCurrentMenuBar == this )
|
|
statusLayout();
|
|
|
|
return true;
|
|
}
|
|
|
|
void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
|
|
{
|
|
MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
|
|
if( pEntry )
|
|
{
|
|
releaseButtonEntry( *pEntry );
|
|
// note: vector guarantees that its contents are in a plain array
|
|
maButtons.erase( maButtons.begin() + (pEntry - maButtons.data()) );
|
|
}
|
|
|
|
if( pCurrentMenuBar == this )
|
|
statusLayout();
|
|
}
|
|
|
|
tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
|
|
{
|
|
if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
|
|
return tools::Rectangle();
|
|
|
|
MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
|
|
|
|
if( ! pEntry )
|
|
return tools::Rectangle();
|
|
|
|
NSStatusItem* pItem = SalData::getStatusItem();
|
|
if( ! pItem )
|
|
return tools::Rectangle();
|
|
|
|
SAL_WNODEPRECATED_DECLARATIONS_PUSH
|
|
// "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button property
|
|
// instead"
|
|
NSView* pNSView = [pItem view];
|
|
SAL_WNODEPRECATED_DECLARATIONS_POP
|
|
if( ! pNSView )
|
|
return tools::Rectangle();
|
|
NSWindow* pNSWin = [pNSView window];
|
|
if( ! pNSWin )
|
|
return tools::Rectangle();
|
|
|
|
NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
|
|
|
|
// make coordinates relative to reference frame
|
|
static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
|
|
aRect.origin.x -= i_pReferenceFrame->maGeometry.x();
|
|
aRect.origin.y -= i_pReferenceFrame->maGeometry.y() + aRect.size.height;
|
|
|
|
return tools::Rectangle( Point(static_cast<tools::Long>(aRect.origin.x),
|
|
static_cast<tools::Long>(aRect.origin.y)
|
|
),
|
|
Size( static_cast<tools::Long>(aRect.size.width),
|
|
static_cast<tools::Long>(aRect.size.height)
|
|
)
|
|
);
|
|
}
|
|
|
|
/*
|
|
* SalMenuItem
|
|
*/
|
|
|
|
AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
|
|
mnId( pItemData->nId ),
|
|
mpVCLMenu( pItemData->pMenu ),
|
|
mpParentMenu( nullptr ),
|
|
mpSubMenu( nullptr ),
|
|
mpMenuItem( nil )
|
|
{
|
|
if (pItemData->eType == MenuItemType::SEPARATOR)
|
|
{
|
|
mpMenuItem = [NSMenuItem separatorItem];
|
|
// these can go occasionally go in and out of a menu, ensure their lifecycle
|
|
// also for the release in AquaSalMenuItem destructor
|
|
[mpMenuItem retain];
|
|
}
|
|
else
|
|
{
|
|
mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
|
|
[mpMenuItem setEnabled: YES];
|
|
|
|
// peel mnemonics because on mac there are no such things for menu items
|
|
// Delete CJK-style mnemonics for the dropdown menu of the 'New button' and lower menu of 'File > New'
|
|
NSString* pString = CreateNSString(MnemonicGenerator::EraseAllMnemonicChars(pItemData->aText));
|
|
if (pString)
|
|
{
|
|
[mpMenuItem setTitle: pString];
|
|
[pString release];
|
|
}
|
|
// anything but a separator should set a menu to dispatch to
|
|
SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
|
|
}
|
|
}
|
|
|
|
AquaSalMenuItem::~AquaSalMenuItem()
|
|
{
|
|
/* #i89860# FIXME:
|
|
using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
|
|
[release] fixes an occasional crash. That should indicate that we release
|
|
menus / menu items in the wrong order somewhere, but I
|
|
could not find that case.
|
|
*/
|
|
if( mpMenuItem )
|
|
[mpMenuItem autorelease];
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|