diff options
Diffstat (limited to 'vcl/source/window/menu.cxx')
-rw-r--r-- | vcl/source/window/menu.cxx | 3143 |
1 files changed, 3143 insertions, 0 deletions
diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx new file mode 100644 index 0000000000..82d630742a --- /dev/null +++ b/vcl/source/window/menu.cxx @@ -0,0 +1,3143 @@ +/* -*- 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 <comphelper/diagnose_ex.hxx> +#include <sal/log.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; + +#define EXTRAITEMHEIGHT 4 +#define 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( "Menu", "SuppressAccelerators" ); + 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), + 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 ); + + // at the window free the reference to the accessible component + // and make sure the MenuFloatingWindow knows about our destruction + if ( pWindow ) + { + MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get()); + if( pFloat->pMenu.get() == this ) + pFloat->pMenu.clear(); + pWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() ); + } + + // dispose accessible components + if ( mxAccessible.is() ) + { + css::uno::Reference< css::lang::XComponent> xComponent( mxAccessible, css::uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + + 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.clear(); + pWindow.clear(); + 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 = ImplGetWindow(); + 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 = ImplGetWindow(); + 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 = ImplGetWindow(); + 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 = ImplGetWindow(); + 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 = ImplGetWindow(); + 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, static_cast<weld::Widget*>(nullptr) ); + if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty()) + pData->aHelpText = pHelp->GetHelpText( pData->aHelpId, static_cast<weld::Widget*>(nullptr) ); + } + } + + //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; +} + +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 pWindow && 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::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() ) + { + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + if ( pWrapper ) + mxAccessible = pWrapper->CreateAccessible(this, IsMenuBar()); + } + + 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->maGeometry.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( 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( 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*>(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]) + { + OUString aTmp = OUString::Concat(aNonMnem.subView(0, nPos)) + "~" + aNonMnem.subView(nPos); + aNonMnem = aTmp; + } + } + 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 || !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 && 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 && 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, *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, *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 | 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()); + mpLayoutData->m_aLineItemIds.push_back(pData->nId); + } + // #i47946# with NWF painted menus the background is transparent + // since DrawCtrlText can depend on the background (e.g. for + // DrawTextFlags::Disable), temporarily set a background which + // hopefully matches the NWF background since it is read + // from the system style settings + bool bSetTmpBackground = !rRenderContext.IsBackground() + && rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire); + if (bSetTmpBackground) + { + Color aBg = IsMenuBar() ? rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor() + : rRenderContext.GetSettings().GetStyleSettings().GetMenuColor(); + rRenderContext.SetBackground(Wallpaper(aBg)); + } + // how much space is there for the text? + tools::Long nMaxItemTextWidth = aOutSz.Width() - aTmpPos.X() - nExtra - nOuterSpaceX; + if (!IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled()) + { + OUString aAccText = pData->aAccelKey.GetName(); + nMaxItemTextWidth -= rRenderContext.GetTextWidth(aAccText) + 3 * nExtra; + } + if (!IsMenuBar() && pData->pSubMenu) + { + nMaxItemTextWidth -= nFontHeight - nExtra; + } + + OUString aItemText(pData->aText); + pData->bHiddenOnGUI = false; + + if (IsMenuBar()) // In case of menubar if we are out of bounds we shouldn't paint the item + { + if (nMaxItemTextWidth < rRenderContext.GetTextWidth(aItemText)) + { + aItemText = ""; + pData->bHiddenOnGUI = true; + bHiddenItems = true; + } + } + else + { + aItemText = getShortenedString(aItemText, rRenderContext, nMaxItemTextWidth); + pData->bHiddenOnGUI = false; + } + + const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(&rRenderContext); + if (aItemText != pData->aText) + // Can't use pre-computed glyphs, item text was + // changed. + pGlyphs = nullptr; + rRenderContext.DrawCtrlText(aTmpPos, aItemText, 0, aItemText.getLength(), + nStyle, pVector, pDisplayText, pGlyphs); + if (bSetTmpBackground) + rRenderContext.SetBackground(); + } + + // Accel + if (!bLayout && !IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled()) + { + OUString aAccText = pData->aAccelKey.GetName(); + aTmpPos.setX( aOutSz.Width() - rRenderContext.GetTextWidth(aAccText) ); + aTmpPos.AdjustX( -(4 * nExtra) ); + + aTmpPos.AdjustX( -nOuterSpaceX ); + aTmpPos.setY( aPos.Y() ); + aTmpPos.AdjustY(nTextOffsetY ); + rRenderContext.DrawCtrlText(aTmpPos, aAccText, 0, aAccText.getLength(), nTextStyle); + } + + // SubMenu? + if (!bLayout && !IsMenuBar() && pData->pSubMenu) + { + bool bNativeOk = false; + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow)) + { + ControlState nState = ControlState::NONE; + Size aTmpSz(0, 0); + tools::Long aSpacing = 0; + + if (!ImplGetNativeSubmenuArrowSize(rRenderContext, aTmpSz, aSpacing)) + { + aTmpSz = Size(nFontHeight, nFontHeight); + aSpacing = nOuterSpaceX; + } + + if (pData->bEnabled && pWindow->IsEnabled()) + nState |= ControlState::ENABLED; + if (bHighlighted) + nState |= ControlState::SELECTED; + + aTmpPos.setX( aOutSz.Width() - aTmpSz.Width() - aSpacing - nOuterSpaceX ); + aTmpPos.setY( aPos.Y() + ( pData->aSz.Height() - aTmpSz.Height() ) / 2 ); + aTmpPos.AdjustY(nExtra / 2 ); + + tools::Rectangle aItemRect(aTmpPos, aTmpSz); + MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect); + bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::SubmenuArrow, + aItemRect, nState, aVal, OUString()); + } + if (!bNativeOk) + { + aTmpPos.setX( aOutSz.Width() - nFontHeight + nExtra - nOuterSpaceX ); + aTmpPos.setY( aPos.Y() ); + aTmpPos.AdjustY(nExtra/2 ); + aTmpPos.AdjustY((pData->aSz.Height() / 2) - (nFontHeight / 4) ); + if (pData->nBits & MenuItemBits::POPUPSELECT) + { + rRenderContext.SetTextColor(rSettings.GetMenuTextColor()); + Point aTmpPos2(aPos); + aTmpPos2.setX( aOutSz.Width() - nFontHeight - nFontHeight/4 ); + aDecoView.DrawFrame(tools::Rectangle(aTmpPos2, Size(nFontHeight + nFontHeight / 4, + pData->aSz.Height())), + DrawFrameStyle::Group); + } + aDecoView.DrawSymbol(tools::Rectangle(aTmpPos, Size(nFontHeight / 2, nFontHeight / 2)), + SymbolType::SPIN_RIGHT, rRenderContext.GetTextColor(), nSymbolStyle); + } + } + + if (pThisItemOnly && bHighlighted) + { + // This restores the normal menu or menu bar text + // color for when it is no longer highlighted. + if (IsMenuBar()) + rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor()); + else + rRenderContext.SetTextColor(rSettings.GetMenuTextColor()); + } + } + if( bLayout ) + { + if (!IsMenuBar()) + mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, Size(aOutSz.Width(), pData->aSz.Height())); + else + mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, pData->aSz); + } + } + + if (!IsMenuBar()) + aTopLeft.AdjustY(pData->aSz.Height() ); + else + aTopLeft.AdjustX(pData->aSz.Width() ); + } + + // draw "more" (">>") indicator if some items have been hidden as they go out of visible area + if (bHiddenItems) + { + sal_Int32 nSize = nFontHeight; + tools::Rectangle aRectangle(Point(aOutSz.Width() - nSize, (aOutSz.Height() / 2) - (nSize / 2)), Size(nSize, nSize)); + lclDrawMoreIndicator(rRenderContext, aRectangle); + } +} + +Menu* Menu::ImplGetStartMenu() +{ + Menu* pStart = this; + while ( pStart && pStart->pStartedFrom && ( pStart->pStartedFrom != pStart ) ) + pStart = pStart->pStartedFrom; + return pStart; +} + +void Menu::ImplCallHighlight(sal_uInt16 nItem) +{ + ImplMenuDelData aDelData( this ); + + nSelectedId = 0; + sSelectedIdent.clear(); + MenuItemData* pData = pItemList->GetDataFromPos(nItem); + if (pData) + { + nSelectedId = pData->nId; + sSelectedIdent = pData->sIdent; + } + ImplCallEventListeners( VclEventId::MenuHighlight, GetItemPos( GetCurItemId() ) ); + + if( !aDelData.isDeleted() ) + { + nSelectedId = 0; + sSelectedIdent.clear(); + } +} + +IMPL_LINK_NOARG(Menu, ImplCallSelect, void*, void) +{ + nEventId = nullptr; + Select(); +} + +Menu* Menu::ImplFindSelectMenu() +{ + Menu* pSelMenu = nEventId ? this : nullptr; + + for ( size_t n = GetItemList()->size(); n && !pSelMenu; ) + { + MenuItemData* pData = GetItemList()->GetDataFromPos( --n ); + + if ( pData->pSubMenu ) + pSelMenu = pData->pSubMenu->ImplFindSelectMenu(); + } + + return pSelMenu; +} + +Menu* Menu::ImplFindMenu( sal_uInt16 nItemId ) +{ + Menu* pSelMenu = nullptr; + + for ( size_t n = GetItemList()->size(); n && !pSelMenu; ) + { + MenuItemData* pData = GetItemList()->GetDataFromPos( --n ); + + if( pData->nId == nItemId ) + pSelMenu = this; + else if ( pData->pSubMenu ) + pSelMenu = pData->pSubMenu->ImplFindMenu( nItemId ); + } + + return pSelMenu; +} + +void Menu::RemoveDisabledEntries( bool bRemoveEmptyPopups ) +{ + for ( sal_uInt16 n = 0; n < GetItemCount(); n++ ) + { + bool bRemove = false; + MenuItemData* pItem = pItemList->GetDataFromPos( n ); + if ( pItem->eType == MenuItemType::SEPARATOR ) + { + if ( !n || ( GetItemType( n-1 ) == MenuItemType::SEPARATOR ) ) + bRemove = true; + } + else + bRemove = !pItem->bEnabled; + + if ( pItem->pSubMenu ) + { + pItem->pSubMenu->RemoveDisabledEntries(); + if ( bRemoveEmptyPopups && !pItem->pSubMenu->GetItemCount() ) + bRemove = true; + } + + if ( bRemove ) + RemoveItem( n-- ); + } + + if ( GetItemCount() ) + { + sal_uInt16 nLast = GetItemCount() - 1; + MenuItemData* pItem = pItemList->GetDataFromPos( nLast ); + if ( pItem->eType == MenuItemType::SEPARATOR ) + RemoveItem( nLast ); + } + mpLayoutData.reset(); +} + +void Menu::UpdateNativeMenu() +{ + if ( ImplGetSalMenu() ) + ImplGetSalMenu()->Update(); +} + +void Menu::MenuBarKeyInput(const KeyEvent&) +{ +} + +void Menu::ImplKillLayoutData() const +{ + mpLayoutData.reset(); +} + +void Menu::ImplFillLayoutData() const +{ + if (!(pWindow && pWindow->IsReallyVisible())) + return; + + mpLayoutData.reset(new MenuLayoutData); + if (IsMenuBar()) + { + ImplPaint(*pWindow->GetOutDev(), pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME + } + else + { + MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get()); + ImplPaint(*pWindow->GetOutDev(), pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(), + nullptr, false, true); //FIXME + } +} + +tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, tools::Long nIndex ) const +{ + tools::Long nItemIndex = -1; + if( ! mpLayoutData ) + ImplFillLayoutData(); + if( mpLayoutData ) + { + for( size_t i = 0; i < mpLayoutData->m_aLineItemIds.size(); i++ ) + { + if( mpLayoutData->m_aLineItemIds[i] == nItemID ) + { + nItemIndex = mpLayoutData->m_aLineIndices[i]; + break; + } + } + } + return (mpLayoutData && nItemIndex != -1) ? mpLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle(); +} + +tools::Long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const +{ + tools::Long nIndex = -1; + rItemID = 0; + if( ! mpLayoutData ) + ImplFillLayoutData(); + if( mpLayoutData ) + { + nIndex = mpLayoutData->GetIndexForPoint( rPoint ); + for( size_t i = 0; i < mpLayoutData->m_aLineIndices.size(); i++ ) + { + if( mpLayoutData->m_aLineIndices[i] <= nIndex && + (i == mpLayoutData->m_aLineIndices.size()-1 || mpLayoutData->m_aLineIndices[i+1] > nIndex) ) + { + // make index relative to item + nIndex -= mpLayoutData->m_aLineIndices[i]; + rItemID = mpLayoutData->m_aLineItemIds[i]; + break; + } + } + } + return nIndex; +} + +tools::Rectangle Menu::GetBoundingRectangle( sal_uInt16 nPos ) const +{ + tools::Rectangle aRet; + + if (!mpLayoutData ) + ImplFillLayoutData(); + if (mpLayoutData) + { + std::map< sal_uInt16, tools::Rectangle >::const_iterator it = mpLayoutData->m_aVisibleItemBoundRects.find( nPos ); + if( it != mpLayoutData->m_aVisibleItemBoundRects.end() ) + aRet = it->second; + } + return aRet; +} + +void Menu::SetAccessibleName( sal_uInt16 nItemId, const OUString& rStr ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if (pData && !rStr.equals(pData->aAccessibleName)) + { + pData->aAccessibleName = rStr; + ImplCallEventListeners(VclEventId::MenuAccessibleNameChanged, nPos); + } +} + +OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return pData->aAccessibleName; + + return OUString(); +} + +void Menu::SetAccessibleDescription( sal_uInt16 nItemId, const OUString& rStr ) +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + pData->aAccessibleDescription = rStr; +} + +OUString Menu::GetAccessibleDescription( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if (pData && !pData->aAccessibleDescription.isEmpty()) + return pData->aAccessibleDescription; + + return GetHelpText(nItemId); +} + +void Menu::GetSystemMenuData( SystemMenuData* pData ) const +{ + Menu* pMenu = const_cast<Menu*>(this); + if( pData && pMenu->ImplGetSalMenu() ) + { + pMenu->ImplGetSalMenu()->GetSystemMenuData( pData ); + } +} + +bool Menu::IsHighlighted( sal_uInt16 nItemPos ) const +{ + bool bRet = false; + + if( pWindow ) + { + if (IsMenuBar()) + bRet = ( nItemPos == static_cast< MenuBarWindow * > (pWindow.get())->GetHighlightedItem() ); + else + bRet = ( nItemPos == static_cast< MenuFloatingWindow * > (pWindow.get())->GetHighlightedItem() ); + } + + return bRet; +} + +void Menu::HighlightItem( sal_uInt16 nItemPos ) +{ + if ( !pWindow ) + return; + + if (IsMenuBar()) + { + MenuBarWindow* pMenuWin = static_cast< MenuBarWindow* >( pWindow.get() ); + pMenuWin->SetAutoPopup( false ); + pMenuWin->ChangeHighlightItem( nItemPos, false ); + } + else + { + static_cast< MenuFloatingWindow* >( pWindow.get() )->ChangeHighlightItem( nItemPos, false ); + } +} + +MenuBarWindow* MenuBar::getMenuBarWindow() +{ + // so far just a dynamic_cast, hopefully to be turned into something saner + // at some stage + MenuBarWindow *pWin = dynamic_cast<MenuBarWindow*>(pWindow.get()); + //either there is no window (fdo#87663) or it is a MenuBarWindow + assert(!pWindow || pWin); + return pWin; +} + +MenuBar::MenuBar() + : mbCloseBtnVisible(false), + mbFloatBtnVisible(false), + mbHideBtnVisible(false), + mbDisplayable(true) +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this); +} + +MenuBar::MenuBar( const MenuBar& rMenu ) + : mbCloseBtnVisible(false), + mbFloatBtnVisible(false), + mbHideBtnVisible(false), + mbDisplayable(true) +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this); + *this = rMenu; +} + +MenuBar::~MenuBar() +{ + disposeOnce(); +} + +void MenuBar::dispose() +{ + ImplDestroy( this, true ); + Menu::dispose(); +} + +void MenuBar::ClosePopup(Menu *pMenu) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (!pMenuWin) + return; + pMenuWin->PopupClosed(pMenu); +} + +void MenuBar::MenuBarKeyInput(const KeyEvent& rEvent) +{ + pWindow->KeyInput(rEvent); +} + +void MenuBar::ShowCloseButton(bool bShow) +{ + ShowButtons( bShow, mbFloatBtnVisible, mbHideBtnVisible ); +} + +void MenuBar::ShowButtons( bool bClose, bool bFloat, bool bHide ) +{ + if ((bClose != mbCloseBtnVisible) || + (bFloat != mbFloatBtnVisible) || + (bHide != mbHideBtnVisible)) + { + mbCloseBtnVisible = bClose; + mbFloatBtnVisible = bFloat; + mbHideBtnVisible = bHide; + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (pMenuWin) + pMenuWin->ShowButtons(bClose, bFloat, bHide); + } +} + +void MenuBar::LayoutChanged() +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (pMenuWin) + pMenuWin->LayoutChanged(); +} + +void MenuBar::SetDisplayable( bool bDisplayable ) +{ + if( bDisplayable != mbDisplayable ) + { + if ( ImplGetSalMenu() ) + ImplGetSalMenu()->ShowMenuBar( bDisplayable ); + + mbDisplayable = bDisplayable; + LayoutChanged(); + } +} + +VclPtr<vcl::Window> MenuBar::ImplCreate(vcl::Window* pParent, vcl::Window* pWindow, MenuBar* pMenu) +{ + VclPtr<MenuBarWindow> pMenuBarWindow = dynamic_cast<MenuBarWindow*>(pWindow); + if (!pMenuBarWindow) + { + pWindow = pMenuBarWindow = VclPtr<MenuBarWindow>::Create( pParent ); + } + + pMenu->pStartedFrom = nullptr; + pMenu->pWindow = pWindow; + pMenuBarWindow->SetMenu(pMenu); + tools::Long nHeight = pWindow ? pMenu->ImplCalcSize(pWindow).Height() : 0; + + // depending on the native implementation or the displayable flag + // the menubar windows is suppressed (ie, height=0) + if (!pMenu->IsDisplayable() || (pMenu->ImplGetSalMenu() && pMenu->ImplGetSalMenu()->VisibleMenuBar())) + { + nHeight = 0; + } + + pMenuBarWindow->SetHeight(nHeight); + return pWindow; +} + +void MenuBar::ImplDestroy( MenuBar* pMenu, bool bDelete ) +{ + vcl::Window *pWindow = pMenu->ImplGetWindow(); + if (pWindow && bDelete) + { + MenuBarWindow* pMenuWin = pMenu->getMenuBarWindow(); + if (pMenuWin) + pMenuWin->KillActivePopup(); + pWindow->disposeOnce(); + } + pMenu->pWindow = nullptr; + if (pMenu->mpSalMenu) { + pMenu->mpSalMenu->ShowMenuBar(false); + } +} + +bool MenuBar::ImplHandleKeyEvent( const KeyEvent& rKEvent ) +{ + // No keyboard processing when our menubar is invisible + if (!IsDisplayable()) + return false; + + // No keyboard processing when system handles the menu. + SalMenu *pNativeMenu = ImplGetSalMenu(); + if (pNativeMenu && pNativeMenu->VisibleMenuBar()) + { + // Except when the event is the F6 cycle pane event and we can put our + // focus into it (i.e. the gtk3 menubar case but not the mac/unity case + // where it's not part of the application window) + if (!TaskPaneList::IsCycleKey(rKEvent.GetKeyCode())) + return false; + if (!pNativeMenu->CanGetFocus()) + return false; + } + + bool bDone = false; + // check for enabled, if this method is called from another window... + vcl::Window* pWin = ImplGetWindow(); + if (pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode()) + { + MenuBarWindow* pMenuWin = getMenuBarWindow(); + bDone = pMenuWin && pMenuWin->HandleKeyEvent(rKEvent, false/*bFromMenu*/); + } + return bDone; +} + +void MenuBar::SelectItem(sal_uInt16 nId) +{ + if (!pWindow) + return; + + pWindow->GrabFocus(); + nId = GetItemPos( nId ); + + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (pMenuWin) + { + // #99705# popup the selected menu + pMenuWin->SetAutoPopup( true ); + if (ITEMPOS_INVALID != pMenuWin->GetHighlightedItem()) + { + pMenuWin->KillActivePopup(); + pMenuWin->ChangeHighlightItem( ITEMPOS_INVALID, false ); + } + if (nId != ITEMPOS_INVALID) + pMenuWin->ChangeHighlightItem( nId, false ); + } +} + +// handler for native menu selection and command events +bool Menu::HandleMenuActivateEvent( Menu *pMenu ) const +{ + if( pMenu ) + { + ImplMenuDelData aDelData( this ); + + pMenu->pStartedFrom = const_cast<Menu*>(this); + pMenu->bInCallback = true; + pMenu->Activate(); + + if( !aDelData.isDeleted() ) + pMenu->bInCallback = false; + } + return true; +} + +bool Menu::HandleMenuDeActivateEvent( Menu *pMenu ) const +{ + if( pMenu ) + { + ImplMenuDelData aDelData( this ); + + pMenu->pStartedFrom = const_cast<Menu*>(this); + pMenu->bInCallback = true; + pMenu->Deactivate(); + if( !aDelData.isDeleted() ) + pMenu->bInCallback = false; + } + return true; +} + +bool MenuBar::HandleMenuHighlightEvent( Menu *pMenu, sal_uInt16 nHighlightEventId ) const +{ + if( !pMenu ) + pMenu = const_cast<MenuBar*>(this)->ImplFindMenu(nHighlightEventId); + if( pMenu ) + { + ImplMenuDelData aDelData( pMenu ); + + if( mnHighlightedItemPos != ITEMPOS_INVALID ) + pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, mnHighlightedItemPos ); + + if( !aDelData.isDeleted() ) + { + pMenu->mnHighlightedItemPos = pMenu->GetItemPos( nHighlightEventId ); + pMenu->nSelectedId = nHighlightEventId; + pMenu->sSelectedIdent = pMenu->GetItemIdent( nHighlightEventId ); + pMenu->pStartedFrom = const_cast<MenuBar*>(this); + pMenu->ImplCallHighlight( pMenu->mnHighlightedItemPos ); + } + return true; + } + else + return false; +} + +bool Menu::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const +{ + if( !pMenu ) + pMenu = const_cast<Menu*>(this)->ImplFindMenu(nCommandEventId); + if( pMenu ) + { + pMenu->nSelectedId = nCommandEventId; + pMenu->sSelectedIdent = pMenu->GetItemIdent(nCommandEventId); + pMenu->pStartedFrom = const_cast<Menu*>(this); + pMenu->ImplSelect(); + return true; + } + else + return false; +} + +sal_uInt16 MenuBar::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + return pMenuWin ? pMenuWin->AddMenuBarButton(i_rImage, i_rLink, i_rToolTip) : 0; +} + +void MenuBar::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (!pMenuWin) + return; + pMenuWin->SetMenuBarButtonHighlightHdl(nId, rLink); +} + +void MenuBar::RemoveMenuBarButton( sal_uInt16 nId ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (!pMenuWin) + return; + pMenuWin->RemoveMenuBarButton(nId); +} + +tools::Rectangle MenuBar::GetMenuBarButtonRectPixel( sal_uInt16 nId ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + return pMenuWin ? pMenuWin->GetMenuBarButtonRectPixel(nId) : tools::Rectangle(); +} + +bool MenuBar::HandleMenuButtonEvent( sal_uInt16 i_nButtonId ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + return pMenuWin && pMenuWin->HandleMenuButtonEvent(i_nButtonId); +} + +int MenuBar::GetMenuBarHeight() const +{ + MenuBar* pMenuBar = const_cast<MenuBar*>(this); + const SalMenu *pNativeMenu = pMenuBar->ImplGetSalMenu(); + int nMenubarHeight; + if (pNativeMenu) + nMenubarHeight = pNativeMenu->GetMenuBarHeight(); + else + { + vcl::Window* pMenubarWin = GetWindow(); + nMenubarHeight = pMenubarWin ? pMenubarWin->GetOutputSizePixel().Height() : 0; + } + return nMenubarHeight; +} + +// bool PopupMenu::bAnyPopupInExecute = false; + +MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const { + return static_cast<MenuFloatingWindow *>(Menu::ImplGetWindow()); +} + +PopupMenu::PopupMenu() +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this); +} + +PopupMenu::PopupMenu( const PopupMenu& rMenu ) +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this); + *this = rMenu; +} + +PopupMenu::~PopupMenu() +{ + disposeOnce(); +} + +void PopupMenu::ClosePopup(Menu* pMenu) +{ + MenuFloatingWindow* p = dynamic_cast<MenuFloatingWindow*>(ImplGetWindow()); + PopupMenu *pPopup = dynamic_cast<PopupMenu*>(pMenu); + if (p && pPopup) + p->KillActivePopup(pPopup); +} + +namespace vcl +{ + bool IsInPopupMenuExecute() + { + return PopupMenu::GetActivePopupMenu() != nullptr; + } +} + +PopupMenu* PopupMenu::GetActivePopupMenu() +{ + ImplSVData* pSVData = ImplGetSVData(); + return pSVData->maAppData.mpActivePopupMenu; +} + +void PopupMenu::EndExecute() +{ + if ( ImplGetWindow() ) + ImplGetFloatingWindow()->EndExecute( 0 ); +} + +void PopupMenu::SelectItem(sal_uInt16 nId) +{ + if ( !ImplGetWindow() ) + return; + + if( nId != ITEMPOS_INVALID ) + { + size_t nPos = 0; + MenuItemData* pData = GetItemList()->GetData( nId, nPos ); + if (pData && pData->pSubMenu) + ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true ); + else + ImplGetFloatingWindow()->EndExecute( nId ); + } + else + { + MenuFloatingWindow* pFloat = ImplGetFloatingWindow(); + pFloat->GrabFocus(); + + for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ ) + { + MenuItemData* pData = GetItemList()->GetDataFromPos( nPos ); + if( pData->pSubMenu ) + { + pFloat->KillActivePopup(); + } + } + pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false ); + } +} + +void PopupMenu::SetSelectedEntry( sal_uInt16 nId ) +{ + nSelectedId = nId; + sSelectedIdent = GetItemIdent(nId); +} + +sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos ) +{ + return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown ); +} + +static FloatWinPopupFlags lcl_TranslateFlags(PopupMenuFlags nFlags) +{ + FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE; + if ( nFlags & PopupMenuFlags::ExecuteDown ) + nPopupModeFlags = FloatWinPopupFlags::Down; + else if ( nFlags & PopupMenuFlags::ExecuteUp ) + nPopupModeFlags = FloatWinPopupFlags::Up; + else if ( nFlags & PopupMenuFlags::ExecuteRight ) + nPopupModeFlags = FloatWinPopupFlags::Right; + else + nPopupModeFlags = FloatWinPopupFlags::Down; + + if (nFlags & PopupMenuFlags::NoMouseUpClose ) // allow popup menus to stay open on mouse button up + nPopupModeFlags |= FloatWinPopupFlags::NoMouseUpClose; // useful if the menu was opened on mousebutton down (eg toolbox configuration) + + return nPopupModeFlags; +} + +sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags ) +{ + ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 ); + return ImplExecute( pExecWindow, rRect, lcl_TranslateFlags(nFlags), nullptr, false ); +} + +void PopupMenu::ImplFlushPendingSelect() +{ + // is there still Select? + Menu* pSelect = ImplFindSelectMenu(); + if (pSelect) + { + // Select should be called prior to leaving execute in a popup menu! + Application::RemoveUserEvent( pSelect->nEventId ); + pSelect->nEventId = nullptr; + pSelect->Select(); + } +} + +bool PopupMenu::PrepareRun(const VclPtr<vcl::Window>& pParentWin, tools::Rectangle& rRect, + FloatWinPopupFlags& nPopupModeFlags, Menu* pSFrom, + bool& bRealExecute, VclPtr<MenuFloatingWindow>& pWin) +{ + bRealExecute = false; + const sal_uInt16 nItemCount = GetItemCount(); + if (!pSFrom && (vcl::IsInPopupMenuExecute() || !nItemCount)) + return false; + + mpLayoutData.reset(); + + ImplSVData* pSVData = ImplGetSVData(); + + pStartedFrom = pSFrom; + nSelectedId = 0; + sSelectedIdent.clear(); + bCanceled = false; + + VclPtr<vcl::Window> xFocusId; + if ( !pStartedFrom ) + { + pSVData->mpWinData->mbNoDeactivate = true; + xFocusId = Window::SaveFocus(); + bRealExecute = true; + } + else + { + // assure that only one menu is open at a time + if (pStartedFrom->IsMenuBar() && pSVData->mpWinData->mpFirstFloat) + pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel + | FloatWinPopupEndFlags::CloseAll); + } + + SAL_WARN_IF( ImplGetWindow(), "vcl", "Win?!" ); + rRect.SetPos(pParentWin->OutputToScreenPixel(rRect.TopLeft())); + + nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose | FloatWinPopupFlags::GrabFocus; + if (bRealExecute) + nPopupModeFlags |= FloatWinPopupFlags::NewLevel; + + bInCallback = true; // set it here, if Activate overridden + Activate(); + bInCallback = false; + + if (pParentWin->isDisposed()) + return false; + + if ( bCanceled || bKilled ) + return false; + + if (!nItemCount) + return false; + + // The flag MenuFlags::HideDisabledEntries is inherited. + if ( pSFrom ) + { + if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries ) + nMenuFlags |= MenuFlags::HideDisabledEntries; + else + nMenuFlags &= ~MenuFlags::HideDisabledEntries; + } + else + { + if (officecfg::Office::Common::View::Menu::DontHideDisabledEntry::get()) + nMenuFlags &= ~MenuFlags::HideDisabledEntries; + else + nMenuFlags |= MenuFlags::HideDisabledEntries; + } + + sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount(); + if ( !nVisibleEntries ) + { + OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE)); + + MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, {}); + size_t nPos = 0; + pData = pItemList->GetData( pData->nId, nPos ); + assert(pData); + if (pData) + { + pData->bIsTemporary = true; + } + ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos); + } + + pWin = VclPtrInstance<MenuFloatingWindow>(this, pParentWin, WB_BORDER | WB_SYSTEMWINDOW); + if (comphelper::LibreOfficeKit::isActive() && get_id() == "editviewspellmenu") + { + VclPtr<vcl::Window> xNotifierParent = pParentWin->GetParentWithLOKNotifier(); + assert(xNotifierParent && xNotifierParent->GetLOKNotifier() && "editview menu without LOKNotifier"); + pWin->SetLOKNotifier(xNotifierParent->GetLOKNotifier()); + } + + if( pSVData->maNWFData.mbFlatMenu ) + pWin->SetBorderStyle( WindowBorderStyle::NOBORDER ); + else + pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU ); + pWindow = pWin; + + Size aSz = ImplCalcSize( pWin ); + + AbsoluteScreenPixelRectangle aDesktopRect(pWin->GetDesktopRectPixel()); + if( Application::GetScreenCount() > 1 ) + { + vcl::Window* pDeskW = pWindow->GetWindow( GetWindowType::RealParent ); + if( ! pDeskW ) + pDeskW = pWindow; + AbsoluteScreenPixelPoint aDesktopTL(pDeskW->OutputToAbsoluteScreenPixel(rRect.TopLeft())); + aDesktopRect = Application::GetScreenPosSizePixel( + Application::GetBestScreen(AbsoluteScreenPixelRectangle(aDesktopTL, rRect.GetSize()))); + } + + tools::Long nMaxHeight = aDesktopRect.GetHeight(); + + //rhbz#1021915. If a menu won't fit in the desired location the default + //mode is to place it somewhere it will fit. e.g. above, left, right. For + //some cases, e.g. menubars, it's desirable to limit the options to + //above/below and force the menu to scroll if it won't fit + if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement) + { + vcl::Window* pRef = pWin; + if ( pRef->GetParent() ) + pRef = pRef->GetParent(); + + AbsoluteScreenPixelRectangle devRect(pRef->OutputToAbsoluteScreenPixel(rRect.TopLeft()), + pRef->OutputToAbsoluteScreenPixel(rRect.BottomRight())); + + tools::Long nHeightAbove = devRect.Top() - aDesktopRect.Top(); + tools::Long nHeightBelow = aDesktopRect.Bottom() - devRect.Bottom(); + nMaxHeight = std::min(nMaxHeight, std::max(nHeightAbove, nHeightBelow)); + } + + // In certain cases this might be misdetected with a height of 0, leading to menus not being displayed. + // So assume that the available screen size matches at least the system requirements + SAL_WARN_IF(nMaxHeight < 768, "vcl", + "Available height misdetected as " << nMaxHeight + << "px. Setting to 768px instead."); + nMaxHeight = std::max(nMaxHeight, tools::Long(768)); + + if (pStartedFrom && pStartedFrom->IsMenuBar()) + nMaxHeight -= pParentWin->GetSizePixel().Height(); + sal_Int32 nLeft, nTop, nRight, nBottom; + pWindow->GetBorder( nLeft, nTop, nRight, nBottom ); + nMaxHeight -= nTop+nBottom; + if ( aSz.Height() > nMaxHeight ) + { + pWin->EnableScrollMenu( true ); + sal_uInt16 nStart = ImplGetFirstVisible(); + sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart ); + aSz.setHeight( ImplCalcHeight( nEntries ) ); + } + + pWin->SetFocusId( xFocusId ); + pWin->SetOutputSizePixel( aSz ); + return true; +} + +bool PopupMenu::Run(const VclPtr<MenuFloatingWindow>& pWin, const bool bRealExecute, const bool bPreSelectFirst, + const FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, const tools::Rectangle& rRect) +{ + SalMenu* pMenu = ImplGetSalMenu(); + if (pMenu && bRealExecute && pMenu->ShowNativePopupMenu(pWin, rRect, nPopupModeFlags)) + return true; + + pWin->StartPopupMode(rRect, nPopupModeFlags); + if (pSFrom) + { + sal_uInt16 aPos; + if (pSFrom->IsMenuBar()) + aPos = static_cast<MenuBarWindow *>(pSFrom->pWindow.get())->GetHighlightedItem(); + else + aPos = static_cast<MenuFloatingWindow *>(pSFrom->pWindow.get())->GetHighlightedItem(); + + pWin->SetPosInParent(aPos); // store position to be sent in SUBMENUDEACTIVATE + pSFrom->ImplCallEventListeners(VclEventId::MenuSubmenuActivate, aPos); + } + + if ( bPreSelectFirst ) + { + for (size_t n = 0; n < static_cast<size_t>(GetItemCount()); n++) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + if ( ( pData->bEnabled + || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus() + ) + && ( pData->eType != MenuItemType::SEPARATOR ) + && ImplIsVisible( n ) + && ImplIsSelectable( n ) + ) + { + pWin->ChangeHighlightItem(n, false); + break; + } + } + } + + if (bRealExecute) + pWin->Execute(); + + return false; +} + +void PopupMenu::FinishRun(const VclPtr<MenuFloatingWindow>& pWin, const VclPtr<vcl::Window>& pParentWin, const bool bRealExecute, const bool bIsNativeMenu) +{ + if (!bRealExecute || pWin->isDisposed()) + return; + + if (!bIsNativeMenu) + { + VclPtr<vcl::Window> xFocusId = pWin->GetFocusId(); + assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End"); + pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId); + + if (nSelectedId) // then clean up .. ( otherwise done by TH ) + { + PopupMenu* pSub = pWin->GetActivePopup(); + while ( pSub ) + { + pSub->ImplGetFloatingWindow()->EndPopupMode(); + pSub = pSub->ImplGetFloatingWindow()->GetActivePopup(); + } + } + } + else + pWin->StopExecute(); + + pWin->doShutdown(); + pWindow.disposeAndClear(); + ImplClosePopupToolBox(pParentWin); + ImplFlushPendingSelect(); +} + +sal_uInt16 PopupMenu::ImplExecute(const VclPtr<vcl::Window>& pParentWin, const tools::Rectangle& rRect, + FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst) +{ + // tdf#126054 hold this until after function completes + VclPtr<PopupMenu> xThis(this); + bool bRealExecute = false; + tools::Rectangle aRect(rRect); + VclPtr<MenuFloatingWindow> pWin; + if (!PrepareRun(pParentWin, aRect, nPopupModeFlags, pSFrom, bRealExecute, pWin)) + return 0; + const bool bNative = Run(pWin, bRealExecute, bPreSelectFirst, nPopupModeFlags, pSFrom, aRect); + FinishRun(pWin, pParentWin, bRealExecute, bNative); + return nSelectedId; +} + +sal_uInt16 PopupMenu::ImplCalcVisEntries( tools::Long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const +{ + nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight(); + + tools::Long nHeight = 0; + size_t nEntries = pItemList->size(); + sal_uInt16 nVisEntries = 0; + + if ( pLastVisible ) + *pLastVisible = 0; + + for ( size_t n = nStartEntry; n < nEntries; n++ ) + { + if ( ImplIsVisible( n ) ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + nHeight += pData->aSz.Height(); + if ( nHeight > nMaxHeight ) + break; + + if ( pLastVisible ) + *pLastVisible = n; + nVisEntries++; + } + } + return nVisEntries; +} + +tools::Long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const +{ + tools::Long nHeight = 0; + + sal_uInt16 nFound = 0; + for ( size_t n = 0; ( nFound < nEntries ) && ( n < pItemList->size() ); n++ ) + { + if ( ImplIsVisible( static_cast<sal_uInt16>(n) ) ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + nHeight += pData->aSz.Height(); + nFound++; + } + } + + nHeight += 2*ImplGetFloatingWindow()->GetScrollerHeight(); + + return nHeight; +} + +css::uno::Reference<css::awt::XPopupMenu> PopupMenu::CreateMenuInterface() +{ + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + if ( pWrapper ) + return pWrapper->CreateMenuInterface(this); + return nullptr; +} + +ImplMenuDelData::ImplMenuDelData( const Menu* pMenu ) +: mpNext( nullptr ) +, mpMenu( nullptr ) +{ + if( pMenu ) + const_cast< Menu* >( pMenu )->ImplAddDel( *this ); +} + +ImplMenuDelData::~ImplMenuDelData() +{ + if( mpMenu ) + const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |