bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / source / window / menufloatingwindow.cxx
blobcfd6a6ae190e8474e179269df73ab642a17be3ed
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 "bufferdevice.hxx"
24 #include <sal/log.hxx>
25 #include <salframe.hxx>
26 #include <svdata.hxx>
27 #include <vcl/decoview.hxx>
28 #include <vcl/settings.hxx>
29 #include <window.h>
31 MenuFloatingWindow::MenuFloatingWindow( Menu* pMen, vcl::Window* pParent, WinBits nStyle ) :
32 FloatingWindow( pParent, nStyle ),
33 pMenu(pMen),
34 aHighlightChangedTimer("vcl::MenuFloatingWindow aHighlightChangedTimer"),
35 aSubmenuCloseTimer( "vcl::MenuFloatingWindow aSubmenuCloseTimer" ),
36 aScrollTimer( "vcl::MenuFloatingWindow aScrollTimer" ),
37 nHighlightedItem(ITEMPOS_INVALID),
38 nMBDownPos(ITEMPOS_INVALID),
39 nScrollerHeight(0),
40 nFirstEntry(0),
41 nPosInParent(ITEMPOS_INVALID),
42 bInExecute(false),
43 bScrollMenu(false),
44 bScrollUp(false),
45 bScrollDown(false),
46 bIgnoreFirstMove(true),
47 bKeyInput(false)
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()
68 if( !pMenu )
69 return;
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 ) )
83 break;
85 if( i < nCount )
87 MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow());
88 if (pPWin)
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
100 if( GetParent() )
102 tools::Rectangle aInvRect( GetWindowExtentsRelative( GetParent() ) );
103 GetParent()->Invalidate( aInvRect );
105 pMenu = nullptr;
106 RemoveEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) );
108 aScrollTimer.Stop();
109 aSubmenuCloseTimer.Stop();
110 aSubmenuCloseTimer.Stop();
111 aHighlightChangedTimer.Stop();
112 aHighlightChangedTimer.Stop();
116 MenuFloatingWindow::~MenuFloatingWindow()
118 disposeOnce();
121 void MenuFloatingWindow::dispose()
123 doShutdown();
124 pMenu.clear();
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
161 else
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
172 tools::Long nY = 0;
173 if( pMenu )
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) )
178 return 0;
181 for ( sal_uInt16 n = 0; n < nFirstEntry; n++ )
182 nY += pMenu->GetItemList()->GetDataFromPos( n )->aSz.Height();
183 nY -= pMenu->GetTitleHeight();
185 return -nY;
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);
197 return aRegion;
200 void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext& rRenderContext)
202 if (IsScrollMenu())
204 rRenderContext.SetClipRegion(ImplCalcClipRegion());
206 else
208 rRenderContext.SetClipRegion();
212 void MenuFloatingWindow::ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown )
214 if( ! pMenu )
215 return;
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 ) );
242 if ( bMBDown )
244 if ( n != nHighlightedItem )
246 ChangeHighlightItem( static_cast<sal_uInt16>(n), false );
249 bool bAllowNewPopup = true;
250 if ( pActivePopup )
252 MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n );
253 bAllowNewPopup = pData && ( pData->pSubMenu != pActivePopup );
254 if ( bAllowNewPopup )
255 KillActivePopup();
258 if ( bPopupArea && bAllowNewPopup )
260 HighlightChanged( nullptr );
263 else
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 );
275 bHighlighted = true;
279 if ( !bHighlighted )
280 ChangeHighlightItem( ITEMPOS_INVALID, true );
282 else
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!
292 Menu* pM = pMenu;
293 if ( bInExecute )
295 End();
296 if ( pActivePopup )
298 KillActivePopup(); // should be ok to just remove it
299 //pActivePopup->bCanceled = true;
301 pMenu->bInCallback = true;
302 pMenu->Deactivate();
303 pMenu->bInCallback = false;
305 else
307 if (pMenu && pMenu->pStartedFrom)
308 pMenu->pStartedFrom->ClosePopup(pMenu);
311 if ( pM )
312 pM->pStartedFrom = nullptr;
315 IMPL_LINK_NOARG(MenuFloatingWindow, AutoScroll, Timer *, void)
317 ImplScroll( GetPointerPosPixel() );
320 IMPL_LINK( MenuFloatingWindow, HighlightChanged, Timer*, pTimer, void )
322 if( ! pMenu )
323 return;
325 MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( nHighlightedItem );
326 if ( !pItemData )
327 return;
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 )) )
337 return;
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 );
359 else
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
369 // displayed now.
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());
386 if( pWin )
387 pWin->KillActivePopup();
391 IMPL_LINK( MenuFloatingWindow, ShowHideListener, VclWindowEvent&, rEvent, void )
393 if( ! pMenu )
394 return;
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 )
404 bScrollMenu = b;
405 nScrollerHeight = b ? static_cast<sal_uInt16>(GetSettings().GetStyleSettings().GetScrollBarSize()) /2 : 0;
406 bScrollDown = true;
407 InitMenuClipRegion(*GetOutDev());
410 void MenuFloatingWindow::Start()
412 if (bInExecute)
413 return;
414 bInExecute = true;
415 if (GetParent())
416 GetParent()->IncModalCount();
419 bool MenuFloatingWindow::MenuInHierarchyHasFocus() const
421 if (HasChildPathFocus())
422 return true;
423 PopupMenu* pSub = GetActivePopup();
424 if (!pSub)
425 return false;
426 return pSub->ImplGetFloatingWindow()->HasChildPathFocus();
429 void MenuFloatingWindow::End()
431 if (!bInExecute)
432 return;
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);
446 bInExecute = false;
449 void MenuFloatingWindow::Execute()
451 ImplSVData* pSVData = ImplGetSVData();
453 pSVData->maAppData.mpActivePopupMenu = static_cast<PopupMenu*>(pMenu.get());
455 Start();
457 while (bInExecute && !Application::IsQuit())
458 Application::Yield();
460 pSVData->maAppData.mpActivePopupMenu = nullptr;
463 void MenuFloatingWindow::StopExecute()
465 End();
467 ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xSaveFocusId);
469 aHighlightChangedTimer.Stop();
470 if (pActivePopup)
472 KillActivePopup();
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 ) ) )
482 return;
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();
503 PaintImmediately();
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();
519 if ( pWin )
520 pCleanUpFrom = pWin;
522 // this window will be destroyed => store date locally...
523 Menu* pM = pMenu;
524 sal_uInt16 nItem = nHighlightedItem;
526 pCleanUpFrom->StopExecute();
528 if ( !(nItem != ITEMPOS_INVALID && pM) )
529 return;
531 MenuItemData* pItemData = pM->GetItemList()->GetDataFromPos( nItem );
532 if ( pItemData && !pItemData->bIsTemporary )
534 pM->nSelectedId = pItemData->nId;
535 pM->sSelectedIdent = pItemData->sIdent;
536 if (pStart)
538 pStart->nSelectedId = pItemData->nId;
539 pStart->sSelectedIdent = pItemData->sIdent;
542 pM->ImplSelect();
546 void MenuFloatingWindow::EndExecute( sal_uInt16 nId )
548 size_t nPos;
549 if ( pMenu && pMenu->GetItemList()->GetData( nId, nPos ) )
550 nHighlightedItem = nPos;
551 else
552 nHighlightedItem = ITEMPOS_INVALID;
554 EndExecute();
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 )) )
578 return;
580 if ( !pData->pSubMenu )
582 EndExecute();
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 ) )
590 EndExecute();
595 void MenuFloatingWindow::MouseMove( const MouseEvent& rMEvt )
597 if ( !IsVisible() || rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() )
598 return;
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() );
614 else
616 aSubmenuCloseTimer.Stop();
617 if( bIgnoreFirstMove )
618 bIgnoreFirstMove = false;
619 else
620 ImplHighlightItem( rMEvt, false );
624 void MenuFloatingWindow::ImplScroll( bool bUp )
626 KillActivePopup();
627 PaintImmediately();
629 if (!pMenu)
630 return;
632 Invalidate();
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 );
643 if ( pItemData )
645 tools::Long nScrollEntryHeight = pItemData->aSz.Height();
647 if ( !bScrollDown )
649 bScrollDown = true;
650 Invalidate();
653 if ( pMenu->ImplGetPrevVisible( nFirstEntry ) == ITEMPOS_INVALID )
655 bScrollUp = false;
656 Invalidate();
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 );
666 if ( pItemData )
668 tools::Long nScrollEntryHeight = pItemData->aSz.Height();
670 nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry );
671 SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" );
673 if ( !bScrollUp )
675 bScrollUp = true;
676 Invalidate();
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 )
684 bScrollDown = false;
685 Invalidate();
688 Scroll( 0, -nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip );
692 Invalidate();
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 ) )
705 ImplScroll( true );
706 nDelta = nY - nMouseY;
708 else if ( bScrollDown && ( nMouseY > ( aOutSz.Height() - nY ) ) )
710 ImplScroll( false );
711 nDelta = nMouseY - ( aOutSz.Height() - nY );
714 if ( !nDelta )
715 return;
717 aScrollTimer.Stop(); // if scrolled through MouseMove.
718 tools::Long nTimeout;
719 if ( nDelta < 3 )
720 nTimeout = 200;
721 else if ( nDelta < 5 )
722 nTimeout = 100;
723 else if ( nDelta < 8 )
724 nTimeout = 70;
725 else if ( nDelta < 12 )
726 nTimeout = 40;
727 else
728 nTimeout = 20;
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();
742 if( ! pMenu )
743 return;
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 ) )
763 break;
765 if( i < nCount )
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 );
778 else
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();
792 else
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();
802 if (pStartY)
803 *pStartY = nStartY;
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)
811 if (!pMenu)
812 return;
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();
820 if (n == nPos)
822 Size aWidth( GetSizePixel() );
823 tools::Rectangle aRect(Point(0, nY), Size(aWidth.Width(), nHeight));
824 Invalidate( aRect );
826 nY += nHeight;
830 void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos)
832 if (!pMenu)
833 return;
835 Size aSz(GetOutputSizePixel());
837 tools::Long nX = 0;
838 tools::Long nStartY;
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 );
847 if (n == nPos)
849 SAL_WARN_IF(!pMenu->ImplIsVisible(n), "vcl", "Highlight: Item not visible!");
850 if (pData->eType != MenuItemType::SEPARATOR)
852 bool bRestoreLineColor = false;
853 Color oldLineColor;
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),
879 aVal, OUString()))
881 bDrawItemRect = true;
884 else
885 bDrawItemRect = true;
886 rRenderContext.Pop();
888 if (bDrawItemRect)
890 if (pData->bEnabled)
891 rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
892 else
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);
906 return;
909 nY += pData->aSz.Height();
913 tools::Rectangle MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos ) const
915 if( ! pMenu )
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 );
927 if ( n == nPos )
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) );
939 break;
941 nY += pData->aSz.Height();
943 return aRect;
946 void MenuFloatingWindow::ImplCursorUpDown( bool bUp, bool bHomeEnd )
948 if( ! pMenu )
949 return;
951 const StyleSettings& rSettings = GetSettings().GetStyleSettings();
953 sal_uInt16 n = nHighlightedItem;
954 if ( n == ITEMPOS_INVALID )
956 if ( bUp )
957 n = 0;
958 else
959 n = pMenu->GetItemCount()-1;
962 sal_uInt16 nLoop = n;
964 if( bHomeEnd )
966 // absolute positioning
967 if( bUp )
969 n = pMenu->GetItemCount();
970 nLoop = n-1;
972 else
974 n = sal_uInt16(-1);
975 nLoop = n+1;
981 if ( bUp )
983 if ( n )
984 n--;
985 else
986 if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
987 n = pMenu->GetItemCount()-1;
988 else
989 break;
991 else
993 n++;
994 if ( n >= pMenu->GetItemCount() )
996 if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) )
997 n = 0;
998 else
999 break;
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 )
1013 ImplScroll( true );
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 );
1025 break;
1027 } while ( n != nLoop );
1030 void MenuFloatingWindow::KeyInput( const KeyEvent& rKEvent )
1032 VclPtr<vcl::Window> xWindow = this;
1034 sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
1035 bKeyInput = true;
1036 switch ( nCode )
1038 case KEY_UP:
1039 case KEY_DOWN:
1041 ImplCursorUpDown( nCode == KEY_UP );
1043 break;
1044 case KEY_END:
1045 case KEY_HOME:
1047 ImplCursorUpDown( nCode == KEY_END, true );
1049 break;
1050 case KEY_F6:
1051 case KEY_ESCAPE:
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() )
1055 break;
1056 if( pMenu )
1058 if ( !pMenu->pStartedFrom )
1060 StopExecute();
1061 KillActivePopup();
1063 else if (pMenu->pStartedFrom->IsMenuBar())
1065 pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
1067 else
1069 StopExecute();
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);
1078 break;
1079 case KEY_LEFT:
1081 if ( pMenu && pMenu->pStartedFrom )
1083 StopExecute();
1084 if (pMenu->pStartedFrom->IsMenuBar())
1086 pMenu->pStartedFrom->MenuBarKeyInput(rKEvent);
1088 else
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);
1098 break;
1099 case KEY_RIGHT:
1101 if( pMenu )
1103 bool bDone = false;
1104 if ( nHighlightedItem != ITEMPOS_INVALID )
1106 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
1107 if ( pData && pData->pSubMenu )
1109 HighlightChanged( nullptr );
1110 bDone = true;
1113 if ( !bDone )
1115 Menu* pStart = pMenu->ImplGetStartMenu();
1116 if (pStart && pStart->IsMenuBar())
1118 // Forward...
1119 pStart->ImplGetWindow()->KeyInput( rKEvent );
1124 break;
1125 case KEY_RETURN:
1127 if( pMenu )
1129 MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem );
1130 if ( pData && pData->bEnabled )
1132 if ( pData->pSubMenu )
1133 HighlightChanged( nullptr );
1134 else
1135 EndExecute();
1137 else
1138 StopExecute();
1141 break;
1142 case KEY_MENU:
1144 if( pMenu )
1146 Menu* pStart = pMenu->ImplGetStartMenu();
1147 if (pStart && pStart->IsMenuBar())
1149 // Forward...
1150 pStart->ImplGetWindow()->KeyInput( rKEvent );
1154 break;
1155 default:
1157 sal_Unicode nCharCode = rKEvent.GetCharCode();
1158 size_t nPos = 0;
1159 size_t nDuplicates = 0;
1160 MenuItemData* pData = (nCharCode && pMenu) ?
1161 pMenu->GetItemList()->SearchItem(nCharCode, rKEvent.GetKeyCode(), nPos, nDuplicates, nHighlightedItem) : nullptr;
1162 if (pData)
1164 if ( pData->pSubMenu || nDuplicates > 1 )
1166 ChangeHighlightItem( nPos, false );
1167 HighlightChanged( nullptr );
1169 else
1171 nHighlightedItem = nPos;
1172 EndExecute();
1175 else
1176 FloatingWindow::KeyInput( rKEvent );
1180 // #105474# check if menu window was not destroyed
1181 if ( !xWindow->isDisposed() )
1183 bKeyInput = false;
1187 void MenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect)
1189 if (!pMenu)
1190 return;
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();
1203 tools::Long nX = 0;
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,
1209 aVal, OUString());
1210 InitMenuClipRegion(*pBuffer);
1212 if (IsScrollMenu())
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);
1222 pBuffer.Dispose();
1223 rRenderContext.Pop();
1226 void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp)
1228 if (!pMenu)
1229 return;
1231 rRenderContext.SetClipRegion();
1233 Size aOutSz(GetOutputSizePixel());
1234 tools::Long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight);
1235 tools::Long nX = 0;
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;
1253 Menu* pM = pMenu;
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;
1262 EndExecute();
1263 pW = nullptr;
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());
1277 Invalidate();
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());
1291 Invalidate();
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();
1315 return xAcc;
1318 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */