1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "menufloatingwindow.hxx"
21 #include "menuitemlist.hxx"
22 #include "bufferdevice.hxx"
24 #include <sal/log.hxx>
25 #include <salframe.hxx>
27 #include <vcl/decoview.hxx>
28 #include <vcl/settings.hxx>
31 MenuFloatingWindow::MenuFloatingWindow( Menu
* pMen
, vcl::Window
* pParent
, WinBits nStyle
) :
32 FloatingWindow( pParent
, nStyle
),
34 aHighlightChangedTimer("vcl::MenuFloatingWindow aHighlightChangedTimer"),
35 aSubmenuCloseTimer( "vcl::MenuFloatingWindow aSubmenuCloseTimer" ),
36 aScrollTimer( "vcl::MenuFloatingWindow aScrollTimer" ),
37 nHighlightedItem(ITEMPOS_INVALID
),
38 nMBDownPos(ITEMPOS_INVALID
),
41 nPosInParent(ITEMPOS_INVALID
),
46 bIgnoreFirstMove(true),
49 mpWindowImpl
->mbMenuFloatingWindow
= true;
51 ApplySettings(*GetOutDev());
53 SetPopupModeEndHdl( LINK( this, MenuFloatingWindow
, PopupEnd
) );
55 aHighlightChangedTimer
.SetInvokeHandler( LINK( this, MenuFloatingWindow
, HighlightChanged
) );
56 aHighlightChangedTimer
.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
58 aSubmenuCloseTimer
.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
59 aSubmenuCloseTimer
.SetInvokeHandler( LINK( this, MenuFloatingWindow
, SubmenuClose
) );
61 aScrollTimer
.SetInvokeHandler( LINK( this, MenuFloatingWindow
, AutoScroll
) );
63 AddEventListener( LINK( this, MenuFloatingWindow
, ShowHideListener
) );
66 void MenuFloatingWindow::doShutdown()
71 // #105373# notify toolkit that highlight was removed
72 // otherwise the entry will not be read when the menu is opened again
73 if( nHighlightedItem
!= ITEMPOS_INVALID
)
74 pMenu
->ImplCallEventListeners( VclEventId::MenuDehighlight
, nHighlightedItem
);
75 if (!bKeyInput
&& pMenu
&& pMenu
->pStartedFrom
&& !pMenu
->pStartedFrom
->IsMenuBar())
77 // #102461# remove highlight in parent
78 size_t i
, nCount
= pMenu
->pStartedFrom
->pItemList
->size();
79 for(i
= 0; i
< nCount
; i
++)
81 MenuItemData
* pData
= pMenu
->pStartedFrom
->pItemList
->GetDataFromPos( i
);
82 if( pData
&& ( pData
->pSubMenu
== pMenu
) )
87 MenuFloatingWindow
* pPWin
= static_cast<MenuFloatingWindow
*>(pMenu
->pStartedFrom
->ImplGetWindow());
89 pPWin
->InvalidateItem(i
);
93 // free the reference to the accessible component
94 SetAccessible( css::uno::Reference
< css::accessibility::XAccessible
>() );
96 aHighlightChangedTimer
.Stop();
98 // #95056# invalidate screen area covered by system window
99 // so this can be taken into account if the commandhandler performs a scroll operation
102 tools::Rectangle
aInvRect( GetWindowExtentsRelative( GetParent() ) );
103 GetParent()->Invalidate( aInvRect
);
106 RemoveEventListener( LINK( this, MenuFloatingWindow
, ShowHideListener
) );
109 aSubmenuCloseTimer
.Stop();
110 aSubmenuCloseTimer
.Stop();
111 aHighlightChangedTimer
.Stop();
112 aHighlightChangedTimer
.Stop();
116 MenuFloatingWindow::~MenuFloatingWindow()
121 void MenuFloatingWindow::dispose()
125 pActivePopup
.clear();
126 xSaveFocusId
.clear();
127 FloatingWindow::dispose();
130 void MenuFloatingWindow::Resize()
132 InitMenuClipRegion(*GetOutDev()); // FIXME
135 void MenuFloatingWindow::ApplySettings(vcl::RenderContext
& rRenderContext
)
137 FloatingWindow::ApplySettings(rRenderContext
);
139 if (IsNativeControlSupported(ControlType::MenuPopup
, ControlPart::MenuItem
) &&
140 IsNativeControlSupported(ControlType::MenuPopup
, ControlPart::Entire
))
142 AllSettings
aSettings(GetSettings());
143 ImplGetFrame()->UpdateSettings(aSettings
); // Update theme colors.
144 StyleSettings
aStyle(aSettings
.GetStyleSettings());
145 Color aHighlightTextColor
= ImplGetSVData()->maNWFData
.maMenuBarHighlightTextColor
;
146 if (aHighlightTextColor
!= COL_TRANSPARENT
)
148 aStyle
.SetMenuHighlightTextColor(aHighlightTextColor
);
150 aSettings
.SetStyleSettings(aStyle
);
151 GetOutDev()->SetSettings(aSettings
);
154 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
155 SetPointFont(rRenderContext
, rStyleSettings
.GetMenuFont());
157 if (rRenderContext
.IsNativeControlSupported(ControlType::MenuPopup
, ControlPart::Entire
))
159 rRenderContext
.SetBackground(); // background will be drawn by NWF
162 rRenderContext
.SetBackground(Wallpaper(rStyleSettings
.GetMenuColor()));
164 rRenderContext
.SetTextColor(rStyleSettings
.GetMenuTextColor());
165 rRenderContext
.SetTextFillColor();
166 rRenderContext
.SetLineColor();
169 /// Get a negative pixel offset for an offset menu
170 tools::Long
MenuFloatingWindow::ImplGetStartY() const
175 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
176 if ( nFirstEntry
> 0 && !pMenu
->GetItemList()->GetDataFromPos(nFirstEntry
- 1) )
181 for ( sal_uInt16 n
= 0; n
< nFirstEntry
; n
++ )
182 nY
+= pMenu
->GetItemList()->GetDataFromPos( n
)->aSz
.Height();
183 nY
-= pMenu
->GetTitleHeight();
188 vcl::Region
MenuFloatingWindow::ImplCalcClipRegion() const
190 Size aOutSz
= GetOutputSizePixel();
191 tools::Rectangle
aRect( Point(), aOutSz
);
192 aRect
.AdjustTop(nScrollerHeight
);
193 aRect
.AdjustBottom( -nScrollerHeight
);
195 vcl::Region
aRegion(aRect
);
200 void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext
& rRenderContext
)
204 rRenderContext
.SetClipRegion(ImplCalcClipRegion());
208 rRenderContext
.SetClipRegion();
212 void MenuFloatingWindow::ImplHighlightItem( const MouseEvent
& rMEvt
, bool bMBDown
)
217 tools::Long nY
= GetInitialItemY();
218 tools::Long nMouseY
= rMEvt
.GetPosPixel().Y();
219 Size aOutSz
= GetOutputSizePixel();
220 if ( ( nMouseY
>= nY
) && ( nMouseY
< aOutSz
.Height() ) )
222 bool bHighlighted
= false;
223 size_t nCount
= pMenu
->pItemList
->size();
224 for ( size_t n
= 0; !bHighlighted
&& ( n
< nCount
); n
++ )
226 if ( pMenu
->ImplIsVisible( n
) )
228 MenuItemData
* pItemData
= pMenu
->pItemList
->GetDataFromPos( n
);
229 tools::Long nOldY
= nY
;
230 nY
+= pItemData
->aSz
.Height();
231 if ( ( nOldY
<= nMouseY
) && ( nY
> nMouseY
) && pMenu
->ImplIsSelectable( n
) )
233 bool bPopupArea
= true;
234 if ( pItemData
->nBits
& MenuItemBits::POPUPSELECT
)
236 // only when clicked over the arrow...
237 Size aSz
= GetOutputSizePixel();
238 tools::Long nFontHeight
= GetTextHeight();
239 bPopupArea
= ( rMEvt
.GetPosPixel().X() >= ( aSz
.Width() - nFontHeight
- nFontHeight
/4 ) );
244 if ( n
!= nHighlightedItem
)
246 ChangeHighlightItem( static_cast<sal_uInt16
>(n
), false );
249 bool bAllowNewPopup
= true;
252 MenuItemData
* pData
= pMenu
->pItemList
->GetDataFromPos( n
);
253 bAllowNewPopup
= pData
&& ( pData
->pSubMenu
!= pActivePopup
);
254 if ( bAllowNewPopup
)
258 if ( bPopupArea
&& bAllowNewPopup
)
260 HighlightChanged( nullptr );
265 if ( n
!= nHighlightedItem
)
267 ChangeHighlightItem( static_cast<sal_uInt16
>(n
), true );
269 else if ( pItemData
->nBits
& MenuItemBits::POPUPSELECT
)
271 if ( bPopupArea
&& ( pActivePopup
!= pItemData
->pSubMenu
) )
272 HighlightChanged( nullptr );
280 ChangeHighlightItem( ITEMPOS_INVALID
, true );
284 ImplScroll( rMEvt
.GetPosPixel() );
285 ChangeHighlightItem( ITEMPOS_INVALID
, true );
289 IMPL_LINK_NOARG(MenuFloatingWindow
, PopupEnd
, FloatingWindow
*, void)
291 // "this" will be deleted before the end of this method!
298 KillActivePopup(); // should be ok to just remove it
299 //pActivePopup->bCanceled = true;
301 pMenu
->bInCallback
= true;
303 pMenu
->bInCallback
= false;
307 if (pMenu
&& pMenu
->pStartedFrom
)
308 pMenu
->pStartedFrom
->ClosePopup(pMenu
);
312 pM
->pStartedFrom
= nullptr;
315 IMPL_LINK_NOARG(MenuFloatingWindow
, AutoScroll
, Timer
*, void)
317 ImplScroll( GetPointerPosPixel() );
320 IMPL_LINK( MenuFloatingWindow
, HighlightChanged
, Timer
*, pTimer
, void )
325 MenuItemData
* pItemData
= pMenu
->pItemList
->GetDataFromPos( nHighlightedItem
);
329 if ( pActivePopup
&& ( pActivePopup
!= pItemData
->pSubMenu
) )
331 FloatWinPopupFlags nOldFlags
= GetPopupModeFlags();
332 SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose
);
334 SetPopupModeFlags( nOldFlags
);
336 if ( !(pItemData
->bEnabled
&& pItemData
->pSubMenu
&& pItemData
->pSubMenu
->GetItemCount() && ( pItemData
->pSubMenu
!= pActivePopup
)) )
339 pActivePopup
= static_cast<PopupMenu
*>(pItemData
->pSubMenu
.get());
340 tools::Long nY
= nScrollerHeight
+ImplGetStartY();
341 MenuItemData
* pData
= nullptr;
342 for ( sal_uLong n
= 0; n
< nHighlightedItem
; n
++ )
344 pData
= pMenu
->pItemList
->GetDataFromPos( n
);
345 nY
+= pData
->aSz
.Height();
347 pData
= pMenu
->pItemList
->GetDataFromPos( nHighlightedItem
);
348 Size MySize
= GetOutputSizePixel();
349 Point
aItemTopLeft( 0, nY
);
350 Point
aItemBottomRight( aItemTopLeft
);
351 aItemBottomRight
.AdjustX(MySize
.Width() );
352 aItemBottomRight
.AdjustY(pData
->aSz
.Height() );
354 // shift the popups a little
355 aItemTopLeft
.AdjustX(2 );
356 aItemBottomRight
.AdjustX( -2 );
357 if ( nHighlightedItem
)
358 aItemTopLeft
.AdjustY( -2 );
361 sal_Int32 nL
, nT
, nR
, nB
;
362 GetBorder( nL
, nT
, nR
, nB
);
363 aItemTopLeft
.AdjustY( -nT
);
366 // pTest: crash due to Reschedule() in call of Activate()
367 // Also it is prevented that submenus are displayed which
368 // were for long in Activate Rescheduled and which should not be
370 Menu
* pTest
= pActivePopup
;
371 FloatWinPopupFlags nOldFlags
= GetPopupModeFlags();
372 SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose
);
373 sal_uInt16 nRet
= pActivePopup
->ImplExecute( this, tools::Rectangle( aItemTopLeft
, aItemBottomRight
), FloatWinPopupFlags::Right
, pMenu
, pTimer
== nullptr );
374 SetPopupModeFlags( nOldFlags
);
376 // nRet != 0, if it was stopped during Activate()...
377 if ( !nRet
&& ( pActivePopup
== pTest
) && pActivePopup
->ImplGetWindow() )
378 pActivePopup
->ImplGetFloatingWindow()->AddPopupModeWindow( this );
381 IMPL_LINK_NOARG(MenuFloatingWindow
, SubmenuClose
, Timer
*, void)
383 if( pMenu
&& pMenu
->pStartedFrom
)
385 MenuFloatingWindow
* pWin
= static_cast<MenuFloatingWindow
*>(pMenu
->pStartedFrom
->GetWindow());
387 pWin
->KillActivePopup();
391 IMPL_LINK( MenuFloatingWindow
, ShowHideListener
, VclWindowEvent
&, rEvent
, void )
396 if( rEvent
.GetId() == VclEventId::WindowShow
)
397 pMenu
->ImplCallEventListeners( VclEventId::MenuShow
, ITEMPOS_INVALID
);
398 else if( rEvent
.GetId() == VclEventId::WindowHide
)
399 pMenu
->ImplCallEventListeners( VclEventId::MenuHide
, ITEMPOS_INVALID
);
402 void MenuFloatingWindow::EnableScrollMenu( bool b
)
405 nScrollerHeight
= b
? static_cast<sal_uInt16
>(GetSettings().GetStyleSettings().GetScrollBarSize()) /2 : 0;
407 InitMenuClipRegion(*GetOutDev());
410 void MenuFloatingWindow::Start()
416 GetParent()->IncModalCount();
419 bool MenuFloatingWindow::MenuInHierarchyHasFocus() const
421 if (HasChildPathFocus())
423 PopupMenu
* pSub
= GetActivePopup();
426 return pSub
->ImplGetFloatingWindow()->HasChildPathFocus();
429 void MenuFloatingWindow::End()
434 if (GetParent() && !GetParent()->isDisposed())
435 GetParent()->DecModalCount();
437 // restore focus to previous window if we still have the focus
438 VclPtr
<vcl::Window
> xFocusId(xSaveFocusId
);
439 xSaveFocusId
= nullptr;
440 if (xFocusId
!= nullptr && MenuInHierarchyHasFocus())
442 ImplGetSVData()->mpWinData
->mbNoDeactivate
= false;
443 Window::EndSaveFocus(xFocusId
);
449 void MenuFloatingWindow::Execute()
451 ImplSVData
* pSVData
= ImplGetSVData();
453 pSVData
->maAppData
.mpActivePopupMenu
= static_cast<PopupMenu
*>(pMenu
.get());
457 while (bInExecute
&& !Application::IsQuit())
458 Application::Yield();
460 pSVData
->maAppData
.mpActivePopupMenu
= nullptr;
463 void MenuFloatingWindow::StopExecute()
467 ImplEndPopupMode(FloatWinPopupEndFlags::NONE
, xSaveFocusId
);
469 aHighlightChangedTimer
.Stop();
474 // notify parent, needed for accessibility
475 if( pMenu
&& pMenu
->pStartedFrom
)
476 pMenu
->pStartedFrom
->ImplCallEventListeners( VclEventId::MenuSubmenuDeactivate
, nPosInParent
);
479 void MenuFloatingWindow::KillActivePopup( PopupMenu
* pThisOnly
)
481 if ( !pActivePopup
|| ( pThisOnly
&& ( pThisOnly
!= pActivePopup
) ) )
484 if( pActivePopup
->pWindow
)
485 if( static_cast<FloatingWindow
*>(pActivePopup
->pWindow
.get())->IsInCleanUp() )
486 return; // kill it later
487 if ( pActivePopup
->bInCallback
)
488 pActivePopup
->bCanceled
= true;
490 // For all actions pActivePopup = 0, if e.g.
491 // PopupModeEndHdl the popups to destroy were called synchronous
492 PopupMenu
* pPopup
= pActivePopup
;
493 pActivePopup
= nullptr;
494 pPopup
->bInCallback
= true;
495 pPopup
->Deactivate();
496 pPopup
->bInCallback
= false;
497 if ( pPopup
->ImplGetWindow() )
499 pPopup
->ImplGetFloatingWindow()->StopExecute();
500 pPopup
->ImplGetFloatingWindow()->doShutdown();
501 pPopup
->pWindow
.disposeAndClear();
507 void MenuFloatingWindow::EndExecute()
509 Menu
* pStart
= pMenu
? pMenu
->ImplGetStartMenu() : nullptr;
511 // if started elsewhere, cleanup there as well
512 MenuFloatingWindow
* pCleanUpFrom
= this;
513 MenuFloatingWindow
* pWin
= this;
514 while (pWin
&& !pWin
->bInExecute
&&
515 pWin
->pMenu
->pStartedFrom
&& !pWin
->pMenu
->pStartedFrom
->IsMenuBar())
517 pWin
= static_cast<PopupMenu
*>(pWin
->pMenu
->pStartedFrom
.get())->ImplGetFloatingWindow();
522 // this window will be destroyed => store date locally...
524 sal_uInt16 nItem
= nHighlightedItem
;
526 pCleanUpFrom
->StopExecute();
528 if ( !(nItem
!= ITEMPOS_INVALID
&& pM
) )
531 MenuItemData
* pItemData
= pM
->GetItemList()->GetDataFromPos( nItem
);
532 if ( pItemData
&& !pItemData
->bIsTemporary
)
534 pM
->nSelectedId
= pItemData
->nId
;
535 pM
->sSelectedIdent
= pItemData
->sIdent
;
538 pStart
->nSelectedId
= pItemData
->nId
;
539 pStart
->sSelectedIdent
= pItemData
->sIdent
;
546 void MenuFloatingWindow::EndExecute( sal_uInt16 nId
)
549 if ( pMenu
&& pMenu
->GetItemList()->GetData( nId
, nPos
) )
550 nHighlightedItem
= nPos
;
552 nHighlightedItem
= ITEMPOS_INVALID
;
557 void MenuFloatingWindow::MouseButtonDown( const MouseEvent
& rMEvt
)
559 // TH creates a ToTop on this window, but the active popup
560 // should stay on top...
561 // due to focus change this would close all menus -> don't do it (#94123)
562 //if ( pActivePopup && pActivePopup->ImplGetWindow() && !pActivePopup->ImplGetFloatingWindow()->pActivePopup )
563 // pActivePopup->ImplGetFloatingWindow()->ToTop( ToTopFlags::NoGrabFocus );
565 ImplHighlightItem( rMEvt
, true );
567 nMBDownPos
= nHighlightedItem
;
570 void MenuFloatingWindow::MouseButtonUp( const MouseEvent
& rMEvt
)
572 MenuItemData
* pData
= pMenu
? pMenu
->GetItemList()->GetDataFromPos( nHighlightedItem
) : nullptr;
573 // nMBDownPos store in local variable and reset immediately,
574 // as it will be too late after EndExecute
575 sal_uInt16 _nMBDownPos
= nMBDownPos
;
576 nMBDownPos
= ITEMPOS_INVALID
;
577 if ( !(pData
&& pData
->bEnabled
&& ( pData
->eType
!= MenuItemType::SEPARATOR
)) )
580 if ( !pData
->pSubMenu
)
584 else if ( ( pData
->nBits
& MenuItemBits::POPUPSELECT
) && ( nHighlightedItem
== _nMBDownPos
) && ( rMEvt
.GetClicks() == 2 ) )
586 // not when clicked over the arrow...
587 Size aSz
= GetOutputSizePixel();
588 tools::Long nFontHeight
= GetTextHeight();
589 if ( rMEvt
.GetPosPixel().X() < ( aSz
.Width() - nFontHeight
- nFontHeight
/4 ) )
595 void MenuFloatingWindow::MouseMove( const MouseEvent
& rMEvt
)
597 if ( !IsVisible() || rMEvt
.IsSynthetic() || rMEvt
.IsEnterWindow() )
600 if ( rMEvt
.IsLeaveWindow() )
602 // #102461# do not remove highlight if a popup menu is open at this position
603 MenuItemData
* pData
= pMenu
? pMenu
->pItemList
->GetDataFromPos( nHighlightedItem
) : nullptr;
604 // close popup with some delayed if we leave somewhere else
605 if( pActivePopup
&& pData
&& pData
->pSubMenu
!= pActivePopup
)
606 pActivePopup
->ImplGetFloatingWindow()->aSubmenuCloseTimer
.Start();
608 if( !pActivePopup
|| (pData
&& pData
->pSubMenu
!= pActivePopup
) )
609 ChangeHighlightItem( ITEMPOS_INVALID
, false );
611 if ( IsScrollMenu() )
612 ImplScroll( rMEvt
.GetPosPixel() );
616 aSubmenuCloseTimer
.Stop();
617 if( bIgnoreFirstMove
)
618 bIgnoreFirstMove
= false;
620 ImplHighlightItem( rMEvt
, false );
624 void MenuFloatingWindow::ImplScroll( bool bUp
)
634 pMenu
->ImplKillLayoutData();
636 if ( bScrollUp
&& bUp
)
638 nFirstEntry
= pMenu
->ImplGetPrevVisible( nFirstEntry
);
639 SAL_WARN_IF( nFirstEntry
== ITEMPOS_INVALID
, "vcl", "Scroll?!" );
641 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
642 const auto pItemData
= pMenu
->GetItemList()->GetDataFromPos( nFirstEntry
);
645 tools::Long nScrollEntryHeight
= pItemData
->aSz
.Height();
653 if ( pMenu
->ImplGetPrevVisible( nFirstEntry
) == ITEMPOS_INVALID
)
659 Scroll( 0, nScrollEntryHeight
, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip
);
662 else if ( bScrollDown
&& !bUp
)
664 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
665 const auto pItemData
= pMenu
->GetItemList()->GetDataFromPos( nFirstEntry
);
668 tools::Long nScrollEntryHeight
= pItemData
->aSz
.Height();
670 nFirstEntry
= pMenu
->ImplGetNextVisible( nFirstEntry
);
671 SAL_WARN_IF( nFirstEntry
== ITEMPOS_INVALID
, "vcl", "Scroll?!" );
679 tools::Long nHeight
= GetOutputSizePixel().Height();
680 sal_uInt16 nLastVisible
;
681 static_cast<PopupMenu
*>(pMenu
.get())->ImplCalcVisEntries( nHeight
, nFirstEntry
, &nLastVisible
);
682 if ( pMenu
->ImplGetNextVisible( nLastVisible
) == ITEMPOS_INVALID
)
688 Scroll( 0, -nScrollEntryHeight
, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip
);
695 void MenuFloatingWindow::ImplScroll( const Point
& rMousePos
)
697 Size aOutSz
= GetOutputSizePixel();
699 tools::Long nY
= nScrollerHeight
;
700 tools::Long nMouseY
= rMousePos
.Y();
701 tools::Long nDelta
= 0;
703 if ( bScrollUp
&& ( nMouseY
< nY
) )
706 nDelta
= nY
- nMouseY
;
708 else if ( bScrollDown
&& ( nMouseY
> ( aOutSz
.Height() - nY
) ) )
711 nDelta
= nMouseY
- ( aOutSz
.Height() - nY
);
717 aScrollTimer
.Stop(); // if scrolled through MouseMove.
718 tools::Long nTimeout
;
721 else if ( nDelta
< 5 )
723 else if ( nDelta
< 8 )
725 else if ( nDelta
< 12 )
729 aScrollTimer
.SetTimeout( nTimeout
);
730 aScrollTimer
.Start();
732 void MenuFloatingWindow::ChangeHighlightItem( sal_uInt16 n
, bool bStartPopupTimer
)
734 // #57934# if necessary, immediately close the active, as TH's backgroundstorage works.
735 // #65750# we prefer to refrain from the background storage of small lines.
736 // otherwise the menus are difficult to operate.
737 // MenuItemData* pNextData = pMenu->pItemList->GetDataFromPos( n );
738 // if ( pActivePopup && pNextData && ( pActivePopup != pNextData->pSubMenu ) )
739 // KillActivePopup();
741 aSubmenuCloseTimer
.Stop();
745 if ( nHighlightedItem
!= ITEMPOS_INVALID
)
747 InvalidateItem(nHighlightedItem
);
748 pMenu
->ImplCallEventListeners( VclEventId::MenuDehighlight
, nHighlightedItem
);
751 nHighlightedItem
= n
;
752 SAL_WARN_IF( !pMenu
->ImplIsVisible( nHighlightedItem
) && nHighlightedItem
!= ITEMPOS_INVALID
, "vcl", "ChangeHighlightItem: Not visible!" );
753 if( nHighlightedItem
!= ITEMPOS_INVALID
)
755 if (pMenu
->pStartedFrom
&& !pMenu
->pStartedFrom
->IsMenuBar())
757 // #102461# make sure parent entry is highlighted as well
758 size_t i
, nCount
= pMenu
->pStartedFrom
->pItemList
->size();
759 for(i
= 0; i
< nCount
; i
++)
761 MenuItemData
* pData
= pMenu
->pStartedFrom
->pItemList
->GetDataFromPos( i
);
762 if( pData
&& ( pData
->pSubMenu
== pMenu
) )
767 MenuFloatingWindow
* pPWin
= static_cast<MenuFloatingWindow
*>(pMenu
->pStartedFrom
->ImplGetWindow());
768 if( pPWin
&& pPWin
->nHighlightedItem
!= i
)
770 pPWin
->InvalidateItem(i
);
771 pPWin
->nHighlightedItem
= i
;
775 InvalidateItem(nHighlightedItem
);
776 pMenu
->ImplCallHighlight( nHighlightedItem
);
780 pMenu
->nSelectedId
= 0;
781 pMenu
->sSelectedIdent
.clear();
784 if ( bStartPopupTimer
)
786 // #102438# Menu items are not selectable
787 // If a menu item is selected by an AT-tool via the XAccessibleAction, XAccessibleValue
788 // or XAccessibleSelection interface, and the parent popup menus are not executed yet,
789 // the parent popup menus must be executed SYNCHRONOUSLY, before the menu item is selected.
790 if ( GetSettings().GetMouseSettings().GetMenuDelay() )
791 aHighlightChangedTimer
.Start();
793 HighlightChanged( &aHighlightChangedTimer
);
797 /// Calculate the initial vertical pixel offset of the first item.
798 /// May be negative for scrolled windows.
799 tools::Long
MenuFloatingWindow::GetInitialItemY(tools::Long
*pStartY
) const
801 tools::Long nStartY
= ImplGetStartY();
804 return nScrollerHeight
+ nStartY
+
805 ImplGetSVData()->maNWFData
.mnMenuFormatBorderY
;
808 /// Emit an Invalidate just for this item's area
809 void MenuFloatingWindow::InvalidateItem(sal_uInt16 nPos
)
814 tools::Long nY
= GetInitialItemY();
815 size_t nCount
= pMenu
->pItemList
->size();
816 for (size_t n
= 0; n
< nCount
; n
++)
818 MenuItemData
* pData
= pMenu
->pItemList
->GetDataFromPos( n
);
819 tools::Long nHeight
= pData
->aSz
.Height();
822 Size
aWidth( GetSizePixel() );
823 tools::Rectangle
aRect(Point(0, nY
), Size(aWidth
.Width(), nHeight
));
830 void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext
& rRenderContext
, sal_uInt16 nPos
)
835 Size
aSz(GetOutputSizePixel());
839 tools::Long nY
= GetInitialItemY(&nStartY
);
841 int nOuterSpaceX
= ImplGetSVData()->maNWFData
.mnMenuFormatBorderX
;
843 size_t nCount
= pMenu
->pItemList
->size();
844 for (size_t n
= 0; n
< nCount
; n
++)
846 MenuItemData
* pData
= pMenu
->pItemList
->GetDataFromPos( n
);
849 SAL_WARN_IF(!pMenu
->ImplIsVisible(n
), "vcl", "Highlight: Item not visible!");
850 if (pData
->eType
!= MenuItemType::SEPARATOR
)
852 bool bRestoreLineColor
= false;
854 bool bDrawItemRect
= true;
856 tools::Rectangle
aItemRect(Point(nX
+ nOuterSpaceX
, nY
), Size(aSz
.Width() - 2 * nOuterSpaceX
, pData
->aSz
.Height()));
857 if (pData
->nBits
& MenuItemBits::POPUPSELECT
)
859 tools::Long nFontHeight
= GetTextHeight();
860 aItemRect
.AdjustRight( -(nFontHeight
+ nFontHeight
/ 4) );
863 if (rRenderContext
.IsNativeControlSupported(ControlType::MenuPopup
, ControlPart::Entire
))
865 Size
aPxSize(GetOutputSizePixel());
866 rRenderContext
.Push(vcl::PushFlags::CLIPREGION
);
867 rRenderContext
.IntersectClipRegion(tools::Rectangle(Point(nX
, nY
), Size(aSz
.Width(), pData
->aSz
.Height())));
868 tools::Rectangle
aCtrlRect(Point(nX
, 0), Size(aPxSize
.Width()-nX
, aPxSize
.Height()));
869 MenupopupValue
aVal(pMenu
->nTextPos
-GUTTERBORDER
, aItemRect
);
870 rRenderContext
.DrawNativeControl(ControlType::MenuPopup
, ControlPart::Entire
,
871 aCtrlRect
, ControlState::ENABLED
, aVal
, OUString());
872 if (rRenderContext
.IsNativeControlSupported(ControlType::MenuPopup
, ControlPart::MenuItem
))
874 bDrawItemRect
= false;
875 if (!rRenderContext
.DrawNativeControl(ControlType::MenuPopup
, ControlPart::MenuItem
, aItemRect
,
876 ControlState::SELECTED
| (pData
->bEnabled
877 ? ControlState::ENABLED
878 : ControlState::NONE
),
881 bDrawItemRect
= true;
885 bDrawItemRect
= true;
886 rRenderContext
.Pop();
891 rRenderContext
.SetFillColor(rRenderContext
.GetSettings().GetStyleSettings().GetMenuHighlightColor());
894 rRenderContext
.SetFillColor();
895 oldLineColor
= rRenderContext
.GetLineColor();
896 rRenderContext
.SetLineColor(rRenderContext
.GetSettings().GetStyleSettings().GetMenuHighlightColor());
897 bRestoreLineColor
= true;
900 rRenderContext
.DrawRect(aItemRect
);
902 pMenu
->ImplPaint(rRenderContext
, GetOutputSizePixel(), nScrollerHeight
, nStartY
, pData
, true/*bHighlight*/);
903 if (bRestoreLineColor
)
904 rRenderContext
.SetLineColor(oldLineColor
);
909 nY
+= pData
->aSz
.Height();
913 tools::Rectangle
MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos
) const
916 return tools::Rectangle();
918 tools::Rectangle aRect
;
919 Size aSz
= GetOutputSizePixel();
920 tools::Long nStartY
= ImplGetStartY();
921 tools::Long nY
= nScrollerHeight
+nStartY
;
923 size_t nCount
= pMenu
->pItemList
->size();
924 for ( size_t n
= 0; n
< nCount
; n
++ )
926 MenuItemData
* pData
= pMenu
->pItemList
->GetDataFromPos( n
);
929 SAL_WARN_IF( !pMenu
->ImplIsVisible( n
), "vcl", "ImplGetItemRect: Item not visible!" );
930 if ( pData
->eType
!= MenuItemType::SEPARATOR
)
932 aRect
= tools::Rectangle( Point( 0, nY
), Size( aSz
.Width(), pData
->aSz
.Height() ) );
933 if ( pData
->nBits
& MenuItemBits::POPUPSELECT
)
935 tools::Long nFontHeight
= GetTextHeight();
936 aRect
.AdjustRight( -(nFontHeight
+ nFontHeight
/4) );
941 nY
+= pData
->aSz
.Height();
946 void MenuFloatingWindow::ImplCursorUpDown( bool bUp
, bool bHomeEnd
)
951 const StyleSettings
& rSettings
= GetSettings().GetStyleSettings();
953 sal_uInt16 n
= nHighlightedItem
;
954 if ( n
== ITEMPOS_INVALID
)
959 n
= pMenu
->GetItemCount()-1;
962 sal_uInt16 nLoop
= n
;
966 // absolute positioning
969 n
= pMenu
->GetItemCount();
986 if ( !IsScrollMenu() || ( nHighlightedItem
== ITEMPOS_INVALID
) )
987 n
= pMenu
->GetItemCount()-1;
994 if ( n
>= pMenu
->GetItemCount() )
996 if ( !IsScrollMenu() || ( nHighlightedItem
== ITEMPOS_INVALID
) )
1003 MenuItemData
* pData
= pMenu
->GetItemList()->GetDataFromPos( n
);
1004 if ( ( pData
->bEnabled
|| !rSettings
.GetSkipDisabledInMenus() )
1005 && ( pData
->eType
!= MenuItemType::SEPARATOR
) && pMenu
->ImplIsVisible( n
) && pMenu
->ImplIsSelectable( n
) )
1007 // Is selection in visible area?
1008 if ( IsScrollMenu() )
1010 ChangeHighlightItem( ITEMPOS_INVALID
, false );
1012 while ( n
< nFirstEntry
)
1015 Size aOutSz
= GetOutputSizePixel();
1016 sal_uInt16 nLastVisible
;
1017 static_cast<PopupMenu
*>(pMenu
.get())->ImplCalcVisEntries( aOutSz
.Height(), nFirstEntry
, &nLastVisible
);
1018 while ( n
> nLastVisible
)
1020 ImplScroll( false );
1021 static_cast<PopupMenu
*>(pMenu
.get())->ImplCalcVisEntries( aOutSz
.Height(), nFirstEntry
, &nLastVisible
);
1024 ChangeHighlightItem( n
, false );
1027 } while ( n
!= nLoop
);
1030 void MenuFloatingWindow::KeyInput( const KeyEvent
& rKEvent
)
1032 VclPtr
<vcl::Window
> xWindow
= this;
1034 sal_uInt16 nCode
= rKEvent
.GetKeyCode().GetCode();
1041 ImplCursorUpDown( nCode
== KEY_UP
);
1047 ImplCursorUpDown( nCode
== KEY_END
, true );
1053 // Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document
1054 if( nCode
== KEY_F6
&& !rKEvent
.GetKeyCode().IsMod1() )
1058 if ( !pMenu
->pStartedFrom
)
1063 else if (pMenu
->pStartedFrom
->IsMenuBar())
1065 pMenu
->pStartedFrom
->MenuBarKeyInput(rKEvent
);
1070 PopupMenu
* pPopupMenu
= static_cast<PopupMenu
*>(pMenu
->pStartedFrom
.get());
1071 MenuFloatingWindow
* pFloat
= pPopupMenu
->ImplGetFloatingWindow();
1072 pFloat
->GrabFocus();
1073 pFloat
->KillActivePopup();
1074 pPopupMenu
->ImplCallHighlight(pFloat
->nHighlightedItem
);
1081 if ( pMenu
&& pMenu
->pStartedFrom
)
1084 if (pMenu
->pStartedFrom
->IsMenuBar())
1086 pMenu
->pStartedFrom
->MenuBarKeyInput(rKEvent
);
1090 MenuFloatingWindow
* pFloat
= static_cast<PopupMenu
*>(pMenu
->pStartedFrom
.get())->ImplGetFloatingWindow();
1091 pFloat
->GrabFocus();
1092 pFloat
->KillActivePopup();
1093 sal_uInt16 highlightItem
= pFloat
->GetHighlightedItem();
1094 pFloat
->ChangeHighlightItem(highlightItem
, false);
1104 if ( nHighlightedItem
!= ITEMPOS_INVALID
)
1106 MenuItemData
* pData
= pMenu
->GetItemList()->GetDataFromPos( nHighlightedItem
);
1107 if ( pData
&& pData
->pSubMenu
)
1109 HighlightChanged( nullptr );
1115 Menu
* pStart
= pMenu
->ImplGetStartMenu();
1116 if (pStart
&& pStart
->IsMenuBar())
1119 pStart
->ImplGetWindow()->KeyInput( rKEvent
);
1129 MenuItemData
* pData
= pMenu
->GetItemList()->GetDataFromPos( nHighlightedItem
);
1130 if ( pData
&& pData
->bEnabled
)
1132 if ( pData
->pSubMenu
)
1133 HighlightChanged( nullptr );
1146 Menu
* pStart
= pMenu
->ImplGetStartMenu();
1147 if (pStart
&& pStart
->IsMenuBar())
1150 pStart
->ImplGetWindow()->KeyInput( rKEvent
);
1157 sal_Unicode nCharCode
= rKEvent
.GetCharCode();
1159 size_t nDuplicates
= 0;
1160 MenuItemData
* pData
= (nCharCode
&& pMenu
) ?
1161 pMenu
->GetItemList()->SearchItem(nCharCode
, rKEvent
.GetKeyCode(), nPos
, nDuplicates
, nHighlightedItem
) : nullptr;
1164 if ( pData
->pSubMenu
|| nDuplicates
> 1 )
1166 ChangeHighlightItem( nPos
, false );
1167 HighlightChanged( nullptr );
1171 nHighlightedItem
= nPos
;
1176 FloatingWindow::KeyInput( rKEvent
);
1180 // #105474# check if menu window was not destroyed
1181 if ( !xWindow
->isDisposed() )
1187 void MenuFloatingWindow::Paint(vcl::RenderContext
& rRenderContext
, const tools::Rectangle
&rPaintRect
)
1192 // Set the clip before the buffering starts: rPaintRect may be larger than the current clip,
1193 // this way the buffer -> render context copy happens with this clip.
1194 rRenderContext
.Push(vcl::PushFlags::CLIPREGION
);
1195 rRenderContext
.SetClipRegion(vcl::Region(rPaintRect
));
1197 // Make sure that all actual rendering happens in one go to avoid flicker.
1198 vcl::BufferDevice
pBuffer(this, rRenderContext
);
1200 if (rRenderContext
.IsNativeControlSupported(ControlType::MenuPopup
, ControlPart::Entire
))
1202 pBuffer
->SetClipRegion();
1204 Size
aPxSize(GetOutputSizePixel());
1205 aPxSize
.AdjustWidth( -nX
);
1206 ImplControlValue
aVal(pMenu
->nTextPos
- GUTTERBORDER
);
1207 pBuffer
->DrawNativeControl(ControlType::MenuPopup
, ControlPart::Entire
,
1208 tools::Rectangle(Point(nX
, 0), aPxSize
), ControlState::ENABLED
,
1210 InitMenuClipRegion(*pBuffer
);
1214 ImplDrawScroller(*pBuffer
, true);
1215 ImplDrawScroller(*pBuffer
, false);
1217 pBuffer
->SetFillColor(rRenderContext
.GetSettings().GetStyleSettings().GetMenuColor());
1218 pMenu
->ImplPaint(*pBuffer
, GetOutputSizePixel(), nScrollerHeight
, ImplGetStartY());
1219 if (nHighlightedItem
!= ITEMPOS_INVALID
)
1220 RenderHighlightItem(*pBuffer
, nHighlightedItem
);
1223 rRenderContext
.Pop();
1226 void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext
& rRenderContext
, bool bUp
)
1231 rRenderContext
.SetClipRegion();
1233 Size
aOutSz(GetOutputSizePixel());
1234 tools::Long nY
= bUp
? 0 : (aOutSz
.Height() - nScrollerHeight
);
1236 tools::Rectangle
aRect(Point(nX
, nY
), Size(aOutSz
.Width() - nX
, nScrollerHeight
));
1238 DecorationView
aDecoView(&rRenderContext
);
1239 SymbolType eSymbol
= bUp
? SymbolType::SPIN_UP
: SymbolType::SPIN_DOWN
;
1241 DrawSymbolFlags nStyle
= DrawSymbolFlags::NONE
;
1242 if ((bUp
&& !bScrollUp
) || (!bUp
&& !bScrollDown
))
1243 nStyle
|= DrawSymbolFlags::Disable
;
1245 aDecoView
.DrawSymbol(aRect
, eSymbol
, rRenderContext
.GetSettings().GetStyleSettings().GetButtonTextColor(), nStyle
);
1247 InitMenuClipRegion(rRenderContext
);
1250 void MenuFloatingWindow::RequestHelp( const HelpEvent
& rHEvt
)
1252 sal_uInt16 nId
= nHighlightedItem
;
1254 vcl::Window
* pW
= this;
1256 // #102618# Get item rect before destroying the window in EndExecute() call
1257 tools::Rectangle
aHighlightRect( ImplGetItemRect( nHighlightedItem
) );
1259 if ( rHEvt
.GetMode() & HelpEventMode::CONTEXT
)
1261 nHighlightedItem
= ITEMPOS_INVALID
;
1266 if( !ImplHandleHelpEvent( pW
, pM
, nId
, rHEvt
, aHighlightRect
) )
1267 Window::RequestHelp( rHEvt
);
1270 void MenuFloatingWindow::StateChanged( StateChangedType nType
)
1272 FloatingWindow::StateChanged( nType
);
1274 if ( ( nType
== StateChangedType::ControlForeground
) || ( nType
== StateChangedType::ControlBackground
) )
1276 ApplySettings(*GetOutDev());
1281 void MenuFloatingWindow::DataChanged( const DataChangedEvent
& rDCEvt
)
1283 FloatingWindow::DataChanged( rDCEvt
);
1285 if ( (rDCEvt
.GetType() == DataChangedEventType::FONTS
) ||
1286 (rDCEvt
.GetType() == DataChangedEventType::FONTSUBSTITUTION
) ||
1287 ((rDCEvt
.GetType() == DataChangedEventType::SETTINGS
) &&
1288 (rDCEvt
.GetFlags() & AllSettingsFlags::STYLE
)) )
1290 ApplySettings(*GetOutDev());
1295 void MenuFloatingWindow::Command( const CommandEvent
& rCEvt
)
1297 if ( rCEvt
.GetCommand() == CommandEventId::Wheel
)
1299 const CommandWheelData
* pData
= rCEvt
.GetWheelData();
1300 if( !pData
->GetModifier() && ( pData
->GetMode() == CommandWheelMode::SCROLL
) )
1302 ImplScroll( pData
->GetDelta() > 0 );
1303 MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) );
1308 css::uno::Reference
<css::accessibility::XAccessible
> MenuFloatingWindow::CreateAccessible()
1310 css::uno::Reference
<css::accessibility::XAccessible
> xAcc
;
1312 if (pMenu
&& !pMenu
->pStartedFrom
)
1313 xAcc
= pMenu
->GetAccessible();
1318 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */