bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / window / menufloatingwindow.cxx
blobb14329f7503c4c21c25a3591d5dbf8e65c4d9e93
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"
23 #include "bufferdevice.hxx"
25 #include <sal/log.hxx>
26 #include <salframe.hxx>
27 #include <svdata.hxx>
28 #include <vcl/decoview.hxx>
29 #include <vcl/settings.hxx>
30 #include <window.h>
32 MenuFloatingWindow::MenuFloatingWindow( Menu* pMen, vcl::Window* pParent, WinBits nStyle ) :
33 FloatingWindow( pParent, nStyle ),
34 pMenu(pMen),
35 nHighlightedItem(ITEMPOS_INVALID),
36 nMBDownPos(ITEMPOS_INVALID),
37 nScrollerHeight(0),
38 nFirstEntry(0),
39 nPosInParent(ITEMPOS_INVALID),
40 bInExecute(false),
41 bScrollMenu(false),
42 bScrollUp(false),
43 bScrollDown(false),
44 bIgnoreFirstMove(true),
45 bKeyInput(false)
47 mpWindowImpl->mbMenuFloatingWindow= true;
49 ApplySettings(*this);
51 SetPopupModeEndHdl( LINK( this, MenuFloatingWindow, PopupEnd ) );
53 aHighlightChangedTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, HighlightChanged ) );
54 aHighlightChangedTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
55 aHighlightChangedTimer.SetDebugName( "vcl::MenuFloatingWindow aHighlightChangedTimer" );
57 aSubmenuCloseTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() );
58 aSubmenuCloseTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, SubmenuClose ) );
59 aSubmenuCloseTimer.SetDebugName( "vcl::MenuFloatingWindow aSubmenuCloseTimer" );
61 aScrollTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, AutoScroll ) );
62 aScrollTimer.SetDebugName( "vcl::MenuFloatingWindow aScrollTimer" );
64 AddEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
67 void MenuFloatingWindow::doShutdown()
69 if( !pMenu )
70 return;
72 // #105373# notify toolkit that highlight was removed
73 // otherwise the entry will not be read when the menu is opened again
74 if( nHighlightedItem != ITEMPOS_INVALID )
75 pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem );
76 if (!bKeyInput && pMenu && pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar())
78 // #102461# remove highlight in parent
79 size_t i, nCount = pMenu->pStartedFrom->pItemList->size();
80 for(i = 0; i < nCount; i++)
82 MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i );
83 if( pData && ( pData->pSubMenu == pMenu ) )
84 break;
86 if( i < nCount )
88 MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
89 if (pPWin)
90 pPWin->InvalidateItem(i);
94 // free the reference to the accessible component
95 SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
97 aHighlightChangedTimer.Stop();
99 // #95056# invalidate screen area covered by system window
100 // so this can be taken into account if the commandhandler performs a scroll operation
101 if( GetParent() )
103 tools::Rectangle aInvRect( GetWindowExtentsRelative( GetParent() ) );
104 GetParent()->Invalidate( aInvRect );
106 pMenu = nullptr;
107 RemoveEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
109 aScrollTimer.Stop();
110 aSubmenuCloseTimer.Stop();
111 aSubmenuCloseTimer.Stop();
112 aHighlightChangedTimer.Stop();
113 aHighlightChangedTimer.Stop();
117 MenuFloatingWindow::~MenuFloatingWindow()
119 disposeOnce();
122 void MenuFloatingWindow::dispose()
124 doShutdown();
125 pMenu.clear();
126 pActivePopup.clear();
127 xSaveFocusId.clear();
128 FloatingWindow::dispose();
131 void MenuFloatingWindow::Resize()
133 InitMenuClipRegion(*this); // FIXME
136 void MenuFloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext)
138 FloatingWindow::ApplySettings(rRenderContext);
140 if (IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem) &&
141 IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
143 AllSettings aSettings(GetSettings());
144 ImplGetFrame()->UpdateSettings(aSettings); // Update theme colors.
145 StyleSettings aStyle(aSettings.GetStyleSettings());
146 Color aHighlightTextColor = ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor;
147 if (aHighlightTextColor != COL_TRANSPARENT)
149 aStyle.SetMenuHighlightTextColor(aHighlightTextColor);
151 aSettings.SetStyleSettings(aStyle);
152 OutputDevice::SetSettings(aSettings);
155 const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
156 SetPointFont(rRenderContext, rStyleSettings.GetMenuFont());
158 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
160 rRenderContext.SetBackground(); // background will be drawn by NWF
162 else
163 rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetMenuColor()));
165 rRenderContext.SetTextColor(rStyleSettings.GetMenuTextColor());
166 rRenderContext.SetTextFillColor();
167 rRenderContext.SetLineColor();
170 /// Get a negative pixel offset for an offset menu
171 long MenuFloatingWindow::ImplGetStartY() const
173 long nY = 0;
174 if( pMenu )
176 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
177 if ( nFirstEntry > 0 && !pMenu->GetItemList()->GetDataFromPos(nFirstEntry - 1) )
179 return 0;
182 for ( sal_uInt16 n = 0; n < nFirstEntry; n++ )
183 nY += pMenu->GetItemList()->GetDataFromPos( n )->aSz.Height();
184 nY -= pMenu->GetTitleHeight();
186 return -nY;
189 vcl::Region MenuFloatingWindow::ImplCalcClipRegion() const
191 Size aOutSz = GetOutputSizePixel();
192 tools::Rectangle aRect( Point(), aOutSz );
193 aRect.AdjustTop(nScrollerHeight );
194 aRect.AdjustBottom( -nScrollerHeight );
196 vcl::Region aRegion(aRect);
198 return aRegion;
201 void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext& rRenderContext)
203 if (IsScrollMenu())
205 rRenderContext.SetClipRegion(ImplCalcClipRegion());
207 else
209 rRenderContext.SetClipRegion();
213 void MenuFloatingWindow::ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown )
215 if( ! pMenu )
216 return;
218 long nY = GetInitialItemY();
219 long nMouseY = rMEvt.GetPosPixel().Y();
220 Size aOutSz = GetOutputSizePixel();
221 if ( ( nMouseY >= nY ) && ( nMouseY < aOutSz.Height() ) )
223 bool bHighlighted = false;
224 size_t nCount = pMenu->pItemList->size();
225 for ( size_t n = 0; !bHighlighted && ( n < nCount ); n++ )
227 if ( pMenu->ImplIsVisible( n ) )
229 MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( n );
230 long nOldY = nY;
231 nY += pItemData->aSz.Height();
232 if ( ( nOldY <= nMouseY ) && ( nY > nMouseY ) && pMenu->ImplIsSelectable( n ) )
234 bool bPopupArea = true;
235 if ( pItemData->nBits & MenuItemBits::POPUPSELECT )
237 // only when clicked over the arrow...
238 Size aSz = GetOutputSizePixel();
239 long nFontHeight = GetTextHeight();
240 bPopupArea = ( rMEvt.GetPosPixel().X() >= ( aSz.Width() - nFontHeight - nFontHeight/4 ) );
243 if ( bMBDown )
245 if ( n != nHighlightedItem )
247 ChangeHighlightItem( static_cast<sal_uInt16>(n), false );
250 bool bAllowNewPopup = true;
251 if ( pActivePopup )
253 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
254 bAllowNewPopup = pData && ( pData->pSubMenu != pActivePopup );
255 if ( bAllowNewPopup )
256 KillActivePopup();
259 if ( bPopupArea && bAllowNewPopup )
261 HighlightChanged( nullptr );
264 else
266 if ( n != nHighlightedItem )
268 ChangeHighlightItem( static_cast<sal_uInt16>(n), true );
270 else if ( pItemData->nBits & MenuItemBits::POPUPSELECT )
272 if ( bPopupArea && ( pActivePopup != pItemData->pSubMenu ) )
273 HighlightChanged( nullptr );
276 bHighlighted = true;
280 if ( !bHighlighted )
281 ChangeHighlightItem( ITEMPOS_INVALID, true );
283 else
285 ImplScroll( rMEvt.GetPosPixel() );
286 ChangeHighlightItem( ITEMPOS_INVALID, true );
290 IMPL_LINK_NOARG(MenuFloatingWindow, PopupEnd, FloatingWindow*, void)
292 // "this" will be deleted before the end of this method!
293 Menu* pM = pMenu;
294 if ( bInExecute )
296 End();
297 if ( pActivePopup )
299 KillActivePopup(); // should be ok to just remove it
300 //pActivePopup->bCanceled = true;
302 pMenu->bInCallback = true;
303 pMenu->Deactivate();
304 pMenu->bInCallback = false;
306 else
308 if (pMenu && pMenu->pStartedFrom)
309 pMenu->pStartedFrom->ClosePopup(pMenu);
312 if ( pM )
313 pM->pStartedFrom = nullptr;
316 IMPL_LINK_NOARG(MenuFloatingWindow, AutoScroll, Timer *, void)
318 ImplScroll( GetPointerPosPixel() );
321 IMPL_LINK( MenuFloatingWindow, HighlightChanged, Timer*, pTimer, void )
323 if( ! pMenu )
324 return;
326 MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
327 if ( pItemData )
329 if ( pActivePopup && ( pActivePopup != pItemData->pSubMenu ) )
331 FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
332 SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose );
333 KillActivePopup();
334 SetPopupModeFlags( nOldFlags );
336 if ( pItemData->bEnabled && pItemData->pSubMenu && pItemData->pSubMenu->GetItemCount() && ( pItemData->pSubMenu != pActivePopup ) )
338 pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get());
339 long nY = nScrollerHeight+ImplGetStartY();
340 MenuItemData* pData = nullptr;
341 for ( sal_uLong n = 0; n < nHighlightedItem; n++ )
343 pData = pMenu->pItemList->GetDataFromPos( n );
344 nY += pData->aSz.Height();
346 pData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
347 Size MySize = GetOutputSizePixel();
348 Point aItemTopLeft( 0, nY );
349 Point aItemBottomRight( aItemTopLeft );
350 aItemBottomRight.AdjustX(MySize.Width() );
351 aItemBottomRight.AdjustY(pData->aSz.Height() );
353 // shift the popups a little
354 aItemTopLeft.AdjustX(2 );
355 aItemBottomRight.AdjustX( -2 );
356 if ( nHighlightedItem )
357 aItemTopLeft.AdjustY( -2 );
358 else
360 sal_Int32 nL, nT, nR, nB;
361 GetBorder( nL, nT, nR, nB );
362 aItemTopLeft.AdjustY( -nT );
365 // pTest: crash due to Reschedule() in call of Activate()
366 // Also it is prevented that submenus are displayed which
367 // were for long in Activate Rescheduled and which should not be
368 // displayed now.
369 Menu* pTest = pActivePopup;
370 FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
371 SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose );
372 sal_uInt16 nRet = pActivePopup->ImplExecute( this, tools::Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Right, pMenu, pTimer == nullptr );
373 SetPopupModeFlags( nOldFlags );
375 // nRet != 0, if it was stopped during Activate()...
376 if ( !nRet && ( pActivePopup == pTest ) && pActivePopup->ImplGetWindow() )
377 pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this );
382 IMPL_LINK_NOARG(MenuFloatingWindow, SubmenuClose, Timer *, void)
384 if( pMenu && pMenu->pStartedFrom )
386 MenuFloatingWindow* pWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->GetWindow());
387 if( pWin )
388 pWin->KillActivePopup();
392 IMPL_LINK( MenuFloatingWindow, ShowHideListener, VclWindowEvent&, rEvent, void )
394 if( ! pMenu )
395 return;
397 if( rEvent.GetId() == VclEventId::WindowShow )
398 pMenu->ImplCallEventListeners( VclEventId::MenuShow, ITEMPOS_INVALID );
399 else if( rEvent.GetId() == VclEventId::WindowHide )
400 pMenu->ImplCallEventListeners( VclEventId::MenuHide, ITEMPOS_INVALID );
403 void MenuFloatingWindow::EnableScrollMenu( bool b )
405 bScrollMenu = b;
406 nScrollerHeight = b ? static_cast<sal_uInt16>(GetSettings().GetStyleSettings().GetScrollBarSize()) /2 : 0;
407 bScrollDown = true;
408 InitMenuClipRegion(*this);
411 void MenuFloatingWindow::Start()
413 if (bInExecute)
414 return;
415 bInExecute = true;
416 if (GetParent())
417 GetParent()->IncModalCount();
420 bool MenuFloatingWindow::MenuInHierarchyHasFocus() const
422 if (HasChildPathFocus())
423 return true;
424 PopupMenu* pSub = GetActivePopup();
425 if (!pSub)
426 return false;
427 return pSub->ImplGetFloatingWindow()->HasChildPathFocus();
430 void MenuFloatingWindow::End()
432 if (!bInExecute)
433 return;
435 if (GetParent() && !GetParent()->IsDisposed())
436 GetParent()->DecModalCount();
438 // restore focus to previous window if we still have the focus
439 VclPtr<vcl::Window> xFocusId(xSaveFocusId);
440 xSaveFocusId = nullptr;
441 if (xFocusId != nullptr && MenuInHierarchyHasFocus())
443 ImplGetSVData()->maWinData.mbNoDeactivate = false;
444 Window::EndSaveFocus(xFocusId);
447 bInExecute = false;
450 void MenuFloatingWindow::Execute()
452 ImplSVData* pSVData = ImplGetSVData();
454 pSVData->maAppData.mpActivePopupMenu = static_cast<PopupMenu*>(pMenu.get());
456 Start();
458 while (bInExecute)
459 Application::Yield();
461 pSVData->maAppData.mpActivePopupMenu = nullptr;
464 void MenuFloatingWindow::StopExecute()
466 End();
468 ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xSaveFocusId);
470 aHighlightChangedTimer.Stop();
471 if (pActivePopup)
473 KillActivePopup();
475 // notify parent, needed for accessibility
476 if( pMenu && pMenu->pStartedFrom )
477 pMenu->pStartedFrom->ImplCallEventListeners( VclEventId::MenuSubmenuDeactivate, nPosInParent );
480 void MenuFloatingWindow::KillActivePopup( PopupMenu* pThisOnly )
482 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->SetParentToDefaultWindow();
502 pPopup->pWindow.disposeAndClear();
504 Update();
509 void MenuFloatingWindow::EndExecute()
511 Menu* pStart = pMenu ? pMenu->ImplGetStartMenu() : nullptr;
513 // if started elsewhere, cleanup there as well
514 MenuFloatingWindow* pCleanUpFrom = this;
515 MenuFloatingWindow* pWin = this;
516 while (pWin && !pWin->bInExecute &&
517 pWin->pMenu->pStartedFrom && !pWin->pMenu->pStartedFrom->IsMenuBar())
519 pWin = static_cast<PopupMenu*>(pWin->pMenu->pStartedFrom.get())->ImplGetFloatingWindow();
521 if ( pWin )
522 pCleanUpFrom = pWin;
524 // this window will be destroyed => store date locally...
525 Menu* pM = pMenu;
526 sal_uInt16 nItem = nHighlightedItem;
528 pCleanUpFrom->StopExecute();
530 if ( nItem != ITEMPOS_INVALID && pM )
532 MenuItemData* pItemData = pM->GetItemList()->GetDataFromPos( nItem );
533 if ( pItemData && !pItemData->bIsTemporary )
535 pM->nSelectedId = pItemData->nId;
536 pM->sSelectedIdent = pItemData->sIdent;
537 if (pStart)
539 pStart->nSelectedId = pItemData->nId;
540 pStart->sSelectedIdent = pItemData->sIdent;
543 pM->ImplSelect();
548 void MenuFloatingWindow::EndExecute( sal_uInt16 nId )
550 size_t nPos;
551 if ( pMenu && pMenu->GetItemList()->GetData( nId, nPos ) )
552 nHighlightedItem = nPos;
553 else
554 nHighlightedItem = ITEMPOS_INVALID;
556 EndExecute();
559 void MenuFloatingWindow::MouseButtonDown( const MouseEvent& rMEvt )
561 // TH creates a ToTop on this window, but the active popup
562 // should stay on top...
563 // due to focus change this would close all menus -> don't do it (#94123)
564 //if ( pActivePopup && pActivePopup->ImplGetWindow() && !pActivePopup->ImplGetFloatingWindow()->pActivePopup )
565 // pActivePopup->ImplGetFloatingWindow()->ToTop( ToTopFlags::NoGrabFocus );
567 ImplHighlightItem( rMEvt, true );
569 nMBDownPos = nHighlightedItem;
572 void MenuFloatingWindow::MouseButtonUp( const MouseEvent& rMEvt )
574 MenuItemData* pData = pMenu ? pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ) : nullptr;
575 // nMBDownPos store in local variable and reset immediately,
576 // as it will be too late after EndExecute
577 sal_uInt16 _nMBDownPos = nMBDownPos;
578 nMBDownPos = ITEMPOS_INVALID;
579 if ( pData && pData->bEnabled && ( pData->eType != MenuItemType::SEPARATOR ) )
581 if ( !pData->pSubMenu )
583 EndExecute();
585 else if ( ( pData->nBits & MenuItemBits::POPUPSELECT ) && ( nHighlightedItem == _nMBDownPos ) && ( rMEvt.GetClicks() == 2 ) )
587 // not when clicked over the arrow...
588 Size aSz = GetOutputSizePixel();
589 long nFontHeight = GetTextHeight();
590 if ( rMEvt.GetPosPixel().X() < ( aSz.Width() - nFontHeight - nFontHeight/4 ) )
591 EndExecute();
597 void MenuFloatingWindow::MouseMove( const MouseEvent& rMEvt )
599 if ( !IsVisible() || rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() )
600 return;
602 if ( rMEvt.IsLeaveWindow() )
604 // #102461# do not remove highlight if a popup menu is open at this position
605 MenuItemData* pData = pMenu ? pMenu->pItemList->GetDataFromPos( nHighlightedItem ) : nullptr;
606 // close popup with some delayed if we leave somewhere else
607 if( pActivePopup && pData && pData->pSubMenu != pActivePopup )
608 pActivePopup->ImplGetFloatingWindow()->aSubmenuCloseTimer.Start();
610 if( !pActivePopup || (pData && pData->pSubMenu != pActivePopup ) )
611 ChangeHighlightItem( ITEMPOS_INVALID, false );
613 if ( IsScrollMenu() )
614 ImplScroll( rMEvt.GetPosPixel() );
616 else
618 aSubmenuCloseTimer.Stop();
619 if( bIgnoreFirstMove )
620 bIgnoreFirstMove = false;
621 else
622 ImplHighlightItem( rMEvt, false );
626 void MenuFloatingWindow::ImplScroll( bool bUp )
628 KillActivePopup();
629 Update();
631 if (!pMenu)
632 return;
634 Invalidate();
636 pMenu->ImplKillLayoutData();
638 if ( bScrollUp && bUp )
640 nFirstEntry = pMenu->ImplGetPrevVisible( nFirstEntry );
641 SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
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 if ( !bScrollDown )
651 bScrollDown = true;
652 Invalidate();
655 if ( pMenu->ImplGetPrevVisible( nFirstEntry ) == ITEMPOS_INVALID )
657 bScrollUp = false;
658 Invalidate();
661 Scroll( 0, nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip );
664 else if ( bScrollDown && !bUp )
666 // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686)
667 const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry );
668 if ( pItemData )
670 long nScrollEntryHeight = pItemData->aSz.Height();
672 nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry );
673 SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
675 if ( !bScrollUp )
677 bScrollUp = true;
678 Invalidate();
681 long nHeight = GetOutputSizePixel().Height();
682 sal_uInt16 nLastVisible;
683 static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( nHeight, nFirstEntry, &nLastVisible );
684 if ( pMenu->ImplGetNextVisible( nLastVisible ) == ITEMPOS_INVALID )
686 bScrollDown = false;
687 Invalidate();
690 Scroll( 0, -nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip );
694 Invalidate();
697 void MenuFloatingWindow::ImplScroll( const Point& rMousePos )
699 Size aOutSz = GetOutputSizePixel();
701 long nY = nScrollerHeight;
702 long nMouseY = rMousePos.Y();
703 long nDelta = 0;
705 if ( bScrollUp && ( nMouseY < nY ) )
707 ImplScroll( true );
708 nDelta = nY - nMouseY;
710 else if ( bScrollDown && ( nMouseY > ( aOutSz.Height() - nY ) ) )
712 ImplScroll( false );
713 nDelta = nMouseY - ( aOutSz.Height() - nY );
716 if ( nDelta )
718 aScrollTimer.Stop(); // if scrolled through MouseMove.
719 long nTimeout;
720 if ( nDelta < 3 )
721 nTimeout = 200;
722 else if ( nDelta < 5 )
723 nTimeout = 100;
724 else if ( nDelta < 8 )
725 nTimeout = 70;
726 else if ( nDelta < 12 )
727 nTimeout = 40;
728 else
729 nTimeout = 20;
730 aScrollTimer.SetTimeout( nTimeout );
731 aScrollTimer.Start();
734 void MenuFloatingWindow::ChangeHighlightItem( sal_uInt16 n, bool bStartPopupTimer )
736 // #57934# if necessary, immediately close the active, as TH's backgroundstorage works.
737 // #65750# we prefer to refrain from the background storage of small lines.
738 // otherwise the menus are difficult to operate.
739 // MenuItemData* pNextData = pMenu->pItemList->GetDataFromPos( n );
740 // if ( pActivePopup && pNextData && ( pActivePopup != pNextData->pSubMenu ) )
741 // KillActivePopup();
743 aSubmenuCloseTimer.Stop();
744 if( ! pMenu )
745 return;
747 if ( nHighlightedItem != ITEMPOS_INVALID )
749 InvalidateItem(nHighlightedItem);
750 pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem );
753 nHighlightedItem = n;
754 SAL_WARN_IF( !pMenu->ImplIsVisible( nHighlightedItem ) && nHighlightedItem != ITEMPOS_INVALID, "vcl", "ChangeHighlightItem: Not visible!" );
755 if( nHighlightedItem != ITEMPOS_INVALID )
757 if (pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar())
759 // #102461# make sure parent entry is highlighted as well
760 size_t i, nCount = pMenu->pStartedFrom->pItemList->size();
761 for(i = 0; i < nCount; i++)
763 MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i );
764 if( pData && ( pData->pSubMenu == pMenu ) )
765 break;
767 if( i < nCount )
769 MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
770 if( pPWin && pPWin->nHighlightedItem != i )
772 pPWin->InvalidateItem(i);
773 pPWin->nHighlightedItem = i;
777 InvalidateItem(nHighlightedItem);
778 pMenu->ImplCallHighlight( nHighlightedItem );
780 else
782 pMenu->nSelectedId = 0;
783 pMenu->sSelectedIdent.clear();
786 if ( bStartPopupTimer )
788 // #102438# Menu items are not selectable
789 // If a menu item is selected by an AT-tool via the XAccessibleAction, XAccessibleValue
790 // or XAccessibleSelection interface, and the parent popup menus are not executed yet,
791 // the parent popup menus must be executed SYNCHRONOUSLY, before the menu item is selected.
792 if ( GetSettings().GetMouseSettings().GetMenuDelay() )
793 aHighlightChangedTimer.Start();
794 else
795 HighlightChanged( &aHighlightChangedTimer );
799 /// Calculate the initial vertical pixel offset of the first item.
800 /// May be negative for scrolled windows.
801 long MenuFloatingWindow::GetInitialItemY(long *pStartY) const
803 long nStartY = ImplGetStartY();
804 if (pStartY)
805 *pStartY = nStartY;
806 return nScrollerHeight + nStartY +
807 ImplGetSVData()->maNWFData.mnMenuFormatBorderY;
810 /// Emit an Invalidate just for this item's area
811 void MenuFloatingWindow::InvalidateItem(sal_uInt16 nPos)
813 if (!pMenu)
814 return;
816 long nY = GetInitialItemY();
817 size_t nCount = pMenu->pItemList->size();
818 for (size_t n = 0; n < nCount; n++)
820 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
821 long nHeight = pData->aSz.Height();
822 if (n == nPos)
824 Size aWidth( GetSizePixel() );
825 tools::Rectangle aRect(Point(0, nY), Size(aWidth.Width(), nHeight));
826 Invalidate( aRect );
828 nY += nHeight;
832 void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos)
834 if (!pMenu)
835 return;
837 Size aSz(GetOutputSizePixel());
839 long nX = 0;
840 long nStartY;
841 long nY = GetInitialItemY(&nStartY);
843 int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
845 size_t nCount = pMenu->pItemList->size();
846 for (size_t n = 0; n < nCount; n++)
848 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
849 if (n == nPos)
851 SAL_WARN_IF(!pMenu->ImplIsVisible(n), "vcl", "Highlight: Item not visible!");
852 if (pData->eType != MenuItemType::SEPARATOR)
854 bool bRestoreLineColor = false;
855 Color oldLineColor;
856 bool bDrawItemRect = true;
858 tools::Rectangle aItemRect(Point(nX + nOuterSpaceX, nY), Size(aSz.Width() - 2 * nOuterSpaceX, pData->aSz.Height()));
859 if (pData->nBits & MenuItemBits::POPUPSELECT)
861 long nFontHeight = GetTextHeight();
862 aItemRect.AdjustRight( -(nFontHeight + nFontHeight / 4) );
865 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
867 Size aPxSize(GetOutputSizePixel());
868 rRenderContext.Push(PushFlags::CLIPREGION);
869 rRenderContext.IntersectClipRegion(tools::Rectangle(Point(nX, nY), Size(aSz.Width(), pData->aSz.Height())));
870 tools::Rectangle aCtrlRect(Point(nX, 0), Size(aPxSize.Width()-nX, aPxSize.Height()));
871 MenupopupValue aVal(pMenu->nTextPos-GUTTERBORDER, aItemRect);
872 rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire,
873 aCtrlRect, ControlState::ENABLED, aVal, OUString());
874 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
876 bDrawItemRect = false;
877 if (!rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem, aItemRect,
878 ControlState::SELECTED | (pData->bEnabled
879 ? ControlState::ENABLED
880 : ControlState::NONE),
881 aVal, OUString()))
883 bDrawItemRect = true;
886 else
887 bDrawItemRect = true;
888 rRenderContext.Pop();
890 if (bDrawItemRect)
892 if (pData->bEnabled)
893 rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
894 else
896 rRenderContext.SetFillColor();
897 oldLineColor = rRenderContext.GetLineColor();
898 rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
899 bRestoreLineColor = true;
902 rRenderContext.DrawRect(aItemRect);
904 pMenu->ImplPaint(rRenderContext, GetOutputSizePixel(), nScrollerHeight, nStartY, pData, true/*bHighlight*/);
905 if (bRestoreLineColor)
906 rRenderContext.SetLineColor(oldLineColor);
908 return;
911 nY += pData->aSz.Height();
915 tools::Rectangle MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos )
917 if( ! pMenu )
918 return tools::Rectangle();
920 tools::Rectangle aRect;
921 Size aSz = GetOutputSizePixel();
922 long nStartY = ImplGetStartY();
923 long nY = nScrollerHeight+nStartY;
925 size_t nCount = pMenu->pItemList->size();
926 for ( size_t n = 0; n < nCount; n++ )
928 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
929 if ( n == nPos )
931 SAL_WARN_IF( !pMenu->ImplIsVisible( n ), "vcl", "ImplGetItemRect: Item not visible!" );
932 if ( pData->eType != MenuItemType::SEPARATOR )
934 aRect = tools::Rectangle( Point( 0, nY ), Size( aSz.Width(), pData->aSz.Height() ) );
935 if ( pData->nBits & MenuItemBits::POPUPSELECT )
937 long nFontHeight = GetTextHeight();
938 aRect.AdjustRight( -(nFontHeight + nFontHeight/4) );
941 break;
943 nY += pData->aSz.Height();
945 return aRect;
948 void MenuFloatingWindow::ImplCursorUpDown( bool bUp, bool bHomeEnd )
950 if( ! pMenu )
951 return;
953 const StyleSettings& rSettings = GetSettings().GetStyleSettings();
955 sal_uInt16 n = nHighlightedItem;
956 if ( n == ITEMPOS_INVALID )
958 if ( bUp )
959 n = 0;
960 else
961 n = pMenu->GetItemCount()-1;
964 sal_uInt16 nLoop = n;
966 if( bHomeEnd )
968 // absolute positioning
969 if( bUp )
971 n = pMenu->GetItemCount();
972 nLoop = n-1;
974 else
976 n = sal_uInt16(-1);
977 nLoop = n+1;
983 if ( bUp )
985 if ( n )
986 n--;
987 else
988 if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
989 n = pMenu->GetItemCount()-1;
990 else
991 break;
993 else
995 n++;
996 if ( n >= pMenu->GetItemCount() )
998 if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
999 n = 0;
1000 else
1001 break;
1005 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( n );
1006 if ( ( pData->bEnabled || !rSettings.GetSkipDisabledInMenus() )
1007 && ( pData->eType != MenuItemType::SEPARATOR ) && pMenu->ImplIsVisible( n ) && pMenu->ImplIsSelectable( n ) )
1009 // Is selection in visible area?
1010 if ( IsScrollMenu() )
1012 ChangeHighlightItem( ITEMPOS_INVALID, false );
1014 while ( n < nFirstEntry )
1015 ImplScroll( true );
1017 Size aOutSz = GetOutputSizePixel();
1018 sal_uInt16 nLastVisible;
1019 static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible );
1020 while ( n > nLastVisible )
1022 ImplScroll( false );
1023 static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible );
1026 ChangeHighlightItem( n, false );
1027 break;
1029 } while ( n != nLoop );
1032 void MenuFloatingWindow::KeyInput( const KeyEvent& rKEvent )
1034 VclPtr<vcl::Window> xWindow = this;
1036 bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
1037 sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
1038 bKeyInput = true;
1039 switch ( nCode )
1041 case KEY_UP:
1042 case KEY_DOWN:
1044 ImplCursorUpDown( nCode == KEY_UP );
1046 break;
1047 case KEY_END:
1048 case KEY_HOME:
1050 ImplCursorUpDown( nCode == KEY_END, true );
1052 break;
1053 case KEY_F6:
1054 case KEY_ESCAPE:
1056 // Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document
1057 if( nCode == KEY_F6 && !rKEvent.GetKeyCode().IsMod1() )
1058 break;
1059 if( pMenu )
1061 if ( !pMenu->pStartedFrom )
1063 StopExecute();
1064 KillActivePopup();
1066 else if (pMenu->pStartedFrom->IsMenuBar())
1068 pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
1070 else
1072 StopExecute();
1073 PopupMenu* pPopupMenu = static_cast<PopupMenu*>(pMenu->pStartedFrom.get());
1074 MenuFloatingWindow* pFloat = pPopupMenu->ImplGetFloatingWindow();
1075 pFloat->GrabFocus();
1076 pFloat->KillActivePopup();
1077 pPopupMenu->ImplCallHighlight(pFloat->nHighlightedItem);
1081 break;
1082 case KEY_LEFT:
1084 if ( pMenu && pMenu->pStartedFrom )
1086 StopExecute();
1087 if (pMenu->pStartedFrom->IsMenuBar())
1089 pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
1091 else
1093 MenuFloatingWindow* pFloat = static_cast<PopupMenu*>(pMenu->pStartedFrom.get())->ImplGetFloatingWindow();
1094 pFloat->GrabFocus();
1095 pFloat->KillActivePopup();
1096 sal_uInt16 highlightItem = pFloat->GetHighlightedItem();
1097 pFloat->ChangeHighlightItem(highlightItem, false);
1101 break;
1102 case KEY_RIGHT:
1104 if( pMenu )
1106 bool bDone = false;
1107 if ( nHighlightedItem != ITEMPOS_INVALID )
1109 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
1110 if ( pData && pData->pSubMenu )
1112 HighlightChanged( nullptr );
1113 bDone = true;
1116 if ( !bDone )
1118 Menu* pStart = pMenu->ImplGetStartMenu();
1119 if (pStart && pStart->IsMenuBar())
1121 // Forward...
1122 pStart->ImplGetWindow()->KeyInput( rKEvent );
1127 break;
1128 case KEY_RETURN:
1130 if( pMenu )
1132 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
1133 if ( pData && pData->bEnabled )
1135 if ( pData->pSubMenu )
1136 HighlightChanged( nullptr );
1137 else
1138 EndExecute();
1140 else
1141 StopExecute();
1144 break;
1145 case KEY_MENU:
1147 if( pMenu )
1149 Menu* pStart = pMenu->ImplGetStartMenu();
1150 if (pStart && pStart->IsMenuBar())
1152 // Forward...
1153 pStart->ImplGetWindow()->KeyInput( rKEvent );
1157 break;
1158 default:
1160 sal_Unicode nCharCode = rKEvent.GetCharCode();
1161 size_t nPos = 0;
1162 size_t nDuplicates = 0;
1163 MenuItemData* pData = (nCharCode && pMenu) ?
1164 pMenu->GetItemList()->SearchItem(nCharCode, rKEvent.GetKeyCode(), nPos, nDuplicates, nHighlightedItem) : nullptr;
1165 if (pData)
1167 if ( pData->pSubMenu || nDuplicates > 1 )
1169 ChangeHighlightItem( nPos, false );
1170 HighlightChanged( nullptr );
1172 else
1174 nHighlightedItem = nPos;
1175 EndExecute();
1178 else
1179 FloatingWindow::KeyInput( rKEvent );
1183 if (pMenu && pMenu->pStartedFrom && pMenu->pStartedFrom->IsMenuBar())
1185 MenuBar *pMenuBar = static_cast<MenuBar*>(pMenu->pStartedFrom.get());
1186 const bool bShowAccels = nCode != KEY_ESCAPE;
1187 if (pMenuBar->getMenuBarWindow()->GetMBWMenuKey() != bShowAccels)
1189 pMenuBar->getMenuBarWindow()->SetMBWMenuKey(bShowAccels);
1190 pMenuBar->getMenuBarWindow()->SetMBWHideAccel(!bShowAccels);
1191 if (autoacc)
1192 Invalidate(InvalidateFlags::Update);
1196 // #105474# check if menu window was not destroyed
1197 if ( !xWindow->IsDisposed() )
1199 bKeyInput = false;
1203 void MenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect)
1205 if (!pMenu)
1206 return;
1208 // Make sure that all actual rendering happens in one go to avoid flicker.
1209 vcl::BufferDevice pBuffer(this, rRenderContext);
1211 pBuffer->Push(PushFlags::CLIPREGION);
1212 pBuffer->SetClipRegion(vcl::Region(rPaintRect));
1214 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
1216 pBuffer->SetClipRegion();
1217 long nX = 0;
1218 Size aPxSize(GetOutputSizePixel());
1219 aPxSize.AdjustWidth( -nX );
1220 ImplControlValue aVal(pMenu->nTextPos - GUTTERBORDER);
1221 pBuffer->DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire,
1222 tools::Rectangle(Point(nX, 0), aPxSize), ControlState::ENABLED,
1223 aVal, OUString());
1224 InitMenuClipRegion(*pBuffer);
1226 if (IsScrollMenu())
1228 ImplDrawScroller(*pBuffer, true);
1229 ImplDrawScroller(*pBuffer, false);
1231 pBuffer->SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuColor());
1232 pMenu->ImplPaint(*pBuffer, GetOutputSizePixel(), nScrollerHeight, ImplGetStartY());
1233 if (nHighlightedItem != ITEMPOS_INVALID)
1234 RenderHighlightItem(*pBuffer, nHighlightedItem);
1236 pBuffer->Pop();
1239 void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp)
1241 if (!pMenu)
1242 return;
1244 rRenderContext.SetClipRegion();
1246 Size aOutSz(GetOutputSizePixel());
1247 long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight);
1248 long nX = 0;
1249 tools::Rectangle aRect(Point(nX, nY), Size(aOutSz.Width() - nX, nScrollerHeight));
1251 DecorationView aDecoView(&rRenderContext);
1252 SymbolType eSymbol = bUp ? SymbolType::SPIN_UP : SymbolType::SPIN_DOWN;
1254 DrawSymbolFlags nStyle = DrawSymbolFlags::NONE;
1255 if ((bUp && !bScrollUp) || (!bUp && !bScrollDown))
1256 nStyle |= DrawSymbolFlags::Disable;
1258 aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nStyle);
1260 InitMenuClipRegion(rRenderContext);
1263 void MenuFloatingWindow::RequestHelp( const HelpEvent& rHEvt )
1265 sal_uInt16 nId = nHighlightedItem;
1266 Menu* pM = pMenu;
1267 vcl::Window* pW = this;
1269 // #102618# Get item rect before destroying the window in EndExecute() call
1270 tools::Rectangle aHighlightRect( ImplGetItemRect( nHighlightedItem ) );
1272 if ( rHEvt.GetMode() & HelpEventMode::CONTEXT )
1274 nHighlightedItem = ITEMPOS_INVALID;
1275 EndExecute();
1276 pW = nullptr;
1279 if( !ImplHandleHelpEvent( pW, pM, nId, rHEvt, aHighlightRect ) )
1280 Window::RequestHelp( rHEvt );
1283 void MenuFloatingWindow::StateChanged( StateChangedType nType )
1285 FloatingWindow::StateChanged( nType );
1287 if ( ( nType == StateChangedType::ControlForeground ) || ( nType == StateChangedType::ControlBackground ) )
1289 ApplySettings(*this);
1290 Invalidate();
1294 void MenuFloatingWindow::DataChanged( const DataChangedEvent& rDCEvt )
1296 FloatingWindow::DataChanged( rDCEvt );
1298 if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
1299 (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
1300 ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
1301 (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
1303 ApplySettings(*this);
1304 Invalidate();
1308 void MenuFloatingWindow::Command( const CommandEvent& rCEvt )
1310 if ( rCEvt.GetCommand() == CommandEventId::Wheel )
1312 const CommandWheelData* pData = rCEvt.GetWheelData();
1313 if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
1315 ImplScroll( pData->GetDelta() > 0 );
1316 MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) );
1321 css::uno::Reference<css::accessibility::XAccessible> MenuFloatingWindow::CreateAccessible()
1323 css::uno::Reference<css::accessibility::XAccessible> xAcc;
1325 if (pMenu && !pMenu->pStartedFrom)
1326 xAcc = pMenu->GetAccessible();
1328 return xAcc;
1331 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */