/* -*- 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 "menufloatingwindow.hxx" #include "menuitemlist.hxx" #include "menubarwindow.hxx" #include "bufferdevice.hxx" #include #include #include #include #include #include MenuFloatingWindow::MenuFloatingWindow( Menu* pMen, vcl::Window* pParent, WinBits nStyle ) : FloatingWindow( pParent, nStyle ), pMenu(pMen), aHighlightChangedTimer("vcl::MenuFloatingWindow aHighlightChangedTimer"), aSubmenuCloseTimer( "vcl::MenuFloatingWindow aSubmenuCloseTimer" ), aScrollTimer( "vcl::MenuFloatingWindow aScrollTimer" ), nHighlightedItem(ITEMPOS_INVALID), nMBDownPos(ITEMPOS_INVALID), nScrollerHeight(0), nFirstEntry(0), nPosInParent(ITEMPOS_INVALID), bInExecute(false), bScrollMenu(false), bScrollUp(false), bScrollDown(false), bIgnoreFirstMove(true), bKeyInput(false) { mpWindowImpl->mbMenuFloatingWindow= true; ApplySettings(*GetOutDev()); SetPopupModeEndHdl( LINK( this, MenuFloatingWindow, PopupEnd ) ); aHighlightChangedTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, HighlightChanged ) ); aHighlightChangedTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() ); aSubmenuCloseTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() ); aSubmenuCloseTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, SubmenuClose ) ); aScrollTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, AutoScroll ) ); AddEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) ); } void MenuFloatingWindow::doShutdown() { if( !pMenu ) return; // #105373# notify toolkit that highlight was removed // otherwise the entry will not be read when the menu is opened again if( nHighlightedItem != ITEMPOS_INVALID ) pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem ); if (!bKeyInput && pMenu && pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar()) { // #102461# remove highlight in parent size_t i, nCount = pMenu->pStartedFrom->pItemList->size(); for(i = 0; i < nCount; i++) { MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i ); if( pData && ( pData->pSubMenu == pMenu ) ) break; } if( i < nCount ) { MenuFloatingWindow* pPWin = static_cast(pMenu->pStartedFrom->ImplGetWindow()); if (pPWin) pPWin->InvalidateItem(i); } } // free the reference to the accessible component SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() ); aHighlightChangedTimer.Stop(); // #95056# invalidate screen area covered by system window // so this can be taken into account if the commandhandler performs a scroll operation if( GetParent() ) { tools::Rectangle aInvRect( GetWindowExtentsRelative( *GetParent() ) ); GetParent()->Invalidate( aInvRect ); } pMenu = nullptr; RemoveEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) ); aScrollTimer.Stop(); aSubmenuCloseTimer.Stop(); aSubmenuCloseTimer.Stop(); aHighlightChangedTimer.Stop(); aHighlightChangedTimer.Stop(); } MenuFloatingWindow::~MenuFloatingWindow() { disposeOnce(); } void MenuFloatingWindow::dispose() { doShutdown(); pMenu.clear(); pActivePopup.clear(); xSaveFocusId.clear(); FloatingWindow::dispose(); } void MenuFloatingWindow::Resize() { InitMenuClipRegion(*GetOutDev()); // FIXME } void MenuFloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext) { FloatingWindow::ApplySettings(rRenderContext); if (IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem) && IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) { AllSettings aSettings(GetSettings()); ImplGetFrame()->UpdateSettings(aSettings); // Update theme colors. StyleSettings aStyle(aSettings.GetStyleSettings()); Color aHighlightTextColor = ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor; if (aHighlightTextColor != COL_TRANSPARENT) { aStyle.SetMenuHighlightTextColor(aHighlightTextColor); } aSettings.SetStyleSettings(aStyle); GetOutDev()->SetSettings(aSettings); } const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); SetPointFont(rRenderContext, rStyleSettings.GetMenuFont()); if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) { rRenderContext.SetBackground(); // background will be drawn by NWF } else rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetMenuColor())); rRenderContext.SetTextColor(rStyleSettings.GetMenuTextColor()); rRenderContext.SetTextFillColor(); rRenderContext.SetLineColor(); } /// Get a negative pixel offset for an offset menu tools::Long MenuFloatingWindow::ImplGetStartY() const { tools::Long nY = 0; if( pMenu ) { // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686) if ( nFirstEntry > 0 && !pMenu->GetItemList()->GetDataFromPos(nFirstEntry - 1) ) { return 0; } for ( sal_uInt16 n = 0; n < nFirstEntry; n++ ) nY += pMenu->GetItemList()->GetDataFromPos( n )->aSz.Height(); nY -= pMenu->GetTitleHeight(); } return -nY; } vcl::Region MenuFloatingWindow::ImplCalcClipRegion() const { Size aOutSz = GetOutputSizePixel(); tools::Rectangle aRect( Point(), aOutSz ); aRect.AdjustTop(nScrollerHeight ); aRect.AdjustBottom( -nScrollerHeight ); vcl::Region aRegion(aRect); return aRegion; } void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext& rRenderContext) { if (IsScrollMenu()) { rRenderContext.SetClipRegion(ImplCalcClipRegion()); } else { rRenderContext.SetClipRegion(); } } void MenuFloatingWindow::ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown ) { if( ! pMenu ) return; tools::Long nY = GetInitialItemY(); tools::Long nMouseY = rMEvt.GetPosPixel().Y(); Size aOutSz = GetOutputSizePixel(); if ( ( nMouseY >= nY ) && ( nMouseY < aOutSz.Height() ) ) { bool bHighlighted = false; size_t nCount = pMenu->pItemList->size(); for ( size_t n = 0; !bHighlighted && ( n < nCount ); n++ ) { if ( pMenu->ImplIsVisible( n ) ) { MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( n ); tools::Long nOldY = nY; nY += pItemData->aSz.Height(); if ( ( nOldY <= nMouseY ) && ( nY > nMouseY ) && pMenu->ImplIsSelectable( n ) ) { bool bPopupArea = true; if ( pItemData->nBits & MenuItemBits::POPUPSELECT ) { // only when clicked over the arrow... Size aSz = GetOutputSizePixel(); tools::Long nFontHeight = GetTextHeight(); bPopupArea = ( rMEvt.GetPosPixel().X() >= ( aSz.Width() - nFontHeight - nFontHeight/4 ) ); } if ( bMBDown ) { if ( n != nHighlightedItem ) { ChangeHighlightItem( static_cast(n), false ); } bool bAllowNewPopup = true; if ( pActivePopup ) { MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); bAllowNewPopup = pData && ( pData->pSubMenu != pActivePopup ); if ( bAllowNewPopup ) KillActivePopup(); } if ( bPopupArea && bAllowNewPopup ) { HighlightChanged( nullptr ); } } else { if ( n != nHighlightedItem ) { ChangeHighlightItem( static_cast(n), true ); } else if ( pItemData->nBits & MenuItemBits::POPUPSELECT ) { if ( bPopupArea && ( pActivePopup != pItemData->pSubMenu ) ) HighlightChanged( nullptr ); } } bHighlighted = true; } } } if ( !bHighlighted ) ChangeHighlightItem( ITEMPOS_INVALID, true ); } else { ImplScroll( rMEvt.GetPosPixel() ); ChangeHighlightItem( ITEMPOS_INVALID, true ); } } IMPL_LINK_NOARG(MenuFloatingWindow, PopupEnd, FloatingWindow*, void) { // "this" will be deleted before the end of this method! Menu* pM = pMenu; if ( bInExecute ) { End(); if ( pActivePopup ) { KillActivePopup(); // should be ok to just remove it //pActivePopup->bCanceled = true; } pMenu->bInCallback = true; pMenu->Deactivate(); pMenu->bInCallback = false; } else { if (pMenu && pMenu->pStartedFrom) pMenu->pStartedFrom->ClosePopup(pMenu); } if ( pM ) pM->pStartedFrom = nullptr; } IMPL_LINK_NOARG(MenuFloatingWindow, AutoScroll, Timer *, void) { ImplScroll( GetPointerPosPixel() ); } IMPL_LINK( MenuFloatingWindow, HighlightChanged, Timer*, pTimer, void ) { if( ! pMenu ) return; MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( nHighlightedItem ); if ( !pItemData ) return; if ( pActivePopup && ( pActivePopup != pItemData->pSubMenu ) ) { FloatWinPopupFlags nOldFlags = GetPopupModeFlags(); SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose ); KillActivePopup(); SetPopupModeFlags( nOldFlags ); } if ( !(pItemData->bEnabled && pItemData->pSubMenu && pItemData->pSubMenu->GetItemCount() && ( pItemData->pSubMenu != pActivePopup )) ) return; pActivePopup = pItemData->pSubMenu.get(); tools::Long nY = nScrollerHeight+ImplGetStartY(); MenuItemData* pData = nullptr; for ( sal_uLong n = 0; n < nHighlightedItem; n++ ) { pData = pMenu->pItemList->GetDataFromPos( n ); nY += pData->aSz.Height(); } pData = pMenu->pItemList->GetDataFromPos( nHighlightedItem ); Size MySize = GetOutputSizePixel(); Point aItemTopLeft( 0, nY ); Point aItemBottomRight( aItemTopLeft ); aItemBottomRight.AdjustX(MySize.Width() ); aItemBottomRight.AdjustY(pData->aSz.Height() ); // shift the popups a little aItemTopLeft.AdjustX(2 ); aItemBottomRight.AdjustX( -2 ); if ( nHighlightedItem ) aItemTopLeft.AdjustY( -2 ); else { sal_Int32 nL, nT, nR, nB; GetBorder( nL, nT, nR, nB ); aItemTopLeft.AdjustY( -nT ); } // pTest: crash due to Reschedule() in call of Activate() // Also it is prevented that submenus are displayed which // were for long in Activate Rescheduled and which should not be // displayed now. Menu* pTest = pActivePopup; FloatWinPopupFlags nOldFlags = GetPopupModeFlags(); SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose ); sal_uInt16 nRet = pActivePopup->ImplExecute( this, tools::Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Right, pMenu, pTimer == nullptr ); SetPopupModeFlags( nOldFlags ); // nRet != 0, if it was stopped during Activate()... if ( !nRet && ( pActivePopup == pTest ) && pActivePopup->ImplGetWindow() ) pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this ); } IMPL_LINK_NOARG(MenuFloatingWindow, SubmenuClose, Timer *, void) { if( pMenu && pMenu->pStartedFrom ) { MenuFloatingWindow* pWin = static_cast(pMenu->pStartedFrom->GetWindow()); if( pWin ) pWin->KillActivePopup(); } } IMPL_LINK( MenuFloatingWindow, ShowHideListener, VclWindowEvent&, rEvent, void ) { if( ! pMenu ) return; if( rEvent.GetId() == VclEventId::WindowShow ) pMenu->ImplCallEventListeners( VclEventId::MenuShow, ITEMPOS_INVALID ); else if( rEvent.GetId() == VclEventId::WindowHide ) pMenu->ImplCallEventListeners( VclEventId::MenuHide, ITEMPOS_INVALID ); } void MenuFloatingWindow::EnableScrollMenu( bool b ) { bScrollMenu = b; nScrollerHeight = b ? static_cast(GetSettings().GetStyleSettings().GetScrollBarSize()) /2 : 0; bScrollDown = true; InitMenuClipRegion(*GetOutDev()); } void MenuFloatingWindow::Start() { if (bInExecute) return; bInExecute = true; if (GetParent()) GetParent()->IncModalCount(); } bool MenuFloatingWindow::MenuInHierarchyHasFocus() const { if (HasChildPathFocus()) return true; PopupMenu* pSub = GetActivePopup(); if (!pSub) return false; return pSub->ImplGetFloatingWindow()->HasChildPathFocus(); } void MenuFloatingWindow::End() { if (!bInExecute) return; if (GetParent() && !GetParent()->isDisposed()) GetParent()->DecModalCount(); // restore focus to previous window if we still have the focus VclPtr xFocusId(xSaveFocusId); xSaveFocusId = nullptr; if (xFocusId != nullptr && MenuInHierarchyHasFocus()) { ImplGetSVData()->mpWinData->mbNoDeactivate = false; Window::EndSaveFocus(xFocusId); } bInExecute = false; } void MenuFloatingWindow::Execute() { ImplSVData* pSVData = ImplGetSVData(); pSVData->maAppData.mpActivePopupMenu = static_cast(pMenu.get()); Start(); while (bInExecute && !Application::IsQuit()) Application::Yield(); pSVData->maAppData.mpActivePopupMenu = nullptr; } void MenuFloatingWindow::StopExecute() { End(); ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xSaveFocusId); aHighlightChangedTimer.Stop(); if (pActivePopup) { KillActivePopup(); } // notify parent, needed for accessibility if( pMenu && pMenu->pStartedFrom ) pMenu->pStartedFrom->ImplCallEventListeners( VclEventId::MenuSubmenuDeactivate, nPosInParent ); } void MenuFloatingWindow::KillActivePopup( PopupMenu* pThisOnly ) { if ( !pActivePopup || ( pThisOnly && ( pThisOnly != pActivePopup ) ) ) return; if( pActivePopup->pWindow ) if( static_cast(pActivePopup->pWindow.get())->IsInCleanUp() ) return; // kill it later if ( pActivePopup->bInCallback ) pActivePopup->bCanceled = true; // For all actions pActivePopup = 0, if e.g. // PopupModeEndHdl the popups to destroy were called synchronous PopupMenu* pPopup = pActivePopup; pActivePopup = nullptr; pPopup->bInCallback = true; pPopup->Deactivate(); pPopup->bInCallback = false; if ( pPopup->ImplGetWindow() ) { pPopup->ImplGetFloatingWindow()->StopExecute(); pPopup->ImplGetFloatingWindow()->doShutdown(); pPopup->pWindow.disposeAndClear(); PaintImmediately(); } } void MenuFloatingWindow::EndExecute() { Menu* pStart = pMenu ? pMenu->ImplGetStartMenu() : nullptr; // if started elsewhere, cleanup there as well MenuFloatingWindow* pCleanUpFrom = this; MenuFloatingWindow* pWin = this; while (pWin && !pWin->bInExecute && pWin->pMenu->pStartedFrom && !pWin->pMenu->pStartedFrom->IsMenuBar()) { pWin = static_cast(pWin->pMenu->pStartedFrom.get())->ImplGetFloatingWindow(); } if ( pWin ) pCleanUpFrom = pWin; // this window will be destroyed => store date locally... Menu* pM = pMenu; sal_uInt16 nItem = nHighlightedItem; pCleanUpFrom->StopExecute(); if ( !(nItem != ITEMPOS_INVALID && pM) ) return; MenuItemData* pItemData = pM->GetItemList()->GetDataFromPos( nItem ); if ( pItemData && !pItemData->bIsTemporary ) { pM->nSelectedId = pItemData->nId; pM->sSelectedIdent = pItemData->sIdent; if (pStart) { pStart->nSelectedId = pItemData->nId; pStart->sSelectedIdent = pItemData->sIdent; } pM->ImplSelect(); } } void MenuFloatingWindow::EndExecute( sal_uInt16 nId ) { size_t nPos; if ( pMenu && pMenu->GetItemList()->GetData( nId, nPos ) ) nHighlightedItem = nPos; else nHighlightedItem = ITEMPOS_INVALID; EndExecute(); } void MenuFloatingWindow::MouseButtonDown( const MouseEvent& rMEvt ) { // TH creates a ToTop on this window, but the active popup // should stay on top... // due to focus change this would close all menus -> don't do it (#94123) //if ( pActivePopup && pActivePopup->ImplGetWindow() && !pActivePopup->ImplGetFloatingWindow()->pActivePopup ) // pActivePopup->ImplGetFloatingWindow()->ToTop( ToTopFlags::NoGrabFocus ); ImplHighlightItem( rMEvt, true ); nMBDownPos = nHighlightedItem; } void MenuFloatingWindow::MouseButtonUp( const MouseEvent& rMEvt ) { MenuItemData* pData = pMenu ? pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ) : nullptr; // nMBDownPos store in local variable and reset immediately, // as it will be too late after EndExecute sal_uInt16 _nMBDownPos = nMBDownPos; nMBDownPos = ITEMPOS_INVALID; if ( !(pData && pData->bEnabled && ( pData->eType != MenuItemType::SEPARATOR )) ) return; if ( !pData->pSubMenu ) { EndExecute(); } else if ( ( pData->nBits & MenuItemBits::POPUPSELECT ) && ( nHighlightedItem == _nMBDownPos ) && ( rMEvt.GetClicks() == 2 ) ) { // not when clicked over the arrow... Size aSz = GetOutputSizePixel(); tools::Long nFontHeight = GetTextHeight(); if ( rMEvt.GetPosPixel().X() < ( aSz.Width() - nFontHeight - nFontHeight/4 ) ) EndExecute(); } } void MenuFloatingWindow::MouseMove( const MouseEvent& rMEvt ) { if ( !IsVisible() || rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() ) return; if ( rMEvt.IsLeaveWindow() ) { // #102461# do not remove highlight if a popup menu is open at this position MenuItemData* pData = pMenu ? pMenu->pItemList->GetDataFromPos( nHighlightedItem ) : nullptr; // close popup with some delayed if we leave somewhere else if( pActivePopup && pData && pData->pSubMenu != pActivePopup ) pActivePopup->ImplGetFloatingWindow()->aSubmenuCloseTimer.Start(); if( !pActivePopup || (pData && pData->pSubMenu != pActivePopup ) ) ChangeHighlightItem( ITEMPOS_INVALID, false ); if ( IsScrollMenu() ) ImplScroll( rMEvt.GetPosPixel() ); } else { aSubmenuCloseTimer.Stop(); if( bIgnoreFirstMove ) bIgnoreFirstMove = false; else ImplHighlightItem( rMEvt, false ); } } void MenuFloatingWindow::ImplScroll( bool bUp ) { KillActivePopup(); PaintImmediately(); if (!pMenu) return; Invalidate(); pMenu->ImplKillLayoutData(); if ( bScrollUp && bUp ) { nFirstEntry = pMenu->ImplGetPrevVisible( nFirstEntry ); SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" ); // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686) const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry ); if ( pItemData ) { tools::Long nScrollEntryHeight = pItemData->aSz.Height(); if ( !bScrollDown ) { bScrollDown = true; Invalidate(); } if ( pMenu->ImplGetPrevVisible( nFirstEntry ) == ITEMPOS_INVALID ) { bScrollUp = false; Invalidate(); } Scroll( 0, nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip ); } } else if ( bScrollDown && !bUp ) { // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686) const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry ); if ( pItemData ) { tools::Long nScrollEntryHeight = pItemData->aSz.Height(); nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry ); SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" ); if ( !bScrollUp ) { bScrollUp = true; Invalidate(); } tools::Long nHeight = GetOutputSizePixel().Height(); sal_uInt16 nLastVisible; static_cast(pMenu.get())->ImplCalcVisEntries( nHeight, nFirstEntry, &nLastVisible ); if ( pMenu->ImplGetNextVisible( nLastVisible ) == ITEMPOS_INVALID ) { bScrollDown = false; Invalidate(); } Scroll( 0, -nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip ); } } Invalidate(); } void MenuFloatingWindow::ImplScroll( const Point& rMousePos ) { Size aOutSz = GetOutputSizePixel(); tools::Long nY = nScrollerHeight; tools::Long nMouseY = rMousePos.Y(); tools::Long nDelta = 0; if ( bScrollUp && ( nMouseY < nY ) ) { ImplScroll( true ); nDelta = nY - nMouseY; } else if ( bScrollDown && ( nMouseY > ( aOutSz.Height() - nY ) ) ) { ImplScroll( false ); nDelta = nMouseY - ( aOutSz.Height() - nY ); } if ( !nDelta ) return; aScrollTimer.Stop(); // if scrolled through MouseMove. tools::Long nTimeout; if ( nDelta < 3 ) nTimeout = 200; else if ( nDelta < 5 ) nTimeout = 100; else if ( nDelta < 8 ) nTimeout = 70; else if ( nDelta < 12 ) nTimeout = 40; else nTimeout = 20; aScrollTimer.SetTimeout( nTimeout ); aScrollTimer.Start(); } void MenuFloatingWindow::ChangeHighlightItem( sal_uInt16 n, bool bStartPopupTimer ) { // #57934# if necessary, immediately close the active, as TH's backgroundstorage works. // #65750# we prefer to refrain from the background storage of small lines. // otherwise the menus are difficult to operate. // MenuItemData* pNextData = pMenu->pItemList->GetDataFromPos( n ); // if ( pActivePopup && pNextData && ( pActivePopup != pNextData->pSubMenu ) ) // KillActivePopup(); aSubmenuCloseTimer.Stop(); if( ! pMenu ) return; if ( nHighlightedItem != ITEMPOS_INVALID ) { InvalidateItem(nHighlightedItem); pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem ); } nHighlightedItem = n; SAL_WARN_IF( !pMenu->ImplIsVisible( nHighlightedItem ) && nHighlightedItem != ITEMPOS_INVALID, "vcl", "ChangeHighlightItem: Not visible!" ); if( nHighlightedItem != ITEMPOS_INVALID ) { if (pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar()) { // #102461# make sure parent entry is highlighted as well size_t i, nCount = pMenu->pStartedFrom->pItemList->size(); for(i = 0; i < nCount; i++) { MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i ); if( pData && ( pData->pSubMenu == pMenu ) ) break; } if( i < nCount ) { MenuFloatingWindow* pPWin = static_cast(pMenu->pStartedFrom->ImplGetWindow()); if( pPWin && pPWin->nHighlightedItem != i ) { pPWin->InvalidateItem(i); pPWin->nHighlightedItem = i; } } } InvalidateItem(nHighlightedItem); pMenu->ImplCallHighlight( nHighlightedItem ); } else { pMenu->nSelectedId = 0; pMenu->sSelectedIdent.clear(); } if ( bStartPopupTimer ) { // #102438# Menu items are not selectable // If a menu item is selected by an AT-tool via the XAccessibleAction, XAccessibleValue // or XAccessibleSelection interface, and the parent popup menus are not executed yet, // the parent popup menus must be executed SYNCHRONOUSLY, before the menu item is selected. if ( GetSettings().GetMouseSettings().GetMenuDelay() ) aHighlightChangedTimer.Start(); else HighlightChanged( &aHighlightChangedTimer ); } } /// Calculate the initial vertical pixel offset of the first item. /// May be negative for scrolled windows. tools::Long MenuFloatingWindow::GetInitialItemY(tools::Long *pStartY) const { tools::Long nStartY = ImplGetStartY(); if (pStartY) *pStartY = nStartY; return nScrollerHeight + nStartY + ImplGetSVData()->maNWFData.mnMenuFormatBorderY; } /// Emit an Invalidate just for this item's area void MenuFloatingWindow::InvalidateItem(sal_uInt16 nPos) { if (!pMenu) return; tools::Long nY = GetInitialItemY(); size_t nCount = pMenu->pItemList->size(); for (size_t n = 0; n < nCount; n++) { MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); tools::Long nHeight = pData->aSz.Height(); if (n == nPos) { Size aWidth( GetSizePixel() ); tools::Rectangle aRect(Point(0, nY), Size(aWidth.Width(), nHeight)); Invalidate( aRect ); } nY += nHeight; } } void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos) { if (!pMenu) return; Size aSz(GetOutputSizePixel()); tools::Long nX = 0; tools::Long nStartY; tools::Long nY = GetInitialItemY(&nStartY); int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX; size_t nCount = pMenu->pItemList->size(); for (size_t n = 0; n < nCount; n++) { MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); if (n == nPos) { SAL_WARN_IF(!pMenu->ImplIsVisible(n), "vcl", "Highlight: Item not visible!"); if (pData->eType != MenuItemType::SEPARATOR) { bool bRestoreLineColor = false; Color oldLineColor; bool bDrawItemRect = true; tools::Rectangle aItemRect(Point(nX + nOuterSpaceX, nY), Size(aSz.Width() - 2 * nOuterSpaceX, pData->aSz.Height())); if (pData->nBits & MenuItemBits::POPUPSELECT) { tools::Long nFontHeight = GetTextHeight(); aItemRect.AdjustRight( -(nFontHeight + nFontHeight / 4) ); } if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) { Size aPxSize(GetOutputSizePixel()); rRenderContext.Push(vcl::PushFlags::CLIPREGION); rRenderContext.IntersectClipRegion(tools::Rectangle(Point(nX, nY), Size(aSz.Width(), pData->aSz.Height()))); tools::Rectangle aCtrlRect(Point(nX, 0), Size(aPxSize.Width()-nX, aPxSize.Height())); MenupopupValue aVal(pMenu->nTextPos-GUTTERBORDER, aItemRect); rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect, ControlState::ENABLED, aVal, OUString()); if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem)) { bDrawItemRect = false; if (!rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem, aItemRect, ControlState::SELECTED | (pData->bEnabled ? ControlState::ENABLED : ControlState::NONE), aVal, OUString())) { bDrawItemRect = true; } } else bDrawItemRect = true; rRenderContext.Pop(); } if (bDrawItemRect) { if (pData->bEnabled) rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor()); else { rRenderContext.SetFillColor(); oldLineColor = rRenderContext.GetLineColor(); rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor()); bRestoreLineColor = true; } rRenderContext.DrawRect(aItemRect); } pMenu->ImplPaint(rRenderContext, GetOutputSizePixel(), nScrollerHeight, nStartY, pData, true/*bHighlight*/); if (bRestoreLineColor) rRenderContext.SetLineColor(oldLineColor); } return; } nY += pData->aSz.Height(); } } tools::Rectangle MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos ) const { if( ! pMenu ) return tools::Rectangle(); tools::Rectangle aRect; Size aSz = GetOutputSizePixel(); tools::Long nStartY = ImplGetStartY(); tools::Long nY = nScrollerHeight+nStartY; size_t nCount = pMenu->pItemList->size(); for ( size_t n = 0; n < nCount; n++ ) { MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); if ( n == nPos ) { SAL_WARN_IF( !pMenu->ImplIsVisible( n ), "vcl", "ImplGetItemRect: Item not visible!" ); if ( pData->eType != MenuItemType::SEPARATOR ) { aRect = tools::Rectangle( Point( 0, nY ), Size( aSz.Width(), pData->aSz.Height() ) ); if ( pData->nBits & MenuItemBits::POPUPSELECT ) { tools::Long nFontHeight = GetTextHeight(); aRect.AdjustRight( -(nFontHeight + nFontHeight/4) ); } } break; } nY += pData->aSz.Height(); } return aRect; } void MenuFloatingWindow::ImplCursorUpDown( bool bUp, bool bHomeEnd ) { if( ! pMenu ) return; const StyleSettings& rSettings = GetSettings().GetStyleSettings(); sal_uInt16 n = nHighlightedItem; if ( n == ITEMPOS_INVALID ) { if ( bUp ) n = 0; else n = pMenu->GetItemCount()-1; } sal_uInt16 nLoop = n; if( bHomeEnd ) { // absolute positioning if( bUp ) { n = pMenu->GetItemCount(); nLoop = n-1; } else { n = sal_uInt16(-1); nLoop = n+1; } } do { if ( bUp ) { if ( n ) n--; else if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) ) n = pMenu->GetItemCount()-1; else break; } else { n++; if ( n >= pMenu->GetItemCount() ) { if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) ) n = 0; else break; } } MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( n ); if ( ( pData->bEnabled || !rSettings.GetSkipDisabledInMenus() ) && ( pData->eType != MenuItemType::SEPARATOR ) && pMenu->ImplIsVisible( n ) && pMenu->ImplIsSelectable( n ) ) { // Is selection in visible area? if ( IsScrollMenu() ) { ChangeHighlightItem( ITEMPOS_INVALID, false ); while ( n < nFirstEntry ) ImplScroll( true ); Size aOutSz = GetOutputSizePixel(); sal_uInt16 nLastVisible; static_cast(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible ); while ( n > nLastVisible ) { ImplScroll( false ); static_cast(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible ); } } ChangeHighlightItem( n, false ); break; } } while ( n != nLoop ); } void MenuFloatingWindow::KeyInput( const KeyEvent& rKEvent ) { VclPtr xWindow = this; bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel; sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode(); bKeyInput = true; switch ( nCode ) { case KEY_UP: case KEY_DOWN: { ImplCursorUpDown( nCode == KEY_UP ); } break; case KEY_END: case KEY_HOME: { ImplCursorUpDown( nCode == KEY_END, true ); } break; case KEY_F6: case KEY_ESCAPE: { // Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document if( nCode == KEY_F6 && !rKEvent.GetKeyCode().IsMod1() ) break; if( pMenu ) { if ( !pMenu->pStartedFrom ) { StopExecute(); KillActivePopup(); } else if (pMenu->pStartedFrom->IsMenuBar()) { pMenu->pStartedFrom->MenuBarKeyInput(rKEvent); } else { StopExecute(); PopupMenu* pPopupMenu = static_cast(pMenu->pStartedFrom.get()); MenuFloatingWindow* pFloat = pPopupMenu->ImplGetFloatingWindow(); pFloat->GrabFocus(); pFloat->KillActivePopup(); pPopupMenu->ImplCallHighlight(pFloat->nHighlightedItem); } } } break; case KEY_LEFT: { if ( pMenu && pMenu->pStartedFrom ) { StopExecute(); if (pMenu->pStartedFrom->IsMenuBar()) { pMenu->pStartedFrom->MenuBarKeyInput(rKEvent); } else { MenuFloatingWindow* pFloat = static_cast(pMenu->pStartedFrom.get())->ImplGetFloatingWindow(); pFloat->GrabFocus(); pFloat->KillActivePopup(); sal_uInt16 highlightItem = pFloat->GetHighlightedItem(); pFloat->ChangeHighlightItem(highlightItem, false); } } } break; case KEY_RIGHT: { if( pMenu ) { bool bDone = false; if ( nHighlightedItem != ITEMPOS_INVALID ) { MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ); if ( pData && pData->pSubMenu ) { HighlightChanged( nullptr ); bDone = true; } } if ( !bDone ) { Menu* pStart = pMenu->ImplGetStartMenu(); if (pStart && pStart->IsMenuBar()) { // Forward... pStart->ImplGetWindow()->KeyInput( rKEvent ); } } } } break; case KEY_RETURN: { if( pMenu ) { MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ); if ( pData && pData->bEnabled ) { if ( pData->pSubMenu ) HighlightChanged( nullptr ); else EndExecute(); } else StopExecute(); } } break; case KEY_MENU: { if( pMenu ) { Menu* pStart = pMenu->ImplGetStartMenu(); if (pStart && pStart->IsMenuBar()) { // Forward... pStart->ImplGetWindow()->KeyInput( rKEvent ); } } } break; default: { sal_Unicode nCharCode = rKEvent.GetCharCode(); size_t nPos = 0; size_t nDuplicates = 0; MenuItemData* pData = (nCharCode && pMenu) ? pMenu->GetItemList()->SearchItem(nCharCode, rKEvent.GetKeyCode(), nPos, nDuplicates, nHighlightedItem) : nullptr; if (pData) { if ( pData->pSubMenu || nDuplicates > 1 ) { ChangeHighlightItem( nPos, false ); HighlightChanged( nullptr ); } else { nHighlightedItem = nPos; EndExecute(); } } else FloatingWindow::KeyInput( rKEvent ); } } if (pMenu && pMenu->pStartedFrom && pMenu->pStartedFrom->IsMenuBar()) { MenuBar *pMenuBar = static_cast(pMenu->pStartedFrom.get()); const bool bShowAccels = !autoacc || nCode != KEY_ESCAPE; if (pMenuBar->getMenuBarWindow()->GetMBWMenuKey() != bShowAccels) { pMenuBar->getMenuBarWindow()->SetMBWMenuKey(bShowAccels); pMenuBar->getMenuBarWindow()->SetMBWHideAccel(!bShowAccels); if (autoacc) Invalidate(InvalidateFlags::Update); } } // #105474# check if menu window was not destroyed if ( !xWindow->isDisposed() ) { bKeyInput = false; } } void MenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect) { if (!pMenu) return; // Set the clip before the buffering starts: rPaintRect may be larger than the current clip, // this way the buffer -> render context copy happens with this clip. rRenderContext.Push(vcl::PushFlags::CLIPREGION); rRenderContext.SetClipRegion(vcl::Region(rPaintRect)); // Make sure that all actual rendering happens in one go to avoid flicker. vcl::BufferDevice pBuffer(this, rRenderContext); if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) { pBuffer->SetClipRegion(); tools::Long nX = 0; Size aPxSize(GetOutputSizePixel()); aPxSize.AdjustWidth( -nX ); ImplControlValue aVal(pMenu->nTextPos - GUTTERBORDER); pBuffer->DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, tools::Rectangle(Point(nX, 0), aPxSize), ControlState::ENABLED, aVal, OUString()); InitMenuClipRegion(*pBuffer); } if (IsScrollMenu()) { ImplDrawScroller(*pBuffer, true); ImplDrawScroller(*pBuffer, false); } pBuffer->SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuColor()); pMenu->ImplPaint(*pBuffer, GetOutputSizePixel(), nScrollerHeight, ImplGetStartY()); if (nHighlightedItem != ITEMPOS_INVALID) RenderHighlightItem(*pBuffer, nHighlightedItem); pBuffer.Dispose(); rRenderContext.Pop(); } void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp) { if (!pMenu) return; rRenderContext.SetClipRegion(); Size aOutSz(GetOutputSizePixel()); tools::Long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight); tools::Long nX = 0; tools::Rectangle aRect(Point(nX, nY), Size(aOutSz.Width() - nX, nScrollerHeight)); DecorationView aDecoView(&rRenderContext); SymbolType eSymbol = bUp ? SymbolType::SPIN_UP : SymbolType::SPIN_DOWN; DrawSymbolFlags nStyle = DrawSymbolFlags::NONE; if ((bUp && !bScrollUp) || (!bUp && !bScrollDown)) nStyle |= DrawSymbolFlags::Disable; aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nStyle); InitMenuClipRegion(rRenderContext); } void MenuFloatingWindow::RequestHelp( const HelpEvent& rHEvt ) { sal_uInt16 nId = nHighlightedItem; Menu* pM = pMenu; vcl::Window* pW = this; // #102618# Get item rect before destroying the window in EndExecute() call tools::Rectangle aHighlightRect( ImplGetItemRect( nHighlightedItem ) ); if ( rHEvt.GetMode() & HelpEventMode::CONTEXT ) { nHighlightedItem = ITEMPOS_INVALID; EndExecute(); pW = nullptr; } if( !ImplHandleHelpEvent( pW, pM, nId, rHEvt, aHighlightRect ) ) Window::RequestHelp( rHEvt ); } void MenuFloatingWindow::StateChanged( StateChangedType nType ) { FloatingWindow::StateChanged( nType ); if ( ( nType == StateChangedType::ControlForeground ) || ( nType == StateChangedType::ControlBackground ) ) { ApplySettings(*GetOutDev()); Invalidate(); } } void MenuFloatingWindow::DataChanged( const DataChangedEvent& rDCEvt ) { FloatingWindow::DataChanged( rDCEvt ); if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) { ApplySettings(*GetOutDev()); Invalidate(); } } void MenuFloatingWindow::Command( const CommandEvent& rCEvt ) { if ( rCEvt.GetCommand() == CommandEventId::Wheel ) { const CommandWheelData* pData = rCEvt.GetWheelData(); if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) ) { ImplScroll( pData->GetDelta() > 0 ); MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) ); } } } css::uno::Reference MenuFloatingWindow::CreateAccessible() { css::uno::Reference xAcc; if (pMenu && !pMenu->pStartedFrom) xAcc = pMenu->GetAccessible(); return xAcc; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */