Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/vcl/source/window/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 102 kB image not shown  

Quelle  menu.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <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>&&nbsp;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>&&nbsp;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, truefalse, 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






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.