build fix: no comphelper/profilezone.hxx in this branch
[LibreOffice.git] / vcl / source / window / menufloatingwindow.cxx
blob9b09e649a7067f11fd6d46ee7c85da17aeea8b3d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 "menubarwindow.hxx"
24 #include <svdata.hxx>
25 #include <vcl/decoview.hxx>
26 #include <vcl/settings.hxx>
27 #include <window.h>
29 MenuFloatingWindow::MenuFloatingWindow( Menu* pMen, vcl::Window* pParent, WinBits nStyle ) :
30 FloatingWindow( pParent, nStyle )
32 mpWindowImpl->mbMenuFloatingWindow= true;
33 pMenu = pMen;
34 pActivePopup = nullptr;
35 bInExecute = false;
36 bScrollMenu = false;
37 nHighlightedItem = ITEMPOS_INVALID;
38 nMBDownPos = ITEMPOS_INVALID;
39 nPosInParent = ITEMPOS_INVALID;
40 nScrollerHeight = 0;
41 nBorder = EXTRASPACEY;
42 nFirstEntry = 0;
43 bScrollUp = false;
44 bScrollDown = false;
45 bIgnoreFirstMove = true;
46 bKeyInput = false;
48 ApplySettings(*this);
50 SetPopupModeEndHdl( LINK( this, MenuFloatingWindow, PopupEnd ) );
52 aHighlightChangedTimer.SetTimeoutHdl( LINK( this, MenuFloatingWindow, HighlightChanged ) );
53 aHighlightChangedTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
54 aHighlightChangedTimer.SetDebugName( "vcl::MenuFloatingWindow aHighlightChangedTimer" );
56 aSubmenuCloseTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
57 aSubmenuCloseTimer.SetTimeoutHdl( LINK( this, MenuFloatingWindow, SubmenuClose ) );
58 aSubmenuCloseTimer.SetDebugName( "vcl::MenuFloatingWindow aSubmenuCloseTimer" );
60 aScrollTimer.SetTimeoutHdl( LINK( this, MenuFloatingWindow, AutoScroll ) );
61 aScrollTimer.SetDebugName( "vcl::MenuFloatingWindow aScrollTimer" );
63 AddEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
66 void MenuFloatingWindow::doShutdown()
68 if( pMenu )
70 // #105373# notify toolkit that highlight was removed
71 // otherwise the entry will not be read when the menu is opened again
72 if( nHighlightedItem != ITEMPOS_INVALID )
73 pMenu->ImplCallEventListeners( VCLEVENT_MENU_DEHIGHLIGHT, nHighlightedItem );
74 if (!bKeyInput && pMenu && pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar())
76 // #102461# remove highlight in parent
77 size_t i, nCount = pMenu->pStartedFrom->pItemList->size();
78 for(i = 0; i < nCount; i++)
80 MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i );
81 if( pData && ( pData->pSubMenu == pMenu ) )
82 break;
84 if( i < nCount )
86 MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
87 if (pPWin)
88 pPWin->InvalidateItem(i);
92 // free the reference to the accessible component
93 SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
95 aHighlightChangedTimer.Stop();
97 // #95056# invalidate screen area covered by system window
98 // so this can be taken into account if the commandhandler performs a scroll operation
99 if( GetParent() )
101 Rectangle aInvRect( GetWindowExtentsRelative( GetParent() ) );
102 GetParent()->Invalidate( aInvRect );
104 pMenu = nullptr;
105 RemoveEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
107 aScrollTimer.Stop();
108 aSubmenuCloseTimer.Stop();
109 aSubmenuCloseTimer.Stop();
110 aHighlightChangedTimer.Stop();
111 aHighlightChangedTimer.Stop();
115 MenuFloatingWindow::~MenuFloatingWindow()
117 disposeOnce();
120 void MenuFloatingWindow::dispose()
122 doShutdown();
123 pMenu.clear();
124 pActivePopup.clear();
125 xSaveFocusId.clear();
126 FloatingWindow::dispose();
129 void MenuFloatingWindow::Resize()
131 InitMenuClipRegion(*this); // FIXME
134 void MenuFloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext)
136 FloatingWindow::ApplySettings(rRenderContext);
138 const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
140 SetPointFont(rRenderContext, rStyleSettings.GetMenuFont());
142 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
144 rRenderContext.SetBackground(); // background will be drawn by NWF
146 else
147 rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetMenuColor()));
149 rRenderContext.SetTextColor(rStyleSettings.GetMenuTextColor());
150 rRenderContext.SetTextFillColor();
151 rRenderContext.SetLineColor();
154 /// Get a negative pixel offset for an offset menu
155 long MenuFloatingWindow::ImplGetStartY() const
157 long nY = 0;
158 if( pMenu )
160 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
161 if ( nFirstEntry > 0 && !pMenu->GetItemList()->GetDataFromPos(nFirstEntry - 1) )
163 return 0;
166 for ( sal_uInt16 n = 0; n < nFirstEntry; n++ )
167 nY += pMenu->GetItemList()->GetDataFromPos( n )->aSz.Height();
168 nY -= pMenu->GetTitleHeight();
170 return -nY;
173 vcl::Region MenuFloatingWindow::ImplCalcClipRegion( bool bIncludeLogo ) const
175 Size aOutSz = GetOutputSizePixel();
176 Point aPos;
177 Rectangle aRect( aPos, aOutSz );
178 aRect.Top() += nScrollerHeight;
179 aRect.Bottom() -= nScrollerHeight;
181 if ( pMenu && pMenu->pLogo && !bIncludeLogo )
182 aRect.Left() += pMenu->pLogo->aBitmap.GetSizePixel().Width();
184 vcl::Region aRegion(aRect);
185 if ( pMenu && pMenu->pLogo && bIncludeLogo && nScrollerHeight )
186 aRegion.Union( Rectangle( Point(), Size( pMenu->pLogo->aBitmap.GetSizePixel().Width(), aOutSz.Height() ) ) );
188 return aRegion;
191 void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext& rRenderContext)
193 if (IsScrollMenu())
195 rRenderContext.SetClipRegion(ImplCalcClipRegion());
197 else
199 rRenderContext.SetClipRegion();
203 void MenuFloatingWindow::ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown )
205 if( ! pMenu )
206 return;
208 long nY = GetInitialItemY();
209 long nMouseY = rMEvt.GetPosPixel().Y();
210 Size aOutSz = GetOutputSizePixel();
211 if ( ( nMouseY >= nY ) && ( nMouseY < aOutSz.Height() ) )
213 bool bHighlighted = false;
214 size_t nCount = pMenu->pItemList->size();
215 for ( size_t n = 0; !bHighlighted && ( n < nCount ); n++ )
217 if ( pMenu->ImplIsVisible( n ) )
219 MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( n );
220 long nOldY = nY;
221 nY += pItemData->aSz.Height();
222 if ( ( nOldY <= nMouseY ) && ( nY > nMouseY ) && pMenu->ImplIsSelectable( n ) )
224 bool bPopupArea = true;
225 if ( pItemData->nBits & MenuItemBits::POPUPSELECT )
227 // only when clicked over the arrow...
228 Size aSz = GetOutputSizePixel();
229 long nFontHeight = GetTextHeight();
230 bPopupArea = ( rMEvt.GetPosPixel().X() >= ( aSz.Width() - nFontHeight - nFontHeight/4 ) );
233 if ( bMBDown )
235 if ( n != nHighlightedItem )
237 ChangeHighlightItem( (sal_uInt16)n, false );
240 bool bAllowNewPopup = true;
241 if ( pActivePopup )
243 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
244 bAllowNewPopup = pData && ( pData->pSubMenu != pActivePopup );
245 if ( bAllowNewPopup )
246 KillActivePopup();
249 if ( bPopupArea && bAllowNewPopup )
251 HighlightChanged( nullptr );
254 else
256 if ( n != nHighlightedItem )
258 ChangeHighlightItem( (sal_uInt16)n, true );
260 else if ( pItemData->nBits & MenuItemBits::POPUPSELECT )
262 if ( bPopupArea && ( pActivePopup != pItemData->pSubMenu ) )
263 HighlightChanged( nullptr );
266 bHighlighted = true;
270 if ( !bHighlighted )
271 ChangeHighlightItem( ITEMPOS_INVALID, true );
273 else
275 ImplScroll( rMEvt.GetPosPixel() );
276 ChangeHighlightItem( ITEMPOS_INVALID, true );
280 IMPL_LINK_NOARG(MenuFloatingWindow, PopupEnd, FloatingWindow*, void)
282 // "this" will be deleted before the end of this method!
283 Menu* pM = pMenu;
284 if ( bInExecute )
286 End();
287 if ( pActivePopup )
289 //SAL_WARN_IF( pActivePopup->ImplGetWindow(), "vcl", "PopupEnd, obwohl pActivePopup MIT Window!" );
290 KillActivePopup(); // should be ok to just remove it
291 //pActivePopup->bCanceled = true;
293 pMenu->bInCallback = true;
294 pMenu->Deactivate();
295 pMenu->bInCallback = false;
297 else
299 if (pMenu && pMenu->pStartedFrom)
300 pMenu->pStartedFrom->ClosePopup(pMenu);
303 if ( pM )
304 pM->pStartedFrom = nullptr;
307 IMPL_LINK_NOARG(MenuFloatingWindow, AutoScroll, Timer *, void)
309 ImplScroll( GetPointerPosPixel() );
312 IMPL_LINK( MenuFloatingWindow, HighlightChanged, Timer*, pTimer, void )
314 if( ! pMenu )
315 return;
317 MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
318 if ( pItemData )
320 if ( pActivePopup && ( pActivePopup != pItemData->pSubMenu ) )
322 FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
323 SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose );
324 KillActivePopup();
325 SetPopupModeFlags( nOldFlags );
327 if ( pItemData->bEnabled && pItemData->pSubMenu && pItemData->pSubMenu->GetItemCount() && ( pItemData->pSubMenu != pActivePopup ) )
329 pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get());
330 long nY = nScrollerHeight+ImplGetStartY();
331 MenuItemData* pData = nullptr;
332 for ( sal_uLong n = 0; n < nHighlightedItem; n++ )
334 pData = pMenu->pItemList->GetDataFromPos( n );
335 nY += pData->aSz.Height();
337 pData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
338 Size MySize = GetOutputSizePixel();
339 Point aItemTopLeft( 0, nY );
340 Point aItemBottomRight( aItemTopLeft );
341 aItemBottomRight.X() += MySize.Width();
342 aItemBottomRight.Y() += pData->aSz.Height();
344 // shift the popups a little
345 aItemTopLeft.X() += 2;
346 aItemBottomRight.X() -= 2;
347 if ( nHighlightedItem )
348 aItemTopLeft.Y() -= 2;
349 else
351 sal_Int32 nL, nT, nR, nB;
352 GetBorder( nL, nT, nR, nB );
353 aItemTopLeft.Y() -= nT;
356 // pTest: crash due to Reschedule() in call of Activate()
357 // Also it is prevented that submenus are displayed which
358 // were for long in Activate Rescheduled and which should not be
359 // displayed now.
360 Menu* pTest = pActivePopup;
361 FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
362 SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose );
363 sal_uInt16 nRet = pActivePopup->ImplExecute( this, Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Right, pMenu, pTimer == nullptr );
364 SetPopupModeFlags( nOldFlags );
366 // nRet != 0, wenn es waerend Activate() abgeschossen wurde...
367 if ( !nRet && ( pActivePopup == pTest ) && pActivePopup->ImplGetWindow() )
368 pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this );
373 IMPL_LINK_NOARG(MenuFloatingWindow, SubmenuClose, Timer *, void)
375 if( pMenu && pMenu->pStartedFrom )
377 MenuFloatingWindow* pWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->GetWindow());
378 if( pWin )
379 pWin->KillActivePopup();
383 IMPL_LINK( MenuFloatingWindow, ShowHideListener, VclWindowEvent&, rEvent, void )
385 if( ! pMenu )
386 return;
388 if( rEvent.GetId() == VCLEVENT_WINDOW_SHOW )
389 pMenu->ImplCallEventListeners( VCLEVENT_MENU_SHOW, ITEMPOS_INVALID );
390 else if( rEvent.GetId() == VCLEVENT_WINDOW_HIDE )
391 pMenu->ImplCallEventListeners( VCLEVENT_MENU_HIDE, ITEMPOS_INVALID );
394 void MenuFloatingWindow::EnableScrollMenu( bool b )
396 bScrollMenu = b;
397 nScrollerHeight = b ? (sal_uInt16) GetSettings().GetStyleSettings().GetScrollBarSize() /2 : 0;
398 bScrollDown = true;
399 InitMenuClipRegion(*this);
402 void MenuFloatingWindow::Start()
404 if (bInExecute)
405 return;
406 bInExecute = true;
407 if (GetParent())
408 GetParent()->ImplIncModalCount();
411 void MenuFloatingWindow::End()
413 if (!bInExecute)
414 return;
416 if (GetParent() && !GetParent()->IsDisposed())
417 GetParent()->ImplDecModalCount();
419 // restore focus to previous window if we still have the focus
420 VclPtr<vcl::Window> xFocusId(xSaveFocusId);
421 xSaveFocusId = nullptr;
422 if (HasChildPathFocus() && xFocusId != nullptr)
424 ImplGetSVData()->maWinData.mbNoDeactivate = false;
425 Window::EndSaveFocus(xFocusId);
428 bInExecute = false;
431 void MenuFloatingWindow::Execute()
433 ImplSVData* pSVData = ImplGetSVData();
435 pSVData->maAppData.mpActivePopupMenu = static_cast<PopupMenu*>(pMenu.get());
437 Start();
439 while (bInExecute)
440 Application::Yield();
442 pSVData->maAppData.mpActivePopupMenu = nullptr;
445 void MenuFloatingWindow::StopExecute()
447 End();
449 ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xSaveFocusId);
451 aHighlightChangedTimer.Stop();
452 if (pActivePopup)
454 KillActivePopup();
456 // notify parent, needed for accessibility
457 if( pMenu && pMenu->pStartedFrom )
458 pMenu->pStartedFrom->ImplCallEventListeners( VCLEVENT_MENU_SUBMENUDEACTIVATE, nPosInParent );
461 void MenuFloatingWindow::KillActivePopup( PopupMenu* pThisOnly )
463 if ( pActivePopup && ( !pThisOnly || ( pThisOnly == pActivePopup ) ) )
465 if( pActivePopup->pWindow )
466 if( static_cast<FloatingWindow *>(pActivePopup->pWindow.get())->IsInCleanUp() )
467 return; // kill it later
468 if ( pActivePopup->bInCallback )
469 pActivePopup->bCanceled = true;
471 // For all actions pActivePopup = 0, if e.g.
472 // PopupModeEndHdl the popups to destroy were called synchronous
473 PopupMenu* pPopup = pActivePopup;
474 pActivePopup = nullptr;
475 pPopup->bInCallback = true;
476 pPopup->Deactivate();
477 pPopup->bInCallback = false;
478 if ( pPopup->ImplGetWindow() )
480 pPopup->ImplGetFloatingWindow()->StopExecute();
481 pPopup->ImplGetFloatingWindow()->doShutdown();
482 pPopup->pWindow->doLazyDelete();
483 pPopup->pWindow = nullptr;
485 Update();
490 void MenuFloatingWindow::EndExecute()
492 Menu* pStart = pMenu ? pMenu->ImplGetStartMenu() : nullptr;
494 // if started elsewhere, cleanup there as well
495 MenuFloatingWindow* pCleanUpFrom = this;
496 MenuFloatingWindow* pWin = this;
497 while (pWin && !pWin->bInExecute &&
498 pWin->pMenu->pStartedFrom && !pWin->pMenu->pStartedFrom->IsMenuBar())
500 pWin = static_cast<PopupMenu*>(pWin->pMenu->pStartedFrom.get())->ImplGetFloatingWindow();
502 if ( pWin )
503 pCleanUpFrom = pWin;
505 // this window will be destroyed => store date locally...
506 Menu* pM = pMenu;
507 sal_uInt16 nItem = nHighlightedItem;
509 pCleanUpFrom->StopExecute();
511 if ( nItem != ITEMPOS_INVALID && pM )
513 MenuItemData* pItemData = pM->GetItemList()->GetDataFromPos( nItem );
514 if ( pItemData && !pItemData->bIsTemporary )
516 pM->nSelectedId = pItemData->nId;
517 if ( pStart )
518 pStart->nSelectedId = pItemData->nId;
520 pM->ImplSelect();
525 void MenuFloatingWindow::EndExecute( sal_uInt16 nId )
527 size_t nPos;
528 if ( pMenu && pMenu->GetItemList()->GetData( nId, nPos ) )
529 nHighlightedItem = nPos;
530 else
531 nHighlightedItem = ITEMPOS_INVALID;
533 EndExecute();
536 void MenuFloatingWindow::MouseButtonDown( const MouseEvent& rMEvt )
538 // TH creates a ToTop on this window, but the active popup
539 // should stay on top...
540 // due to focus change this would close all menus -> don't do it (#94123)
541 //if ( pActivePopup && pActivePopup->ImplGetWindow() && !pActivePopup->ImplGetFloatingWindow()->pActivePopup )
542 // pActivePopup->ImplGetFloatingWindow()->ToTop( ToTopFlags::NoGrabFocus );
544 ImplHighlightItem( rMEvt, true );
546 nMBDownPos = nHighlightedItem;
549 void MenuFloatingWindow::MouseButtonUp( const MouseEvent& rMEvt )
551 MenuItemData* pData = pMenu ? pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ) : nullptr;
552 // nMBDownPos store in local variable and reset immediately,
553 // as it will be too late after EndExecute
554 sal_uInt16 _nMBDownPos = nMBDownPos;
555 nMBDownPos = ITEMPOS_INVALID;
556 if ( pData && pData->bEnabled && ( pData->eType != MenuItemType::SEPARATOR ) )
558 if ( !pData->pSubMenu )
560 EndExecute();
562 else if ( ( pData->nBits & MenuItemBits::POPUPSELECT ) && ( nHighlightedItem == _nMBDownPos ) && ( rMEvt.GetClicks() == 2 ) )
564 // not when clicked over the arrow...
565 Size aSz = GetOutputSizePixel();
566 long nFontHeight = GetTextHeight();
567 if ( rMEvt.GetPosPixel().X() < ( aSz.Width() - nFontHeight - nFontHeight/4 ) )
568 EndExecute();
574 void MenuFloatingWindow::MouseMove( const MouseEvent& rMEvt )
576 if ( !IsVisible() || rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() )
577 return;
579 if ( rMEvt.IsLeaveWindow() )
581 // #102461# do not remove highlight if a popup menu is open at this position
582 MenuItemData* pData = pMenu ? pMenu->pItemList->GetDataFromPos( nHighlightedItem ) : nullptr;
583 // close popup with some delayed if we leave somewhere else
584 if( pActivePopup && pData && pData->pSubMenu != pActivePopup )
585 pActivePopup->ImplGetFloatingWindow()->aSubmenuCloseTimer.Start();
587 if( !pActivePopup || (pData && pData->pSubMenu != pActivePopup ) )
588 ChangeHighlightItem( ITEMPOS_INVALID, false );
590 if ( IsScrollMenu() )
591 ImplScroll( rMEvt.GetPosPixel() );
593 else
595 aSubmenuCloseTimer.Stop();
596 if( bIgnoreFirstMove )
597 bIgnoreFirstMove = false;
598 else
599 ImplHighlightItem( rMEvt, false );
603 void MenuFloatingWindow::ImplScroll( bool bUp )
605 KillActivePopup();
606 Update();
608 if (!pMenu)
609 return;
611 Invalidate();
613 pMenu->ImplKillLayoutData();
615 if ( bScrollUp && bUp )
617 nFirstEntry = pMenu->ImplGetPrevVisible( nFirstEntry );
618 SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
620 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
621 const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry );
622 if ( pItemData )
624 long nScrollEntryHeight = pItemData->aSz.Height();
626 if ( !bScrollDown )
628 bScrollDown = true;
629 Invalidate();
632 if ( pMenu->ImplGetPrevVisible( nFirstEntry ) == ITEMPOS_INVALID )
634 bScrollUp = false;
635 Invalidate();
638 Scroll( 0, nScrollEntryHeight, ImplCalcClipRegion( false ).GetBoundRect(), ScrollFlags::Clip );
641 else if ( bScrollDown && !bUp )
643 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
644 const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry );
645 if ( pItemData )
647 long nScrollEntryHeight = pItemData->aSz.Height();
649 nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry );
650 SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
652 if ( !bScrollUp )
654 bScrollUp = true;
655 Invalidate();
658 long nHeight = GetOutputSizePixel().Height();
659 sal_uInt16 nLastVisible;
660 static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( nHeight, nFirstEntry, &nLastVisible );
661 if ( pMenu->ImplGetNextVisible( nLastVisible ) == ITEMPOS_INVALID )
663 bScrollDown = false;
664 Invalidate();
667 Scroll( 0, -nScrollEntryHeight, ImplCalcClipRegion( false ).GetBoundRect(), ScrollFlags::Clip );
671 Invalidate();
674 void MenuFloatingWindow::ImplScroll( const Point& rMousePos )
676 Size aOutSz = GetOutputSizePixel();
678 long nY = nScrollerHeight;
679 long nMouseY = rMousePos.Y();
680 long nDelta = 0;
682 if ( bScrollUp && ( nMouseY < nY ) )
684 ImplScroll( true );
685 nDelta = nY - nMouseY;
687 else if ( bScrollDown && ( nMouseY > ( aOutSz.Height() - nY ) ) )
689 ImplScroll( false );
690 nDelta = nMouseY - ( aOutSz.Height() - nY );
693 if ( nDelta )
695 aScrollTimer.Stop(); // if scrolled through MouseMove.
696 long nTimeout;
697 if ( nDelta < 3 )
698 nTimeout = 200;
699 else if ( nDelta < 5 )
700 nTimeout = 100;
701 else if ( nDelta < 8 )
702 nTimeout = 70;
703 else if ( nDelta < 12 )
704 nTimeout = 40;
705 else
706 nTimeout = 20;
707 aScrollTimer.SetTimeout( nTimeout );
708 aScrollTimer.Start();
711 void MenuFloatingWindow::ChangeHighlightItem( sal_uInt16 n, bool bStartPopupTimer )
713 // #57934# if necessary, immediately close the active, as TH's backgroundstorage works.
714 // #65750# we prefer to refrain from the background storage of small lines.
715 // otherwise the menus are difficult to operate.
716 // MenuItemData* pNextData = pMenu->pItemList->GetDataFromPos( n );
717 // if ( pActivePopup && pNextData && ( pActivePopup != pNextData->pSubMenu ) )
718 // KillActivePopup();
720 aSubmenuCloseTimer.Stop();
721 if( ! pMenu )
722 return;
724 if ( nHighlightedItem != ITEMPOS_INVALID )
726 InvalidateItem(nHighlightedItem);
727 pMenu->ImplCallEventListeners( VCLEVENT_MENU_DEHIGHLIGHT, nHighlightedItem );
730 nHighlightedItem = (sal_uInt16)n;
731 SAL_WARN_IF( !pMenu->ImplIsVisible( nHighlightedItem ) && nHighlightedItem != ITEMPOS_INVALID, "vcl", "ChangeHighlightItem: Not visible!" );
732 if( nHighlightedItem != ITEMPOS_INVALID )
734 if (pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar())
736 // #102461# make sure parent entry is highlighted as well
737 size_t i, nCount = pMenu->pStartedFrom->pItemList->size();
738 for(i = 0; i < nCount; i++)
740 MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i );
741 if( pData && ( pData->pSubMenu == pMenu ) )
742 break;
744 if( i < nCount )
746 MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
747 if( pPWin && pPWin->nHighlightedItem != i )
749 pPWin->InvalidateItem(i);
750 pPWin->nHighlightedItem = i;
754 InvalidateItem(nHighlightedItem);
755 pMenu->ImplCallHighlight( nHighlightedItem );
757 else
758 pMenu->nSelectedId = 0;
760 if ( bStartPopupTimer )
762 // #102438# Menu items are not selectable
763 // If a menu item is selected by an AT-tool via the XAccessibleAction, XAccessibleValue
764 // or XAccessibleSelection interface, and the parent popup menus are not executed yet,
765 // the parent popup menus must be executed SYNCHRONOUSLY, before the menu item is selected.
766 if ( GetSettings().GetMouseSettings().GetMenuDelay() )
767 aHighlightChangedTimer.Start();
768 else
769 HighlightChanged( &aHighlightChangedTimer );
773 /// Calculate the initial vertical pixel offset of the first item.
774 /// May be negative for scrolled windows.
775 long MenuFloatingWindow::GetInitialItemY(long *pStartY) const
777 long nStartY = ImplGetStartY();
778 if (pStartY)
779 *pStartY = nStartY;
780 return nScrollerHeight + nStartY +
781 ImplGetSVData()->maNWFData.mnMenuFormatBorderY;
784 /// Emit an Invalidate just for this item's area
785 void MenuFloatingWindow::InvalidateItem(sal_uInt16 nPos)
787 if (!pMenu)
788 return;
790 long nY = GetInitialItemY();
791 size_t nCount = pMenu->pItemList->size();
792 for (size_t n = 0; n < nCount; n++)
794 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
795 long nHeight = pData->aSz.Height();
796 if (n == nPos)
798 Size aWidth( GetSizePixel() );
799 Rectangle aRect(Point(0, nY), Size(aWidth.Width(), nHeight));
800 Invalidate( aRect );
802 nY += nHeight;
806 void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos)
808 if (!pMenu)
809 return;
811 Size aSz(GetOutputSizePixel());
813 long nX = 0;
814 long nStartY;
815 long nY = GetInitialItemY(&nStartY);
817 if (pMenu->pLogo)
818 nX = pMenu->pLogo->aBitmap.GetSizePixel().Width();
820 int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
822 size_t nCount = pMenu->pItemList->size();
823 for (size_t n = 0; n < nCount; n++)
825 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
826 if (n == nPos)
828 SAL_WARN_IF(!pMenu->ImplIsVisible(n), "vcl", "Highlight: Item not visible!");
829 if (pData->eType != MenuItemType::SEPARATOR)
831 bool bRestoreLineColor = false;
832 Color oldLineColor;
833 bool bDrawItemRect = true;
835 Rectangle aItemRect(Point(nX + nOuterSpaceX, nY), Size(aSz.Width() - 2 * nOuterSpaceX, pData->aSz.Height()));
836 if (pData->nBits & MenuItemBits::POPUPSELECT)
838 long nFontHeight = GetTextHeight();
839 aItemRect.Right() -= nFontHeight + nFontHeight / 4;
842 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
844 Size aPxSize(GetOutputSizePixel());
845 rRenderContext.Push(PushFlags::CLIPREGION);
846 rRenderContext.IntersectClipRegion(Rectangle(Point(nX, nY), Size(aSz.Width(), pData->aSz.Height())));
847 Rectangle aCtrlRect(Point(nX, 0), Size(aPxSize.Width()-nX, aPxSize.Height()));
848 MenupopupValue aVal(pMenu->nTextPos-GUTTERBORDER, aItemRect);
849 rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire,
850 aCtrlRect, ControlState::ENABLED, aVal, OUString());
851 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
853 bDrawItemRect = false;
854 if (!rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem, aItemRect,
855 ControlState::SELECTED | (pData->bEnabled
856 ? ControlState::ENABLED
857 : ControlState::NONE),
858 aVal, OUString()))
860 bDrawItemRect = true;
863 else
864 bDrawItemRect = true;
865 rRenderContext.Pop();
867 if (bDrawItemRect)
869 if (pData->bEnabled)
870 rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
871 else
873 rRenderContext.SetFillColor();
874 oldLineColor = rRenderContext.GetLineColor();
875 rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
876 bRestoreLineColor = true;
879 rRenderContext.DrawRect(aItemRect);
881 pMenu->ImplPaint(rRenderContext, nScrollerHeight, nStartY, pData, true/*bHighlight*/);
882 if (bRestoreLineColor)
883 rRenderContext.SetLineColor(oldLineColor);
885 return;
888 nY += pData->aSz.Height();
892 Rectangle MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos )
894 if( ! pMenu )
895 return Rectangle();
897 Rectangle aRect;
898 Size aSz = GetOutputSizePixel();
899 long nStartY = ImplGetStartY();
900 long nY = nScrollerHeight+nStartY;
901 long nX = 0;
903 if ( pMenu->pLogo )
904 nX = pMenu->pLogo->aBitmap.GetSizePixel().Width();
906 size_t nCount = pMenu->pItemList->size();
907 for ( size_t n = 0; n < nCount; n++ )
909 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
910 if ( n == nPos )
912 SAL_WARN_IF( !pMenu->ImplIsVisible( n ), "vcl", "ImplGetItemRect: Item not visible!" );
913 if ( pData->eType != MenuItemType::SEPARATOR )
915 aRect = Rectangle( Point( nX, nY ), Size( aSz.Width(), pData->aSz.Height() ) );
916 if ( pData->nBits & MenuItemBits::POPUPSELECT )
918 long nFontHeight = GetTextHeight();
919 aRect.Right() -= nFontHeight + nFontHeight/4;
922 break;
924 nY += pData->aSz.Height();
926 return aRect;
929 void MenuFloatingWindow::ImplCursorUpDown( bool bUp, bool bHomeEnd )
931 if( ! pMenu )
932 return;
934 const StyleSettings& rSettings = GetSettings().GetStyleSettings();
936 sal_uInt16 n = nHighlightedItem;
937 if ( n == ITEMPOS_INVALID )
939 if ( bUp )
940 n = 0;
941 else
942 n = pMenu->GetItemCount()-1;
945 sal_uInt16 nLoop = n;
947 if( bHomeEnd )
949 // absolute positioning
950 if( bUp )
952 n = pMenu->GetItemCount();
953 nLoop = n-1;
955 else
957 n = (sal_uInt16)-1;
958 nLoop = n+1;
964 if ( bUp )
966 if ( n )
967 n--;
968 else
969 if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
970 n = pMenu->GetItemCount()-1;
971 else
972 break;
974 else
976 n++;
977 if ( n >= pMenu->GetItemCount() )
979 if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
980 n = 0;
981 else
982 break;
986 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( n );
987 if ( ( pData->bEnabled || !rSettings.GetSkipDisabledInMenus() )
988 && ( pData->eType != MenuItemType::SEPARATOR ) && pMenu->ImplIsVisible( n ) && pMenu->ImplIsSelectable( n ) )
990 // Is selection in visible area?
991 if ( IsScrollMenu() )
993 ChangeHighlightItem( ITEMPOS_INVALID, false );
995 while ( n < nFirstEntry )
996 ImplScroll( true );
998 Size aOutSz = GetOutputSizePixel();
999 sal_uInt16 nLastVisible;
1000 static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible );
1001 while ( n > nLastVisible )
1003 ImplScroll( false );
1004 static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible );
1007 ChangeHighlightItem( n, false );
1008 break;
1010 } while ( n != nLoop );
1013 void MenuFloatingWindow::KeyInput( const KeyEvent& rKEvent )
1015 VclPtr<vcl::Window> xWindow = this;
1017 bool accel = ImplGetSVData()->maNWFData.mbEnableAccel;
1018 bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
1019 sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
1020 bKeyInput = true;
1021 switch ( nCode )
1023 case KEY_UP:
1024 case KEY_DOWN:
1026 ImplCursorUpDown( nCode == KEY_UP );
1028 break;
1029 case KEY_END:
1030 case KEY_HOME:
1032 ImplCursorUpDown( nCode == KEY_END, true );
1034 break;
1035 case KEY_F6:
1036 case KEY_ESCAPE:
1038 // Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document
1039 if( nCode == KEY_F6 && !rKEvent.GetKeyCode().IsMod1() )
1040 break;
1041 if( pMenu )
1043 if ( !pMenu->pStartedFrom )
1045 StopExecute();
1046 KillActivePopup();
1048 else if (pMenu->pStartedFrom->IsMenuBar())
1050 pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
1052 else
1054 StopExecute();
1055 PopupMenu* pPopupMenu = static_cast<PopupMenu*>(pMenu->pStartedFrom.get());
1056 MenuFloatingWindow* pFloat = pPopupMenu->ImplGetFloatingWindow();
1057 pFloat->GrabFocus();
1058 pFloat->KillActivePopup();
1059 pPopupMenu->ImplCallHighlight(pFloat->nHighlightedItem);
1063 break;
1064 case KEY_LEFT:
1066 if ( pMenu && pMenu->pStartedFrom )
1068 StopExecute();
1069 if (pMenu->pStartedFrom->IsMenuBar())
1071 pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
1073 else
1075 MenuFloatingWindow* pFloat = static_cast<PopupMenu*>(pMenu->pStartedFrom.get())->ImplGetFloatingWindow();
1076 pFloat->GrabFocus();
1077 pFloat->KillActivePopup();
1078 sal_uInt16 highlightItem = pFloat->GetHighlightedItem();
1079 pFloat->ChangeHighlightItem(highlightItem, false);
1083 break;
1084 case KEY_RIGHT:
1086 if( pMenu )
1088 bool bDone = false;
1089 if ( nHighlightedItem != ITEMPOS_INVALID )
1091 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
1092 if ( pData && pData->pSubMenu )
1094 HighlightChanged( nullptr );
1095 bDone = true;
1098 if ( !bDone )
1100 Menu* pStart = pMenu->ImplGetStartMenu();
1101 if (pStart && pStart->IsMenuBar())
1103 // Forward...
1104 pStart->ImplGetWindow()->KeyInput( rKEvent );
1109 break;
1110 case KEY_RETURN:
1112 if( pMenu )
1114 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
1115 if ( pData && pData->bEnabled )
1117 if ( pData->pSubMenu )
1118 HighlightChanged( nullptr );
1119 else
1120 EndExecute();
1122 else
1123 StopExecute();
1126 break;
1127 case KEY_MENU:
1129 if( pMenu )
1131 Menu* pStart = pMenu->ImplGetStartMenu();
1132 if (pStart && pStart->IsMenuBar())
1134 // Forward...
1135 pStart->ImplGetWindow()->KeyInput( rKEvent );
1139 break;
1140 default:
1142 sal_Unicode nCharCode = rKEvent.GetCharCode();
1143 sal_uInt16 nPos = 0;
1144 sal_uInt16 nDuplicates = 0;
1145 MenuItemData* pData = (nCharCode && pMenu && accel) ?
1146 pMenu->GetItemList()->SearchItem(nCharCode, rKEvent.GetKeyCode(), nPos, nDuplicates, nHighlightedItem) : nullptr;
1147 if (pData)
1149 if ( pData->pSubMenu || nDuplicates > 1 )
1151 ChangeHighlightItem( nPos, false );
1152 HighlightChanged( nullptr );
1154 else
1156 nHighlightedItem = nPos;
1157 EndExecute();
1160 else
1161 FloatingWindow::KeyInput( rKEvent );
1165 if (pMenu && pMenu->pStartedFrom && pMenu->pStartedFrom->IsMenuBar())
1167 MenuBar *pMenuBar = static_cast<MenuBar*>(pMenu->pStartedFrom.get());
1168 const bool bShowAccels = nCode != KEY_ESCAPE;
1169 if (pMenuBar->getMenuBarWindow()->GetMBWMenuKey() != bShowAccels)
1171 pMenuBar->getMenuBarWindow()->SetMBWMenuKey(bShowAccels);
1172 pMenuBar->getMenuBarWindow()->SetMBWHideAccel(!bShowAccels);
1173 if (accel && autoacc)
1174 Invalidate(InvalidateFlags::Update);
1178 // #105474# check if menu window was not destroyed
1179 if ( !xWindow->IsDisposed() )
1181 bKeyInput = false;
1185 void MenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const Rectangle &rPaintRect)
1187 if (!pMenu)
1188 return;
1190 rRenderContext.Push( PushFlags::CLIPREGION );
1191 rRenderContext.SetClipRegion(vcl::Region(rPaintRect));
1193 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
1195 rRenderContext.SetClipRegion();
1196 long nX = pMenu->pLogo ? pMenu->pLogo->aBitmap.GetSizePixel().Width() : 0;
1197 Size aPxSize(GetOutputSizePixel());
1198 aPxSize.Width() -= nX;
1199 ImplControlValue aVal(pMenu->nTextPos - GUTTERBORDER);
1200 rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire,
1201 Rectangle(Point(nX, 0), aPxSize),
1202 ControlState::ENABLED, aVal, OUString());
1203 InitMenuClipRegion(rRenderContext);
1205 if (IsScrollMenu())
1207 ImplDrawScroller(rRenderContext, true);
1208 ImplDrawScroller(rRenderContext, false);
1210 rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuColor());
1211 pMenu->ImplPaint(rRenderContext, nScrollerHeight, ImplGetStartY());
1212 if (nHighlightedItem != ITEMPOS_INVALID)
1213 RenderHighlightItem(rRenderContext, nHighlightedItem);
1215 rRenderContext.Pop();
1218 void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp)
1220 if (!pMenu)
1221 return;
1223 rRenderContext.SetClipRegion();
1225 Size aOutSz(GetOutputSizePixel());
1226 long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight);
1227 long nX = pMenu->pLogo ? pMenu->pLogo->aBitmap.GetSizePixel().Width() : 0;
1228 Rectangle aRect(Point(nX, nY), Size(aOutSz.Width() - nX, nScrollerHeight));
1230 DecorationView aDecoView(&rRenderContext);
1231 SymbolType eSymbol = bUp ? SymbolType::SPIN_UP : SymbolType::SPIN_DOWN;
1233 DrawSymbolFlags nStyle = DrawSymbolFlags::NONE;
1234 if ((bUp && !bScrollUp) || (!bUp && !bScrollDown))
1235 nStyle |= DrawSymbolFlags::Disable;
1237 aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nStyle);
1239 InitMenuClipRegion(rRenderContext);
1242 void MenuFloatingWindow::RequestHelp( const HelpEvent& rHEvt )
1244 sal_uInt16 nId = nHighlightedItem;
1245 Menu* pM = pMenu;
1246 vcl::Window* pW = this;
1248 // #102618# Get item rect before destroying the window in EndExecute() call
1249 Rectangle aHighlightRect( ImplGetItemRect( nHighlightedItem ) );
1251 if ( rHEvt.GetMode() & (HelpEventMode::CONTEXT | HelpEventMode::EXTENDED) )
1253 nHighlightedItem = ITEMPOS_INVALID;
1254 EndExecute();
1255 pW = nullptr;
1258 if( !ImplHandleHelpEvent( pW, pM, nId, rHEvt, aHighlightRect ) )
1259 Window::RequestHelp( rHEvt );
1262 void MenuFloatingWindow::StateChanged( StateChangedType nType )
1264 FloatingWindow::StateChanged( nType );
1266 if ( ( nType == StateChangedType::ControlForeground ) || ( nType == StateChangedType::ControlBackground ) )
1268 ApplySettings(*this);
1269 Invalidate();
1273 void MenuFloatingWindow::DataChanged( const DataChangedEvent& rDCEvt )
1275 FloatingWindow::DataChanged( rDCEvt );
1277 if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
1278 (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
1279 ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
1280 (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
1282 ApplySettings(*this);
1283 Invalidate();
1287 void MenuFloatingWindow::Command( const CommandEvent& rCEvt )
1289 if ( rCEvt.GetCommand() == CommandEventId::Wheel )
1291 const CommandWheelData* pData = rCEvt.GetWheelData();
1292 if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
1294 ImplScroll( pData->GetDelta() > 0 );
1295 MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) );
1300 css::uno::Reference<css::accessibility::XAccessible> MenuFloatingWindow::CreateAccessible()
1302 css::uno::Reference<css::accessibility::XAccessible> xAcc;
1304 if (pMenu && !pMenu->pStartedFrom)
1305 xAcc = pMenu->GetAccessible();
1307 return xAcc;
1310 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */