/* -*- 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 <accessibility/accessiblemenubasecomponent.hxx>
#include <accessibility/vclxaccessiblemenubar.hxx>
#include <accessibility/vclxaccessiblepopupmenu.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <comphelper/types.hxx>
#include <comphelper/lok.hxx>
#include <vcl/dialoghelper.hxx>
#include <vcl/svapp.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/image.hxx>
#include <vcl/event.hxx>
#include <vcl/help.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <vcl/decoview.hxx>
#include <vcl/menu.hxx>
#include <vcl/taskpanelist.hxx>
#include <vcl/settings.hxx>
#include <vcl/commandinfoprovider.hxx>
#include <salinst.hxx>
#include <svdata.hxx>
#include <strings.hrc>
#include <window.h>
#include <salmenu.hxx>
#include <salframe.hxx>
#include "menubarwindow.hxx"
#include "menufloatingwindow.hxx"
#include "menuitemlist.hxx"
#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <vcl/toolkit/unowrap.hxx>
#include <rtl/ustrbuf.hxx>
#include <configsettings.hxx>
#include <map>
#include <string_view>
#include <vector>
#include <officecfg/Office/Common.hxx>
namespace vcl
{
struct MenuLayoutData :
public ControlLayoutData
{
std::vector< sal_uInt16 > m_aLineItemIds;
std::map< sal_uInt16, tools::Rectangle > m_aVisibleItemBoundRects;
};
}
using namespace vcl;
constexpr
auto EXTRAITEMHEIGHT = 4;
constexpr
auto SPACE_AROUND_TITLE = 4;
static bool ImplAccelDisabled()
{
// display of accelerator strings may be suppressed via configuration
static int nAccelDisabled = -1;
if ( nAccelDisabled == -1 )
{
OUString aStr =
vcl::SettingsConfigItem::get()->
getValue( u
"Menu" _ustr, u
"SuppressAccelerators" _ustr );
nAccelDisabled = aStr.equalsIgnoreAsciiCase(
"true" ) ? 1 : 0;
}
return nAccelDisabled == 1;
}
static void ImplSetMenuItemData( MenuItemData* pData )
{
// convert data
if ( !pData->aImage )
pData->eType = MenuItemType::STRING;
else if ( pData->aText.isEmpty() )
pData->eType = MenuItemType::IMAGE;
else
pData->eType = MenuItemType::STRINGIMAGE;
}
namespace {
void ImplClosePopupToolBox(
const VclPtr<vcl::Window>& pWin )
{
if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin
) )
{
SystemWindow* pFloatingWindow = ImplGetDockingManager()->GetFloatingWindow(pWin);
if (pFloatingWindow)
static_cast <FloatingWindow*>(pFloatingWindow)->EndPopupMode( FloatWinPopupEndFlags::CloseAll );
}
}
// TODO: Move to common code with the same function in toolbox
// Draw the ">>" - more indicator at the coordinates
void lclDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
rRenderContext.Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
rRenderContext.SetLineColor();
if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
rRenderContext.SetFillColor(COL_WHITE);
else
rRenderContext.SetFillColor(COL_BLACK);
float fScaleFactor = rRenderContext.GetDPIScaleFactor();
int linewidth = 1 * fScaleFactor;
int space = 4 * fScaleFactor;
tools::Long width = 8 * fScaleFactor;
tools::Long height = 5 * fScaleFactor;
//Keep odd b/c drawing code works better
if ( height % 2 == 0 )
height--;
tools::Long heightOrig = height;
tools::Long x = rRect.Left() + (rRect.getOpenWidth() - width)/2 + 1;
tools::Long y = rRect.Top() + (rRect.getOpenHeight() - height)/2 + 1;
while ( height >= 1)
{
rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
x += space;
rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
x -= space;
y++;
if ( height <= heightOrig / 2 + 1) x--;
else x++;
height--;
}
rRenderContext.Pop();
}
} // end anonymous namespace
Menu::Menu()
: mpFirstDel(nullptr),
pItemList(new MenuItemList),
pStartedFrom(nullptr),
m_pWindow(nullptr),
nTitleHeight(0),
nEventId(nullptr),
mnHighlightedItemPos(ITEMPOS_INVALID),
nMenuFlags(MenuFlags::NONE),
nSelectedId(0),
nImgOrChkPos(0),
nTextPos(0),
bCanceled(false ),
bInCallback(false ),
bKilled(false )
{
}
Menu::~Menu()
{
disposeOnce();
}
void Menu::dispose()
{
ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID );
m_pWindow.disposeAndClear();
// dispose accessible components
comphelper::disposeComponent(mxAccessible);
if ( nEventId )
Application::RemoveUserEvent( nEventId );
// Notify deletion of this menu
ImplMenuDelData* pDelData = mpFirstDel;
while ( pDelData )
{
pDelData->mpMenu = nullptr;
pDelData = pDelData->mpNext;
}
bKilled = true ;
// tdf#140225 when clearing pItemList, keep SalMenu in sync with
// their removal during menu teardown
for (size_t n = pItemList->size(); n;)
{
--n;
if (mpSalMenu)
mpSalMenu->RemoveItem(n);
pItemList->Remove(n);
}
assert(!pItemList->size());
mpLayoutData.reset();
// Native-support: destroy SalMenu
mpSalMenu.reset();
pStartedFrom.reset();
m_pWindow.reset();
VclReferenceBase::dispose();
}
void Menu::CreateAutoMnemonics()
{
MnemonicGenerator aMnemonicGenerator;
size_t n;
for ( n = 0; n < pItemList->size(); n++ )
{
MenuItemData* pData = pItemList->GetDataFromPos( n );
if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
aMnemonicGenerator.RegisterMnemonic( pData->aText );
}
for ( n = 0; n < pItemList->size(); n++ )
{
MenuItemData* pData = pItemList->GetDataFromPos( n );
if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText );
}
}
void Menu::Activate()
{
bInCallback = true ;
ImplMenuDelData aDelData( this );
ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID );
if ( !aDelData.isDeleted() )
{
if ( !aActivateHdl.Call( this ) )
{
if ( !aDelData.isDeleted() )
{
Menu* pStartMenu = ImplGetStartMenu();
if ( pStartMenu && ( pStartMenu != this ) )
{
pStartMenu->bInCallback = true ;
// MT 11/01: Call EventListener here? I don't know...
pStartMenu->aActivateHdl.Call( this );
pStartMenu->bInCallback = false ;
}
}
}
bInCallback = false ;
}
if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics))
CreateAutoMnemonics();
}
void Menu::Deactivate()
{
for ( size_t n = pItemList->size(); n; )
{
MenuItemData* pData = pItemList->GetDataFromPos( --n );
if ( pData->bIsTemporary )
{
if ( ImplGetSalMenu() )
ImplGetSalMenu()->RemoveItem( n );
pItemList->Remove( n );
}
}
bInCallback = true ;
ImplMenuDelData aDelData( this );
Menu* pStartMenu = ImplGetStartMenu();
ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID );
if ( !aDelData.isDeleted() )
{
if ( !aDeactivateHdl.Call( this ) )
{
if ( !aDelData.isDeleted() )
{
if ( pStartMenu && ( pStartMenu != this ) )
{
pStartMenu->bInCallback = true ;
pStartMenu->aDeactivateHdl.Call( this );
pStartMenu->bInCallback = false ;
}
}
}
}
if ( !aDelData.isDeleted() )
{
bInCallback = false ;
}
}
void Menu::ImplSelect()
{
MenuItemData* pData = GetItemList()->GetData( nSelectedId );
if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) )
{
bool bChecked = IsItemChecked( nSelectedId );
if ( pData->nBits & MenuItemBits::RADIOCHECK )
{
if ( !bChecked )
CheckItem( nSelectedId );
}
else
CheckItem( nSelectedId, !bChecked );
}
// call select
ImplSVData* pSVData = ImplGetSVData();
pSVData->maAppData.mpActivePopupMenu = nullptr; // if new execute in select()
nEventId = Application::PostUserEvent( LINK( this , Menu, ImplCallSelect ) );
}
void Menu::Select()
{
ImplMenuDelData aDelData( this );
ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) );
if (aDelData.isDeleted())
return ;
if (aSelectHdl.Call(this ))
return ;
if (aDelData.isDeleted())
return ;
Menu* pStartMenu = ImplGetStartMenu();
if (!pStartMenu || (pStartMenu == this ))
return ;
pStartMenu->nSelectedId = nSelectedId;
pStartMenu->sSelectedIdent = sSelectedIdent;
pStartMenu->aSelectHdl.Call( this );
}
#if defined (MACOSX)
void Menu::ImplSelectWithStart( Menu* pSMenu )
{
auto pOldStartedFrom = pStartedFrom;
pStartedFrom = pSMenu;
auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>();
Select();
if ( pOldStartedFrom )
pOldStartedFrom->pStartedFrom = pOldStartedStarted;
pStartedFrom = pOldStartedFrom;
}
#endif
void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos )
{
ImplMenuDelData aDelData( this );
VclMenuEvent aEvent( this , nEvent, nPos );
// This is needed by atk accessibility bridge
if ( nEvent == VclEventId::MenuHighlight )
{
Application::ImplCallEventListeners( aEvent );
}
if ( !aDelData.isDeleted() )
{
// Copy the list, because this can be destroyed when calling a Link...
std::list<Link<VclMenuEvent&,void >> aCopy( maEventListeners );
for ( const auto & rLink : aCopy )
{
if ( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() )
rLink.Call( aEvent );
}
}
}
void Menu::AddEventListener( const Link<VclMenuEvent&,void >& rEventListener )
{
maEventListeners.push_back( rEventListener );
}
void Menu::RemoveEventListener( const Link<VclMenuEvent&,void >& rEventListener )
{
maEventListeners.remove( rEventListener );
}
MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits,
const OUString& rStr, Menu* pMenu,
size_t nPos, const OUString &rIdent)
{
// put Item in MenuItemList
MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING,
nBits, rStr, pMenu, nPos, rIdent);
// update native menu
if (ImplGetSalMenu() && pData->pSalMenuItem)
ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos);
return pData;
}
void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits,
const OUString &rIdent, sal_uInt16 nPos)
{
SAL_WARN_IF( !nItemId, "vcl" , "Menu::InsertItem(): ItemId == 0" );
SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl" ,
"Menu::InsertItem(): ItemId already exists" );
// if Position > ItemCount, append
if ( nPos >= pItemList->size() )
nPos = MENU_APPEND;
// put Item in MenuItemList
NbcInsertItem(nItemId, nItemBits, rStr, this , nPos, rIdent);
vcl::Window* pWin = GetWindow();
mpLayoutData.reset();
if ( pWin )
{
ImplCalcSize( pWin );
if ( pWin->IsVisible() )
pWin->Invalidate();
}
ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
}
void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage,
MenuItemBits nItemBits, const OUString &rIdent, sal_uInt16 nPos)
{
InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos);
SetItemImage( nItemId, rImage );
}
void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr,
const Image& rImage, MenuItemBits nItemBits,
const OUString &rIdent, sal_uInt16 nPos)
{
InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
SetItemImage( nItemId, rImage );
}
void Menu::InsertSeparator(const OUString &rIdent, sal_uInt16 nPos)
{
// do nothing if it's a menu bar
if (IsMenuBar())
return ;
// if position > ItemCount, append
if ( nPos >= pItemList->size() )
nPos = MENU_APPEND;
// put separator in item list
pItemList->InsertSeparator(rIdent, nPos);
// update native menu
size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1;
MenuItemData *pData = pItemList->GetDataFromPos( itemPos );
if ( ImplGetSalMenu() && pData && pData->pSalMenuItem )
ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos );
mpLayoutData.reset();
ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
}
void Menu::RemoveItem( sal_uInt16 nPos )
{
bool bRemove = false ;
if ( nPos < GetItemCount() )
{
// update native menu
if ( ImplGetSalMenu() )
ImplGetSalMenu()->RemoveItem( nPos );
pItemList->Remove( nPos );
bRemove = true ;
}
vcl::Window* pWin = GetWindow();
if ( pWin )
{
ImplCalcSize( pWin );
if ( pWin->IsVisible() )
pWin->Invalidate();
}
mpLayoutData.reset();
if ( bRemove )
ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos );
}
static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos )
{
MenuItemType eType = rMenu.GetItemType( nPos );
if ( eType == MenuItemType::DONTKNOW )
return ;
if ( eType == MenuItemType::SEPARATOR )
pThis->InsertSeparator( {}, nNewPos );
else
{
sal_uInt16 nId = rMenu.GetItemId( nPos );
SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl" ,
"Menu::CopyItem(): ItemId already exists" );
MenuItemData* pData = rMenu.GetItemList()->GetData( nId );
if (!pData)
return ;
if ( eType == MenuItemType::STRINGIMAGE )
pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
else if ( eType == MenuItemType::STRING )
pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos );
else
pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
if ( rMenu.IsItemChecked( nId ) )
pThis->CheckItem( nId );
if ( !rMenu.IsItemEnabled( nId ) )
pThis->EnableItem( nId, false );
pThis->SetHelpId( nId, pData->aHelpId );
pThis->SetHelpText( nId, pData->aHelpText );
pThis->SetAccelKey( nId, pData->aAccelKey );
pThis->SetItemCommand( nId, pData->aCommandStr );
pThis->SetHelpCommand( nId, pData->aHelpCommandStr );
PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId );
if ( pSubMenu )
{
// create auto-copy
VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu );
pThis->SetPopupMenu( nId, pNewMenu );
}
}
}
void Menu::Clear()
{
for ( sal_uInt16 i = GetItemCount(); i; i-- )
RemoveItem( 0 );
}
sal_uInt16 Menu::GetItemCount() const
{
return static_cast <sal_uInt16>(pItemList->size());
}
bool Menu::HasValidEntries(bool bCheckPopups) const
{
bool bValidEntries = false ;
sal_uInt16 nCount = GetItemCount();
for (sal_uInt16 n = 0; !bValidEntries && (n < nCount); n++)
{
MenuItemData* pItem = pItemList->GetDataFromPos(n);
if (pItem->bEnabled && (pItem->eType != MenuItemType::SEPARATOR))
{
if (bCheckPopups && pItem->pSubMenu)
bValidEntries = pItem->pSubMenu->HasValidEntries(true );
else
bValidEntries = true ;
}
}
return bValidEntries;
}
sal_uInt16 Menu::ImplGetVisibleItemCount() const
{
sal_uInt16 nItems = 0;
for ( size_t n = pItemList->size(); n; )
{
if ( ImplIsVisible( --n ) )
nItems++;
}
return nItems;
}
sal_uInt16 Menu::ImplGetFirstVisible() const
{
for ( size_t n = 0; n < pItemList->size(); n++ )
{
if ( ImplIsVisible( n ) )
return n;
}
return ITEMPOS_INVALID;
}
sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const
{
for ( size_t n = nPos; n; )
{
if (ImplIsVisible(--n))
return n;
}
return ITEMPOS_INVALID;
}
sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const
{
for ( size_t n = nPos+1; n < pItemList->size(); n++ )
{
if ( ImplIsVisible( n ) )
return n;
}
return ITEMPOS_INVALID;
}
sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const
{
MenuItemData* pData = pItemList->GetDataFromPos( nPos );
if ( pData )
return pData->nId;
else
return 0;
}
sal_uInt16 Menu::GetItemId(std::u16string_view rIdent) const
{
for (size_t n = 0; n < pItemList->size(); ++n)
{
MenuItemData* pData = pItemList->GetDataFromPos(n);
if (pData && pData->sIdent == rIdent)
return pData->nId;
}
return MENU_ITEM_NOTFOUND;
}
sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( pData )
return static_cast <sal_uInt16>(nPos);
else
return MENU_ITEM_NOTFOUND;
}
MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const
{
MenuItemData* pData = pItemList->GetDataFromPos( nPos );
if ( pData )
return pData->eType;
else
return MenuItemType::DONTKNOW;
}
OUString Menu::GetItemIdent(sal_uInt16 nId) const
{
const MenuItemData* pData = pItemList->GetData(nId);
return pData ? pData->sIdent : OUString();
}
void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData(nItemId, nPos);
if (pData && (pData->nBits != nBits))
{
// these two menu item bits are relevant for (accessible) role
const MenuItemBits nRoleMask = MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK;
const bool bRoleBitsChanged = (pData->nBits & nRoleMask) != (nBits & nRoleMask);
pData->nBits = nBits;
// update native menu
if (ImplGetSalMenu())
ImplGetSalMenu()->SetItemBits(nPos, nBits);
if (bRoleBitsChanged)
ImplCallEventListeners(VclEventId::MenuItemRoleChanged, nPos);
}
}
MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const
{
MenuItemBits nBits = MenuItemBits::NONE;
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
nBits = pData->nBits;
return nBits;
}
void Menu::SetUserValue(sal_uInt16 nItemId, void * nUserValue, MenuUserDataReleaseFunction aFunc)
{
MenuItemData* pData = pItemList->GetData(nItemId);
if (pData)
{
if (pData->aUserValueReleaseFunc)
pData->aUserValueReleaseFunc(pData->nUserValue);
pData->aUserValueReleaseFunc = aFunc;
pData->nUserValue = nUserValue;
}
}
void * Menu::GetUserValue( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
return pData ? pData->nUserValue : nullptr;
}
void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
// Item does not exist -> return NULL
if ( !pData )
return ;
// same menu, nothing to do
if ( pData->pSubMenu.get() == pMenu )
return ;
// remove old menu
auto oldSubMenu = pData->pSubMenu;
// data exchange
pData->pSubMenu = pMenu;
// #112023# Make sure pStartedFrom does not point to invalid (old) data
if ( pData->pSubMenu )
pData->pSubMenu->pStartedFrom = nullptr;
// set native submenu
if ( ImplGetSalMenu() && pData->pSalMenuItem )
{
if ( pMenu )
ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos );
else
ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos );
}
oldSubMenu.disposeAndClear();
ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos );
}
PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
return pData->pSubMenu.get();
else
return nullptr;
}
void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( !pData )
return ;
if ( pData->aAccelKey == rKeyCode )
return ;
pData->aAccelKey = rKeyCode;
// update native menu
if ( ImplGetSalMenu() && pData->pSalMenuItem )
ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() );
}
KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
return pData->aAccelKey;
else
return KeyCode();
}
KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const
{
KeyEvent aRet;
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
{
sal_Int32 nPos = pData->aText.indexOf( '~' );
if ( nPos != -1 && nPos < pData->aText.getLength()-1 )
{
sal_uInt16 nCode = 0;
sal_Unicode cAccel = pData->aText[nPos+1];
if ( cAccel >= 'a' && cAccel <= 'z' )
nCode = KEY_A + (cAccel-'a' );
else if ( cAccel >= 'A' && cAccel <= 'Z' )
nCode = KEY_A + (cAccel-'A' );
else if ( cAccel >= '0' && cAccel <= '9' )
nCode = KEY_0 + (cAccel-'0' );
aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) );
}
}
return aRet;
}
void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( !pData || pData->bChecked == bCheck )
return ;
// if radio-check, then uncheck previous
if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) &&
(pData->nBits & MenuItemBits::RADIOCHECK) )
{
MenuItemData* pGroupData;
sal_uInt16 nGroupPos;
sal_uInt16 nItemCount = GetItemCount();
bool bFound = false ;
nGroupPos = nPos;
while ( nGroupPos )
{
pGroupData = pItemList->GetDataFromPos( nGroupPos-1 );
if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
{
if ( IsItemChecked( pGroupData->nId ) )
{
CheckItem( pGroupData->nId, false );
bFound = true ;
break ;
}
}
else
break ;
nGroupPos--;
}
if ( !bFound )
{
nGroupPos = nPos+1;
while ( nGroupPos < nItemCount )
{
pGroupData = pItemList->GetDataFromPos( nGroupPos );
if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
{
if ( IsItemChecked( pGroupData->nId ) )
{
CheckItem( pGroupData->nId, false );
break ;
}
}
else
break ;
nGroupPos++;
}
}
}
pData->bChecked = bCheck;
// update native menu
if ( ImplGetSalMenu() )
ImplGetSalMenu()->CheckItem( nPos, bCheck );
ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos );
}
void Menu::CheckItem( std::u16string_view rIdent , bool bCheck )
{
CheckItem( GetItemId( rIdent ), bCheck );
}
bool Menu::IsItemCheckable(sal_uInt16 nItemId) const
{
size_t nPos;
MenuItemData* pData = pItemList->GetData(nItemId, nPos);
if (!pData)
return false ;
return pData->HasCheck();
}
bool Menu::IsItemChecked( sal_uInt16 nItemId ) const
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( !pData )
return false ;
return pData->bChecked;
}
void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable )
{
size_t nPos;
MenuItemData* pItemData = pItemList->GetData( nItemId, nPos );
if ( !(pItemData && ( pItemData->bEnabled != bEnable )) )
return ;
pItemData->bEnabled = bEnable;
vcl::Window* pWin = GetWindow();
if ( pWin && pWin->IsVisible() )
{
SAL_WARN_IF(!IsMenuBar(), "vcl" , "Menu::EnableItem - Popup visible!" );
tools::Long nX = 0;
size_t nCount = pItemList->size();
for ( size_t n = 0; n < nCount; n++ )
{
MenuItemData* pData = pItemList->GetDataFromPos( n );
if ( n == nPos )
{
pWin->Invalidate( tools::Rectangle( Point( nX, 0 ), Size( pData->aSz.Width(), pData->aSz.Height() ) ) );
break ;
}
nX += pData->aSz.Width();
}
}
// update native menu
if ( ImplGetSalMenu() )
ImplGetSalMenu()->EnableItem( nPos, bEnable );
ImplCallEventListeners( bEnable ? VclEventId::MenuEnable : VclEventId::MenuDisable, nPos );
}
bool Menu::IsItemEnabled( sal_uInt16 nItemId ) const
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( !pData )
return false ;
return pData->bEnabled;
}
void Menu::ShowItem( sal_uInt16 nItemId, bool bVisible )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
SAL_WARN_IF(IsMenuBar() && !bVisible , "vcl" , "Menu::ShowItem - ignored for menu bar entries!" );
if (IsMenuBar() || !pData || (pData->bVisible == bVisible))
return ;
vcl::Window* pWin = GetWindow();
if ( pWin && pWin->IsVisible() )
{
SAL_WARN( "vcl" , "Menu::ShowItem - ignored for visible popups!" );
return ;
}
pData->bVisible = bVisible;
// update native menu
if ( ImplGetSalMenu() )
ImplGetSalMenu()->ShowItem( nPos, bVisible );
}
void Menu::SetItemText( sal_uInt16 nItemId, const OUString& rStr )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( !pData )
return ;
if ( rStr == pData->aText )
return ;
pData->aText = rStr;
// Clear layout for aText.
pData->aTextGlyphs.Invalidate();
ImplSetMenuItemData( pData );
// update native menu
if ( ImplGetSalMenu() && pData->pSalMenuItem )
ImplGetSalMenu()->SetItemText( nPos, pData->pSalMenuItem.get(), rStr );
vcl::Window* pWin = GetWindow();
mpLayoutData.reset();
if (pWin && IsMenuBar())
{
ImplCalcSize( pWin );
if ( pWin->IsVisible() )
pWin->Invalidate();
}
ImplCallEventListeners( VclEventId::MenuItemTextChanged, nPos );
}
OUString Menu::GetItemText( sal_uInt16 nItemId ) const
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( pData )
return pData->aText;
return OUString();
}
void Menu::SetItemImage( sal_uInt16 nItemId, const Image& rImage )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( !pData )
return ;
pData->aImage = rImage;
ImplSetMenuItemData( pData );
// update native menu
if ( ImplGetSalMenu() && pData->pSalMenuItem )
ImplGetSalMenu()->SetItemImage( nPos, pData->pSalMenuItem.get(), rImage );
}
Image Menu::GetItemImage( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
return pData->aImage;
else
return Image();
}
void Menu::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
{
size_t nPos;
MenuItemData* pData = pItemList->GetData( nItemId, nPos );
if ( pData )
pData->aCommandStr = rCommand;
}
OUString Menu::GetItemCommand( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if (pData)
return pData->aCommandStr;
return OUString();
}
void Menu::SetHelpCommand( sal_uInt16 nItemId, const OUString& rStr )
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
pData->aHelpCommandStr = rStr;
}
OUString Menu::GetHelpCommand( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
return pData->aHelpCommandStr;
return OUString();
}
void Menu::SetHelpText( sal_uInt16 nItemId, const OUString& rStr )
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
pData->aHelpText = rStr;
}
OUString Menu::ImplGetHelpText( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if (!pData)
return OUString();
if ( pData->aHelpText.isEmpty() &&
(( !pData->aHelpId.isEmpty() ) || ( !pData->aCommandStr.isEmpty() )))
{
Help* pHelp = Application::GetHelp();
if ( pHelp )
{
if (!pData->aCommandStr.isEmpty())
pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr );
if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty())
pData->aHelpText = pHelp->GetHelpText( pData->aHelpId );
}
}
//Fallback to Menu::GetAccessibleDescription without reentry to GetHelpText()
if (pData->aHelpText.isEmpty())
return pData->aAccessibleDescription;
return pData->aHelpText;
}
OUString Menu::GetHelpText( sal_uInt16 nItemId ) const
{
return ImplGetHelpText( nItemId );
}
void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr )
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
{
pData->aTipHelpText = rStr;
if (ImplGetSalMenu() && pData->pSalMenuItem)
ImplGetSalMenu()->SetItemTooltip(pData->pSalMenuItem.get(), rStr);
}
}
OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
return pData->aTipHelpText;
return OUString();
}
void Menu::SetHelpId( sal_uInt16 nItemId, const OUString& rHelpId )
{
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
pData->aHelpId = rHelpId;
}
OUString Menu::GetHelpId( sal_uInt16 nItemId ) const
{
OUString aRet;
MenuItemData* pData = pItemList->GetData( nItemId );
if ( pData )
{
if ( !pData->aHelpId.isEmpty() )
aRet = pData->aHelpId;
else
aRet = pData->aCommandStr;
}
return aRet;
}
Menu& Menu::operator =( const Menu& rMenu )
{
if (this == &rMenu)
return *this ;
// clean up
Clear();
// copy items
sal_uInt16 nCount = rMenu.GetItemCount();
for ( sal_uInt16 i = 0; i < nCount; i++ )
ImplCopyItem( this , rMenu, i, MENU_APPEND );
aActivateHdl = rMenu.aActivateHdl;
aDeactivateHdl = rMenu.aDeactivateHdl;
aSelectHdl = rMenu.aSelectHdl;
aTitleText = rMenu.aTitleText;
nTitleHeight = rMenu.nTitleHeight;
return *this ;
}
// Returns true if the item is completely hidden on the GUI and shouldn't
// be possible to interact with
bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const
{
MenuItemData* pData = pItemList->GetDataFromPos(nPos);
if (pData)
{
MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 );
if (pPreviousData && pPreviousData->bHiddenOnGUI)
{
return true ;
}
}
return false ;
}
bool Menu::ImplIsVisible( sal_uInt16 nPos ) const
{
bool bVisible = true ;
MenuItemData* pData = pItemList->GetDataFromPos( nPos );
// check general visibility first
if ( pData && !pData->bVisible )
bVisible = false ;
if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR )
{
if ( nPos == 0 ) // no separator should be shown at the very beginning
bVisible = false ;
else
{
// always avoid adjacent separators
size_t nCount = pItemList->size();
size_t n;
MenuItemData* pNextData = nullptr;
// search next visible item
for ( n = nPos + 1; n < nCount; n++ )
{
pNextData = pItemList->GetDataFromPos( n );
if ( pNextData && pNextData->bVisible )
{
if ( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) )
break ;
}
}
if ( n == nCount ) // no next visible item
bVisible = false ;
// check for separator
if ( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR )
bVisible = false ;
if ( bVisible )
{
for ( n = nPos; n > 0; n-- )
{
pNextData = pItemList->GetDataFromPos( n-1 );
if ( pNextData && pNextData->bVisible )
{
if ( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) )
break ;
}
}
if ( n == 0 ) // no previous visible item
bVisible = false ;
}
}
}
// not allowed for menubar, as I do not know
// whether a menu-entry will disappear or will appear
if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) &&
!(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries))
{
if ( !pData ) // e.g. nPos == ITEMPOS_INVALID
bVisible = false ;
else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above
{
// tdf#86850 Always display clipboard functions
if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" ||
pData->sIdent == ".uno:Cut" || pData->sIdent == ".uno:Copy" || pData->sIdent == ".uno:Paste" )
bVisible = true ;
else
// bVisible = pData->bEnabled && ( !pData->pSubMenu || pData->pSubMenu->HasValidEntries( true ) );
bVisible = pData->bEnabled; // do not check submenus as they might be filled at Activate().
}
}
return bVisible;
}
bool Menu::IsItemPosVisible( sal_uInt16 nItemPos ) const
{
return IsMenuVisible() && ImplIsVisible( nItemPos );
}
bool Menu::IsMenuVisible() const
{
return m_pWindow && m_pWindow->IsReallyVisible();
}
bool Menu::ImplIsSelectable( sal_uInt16 nPos ) const
{
bool bSelectable = true ;
MenuItemData* pData = pItemList->GetDataFromPos( nPos );
// check general visibility first
if ( pData && ( pData->nBits & MenuItemBits::NOSELECT ) )
bSelectable = false ;
return bSelectable;
}
css::uno::Reference<css::accessibility::XAccessible> Menu::CreateAccessible()
{
rtl::Reference<OAccessibleMenuBaseComponent> xAccessible;
if (IsMenuBar())
xAccessible = new VCLXAccessibleMenuBar(this );
else
xAccessible = new VCLXAccessiblePopupMenu(this );
xAccessible->SetStates();
return xAccessible;
}
css::uno::Reference<css::accessibility::XAccessible> Menu::GetAccessible()
{
// Since PopupMenu are sometimes shared by different instances of MenuBar, the mxAccessible member gets
// overwritten and may contain a disposed object when the initial menubar gets set again. So use the
// mxAccessible member only for sub menus.
if (pStartedFrom && pStartedFrom != this )
{
for ( sal_uInt16 i = 0, nCount = pStartedFrom->GetItemCount(); i < nCount; ++i )
{
sal_uInt16 nItemId = pStartedFrom->GetItemId( i );
if ( static_cast < Menu* >( pStartedFrom->GetPopupMenu( nItemId ) ) == this )
{
css::uno::Reference<css::accessibility::XAccessible> xParent = pStartedFrom->GetAccessible();
if ( xParent.is() )
{
css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( xParent->getAccessibleContext() );
if (xParentContext.is())
return xParentContext->getAccessibleChild( i );
}
}
}
}
else if ( !mxAccessible.is() )
mxAccessible = CreateAccessible();
return mxAccessible;
}
void Menu::SetAccessible(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible )
{
mxAccessible = rxAccessible;
}
Size Menu::ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, tools::Long & rCheckHeight, tools::Long & rRadioHeight ) const
{
tools::Long nCheckWidth = 0, nRadioWidth = 0;
rCheckHeight = rRadioHeight = 0;
if (!IsMenuBar())
{
ImplControlValue aVal;
tools::Rectangle aNativeBounds;
tools::Rectangle aNativeContent;
tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark))
{
if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark,
aCtrlRegion, ControlState::ENABLED, aVal,
aNativeBounds, aNativeContent))
{
rCheckHeight = aNativeBounds.GetHeight() - 1;
nCheckWidth = aNativeContent.GetWidth() - 1;
}
}
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
{
if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
aCtrlRegion, ControlState::ENABLED, aVal,
aNativeBounds, aNativeContent))
{
rRadioHeight = aNativeBounds.GetHeight() - 1;
nRadioWidth = aNativeContent.GetWidth() - 1;
}
}
}
return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight));
}
bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, tools::Long & rArrowSpacing)
{
ImplControlValue aVal;
tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
{
tools::Rectangle aNativeContent;
tools::Rectangle aNativeBounds;
if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::SubmenuArrow,
aCtrlRegion, ControlState::ENABLED,
aVal, aNativeBounds, aNativeContent))
{
Size aSize(aNativeContent.GetWidth(), aNativeContent.GetHeight());
rArrowSize = aSize;
rArrowSpacing = aNativeBounds.GetWidth() - aNativeContent.GetWidth();
return true ;
}
}
return false ;
}
void Menu::ImplAddDel( ImplMenuDelData& rDel )
{
SAL_WARN_IF( rDel.mpMenu, "vcl" , "Menu::ImplAddDel(): cannot add ImplMenuDelData twice !" );
if ( !rDel.mpMenu )
{
rDel.mpMenu = this ;
rDel.mpNext = mpFirstDel;
mpFirstDel = &rDel;
}
}
void Menu::ImplRemoveDel( ImplMenuDelData& rDel )
{
rDel.mpMenu = nullptr;
if ( mpFirstDel == &rDel )
{
mpFirstDel = rDel.mpNext;
}
else
{
ImplMenuDelData* pData = mpFirstDel;
while ( pData && (pData->mpNext != &rDel) )
pData = pData->mpNext;
SAL_WARN_IF( !pData, "vcl" , "Menu::ImplRemoveDel(): ImplMenuDelData not registered !" );
if ( pData )
pData->mpNext = rDel.mpNext;
}
}
Size Menu::ImplCalcSize( vcl::Window* pWin )
{
// | Check/Radio/Image| Text| Accel/Popup|
// for symbols: nFontHeight x nFontHeight
tools::Long nFontHeight = pWin->GetTextHeight();
tools::Long nExtra = nFontHeight/4;
tools::Long nMinMenuItemHeight = nFontHeight;
tools::Long nCheckHeight = 0, nRadioHeight = 0;
Size aMarkSize = ImplGetNativeCheckAndRadioSize(*pWin->GetOutDev(), nCheckHeight, nRadioHeight);
if ( aMarkSize.Height() > nMinMenuItemHeight )
nMinMenuItemHeight = aMarkSize.Height();
tools::Long aMaxImgWidth = 0;
const StyleSettings& rSettings = pWin->GetSettings().GetStyleSettings();
if ( rSettings.GetUseImagesInMenus() )
{
if ( 16 > nMinMenuItemHeight )
nMinMenuItemHeight = 16;
for ( size_t i = pItemList->size(); i; )
{
MenuItemData* pData = pItemList->GetDataFromPos( --i );
if ( ImplIsVisible( i )
&& ( ( pData->eType == MenuItemType::IMAGE )
|| ( pData->eType == MenuItemType::STRINGIMAGE )
)
)
{
Size aImgSz = pData->aImage.GetSizePixel();
if ( aImgSz.Width() > aMaxImgWidth )
aMaxImgWidth = aImgSz.Width();
if ( aImgSz.Height() > nMinMenuItemHeight )
nMinMenuItemHeight = aImgSz.Height();
break ;
}
}
}
Size aSz;
tools::Long nMaxWidth = 0;
for ( size_t n = pItemList->size(); n; )
{
MenuItemData* pData = pItemList->GetDataFromPos( --n );
pData->aSz.setHeight( 0 );
pData->aSz.setWidth( 0 );
if ( ImplIsVisible( n ) )
{
tools::Long nWidth = 0;
// Separator
if (!IsMenuBar()&& (pData->eType == MenuItemType::SEPARATOR))
{
pData->aSz.setHeight( 4 );
}
// Image:
if (!IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
{
tools::Long aImgHeight = pData->aImage.GetSizePixel().Height();
aImgHeight += 4; // add a border for native marks
if (aImgHeight > pData->aSz.Height())
pData->aSz.setHeight(aImgHeight);
}
// Check Buttons:
if (!IsMenuBar() && pData->HasCheck())
{
// checks / images take the same place
if ( ( pData->eType != MenuItemType::IMAGE ) && ( pData->eType != MenuItemType::STRINGIMAGE ) )
{
nWidth += aMarkSize.Width() + nExtra * 2;
if (aMarkSize.Height() > pData->aSz.Height())
pData->aSz.setHeight(aMarkSize.Height());
}
}
// Text:
if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) )
{
const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin->GetOutDev());
tools::Long nTextWidth = pWin->GetOutDev()->GetCtrlTextWidth(pData->aText, pGlyphs);
tools::Long nTextHeight = pWin->GetTextHeight() + EXTRAITEMHEIGHT;
if (IsMenuBar())
{
if ( nTextHeight > pData->aSz.Height() )
pData->aSz.setHeight( nTextHeight );
pData->aSz.setWidth( nTextWidth + 4*nExtra );
aSz.AdjustWidth(pData->aSz.Width() );
}
else
pData->aSz.setHeight( std::max( std::max( nTextHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
nWidth += nTextWidth;
}
// Accel
if (!IsMenuBar()&& pData->aAccelKey.GetCode() && !ImplAccelDisabled())
{
OUString aName = pData->aAccelKey.GetName();
tools::Long nAccWidth = pWin->GetTextWidth( aName );
nAccWidth += nExtra;
nWidth += nAccWidth;
}
// SubMenu?
if (!IsMenuBar() && pData->pSubMenu)
{
if ( nFontHeight > nWidth )
nWidth += nFontHeight;
pData->aSz.setHeight( std::max( std::max( nFontHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
}
if (!IsMenuBar())
aSz.AdjustHeight(pData->aSz.Height() );
if ( nWidth > nMaxWidth )
nMaxWidth = nWidth;
}
}
// Additional space for title
nTitleHeight = 0;
if (!IsMenuBar() && aTitleText.getLength() > 0) {
// Set expected font
pWin->GetOutDev()->Push(PushFlags::FONT);
vcl::Font aFont = pWin->GetFont();
aFont.SetWeight(WEIGHT_BOLD);
pWin->SetFont(aFont);
// Compute text bounding box
tools::Rectangle aTextBoundRect;
pWin->GetOutDev()->GetTextBoundRect(aTextBoundRect, aTitleText);
// Vertically, one height of char + extra space for decoration
nTitleHeight = aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ;
aSz.AdjustHeight(nTitleHeight );
tools::Long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE;
pWin->GetOutDev()->Pop();
if ( nWidth > nMaxWidth )
nMaxWidth = nWidth;
}
if (!IsMenuBar())
{
// popup menus should not be wider than half the screen
// except on rather small screens
// TODO: move GetScreenNumber from SystemWindow to Window ?
// currently we rely on internal privileges
unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->GetUnmirroredGeometry().screen();
tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) );
tools::Long nScreenWidth = aDispRect.GetWidth() >= 800 ? aDispRect.GetWidth() : 800;
if ( nMaxWidth > nScreenWidth/2 )
nMaxWidth = nScreenWidth/2;
sal_uInt16 gfxExtra = static_cast <sal_uInt16>(std::max( nExtra, tools::Long (7) )); // #107710# increase space between checkmarks/images/text
nImgOrChkPos = static_cast <sal_uInt16>(nExtra);
tools::Long nImgOrChkWidth = 0;
if ( aMarkSize.Height() > 0 ) // NWF case
nImgOrChkWidth = aMarkSize.Height() + nExtra;
else // non NWF case
nImgOrChkWidth = nFontHeight/2 + gfxExtra;
nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgWidth + gfxExtra );
nTextPos = static_cast <sal_uInt16>(nImgOrChkPos + nImgOrChkWidth);
nTextPos = nTextPos + gfxExtra;
aSz.setWidth( nTextPos + nMaxWidth + nExtra );
aSz.AdjustWidth(4*nExtra ); // a _little_ more ...
aSz.AdjustWidth(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderX );
aSz.AdjustHeight(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
}
else
{
nTextPos = static_cast <sal_uInt16>(2*nExtra);
aSz.setHeight( nFontHeight+6 );
// get menubar height from native methods if supported
if (m_pWindow->IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
{
ImplControlValue aVal;
tools::Rectangle aNativeBounds;
tools::Rectangle aNativeContent;
Point tmp( 0, 0 );
tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) );
if (m_pWindow->GetNativeControlRegion(ControlType::Menubar,
ControlPart::Entire,
aCtrlRegion,
ControlState::ENABLED,
aVal,
aNativeBounds,
aNativeContent)
)
{
int nNativeHeight = aNativeBounds.GetHeight();
if ( nNativeHeight > aSz.Height() )
aSz.setHeight( nNativeHeight );
}
}
// account for the size of the close button, which actually is a toolbox
// due to NWF this is variable
tools::Long nCloseButtonHeight = static_cast <MenuBarWindow*>(m_pWindow.get())->MinCloseButtonSize().Height();
if (aSz.Height() < nCloseButtonHeight)
aSz.setHeight( nCloseButtonHeight );
}
return aSz;
}
static void ImplPaintCheckBackground(vcl::RenderContext & rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight)
{
bool bNativeOk = false ;
if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button))
{
ImplControlValue aControlValue;
aControlValue.setTristateVal(ButtonValue::On);
tools::Rectangle r = i_rRect;
r.AdjustBottom(1);
bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
r,
ControlState::PRESSED | ControlState::ENABLED,
aControlValue,
OUString());
}
if (!bNativeOk)
{
const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
Color aColor( i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor() );
RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true , false , nullptr, 2, &aColor);
}
}
static OUString getShortenedString( const OUString& i_rLong, vcl::RenderContext const & rRenderContext, tools::Long i_nMaxWidth )
{
sal_Int32 nPos = -1;
OUString aNonMnem(removeMnemonicFromString(i_rLong, nPos));
aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis);
// re-insert mnemonic
if (nPos != -1)
{
if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos])
aNonMnem = OUString::Concat(aNonMnem.subView(0, nPos)) + "~" + aNonMnem.subView(nPos);
}
return aNonMnem;
}
void Menu::ImplPaintMenuTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) const
{
// Save previous graphical settings, set new one
rRenderContext.Push(PushFlags::FONT | PushFlags::FILLCOLOR);
Wallpaper aOldBackground = rRenderContext.GetBackground();
Color aBackgroundColor = rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor();
rRenderContext.SetBackground(Wallpaper(aBackgroundColor));
rRenderContext.SetFillColor(aBackgroundColor);
vcl::Font aFont = rRenderContext.GetFont();
aFont.SetWeight(WEIGHT_BOLD);
rRenderContext.SetFont(aFont);
// Draw background rectangle
tools::Rectangle aBgRect(rRect);
int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
aBgRect.Move(SPACE_AROUND_TITLE, SPACE_AROUND_TITLE);
aBgRect.setWidth(aBgRect.getOpenWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX);
aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE);
rRenderContext.DrawRect(aBgRect);
// Draw the text centered
Point aTextTopLeft(aBgRect.TopLeft());
tools::Rectangle aTextBoundRect;
rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText );
aTextTopLeft.AdjustX((aBgRect.getOpenWidth() - aTextBoundRect.GetSize().Width()) / 2 );
aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2
- aTextBoundRect.Top() );
rRenderContext.DrawText(aTextTopLeft, aTitleText, 0, aTitleText.getLength());
// Restore
rRenderContext.Pop();
rRenderContext.SetBackground(aOldBackground);
}
void Menu::ImplPaint(vcl::RenderContext& rRenderContext, Size const & rSize,
sal_uInt16 nBorder, tools::Long nStartY, MenuItemData const * pThisItemOnly,
bool bHighlighted, bool bLayout, bool bRollover) const
{
// for symbols: nFontHeight x nFontHeight
tools::Long nFontHeight = rRenderContext.GetTextHeight();
tools::Long nExtra = nFontHeight / 4;
tools::Long nCheckHeight = 0, nRadioHeight = 0;
ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight);
DecorationView aDecoView(&rRenderContext);
const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
Point aTopLeft, aTmpPos;
int nOuterSpaceX = 0;
if (!IsMenuBar())
{
nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
aTopLeft.AdjustX(nOuterSpaceX );
aTopLeft.AdjustY(ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
}
// for the computations, use size of the underlying window, not of RenderContext
Size aOutSz(rSize);
size_t nCount = pItemList->size();
if (bLayout)
mpLayoutData->m_aVisibleItemBoundRects.clear();
// Paint title
if (!pThisItemOnly && !IsMenuBar() && nTitleHeight > 0)
ImplPaintMenuTitle(rRenderContext, tools::Rectangle(aTopLeft, aOutSz));
bool bHiddenItems = false ; // are any items on the GUI hidden
for (size_t n = 0; n < nCount; n++)
{
MenuItemData* pData = pItemList->GetDataFromPos( n );
if (ImplIsVisible(n) && (!pThisItemOnly || (pData == pThisItemOnly)))
{
if (pThisItemOnly)
{
if (IsMenuBar())
{
if (!ImplGetSVData()->maNWFData.mbRolloverMenubar)
{
if (bRollover)
rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
else if (bHighlighted)
rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
}
else
{
if (bHighlighted)
rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
else if (bRollover)
rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
}
if (!bRollover && !bHighlighted)
rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
}
else if (bHighlighted)
rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor());
}
Point aPos(aTopLeft);
aPos.AdjustY(nBorder );
aPos.AdjustY(nStartY );
if (aPos.Y() >= 0)
{
tools::Long nTextOffsetY = (pData->aSz.Height() - nFontHeight) / 2;
if (IsMenuBar())
nTextOffsetY += (aOutSz.Height()-pData->aSz.Height()) / 2;
DrawTextFlags nTextStyle = DrawTextFlags::NONE;
DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
DrawImageFlags nImageStyle = DrawImageFlags::NONE;
// submenus without items are not disabled when no items are
// contained. The application itself should check for this!
// Otherwise it could happen entries are disabled due to
// asynchronous loading
if (!pData->bEnabled || !m_pWindow->IsEnabled())
{
nTextStyle |= DrawTextFlags::Disable;
nSymbolStyle |= DrawSymbolFlags::Disable;
nImageStyle |= DrawImageFlags::Disable;
}
// Separator
if (!bLayout && !IsMenuBar() && (pData->eType == MenuItemType::SEPARATOR))
{
bool bNativeOk = false ;
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
{
ControlState nState = ControlState::NONE;
if (pData->bEnabled && m_pWindow->IsEnabled())
nState |= ControlState::ENABLED;
if (bHighlighted)
nState |= ControlState::SELECTED;
Size aSz(pData->aSz);
aSz.setWidth( aOutSz.Width() - 2*nOuterSpaceX );
tools::Rectangle aItemRect(aPos, aSz);
MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
aItemRect, nState, aVal, OUString());
}
if (!bNativeOk)
{
aTmpPos.setY( aPos.Y() + ((pData->aSz.Height() - 2) / 2) );
aTmpPos.setX( aPos.X() + 2 + nOuterSpaceX );
rRenderContext.SetLineColor(rSettings.GetShadowColor());
rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
aTmpPos.AdjustY( 1 );
rRenderContext.SetLineColor(rSettings.GetLightColor());
rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
rRenderContext.SetLineColor();
}
}
tools::Rectangle aOuterCheckRect(Point(aPos.X()+nImgOrChkPos, aPos.Y()),
Size(pData->aSz.Height(), pData->aSz.Height()));
// CheckMark
if (!bLayout && !IsMenuBar() && pData->HasCheck())
{
// draw selection transparent marker if checked
// onto that either a checkmark or the item image
// will be painted
// however do not do this if native checks will be painted since
// the selection color too often does not fit the theme's check and/or radio
if ( (pData->eType != MenuItemType::IMAGE) && (pData->eType != MenuItemType::STRINGIMAGE))
{
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup,
(pData->nBits & MenuItemBits::RADIOCHECK)
? ControlPart::MenuItemCheckMark
: ControlPart::MenuItemRadioMark))
{
ControlPart nPart = ((pData->nBits & MenuItemBits::RADIOCHECK)
? ControlPart::MenuItemRadioMark
: ControlPart::MenuItemCheckMark);
ControlState nState = ControlState::NONE;
if (pData->bChecked)
nState |= ControlState::PRESSED;
if (pData->bEnabled && m_pWindow->IsEnabled())
nState |= ControlState::ENABLED;
if (bHighlighted)
nState |= ControlState::SELECTED;
tools::Long nCtrlHeight = (pData->nBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 );
aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 );
tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight));
Size aSz(pData->aSz);
aSz.setWidth( aOutSz.Width() - 2 * nOuterSpaceX );
tools::Rectangle aItemRect(aPos, aSz);
MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect,
nState, aVal, OUString());
}
else if (pData->bChecked) // by default do nothing for unchecked items
{
ImplPaintCheckBackground(rRenderContext, *m_pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
SymbolType eSymbol;
Size aSymbolSize;
if (pData->nBits & MenuItemBits::RADIOCHECK)
{
eSymbol = SymbolType::RADIOCHECKMARK;
aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2);
}
else
{
eSymbol = SymbolType::CHECKMARK;
aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2);
}
aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width()) / 2 );
aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height()) / 2 );
tools::Rectangle aRect(aTmpPos, aSymbolSize);
aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetTextColor(), nSymbolStyle);
}
}
}
// Image:
if (!bLayout && !IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
{
// Don't render an image for a check thing
if (pData->bChecked)
ImplPaintCheckBackground(rRenderContext, *m_pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
Image aImage = pData->aImage;
aTmpPos = aOuterCheckRect.TopLeft();
aTmpPos.AdjustX((aOuterCheckRect.GetWidth() - aImage.GetSizePixel().Width()) / 2 );
aTmpPos.AdjustY((aOuterCheckRect.GetHeight() - aImage.GetSizePixel().Height()) / 2 );
rRenderContext.DrawImage(aTmpPos, aImage, nImageStyle);
}
// Text:
if ((pData->eType == MenuItemType::STRING ) || (pData->eType == MenuItemType::STRINGIMAGE))
{
aTmpPos.setX( aPos.X() + nTextPos );
aTmpPos.setY( aPos.Y() );
aTmpPos.AdjustY(nTextOffsetY );
DrawTextFlags nStyle = nTextStyle;
const Menu *pMenu = this ;
while (!pMenu->IsMenuBar() && pMenu->pStartedFrom)
pMenu = pMenu->pStartedFrom;
if (!pMenu->IsMenuBar() || !static_cast <MenuBarWindow*>(pMenu->m_pWindow.get())->GetMBWHideAccel())
nStyle |= DrawTextFlags::Mnemonic;
if (pData->bIsTemporary)
nStyle |= DrawTextFlags::Disable;
std::vector< tools::Rectangle >* pVector = bLayout ? &mpLayoutData->m_aUnicodeBoundRects : nullptr;
OUString* pDisplayText = bLayout ? &mpLayoutData->m_aDisplayText : nullptr;
if (bLayout)
{
mpLayoutData->m_aLineIndices.push_back(mpLayoutData->m_aDisplayText.getLength());
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=94 H=93 G=93
¤ Dauer der Verarbeitung: 0.26 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland