bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / window / menu.cxx
blob1d25c089e9767b710ecf78d228348e03739e881f
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 <tools/diagnose_ex.h>
21 #include <sal/log.hxx>
23 #include <comphelper/lok.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/mnemonic.hxx>
26 #include <vcl/image.hxx>
27 #include <vcl/event.hxx>
28 #include <vcl/help.hxx>
29 #include <vcl/floatwin.hxx>
30 #include <vcl/decoview.hxx>
31 #include <vcl/menu.hxx>
32 #include <vcl/taskpanelist.hxx>
33 #include <vcl/controllayout.hxx>
34 #include <vcl/settings.hxx>
35 #include <vcl/commandinfoprovider.hxx>
37 #include <salinst.hxx>
38 #include <svdata.hxx>
39 #include <strings.hrc>
40 #include <window.h>
41 #include <salmenu.hxx>
42 #include <salframe.hxx>
44 #include "menubarwindow.hxx"
45 #include "menufloatingwindow.hxx"
46 #include "menuitemlist.hxx"
48 #include <com/sun/star/uno/Reference.h>
49 #include <com/sun/star/lang/XComponent.hpp>
50 #include <com/sun/star/accessibility/XAccessible.hpp>
51 #include <vcl/toolkit/unowrap.hxx>
53 #include <vcl/configsettings.hxx>
55 #include <map>
56 #include <string_view>
57 #include <vector>
59 namespace vcl
62 struct MenuLayoutData : public ControlLayoutData
64 std::vector< sal_uInt16 > m_aLineItemIds;
65 std::map< sal_uInt16, tools::Rectangle > m_aVisibleItemBoundRects;
70 using namespace vcl;
72 #define EXTRAITEMHEIGHT 4
73 #define SPACE_AROUND_TITLE 4
75 static bool ImplAccelDisabled()
77 // display of accelerator strings may be suppressed via configuration
78 static int nAccelDisabled = -1;
80 if( nAccelDisabled == -1 )
82 OUString aStr =
83 vcl::SettingsConfigItem::get()->
84 getValue( "Menu", "SuppressAccelerators" );
85 nAccelDisabled = aStr.equalsIgnoreAsciiCase("true") ? 1 : 0;
87 return nAccelDisabled == 1;
90 static void ImplSetMenuItemData( MenuItemData* pData )
92 // convert data
93 if ( !pData->aImage )
94 pData->eType = MenuItemType::STRING;
95 else if ( pData->aText.isEmpty() )
96 pData->eType = MenuItemType::IMAGE;
97 else
98 pData->eType = MenuItemType::STRINGIMAGE;
101 namespace {
103 void ImplClosePopupToolBox( const VclPtr<vcl::Window>& pWin )
105 if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin ) )
107 ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( pWin );
108 if ( pWrapper && pWrapper->GetFloatingWindow() )
109 pWrapper->GetFloatingWindow()->EndPopupMode( FloatWinPopupEndFlags::CloseAll );
113 // TODO: Move to common code with the same function in toolbox
114 // Draw the ">>" - more indicator at the coordinates
115 void lclDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
117 rRenderContext.Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
118 rRenderContext.SetLineColor();
120 if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
121 rRenderContext.SetFillColor(COL_WHITE);
122 else
123 rRenderContext.SetFillColor(COL_BLACK);
124 float fScaleFactor = rRenderContext.GetDPIScaleFactor();
126 int linewidth = 1 * fScaleFactor;
127 int space = 4 * fScaleFactor;
129 long width = 8 * fScaleFactor;
130 long height = 5 * fScaleFactor;
132 //Keep odd b/c drawing code works better
133 if ( height % 2 == 0 )
134 height--;
136 long heightOrig = height;
138 long x = rRect.Left() + (rRect.getWidth() - width)/2 + 1;
139 long y = rRect.Top() + (rRect.getHeight() - height)/2 + 1;
140 while( height >= 1)
142 rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
143 x += space;
144 rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
145 x -= space;
146 y++;
147 if( height <= heightOrig / 2 + 1) x--;
148 else x++;
149 height--;
151 rRenderContext.Pop();
154 } // end anonymous namespace
157 Menu::Menu()
158 : mpFirstDel(nullptr),
159 pItemList(new MenuItemList),
160 pStartedFrom(nullptr),
161 pWindow(nullptr),
162 nTitleHeight(0),
163 nEventId(nullptr),
164 mnHighlightedItemPos(ITEMPOS_INVALID),
165 nMenuFlags(MenuFlags::NONE),
166 nSelectedId(0),
167 nImgOrChkPos(0),
168 nTextPos(0),
169 bCanceled(false),
170 bInCallback(false),
171 bKilled(false)
175 Menu::~Menu()
177 disposeOnce();
180 void Menu::dispose()
182 ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID );
184 // at the window free the reference to the accessible component
185 // and make sure the MenuFloatingWindow knows about our destruction
186 if ( pWindow )
188 MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get());
189 if( pFloat->pMenu.get() == this )
190 pFloat->pMenu.clear();
191 pWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() );
194 // dispose accessible components
195 if ( mxAccessible.is() )
197 css::uno::Reference< css::lang::XComponent> xComponent( mxAccessible, css::uno::UNO_QUERY );
198 if ( xComponent.is() )
199 xComponent->dispose();
202 if ( nEventId )
203 Application::RemoveUserEvent( nEventId );
205 // Notify deletion of this menu
206 ImplMenuDelData* pDelData = mpFirstDel;
207 while ( pDelData )
209 pDelData->mpMenu = nullptr;
210 pDelData = pDelData->mpNext;
213 bKilled = true;
215 pItemList->Clear();
216 mpLayoutData.reset();
218 // Native-support: destroy SalMenu
219 ImplClearSalMenu();
221 pStartedFrom.clear();
222 pWindow.clear();
223 VclReferenceBase::dispose();
226 void Menu::CreateAutoMnemonics()
228 MnemonicGenerator aMnemonicGenerator;
229 size_t n;
230 for ( n = 0; n < pItemList->size(); n++ )
232 MenuItemData* pData = pItemList->GetDataFromPos( n );
233 if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
234 aMnemonicGenerator.RegisterMnemonic( pData->aText );
236 for ( n = 0; n < pItemList->size(); n++ )
238 MenuItemData* pData = pItemList->GetDataFromPos( n );
239 if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
240 pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText );
244 void Menu::Activate()
246 bInCallback = true;
248 ImplMenuDelData aDelData( this );
250 ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID );
252 if( !aDelData.isDeleted() )
254 if ( !aActivateHdl.Call( this ) )
256 if( !aDelData.isDeleted() )
258 Menu* pStartMenu = ImplGetStartMenu();
259 if ( pStartMenu && ( pStartMenu != this ) )
261 pStartMenu->bInCallback = true;
262 // MT 11/01: Call EventListener here? I don't know...
263 pStartMenu->aActivateHdl.Call( this );
264 pStartMenu->bInCallback = false;
268 bInCallback = false;
271 if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics))
272 CreateAutoMnemonics();
275 void Menu::Deactivate()
277 for ( size_t n = pItemList->size(); n; )
279 MenuItemData* pData = pItemList->GetDataFromPos( --n );
280 if ( pData->bIsTemporary )
282 if ( ImplGetSalMenu() )
283 ImplGetSalMenu()->RemoveItem( n );
285 pItemList->Remove( n );
289 bInCallback = true;
291 ImplMenuDelData aDelData( this );
293 Menu* pStartMenu = ImplGetStartMenu();
294 ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID );
296 if( !aDelData.isDeleted() )
298 if ( !aDeactivateHdl.Call( this ) )
300 if( !aDelData.isDeleted() )
302 if ( pStartMenu && ( pStartMenu != this ) )
304 pStartMenu->bInCallback = true;
305 pStartMenu->aDeactivateHdl.Call( this );
306 pStartMenu->bInCallback = false;
312 if( !aDelData.isDeleted() )
314 bInCallback = false;
318 void Menu::ImplSelect()
320 MenuItemData* pData = GetItemList()->GetData( nSelectedId );
321 if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) )
323 bool bChecked = IsItemChecked( nSelectedId );
324 if ( pData->nBits & MenuItemBits::RADIOCHECK )
326 if ( !bChecked )
327 CheckItem( nSelectedId );
329 else
330 CheckItem( nSelectedId, !bChecked );
333 // call select
334 ImplSVData* pSVData = ImplGetSVData();
335 pSVData->maAppData.mpActivePopupMenu = nullptr; // if new execute in select()
336 nEventId = Application::PostUserEvent( LINK( this, Menu, ImplCallSelect ) );
339 void Menu::Select()
341 ImplMenuDelData aDelData( this );
343 ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) );
344 if (aDelData.isDeleted())
345 return;
346 if (aSelectHdl.Call(this))
347 return;
348 if (aDelData.isDeleted())
349 return;
350 Menu* pStartMenu = ImplGetStartMenu();
351 if (!pStartMenu || (pStartMenu == this))
352 return;
353 pStartMenu->nSelectedId = nSelectedId;
354 pStartMenu->sSelectedIdent = sSelectedIdent;
355 pStartMenu->aSelectHdl.Call( this );
358 #if defined(MACOSX)
359 void Menu::ImplSelectWithStart( Menu* pSMenu )
361 auto pOldStartedFrom = pStartedFrom;
362 pStartedFrom = pSMenu;
363 auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>();
364 Select();
365 if( pOldStartedFrom )
366 pOldStartedFrom->pStartedFrom = pOldStartedStarted;
367 pStartedFrom = pOldStartedFrom;
369 #endif
371 void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos )
373 ImplMenuDelData aDelData( this );
375 VclMenuEvent aEvent( this, nEvent, nPos );
377 // This is needed by atk accessibility bridge
378 if ( nEvent == VclEventId::MenuHighlight )
380 Application::ImplCallEventListeners( aEvent );
383 if ( !aDelData.isDeleted() )
385 // Copy the list, because this can be destroyed when calling a Link...
386 std::list<Link<VclMenuEvent&,void>> aCopy( maEventListeners );
387 for ( const auto& rLink : aCopy )
389 if( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() )
390 rLink.Call( aEvent );
395 void Menu::AddEventListener( const Link<VclMenuEvent&,void>& rEventListener )
397 maEventListeners.push_back( rEventListener );
400 void Menu::RemoveEventListener( const Link<VclMenuEvent&,void>& rEventListener )
402 maEventListeners.remove( rEventListener );
405 MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits,
406 const OUString& rStr, Menu* pMenu,
407 size_t nPos, const OString &rIdent)
409 // put Item in MenuItemList
410 MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING,
411 nBits, rStr, pMenu, nPos, rIdent);
413 // update native menu
414 if (ImplGetSalMenu() && pData->pSalMenuItem)
415 ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos);
417 return pData;
420 void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits,
421 const OString &rIdent, sal_uInt16 nPos)
423 SAL_WARN_IF( !nItemId, "vcl", "Menu::InsertItem(): ItemId == 0" );
424 SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl",
425 "Menu::InsertItem(): ItemId already exists" );
427 // if Position > ItemCount, append
428 if ( nPos >= pItemList->size() )
429 nPos = MENU_APPEND;
431 // put Item in MenuItemList
432 NbcInsertItem(nItemId, nItemBits, rStr, this, nPos, rIdent);
434 vcl::Window* pWin = ImplGetWindow();
435 mpLayoutData.reset();
436 if ( pWin )
438 ImplCalcSize( pWin );
439 if ( pWin->IsVisible() )
440 pWin->Invalidate();
442 ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
445 void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage,
446 MenuItemBits nItemBits, const OString &rIdent, sal_uInt16 nPos)
448 InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos);
449 SetItemImage( nItemId, rImage );
452 void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr,
453 const Image& rImage, MenuItemBits nItemBits,
454 const OString &rIdent, sal_uInt16 nPos)
456 InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
457 SetItemImage( nItemId, rImage );
460 void Menu::InsertItem(const OUString& rCommand, const css::uno::Reference<css::frame::XFrame>& rFrame)
462 sal_uInt16 nItemId = GetItemCount() + 1;
464 if (rFrame.is())
466 OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame));
467 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName);
468 OUString aLabel(CommandInfoProvider::GetPopupLabelForCommand(aProperties));
469 OUString aTooltip(CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame));
470 Image aImage(CommandInfoProvider::GetImageForCommand(rCommand, rFrame));
472 InsertItem(nItemId, aLabel, aImage);
473 SetHelpText(nItemId, aTooltip);
475 else
476 InsertItem(nItemId, OUString());
478 SetItemCommand(nItemId, rCommand);
482 void Menu::InsertSeparator(const OString &rIdent, sal_uInt16 nPos)
484 // do nothing if it's a menu bar
485 if (IsMenuBar())
486 return;
488 // if position > ItemCount, append
489 if ( nPos >= pItemList->size() )
490 nPos = MENU_APPEND;
492 // put separator in item list
493 pItemList->InsertSeparator(rIdent, nPos);
495 // update native menu
496 size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1;
497 MenuItemData *pData = pItemList->GetDataFromPos( itemPos );
498 if( ImplGetSalMenu() && pData && pData->pSalMenuItem )
499 ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos );
501 mpLayoutData.reset();
503 ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
506 void Menu::RemoveItem( sal_uInt16 nPos )
508 bool bRemove = false;
510 if ( nPos < GetItemCount() )
512 // update native menu
513 if( ImplGetSalMenu() )
514 ImplGetSalMenu()->RemoveItem( nPos );
516 pItemList->Remove( nPos );
517 bRemove = true;
520 vcl::Window* pWin = ImplGetWindow();
521 if ( pWin )
523 ImplCalcSize( pWin );
524 if ( pWin->IsVisible() )
525 pWin->Invalidate();
527 mpLayoutData.reset();
529 if ( bRemove )
530 ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos );
533 static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos )
535 MenuItemType eType = rMenu.GetItemType( nPos );
537 if ( eType == MenuItemType::DONTKNOW )
538 return;
540 if ( eType == MenuItemType::SEPARATOR )
541 pThis->InsertSeparator( OString(), nNewPos );
542 else
544 sal_uInt16 nId = rMenu.GetItemId( nPos );
546 SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl",
547 "Menu::CopyItem(): ItemId already exists" );
549 MenuItemData* pData = rMenu.GetItemList()->GetData( nId );
551 if (!pData)
552 return;
554 if ( eType == MenuItemType::STRINGIMAGE )
555 pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
556 else if ( eType == MenuItemType::STRING )
557 pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos );
558 else
559 pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
561 if ( rMenu.IsItemChecked( nId ) )
562 pThis->CheckItem( nId );
563 if ( !rMenu.IsItemEnabled( nId ) )
564 pThis->EnableItem( nId, false );
565 pThis->SetHelpId( nId, pData->aHelpId );
566 pThis->SetHelpText( nId, pData->aHelpText );
567 pThis->SetAccelKey( nId, pData->aAccelKey );
568 pThis->SetItemCommand( nId, pData->aCommandStr );
569 pThis->SetHelpCommand( nId, pData->aHelpCommandStr );
571 PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId );
572 if ( pSubMenu )
574 // create auto-copy
575 VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu );
576 pThis->SetPopupMenu( nId, pNewMenu );
581 void Menu::Clear()
583 for ( sal_uInt16 i = GetItemCount(); i; i-- )
584 RemoveItem( 0 );
587 sal_uInt16 Menu::GetItemCount() const
589 return static_cast<sal_uInt16>(pItemList->size());
592 sal_uInt16 Menu::ImplGetVisibleItemCount() const
594 sal_uInt16 nItems = 0;
595 for ( size_t n = pItemList->size(); n; )
597 if ( ImplIsVisible( --n ) )
598 nItems++;
600 return nItems;
603 sal_uInt16 Menu::ImplGetFirstVisible() const
605 for ( size_t n = 0; n < pItemList->size(); n++ )
607 if ( ImplIsVisible( n ) )
608 return n;
610 return ITEMPOS_INVALID;
613 sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const
615 for ( size_t n = nPos; n; )
617 if (ImplIsVisible(--n))
618 return n;
620 return ITEMPOS_INVALID;
623 sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const
625 for ( size_t n = nPos+1; n < pItemList->size(); n++ )
627 if ( ImplIsVisible( n ) )
628 return n;
630 return ITEMPOS_INVALID;
633 sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const
635 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
637 if ( pData )
638 return pData->nId;
639 else
640 return 0;
643 sal_uInt16 Menu::GetItemId(const OString &rIdent) const
645 for (size_t n = 0; n < pItemList->size(); ++n)
647 MenuItemData* pData = pItemList->GetDataFromPos(n);
648 if (pData && pData->sIdent == rIdent)
649 return pData->nId;
651 return MENU_ITEM_NOTFOUND;
654 sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const
656 size_t nPos;
657 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
659 if ( pData )
660 return static_cast<sal_uInt16>(nPos);
661 else
662 return MENU_ITEM_NOTFOUND;
665 MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const
667 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
669 if ( pData )
670 return pData->eType;
671 else
672 return MenuItemType::DONTKNOW;
675 OString Menu::GetItemIdent(sal_uInt16 nId) const
677 const MenuItemData* pData = pItemList->GetData(nId);
678 return pData ? pData->sIdent : OString();
681 void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
683 size_t nPos;
684 MenuItemData* pData = pItemList->GetData(nItemId, nPos);
686 if (pData && (pData->nBits != nBits))
688 pData->nBits = nBits;
690 // update native menu
691 if (ImplGetSalMenu())
692 ImplGetSalMenu()->SetItemBits(nPos, nBits);
696 MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const
698 MenuItemBits nBits = MenuItemBits::NONE;
699 MenuItemData* pData = pItemList->GetData( nItemId );
700 if ( pData )
701 nBits = pData->nBits;
702 return nBits;
705 void Menu::SetUserValue(sal_uInt16 nItemId, void* nUserValue, MenuUserDataReleaseFunction aFunc)
707 MenuItemData* pData = pItemList->GetData(nItemId);
708 if (pData)
710 if (pData->aUserValueReleaseFunc)
711 pData->aUserValueReleaseFunc(pData->nUserValue);
712 pData->aUserValueReleaseFunc = aFunc;
713 pData->nUserValue = nUserValue;
717 void* Menu::GetUserValue( sal_uInt16 nItemId ) const
719 MenuItemData* pData = pItemList->GetData( nItemId );
720 return pData ? pData->nUserValue : nullptr;
723 void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu )
725 size_t nPos;
726 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
728 // Item does not exist -> return NULL
729 if ( !pData )
730 return;
732 // same menu, nothing to do
733 if ( static_cast<PopupMenu*>(pData->pSubMenu.get()) == pMenu )
734 return;
736 // remove old menu
737 auto oldSubMenu = pData->pSubMenu;
739 // data exchange
740 pData->pSubMenu = pMenu;
742 // #112023# Make sure pStartedFrom does not point to invalid (old) data
743 if ( pData->pSubMenu )
744 pData->pSubMenu->pStartedFrom = nullptr;
746 // set native submenu
747 if( ImplGetSalMenu() && pData->pSalMenuItem )
749 if( pMenu )
750 ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos );
751 else
752 ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos );
755 oldSubMenu.disposeAndClear();
757 ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos );
760 PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const
762 MenuItemData* pData = pItemList->GetData( nItemId );
764 if ( pData )
765 return static_cast<PopupMenu*>(pData->pSubMenu.get());
766 else
767 return nullptr;
770 void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode )
772 size_t nPos;
773 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
775 if ( !pData )
776 return;
778 if ( pData->aAccelKey == rKeyCode )
779 return;
781 pData->aAccelKey = rKeyCode;
783 // update native menu
784 if( ImplGetSalMenu() && pData->pSalMenuItem )
785 ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() );
788 KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const
790 MenuItemData* pData = pItemList->GetData( nItemId );
792 if ( pData )
793 return pData->aAccelKey;
794 else
795 return KeyCode();
798 KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const
800 KeyEvent aRet;
801 MenuItemData* pData = pItemList->GetData( nItemId );
802 if( pData )
804 sal_Int32 nPos = pData->aText.indexOf( '~' );
805 if( nPos != -1 && nPos < pData->aText.getLength()-1 )
807 sal_uInt16 nCode = 0;
808 sal_Unicode cAccel = pData->aText[nPos+1];
809 if( cAccel >= 'a' && cAccel <= 'z' )
810 nCode = KEY_A + (cAccel-'a');
811 else if( cAccel >= 'A' && cAccel <= 'Z' )
812 nCode = KEY_A + (cAccel-'A');
813 else if( cAccel >= '0' && cAccel <= '9' )
814 nCode = KEY_0 + (cAccel-'0');
816 aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) );
820 return aRet;
823 void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck )
825 size_t nPos;
826 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
828 if ( !pData || pData->bChecked == bCheck )
829 return;
831 // if radio-check, then uncheck previous
832 if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) &&
833 (pData->nBits & MenuItemBits::RADIOCHECK) )
835 MenuItemData* pGroupData;
836 sal_uInt16 nGroupPos;
837 sal_uInt16 nItemCount = GetItemCount();
838 bool bFound = false;
840 nGroupPos = nPos;
841 while ( nGroupPos )
843 pGroupData = pItemList->GetDataFromPos( nGroupPos-1 );
844 if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
846 if ( IsItemChecked( pGroupData->nId ) )
848 CheckItem( pGroupData->nId, false );
849 bFound = true;
850 break;
853 else
854 break;
855 nGroupPos--;
858 if ( !bFound )
860 nGroupPos = nPos+1;
861 while ( nGroupPos < nItemCount )
863 pGroupData = pItemList->GetDataFromPos( nGroupPos );
864 if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
866 if ( IsItemChecked( pGroupData->nId ) )
868 CheckItem( pGroupData->nId, false );
869 break;
872 else
873 break;
874 nGroupPos++;
879 pData->bChecked = bCheck;
881 // update native menu
882 if( ImplGetSalMenu() )
883 ImplGetSalMenu()->CheckItem( nPos, bCheck );
885 ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos );
888 void Menu::CheckItem( const OString &rIdent , bool bCheck )
890 CheckItem( GetItemId( rIdent ), bCheck );
893 bool Menu::IsItemChecked( sal_uInt16 nItemId ) const
895 size_t nPos;
896 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
898 if ( !pData )
899 return false;
901 return pData->bChecked;
904 void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable )
906 size_t nPos;
907 MenuItemData* pItemData = pItemList->GetData( nItemId, nPos );
909 if ( pItemData && ( pItemData->bEnabled != bEnable ) )
911 pItemData->bEnabled = bEnable;
913 vcl::Window* pWin = ImplGetWindow();
914 if ( pWin && pWin->IsVisible() )
916 SAL_WARN_IF(!IsMenuBar(), "vcl", "Menu::EnableItem - Popup visible!" );
917 long nX = 0;
918 size_t nCount = pItemList->size();
919 for ( size_t n = 0; n < nCount; n++ )
921 MenuItemData* pData = pItemList->GetDataFromPos( n );
922 if ( n == nPos )
924 pWin->Invalidate( tools::Rectangle( Point( nX, 0 ), Size( pData->aSz.Width(), pData->aSz.Height() ) ) );
925 break;
927 nX += pData->aSz.Width();
930 // update native menu
931 if( ImplGetSalMenu() )
932 ImplGetSalMenu()->EnableItem( nPos, bEnable );
934 ImplCallEventListeners( bEnable ? VclEventId::MenuEnable : VclEventId::MenuDisable, nPos );
938 bool Menu::IsItemEnabled( sal_uInt16 nItemId ) const
940 size_t nPos;
941 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
943 if ( !pData )
944 return false;
946 return pData->bEnabled;
949 void Menu::ShowItem( sal_uInt16 nItemId, bool bVisible )
951 size_t nPos;
952 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
954 SAL_WARN_IF(IsMenuBar() && !bVisible , "vcl", "Menu::ShowItem - ignored for menu bar entries!");
955 if (!IsMenuBar()&& pData && (pData->bVisible != bVisible))
957 vcl::Window* pWin = ImplGetWindow();
958 if ( pWin && pWin->IsVisible() )
960 SAL_WARN( "vcl", "Menu::ShowItem - ignored for visible popups!" );
961 return;
963 pData->bVisible = bVisible;
965 // update native menu
966 if( ImplGetSalMenu() )
967 ImplGetSalMenu()->ShowItem( nPos, bVisible );
971 void Menu::SetItemText( sal_uInt16 nItemId, const OUString& rStr )
973 size_t nPos;
974 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
976 if ( !pData )
977 return;
979 if ( rStr != pData->aText )
981 pData->aText = rStr;
982 // Clear layout for aText.
983 pData->aTextGlyphs.Invalidate();
984 ImplSetMenuItemData( pData );
985 // update native menu
986 if( ImplGetSalMenu() && pData->pSalMenuItem )
987 ImplGetSalMenu()->SetItemText( nPos, pData->pSalMenuItem.get(), rStr );
989 vcl::Window* pWin = ImplGetWindow();
990 mpLayoutData.reset();
991 if (pWin && IsMenuBar())
993 ImplCalcSize( pWin );
994 if ( pWin->IsVisible() )
995 pWin->Invalidate();
998 ImplCallEventListeners( VclEventId::MenuItemTextChanged, nPos );
1002 OUString Menu::GetItemText( sal_uInt16 nItemId ) const
1004 size_t nPos;
1005 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1007 if ( pData )
1008 return pData->aText;
1010 return OUString();
1013 void Menu::SetItemImage( sal_uInt16 nItemId, const Image& rImage )
1015 size_t nPos;
1016 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1018 if ( !pData )
1019 return;
1021 pData->aImage = rImage;
1022 ImplSetMenuItemData( pData );
1024 // update native menu
1025 if( ImplGetSalMenu() && pData->pSalMenuItem )
1026 ImplGetSalMenu()->SetItemImage( nPos, pData->pSalMenuItem.get(), rImage );
1029 Image Menu::GetItemImage( sal_uInt16 nItemId ) const
1031 MenuItemData* pData = pItemList->GetData( nItemId );
1033 if ( pData )
1034 return pData->aImage;
1035 else
1036 return Image();
1039 void Menu::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
1041 size_t nPos;
1042 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1044 if ( pData )
1045 pData->aCommandStr = rCommand;
1048 OUString Menu::GetItemCommand( sal_uInt16 nItemId ) const
1050 MenuItemData* pData = pItemList->GetData( nItemId );
1052 if (pData)
1053 return pData->aCommandStr;
1055 return OUString();
1058 void Menu::SetHelpCommand( sal_uInt16 nItemId, const OUString& rStr )
1060 MenuItemData* pData = pItemList->GetData( nItemId );
1062 if ( pData )
1063 pData->aHelpCommandStr = rStr;
1066 OUString Menu::GetHelpCommand( sal_uInt16 nItemId ) const
1068 MenuItemData* pData = pItemList->GetData( nItemId );
1070 if ( pData )
1071 return pData->aHelpCommandStr;
1073 return OUString();
1076 void Menu::SetHelpText( sal_uInt16 nItemId, const OUString& rStr )
1078 MenuItemData* pData = pItemList->GetData( nItemId );
1080 if ( pData )
1081 pData->aHelpText = rStr;
1084 OUString Menu::ImplGetHelpText( sal_uInt16 nItemId ) const
1086 MenuItemData* pData = pItemList->GetData( nItemId );
1088 if ( pData && pData->aHelpText.isEmpty() &&
1089 (( !pData->aHelpId.isEmpty() ) || ( !pData->aCommandStr.isEmpty() )))
1091 Help* pHelp = Application::GetHelp();
1092 if ( pHelp )
1094 if (!pData->aCommandStr.isEmpty())
1095 pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr, static_cast<weld::Widget*>(nullptr) );
1096 if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty())
1097 pData->aHelpText = pHelp->GetHelpText( OStringToOUString( pData->aHelpId, RTL_TEXTENCODING_UTF8 ), static_cast<weld::Widget*>(nullptr) );
1101 return OUString();
1104 OUString Menu::GetHelpText( sal_uInt16 nItemId ) const
1106 return ImplGetHelpText( nItemId );
1109 void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr )
1111 MenuItemData* pData = pItemList->GetData( nItemId );
1113 if ( pData )
1114 pData->aTipHelpText = rStr;
1117 OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const
1119 MenuItemData* pData = pItemList->GetData( nItemId );
1121 if ( pData )
1122 return pData->aTipHelpText;
1124 return OUString();
1127 void Menu::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId )
1129 MenuItemData* pData = pItemList->GetData( nItemId );
1131 if ( pData )
1132 pData->aHelpId = rHelpId;
1135 OString Menu::GetHelpId( sal_uInt16 nItemId ) const
1137 OString aRet;
1139 MenuItemData* pData = pItemList->GetData( nItemId );
1141 if ( pData )
1143 if ( !pData->aHelpId.isEmpty() )
1144 aRet = pData->aHelpId;
1145 else
1146 aRet = OUStringToOString( pData->aCommandStr, RTL_TEXTENCODING_UTF8 );
1149 return aRet;
1152 Menu& Menu::operator=( const Menu& rMenu )
1154 if(this == &rMenu)
1155 return *this;
1157 // clean up
1158 Clear();
1160 // copy items
1161 sal_uInt16 nCount = rMenu.GetItemCount();
1162 for ( sal_uInt16 i = 0; i < nCount; i++ )
1163 ImplCopyItem( this, rMenu, i, MENU_APPEND );
1165 aActivateHdl = rMenu.aActivateHdl;
1166 aDeactivateHdl = rMenu.aDeactivateHdl;
1167 aSelectHdl = rMenu.aSelectHdl;
1168 aTitleText = rMenu.aTitleText;
1169 nTitleHeight = rMenu.nTitleHeight;
1171 return *this;
1174 // Returns true if the item is completely hidden on the GUI and shouldn't
1175 // be possible to interact with
1176 bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const
1178 MenuItemData* pData = pItemList->GetDataFromPos(nPos);
1179 if (pData)
1181 MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 );
1182 if (pPreviousData && pPreviousData->bHiddenOnGUI)
1184 return true;
1187 return false;
1190 bool Menu::ImplIsVisible( sal_uInt16 nPos ) const
1192 bool bVisible = true;
1194 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
1195 // check general visibility first
1196 if( pData && !pData->bVisible )
1197 bVisible = false;
1199 if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR )
1201 if( nPos == 0 ) // no separator should be shown at the very beginning
1202 bVisible = false;
1203 else
1205 // always avoid adjacent separators
1206 size_t nCount = pItemList->size();
1207 size_t n;
1208 MenuItemData* pNextData = nullptr;
1209 // search next visible item
1210 for( n = nPos + 1; n < nCount; n++ )
1212 pNextData = pItemList->GetDataFromPos( n );
1213 if( pNextData && pNextData->bVisible )
1215 if( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) )
1216 break;
1219 if( n == nCount ) // no next visible item
1220 bVisible = false;
1221 // check for separator
1222 if( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR )
1223 bVisible = false;
1225 if( bVisible )
1227 for( n = nPos; n > 0; n-- )
1229 pNextData = pItemList->GetDataFromPos( n-1 );
1230 if( pNextData && pNextData->bVisible )
1232 if( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) )
1233 break;
1236 if( n == 0 ) // no previous visible item
1237 bVisible = false;
1242 // not allowed for menubar, as I do not know
1243 // whether a menu-entry will disappear or will appear
1244 if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) &&
1245 !(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries))
1247 if( !pData ) // e.g. nPos == ITEMPOS_INVALID
1248 bVisible = false;
1249 else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above
1251 // tdf#86850 Always display clipboard functions
1252 if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" )
1253 bVisible = true;
1254 else
1255 // bVisible = pData->bEnabled && ( !pData->pSubMenu || pData->pSubMenu->HasValidEntries( true ) );
1256 bVisible = pData->bEnabled; // do not check submenus as they might be filled at Activate().
1260 return bVisible;
1263 bool Menu::IsItemPosVisible( sal_uInt16 nItemPos ) const
1265 return IsMenuVisible() && ImplIsVisible( nItemPos );
1268 bool Menu::IsMenuVisible() const
1270 return pWindow && pWindow->IsReallyVisible();
1273 bool Menu::ImplIsSelectable( sal_uInt16 nPos ) const
1275 bool bSelectable = true;
1277 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
1278 // check general visibility first
1279 if ( pData && ( pData->nBits & MenuItemBits::NOSELECT ) )
1280 bSelectable = false;
1282 return bSelectable;
1285 css::uno::Reference<css::accessibility::XAccessible> Menu::GetAccessible()
1287 // Since PopupMenu are sometimes shared by different instances of MenuBar, the mxAccessible member gets
1288 // overwritten and may contain a disposed object when the initial menubar gets set again. So use the
1289 // mxAccessible member only for sub menus.
1290 if ( pStartedFrom )
1292 for ( sal_uInt16 i = 0, nCount = pStartedFrom->GetItemCount(); i < nCount; ++i )
1294 sal_uInt16 nItemId = pStartedFrom->GetItemId( i );
1295 if ( static_cast< Menu* >( pStartedFrom->GetPopupMenu( nItemId ) ) == this )
1297 css::uno::Reference<css::accessibility::XAccessible> xParent = pStartedFrom->GetAccessible();
1298 if ( xParent.is() )
1300 css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( xParent->getAccessibleContext() );
1301 if (xParentContext.is())
1302 return xParentContext->getAccessibleChild( i );
1307 else if ( !mxAccessible.is() )
1309 UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
1310 if ( pWrapper )
1311 mxAccessible = pWrapper->CreateAccessible(this, IsMenuBar());
1314 return mxAccessible;
1317 void Menu::SetAccessible(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible )
1319 mxAccessible = rxAccessible;
1322 Size Menu::ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, long& rCheckHeight, long& rRadioHeight ) const
1324 long nCheckWidth = 0, nRadioWidth = 0;
1325 rCheckHeight = rRadioHeight = 0;
1327 if (!IsMenuBar())
1329 ImplControlValue aVal;
1330 tools::Rectangle aNativeBounds;
1331 tools::Rectangle aNativeContent;
1333 tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
1334 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark))
1336 if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark,
1337 aCtrlRegion, ControlState::ENABLED, aVal,
1338 aNativeBounds, aNativeContent))
1340 rCheckHeight = aNativeBounds.GetHeight();
1341 nCheckWidth = aNativeContent.GetWidth();
1344 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
1346 if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
1347 aCtrlRegion, ControlState::ENABLED, aVal,
1348 aNativeBounds, aNativeContent))
1350 rRadioHeight = aNativeBounds.GetHeight();
1351 nRadioWidth = aNativeContent.GetWidth();
1355 return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight));
1358 bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, long& rArrowSpacing)
1360 ImplControlValue aVal;
1361 tools::Rectangle aNativeBounds;
1362 tools::Rectangle aNativeContent;
1363 tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
1364 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
1366 if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::SubmenuArrow,
1367 aCtrlRegion, ControlState::ENABLED,
1368 aVal, aNativeBounds, aNativeContent))
1370 Size aSize(aNativeContent.GetWidth(), aNativeContent.GetHeight());
1371 rArrowSize = aSize;
1372 rArrowSpacing = aNativeBounds.GetWidth() - aNativeContent.GetWidth();
1373 return true;
1376 return false;
1379 void Menu::ImplAddDel( ImplMenuDelData& rDel )
1381 SAL_WARN_IF( rDel.mpMenu, "vcl", "Menu::ImplAddDel(): cannot add ImplMenuDelData twice !" );
1382 if( !rDel.mpMenu )
1384 rDel.mpMenu = this;
1385 rDel.mpNext = mpFirstDel;
1386 mpFirstDel = &rDel;
1390 void Menu::ImplRemoveDel( ImplMenuDelData& rDel )
1392 rDel.mpMenu = nullptr;
1393 if ( mpFirstDel == &rDel )
1395 mpFirstDel = rDel.mpNext;
1397 else
1399 ImplMenuDelData* pData = mpFirstDel;
1400 while ( pData && (pData->mpNext != &rDel) )
1401 pData = pData->mpNext;
1403 SAL_WARN_IF( !pData, "vcl", "Menu::ImplRemoveDel(): ImplMenuDelData not registered !" );
1404 if( pData )
1405 pData->mpNext = rDel.mpNext;
1409 Size Menu::ImplCalcSize( vcl::Window* pWin )
1411 // | Check/Radio/Image| Text| Accel/Popup|
1413 // for symbols: nFontHeight x nFontHeight
1414 long nFontHeight = pWin->GetTextHeight();
1415 long nExtra = nFontHeight/4;
1417 long nMinMenuItemHeight = nFontHeight;
1418 long nCheckHeight = 0, nRadioHeight = 0;
1419 Size aMaxSize = ImplGetNativeCheckAndRadioSize(*pWin, nCheckHeight, nRadioHeight); // FIXME
1420 if( aMaxSize.Height() > nMinMenuItemHeight )
1421 nMinMenuItemHeight = aMaxSize.Height();
1423 Size aMaxImgSz;
1425 const StyleSettings& rSettings = pWin->GetSettings().GetStyleSettings();
1426 if ( rSettings.GetUseImagesInMenus() )
1428 if ( 16 > nMinMenuItemHeight )
1429 nMinMenuItemHeight = 16;
1430 for ( size_t i = pItemList->size(); i; )
1432 MenuItemData* pData = pItemList->GetDataFromPos( --i );
1433 if ( ImplIsVisible( i )
1434 && ( ( pData->eType == MenuItemType::IMAGE )
1435 || ( pData->eType == MenuItemType::STRINGIMAGE )
1439 Size aImgSz = pData->aImage.GetSizePixel();
1440 if ( aImgSz.Height() > aMaxImgSz.Height() )
1441 aMaxImgSz.setHeight( aImgSz.Height() );
1442 if ( aImgSz.Height() > nMinMenuItemHeight )
1443 nMinMenuItemHeight = aImgSz.Height();
1444 break;
1449 Size aSz;
1450 long nCheckWidth = 0;
1451 long nMaxWidth = 0;
1453 for ( size_t n = pItemList->size(); n; )
1455 MenuItemData* pData = pItemList->GetDataFromPos( --n );
1457 pData->aSz.setHeight( 0 );
1458 pData->aSz.setWidth( 0 );
1460 if ( ImplIsVisible( n ) )
1462 long nWidth = 0;
1464 // Separator
1465 if (!IsMenuBar()&& (pData->eType == MenuItemType::SEPARATOR))
1467 pData->aSz.setHeight( 4 );
1470 // Image:
1471 if (!IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1473 Size aImgSz = pData->aImage.GetSizePixel();
1475 aImgSz.AdjustHeight(4 ); // add a border for native marks
1476 aImgSz.AdjustWidth(4 ); // add a border for native marks
1477 if ( aImgSz.Width() > aMaxImgSz.Width() )
1478 aMaxImgSz.setWidth( aImgSz.Width() );
1479 if ( aImgSz.Height() > aMaxImgSz.Height() )
1480 aMaxImgSz.setHeight( aImgSz.Height() );
1481 if ( aImgSz.Height() > pData->aSz.Height() )
1482 pData->aSz.setHeight( aImgSz.Height() );
1485 // Check Buttons:
1486 if (!IsMenuBar() && pData->HasCheck())
1488 nCheckWidth = aMaxSize.Width();
1489 // checks / images take the same place
1490 if( ! ( ( pData->eType == MenuItemType::IMAGE ) || ( pData->eType == MenuItemType::STRINGIMAGE ) ) )
1491 nWidth += nCheckWidth + nExtra * 2;
1494 // Text:
1495 if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) )
1497 const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin);
1498 long nTextWidth = pWin->GetCtrlTextWidth(pData->aText, pGlyphs);
1499 long nTextHeight = pWin->GetTextHeight();
1501 if (IsMenuBar())
1503 if ( nTextHeight > pData->aSz.Height() )
1504 pData->aSz.setHeight( nTextHeight );
1506 pData->aSz.setWidth( nTextWidth + 4*nExtra );
1507 aSz.AdjustWidth(pData->aSz.Width() );
1509 else
1510 pData->aSz.setHeight( std::max( std::max( nTextHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
1512 nWidth += nTextWidth;
1515 // Accel
1516 if (!IsMenuBar()&& pData->aAccelKey.GetCode() && !ImplAccelDisabled())
1518 OUString aName = pData->aAccelKey.GetName();
1519 long nAccWidth = pWin->GetTextWidth( aName );
1520 nAccWidth += nExtra;
1521 nWidth += nAccWidth;
1524 // SubMenu?
1525 if (!IsMenuBar() && pData->pSubMenu)
1527 if ( nFontHeight > nWidth )
1528 nWidth += nFontHeight;
1530 pData->aSz.setHeight( std::max( std::max( nFontHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
1533 pData->aSz.AdjustHeight(EXTRAITEMHEIGHT ); // little bit more distance
1535 if (!IsMenuBar())
1536 aSz.AdjustHeight(pData->aSz.Height() );
1538 if ( nWidth > nMaxWidth )
1539 nMaxWidth = nWidth;
1544 // Additional space for title
1545 nTitleHeight = 0;
1546 if (!IsMenuBar() && aTitleText.getLength() > 0) {
1547 // Set expected font
1548 pWin->Push(PushFlags::FONT);
1549 vcl::Font aFont = pWin->GetFont();
1550 aFont.SetWeight(WEIGHT_BOLD);
1551 pWin->SetFont(aFont);
1553 // Compute text bounding box
1554 tools::Rectangle aTextBoundRect;
1555 pWin->GetTextBoundRect(aTextBoundRect, aTitleText);
1557 // Vertically, one height of char + extra space for decoration
1558 nTitleHeight = aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ;
1559 aSz.AdjustHeight(nTitleHeight );
1561 long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE;
1562 pWin->Pop();
1563 if ( nWidth > nMaxWidth )
1564 nMaxWidth = nWidth;
1567 if (!IsMenuBar())
1569 // popup menus should not be wider than half the screen
1570 // except on rather small screens
1571 // TODO: move GetScreenNumber from SystemWindow to Window ?
1572 // currently we rely on internal privileges
1573 unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->maGeometry.nDisplayScreenNumber;
1574 tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) );
1575 long nScreenWidth = aDispRect.GetWidth() >= 800 ? aDispRect.GetWidth() : 800;
1576 if( nMaxWidth > nScreenWidth/2 )
1577 nMaxWidth = nScreenWidth/2;
1579 sal_uInt16 gfxExtra = static_cast<sal_uInt16>(std::max( nExtra, 7L )); // #107710# increase space between checkmarks/images/text
1580 nImgOrChkPos = static_cast<sal_uInt16>(nExtra);
1581 long nImgOrChkWidth = 0;
1582 if( aMaxSize.Height() > 0 ) // NWF case
1583 nImgOrChkWidth = aMaxSize.Height() + nExtra;
1584 else // non NWF case
1585 nImgOrChkWidth = nFontHeight/2 + gfxExtra;
1586 nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgSz.Width() + gfxExtra );
1587 nTextPos = static_cast<sal_uInt16>(nImgOrChkPos + nImgOrChkWidth);
1588 nTextPos = nTextPos + gfxExtra;
1590 aSz.setWidth( nTextPos + nMaxWidth + nExtra );
1591 aSz.AdjustWidth(4*nExtra ); // a _little_ more ...
1593 aSz.AdjustWidth(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderX );
1594 aSz.AdjustHeight(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
1596 else
1598 nTextPos = static_cast<sal_uInt16>(2*nExtra);
1599 aSz.setHeight( nFontHeight+6 );
1601 // get menubar height from native methods if supported
1602 if( pWindow->IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire ) )
1604 ImplControlValue aVal;
1605 tools::Rectangle aNativeBounds;
1606 tools::Rectangle aNativeContent;
1607 Point tmp( 0, 0 );
1608 tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) );
1609 if( pWindow->GetNativeControlRegion( ControlType::Menubar,
1610 ControlPart::Entire,
1611 aCtrlRegion,
1612 ControlState::ENABLED,
1613 aVal,
1614 aNativeBounds,
1615 aNativeContent )
1618 int nNativeHeight = aNativeBounds.GetHeight();
1619 if( nNativeHeight > aSz.Height() )
1620 aSz.setHeight( nNativeHeight );
1624 // account for the size of the close button, which actually is a toolbox
1625 // due to NWF this is variable
1626 long nCloseButtonHeight = static_cast<MenuBarWindow*>(pWindow.get())->MinCloseButtonSize().Height();
1627 if (aSz.Height() < nCloseButtonHeight)
1628 aSz.setHeight( nCloseButtonHeight );
1631 return aSz;
1634 static void ImplPaintCheckBackground(vcl::RenderContext & rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight)
1636 bool bNativeOk = false;
1637 if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button))
1639 ImplControlValue aControlValue;
1640 aControlValue.setTristateVal(ButtonValue::On);
1642 bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
1643 i_rRect,
1644 ControlState::PRESSED | ControlState::ENABLED,
1645 aControlValue,
1646 OUString());
1649 if (!bNativeOk)
1651 const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
1652 Color aColor( i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor() );
1653 RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true, false, nullptr, 2, &aColor);
1657 static OUString getShortenedString( const OUString& i_rLong, vcl::RenderContext const & rRenderContext, long i_nMaxWidth )
1659 sal_Int32 nPos = -1;
1660 OUString aNonMnem(OutputDevice::GetNonMnemonicString(i_rLong, nPos));
1661 aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis);
1662 // re-insert mnemonic
1663 if (nPos != -1)
1665 if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos])
1667 OUStringBuffer aBuf( i_rLong.getLength() );
1668 aBuf.append( std::u16string_view(aNonMnem).substr(0, nPos) );
1669 aBuf.append( '~' );
1670 aBuf.append( std::u16string_view(aNonMnem).substr(nPos) );
1671 aNonMnem = aBuf.makeStringAndClear();
1674 return aNonMnem;
1677 void Menu::ImplPaintMenuTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) const
1679 // Save previous graphical settings, set new one
1680 rRenderContext.Push(PushFlags::FONT | PushFlags::FILLCOLOR);
1681 Wallpaper aOldBackground = rRenderContext.GetBackground();
1683 Color aBackgroundColor = rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor();
1684 rRenderContext.SetBackground(Wallpaper(aBackgroundColor));
1685 rRenderContext.SetFillColor(aBackgroundColor);
1686 vcl::Font aFont = rRenderContext.GetFont();
1687 aFont.SetWeight(WEIGHT_BOLD);
1688 rRenderContext.SetFont(aFont);
1690 // Draw background rectangle
1691 tools::Rectangle aBgRect(rRect);
1692 int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
1693 aBgRect.setX(aBgRect.getX() + SPACE_AROUND_TITLE);
1694 aBgRect.setWidth(aBgRect.getWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX);
1695 aBgRect.setY(aBgRect.getY() + SPACE_AROUND_TITLE);
1696 aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE);
1697 rRenderContext.DrawRect(aBgRect);
1699 // Draw the text centered
1700 Point aTextTopLeft(aBgRect.TopLeft());
1701 tools::Rectangle aTextBoundRect;
1702 rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText );
1703 aTextTopLeft.AdjustX((aBgRect.getWidth() - aTextBoundRect.GetSize().Width()) / 2 );
1704 aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2
1705 - aTextBoundRect.TopLeft().Y() );
1706 rRenderContext.DrawText(aTextTopLeft, aTitleText, 0, aTitleText.getLength());
1708 // Restore
1709 rRenderContext.Pop();
1710 rRenderContext.SetBackground(aOldBackground);
1713 void Menu::ImplPaint(vcl::RenderContext& rRenderContext, Size const & rSize,
1714 sal_uInt16 nBorder, long nStartY, MenuItemData const * pThisItemOnly,
1715 bool bHighlighted, bool bLayout, bool bRollover) const
1717 // for symbols: nFontHeight x nFontHeight
1718 long nFontHeight = rRenderContext.GetTextHeight();
1719 long nExtra = nFontHeight / 4;
1721 long nCheckHeight = 0, nRadioHeight = 0;
1722 ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight);
1724 DecorationView aDecoView(&rRenderContext);
1725 const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
1727 Point aTopLeft, aTmpPos;
1729 int nOuterSpaceX = 0;
1730 if (!IsMenuBar())
1732 nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
1733 aTopLeft.AdjustX(nOuterSpaceX );
1734 aTopLeft.AdjustY(ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
1737 // for the computations, use size of the underlying window, not of RenderContext
1738 Size aOutSz(rSize);
1740 size_t nCount = pItemList->size();
1741 if (bLayout)
1742 mpLayoutData->m_aVisibleItemBoundRects.clear();
1744 // Paint title
1745 if (!pThisItemOnly && !IsMenuBar() && nTitleHeight > 0)
1746 ImplPaintMenuTitle(rRenderContext, tools::Rectangle(aTopLeft, aOutSz));
1748 bool bHiddenItems = false; // are any items on the GUI hidden
1750 for (size_t n = 0; n < nCount; n++)
1752 MenuItemData* pData = pItemList->GetDataFromPos( n );
1753 if (ImplIsVisible(n) && (!pThisItemOnly || (pData == pThisItemOnly)))
1755 if (pThisItemOnly)
1757 if (IsMenuBar())
1759 if (!ImplGetSVData()->maNWFData.mbRolloverMenubar)
1761 if (bRollover)
1762 rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
1763 else if (bHighlighted)
1764 rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
1766 else
1768 if (bHighlighted)
1769 rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
1770 else if (bRollover)
1771 rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
1773 if (!bRollover && !bHighlighted)
1774 rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
1776 else if (bHighlighted)
1777 rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor());
1780 Point aPos(aTopLeft);
1781 aPos.AdjustY(nBorder );
1782 aPos.AdjustY(nStartY );
1784 if (aPos.Y() >= 0)
1786 long nTextOffsetY = (pData->aSz.Height() - nFontHeight) / 2;
1787 if (IsMenuBar())
1788 nTextOffsetY += (aOutSz.Height()-pData->aSz.Height()) / 2;
1789 DrawTextFlags nTextStyle = DrawTextFlags::NONE;
1790 DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
1791 DrawImageFlags nImageStyle = DrawImageFlags::NONE;
1793 // submenus without items are not disabled when no items are
1794 // contained. The application itself should check for this!
1795 // Otherwise it could happen entries are disabled due to
1796 // asynchronous loading
1797 if (!pData->bEnabled || !pWindow->IsEnabled())
1799 nTextStyle |= DrawTextFlags::Disable;
1800 nSymbolStyle |= DrawSymbolFlags::Disable;
1801 nImageStyle |= DrawImageFlags::Disable;
1804 // Separator
1805 if (!bLayout && !IsMenuBar() && (pData->eType == MenuItemType::SEPARATOR))
1807 bool bNativeOk = false;
1808 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
1810 ControlState nState = ControlState::NONE;
1811 if (pData->bEnabled && pWindow->IsEnabled())
1812 nState |= ControlState::ENABLED;
1813 if (bHighlighted)
1814 nState |= ControlState::SELECTED;
1815 Size aSz(pData->aSz);
1816 aSz.setWidth( aOutSz.Width() - 2*nOuterSpaceX );
1817 tools::Rectangle aItemRect(aPos, aSz);
1818 MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
1819 bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
1820 aItemRect, nState, aVal, OUString());
1822 if (!bNativeOk)
1824 aTmpPos.setY( aPos.Y() + ((pData->aSz.Height() - 2) / 2) );
1825 aTmpPos.setX( aPos.X() + 2 + nOuterSpaceX );
1826 rRenderContext.SetLineColor(rSettings.GetShadowColor());
1827 rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
1828 aTmpPos.AdjustY( 1 );
1829 rRenderContext.SetLineColor(rSettings.GetLightColor());
1830 rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
1831 rRenderContext.SetLineColor();
1835 tools::Rectangle aOuterCheckRect(Point(aPos.X()+nImgOrChkPos, aPos.Y()),
1836 Size(pData->aSz.Height(), pData->aSz.Height()));
1837 aOuterCheckRect.AdjustLeft(1 );
1838 aOuterCheckRect.AdjustRight( -1 );
1839 aOuterCheckRect.AdjustTop(1 );
1840 aOuterCheckRect.AdjustBottom( -1 );
1842 // CheckMark
1843 if (!bLayout && !IsMenuBar() && pData->HasCheck())
1845 // draw selection transparent marker if checked
1846 // onto that either a checkmark or the item image
1847 // will be painted
1848 // however do not do this if native checks will be painted since
1849 // the selection color too often does not fit the theme's check and/or radio
1851 if( !((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1853 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup,
1854 (pData->nBits & MenuItemBits::RADIOCHECK)
1855 ? ControlPart::MenuItemCheckMark
1856 : ControlPart::MenuItemRadioMark))
1858 ControlPart nPart = ((pData->nBits & MenuItemBits::RADIOCHECK)
1859 ? ControlPart::MenuItemRadioMark
1860 : ControlPart::MenuItemCheckMark);
1862 ControlState nState = ControlState::NONE;
1864 if (pData->bChecked)
1865 nState |= ControlState::PRESSED;
1867 if (pData->bEnabled && pWindow->IsEnabled())
1868 nState |= ControlState::ENABLED;
1870 if (bHighlighted)
1871 nState |= ControlState::SELECTED;
1873 long nCtrlHeight = (pData->nBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
1874 aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 );
1875 aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 );
1877 tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight));
1878 Size aSz(pData->aSz);
1879 aSz.setWidth( aOutSz.Width() - 2 * nOuterSpaceX );
1880 tools::Rectangle aItemRect(aPos, aSz);
1881 MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
1882 rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect,
1883 nState, aVal, OUString());
1885 else if (pData->bChecked) // by default do nothing for unchecked items
1887 ImplPaintCheckBackground(rRenderContext, *pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
1889 SymbolType eSymbol;
1890 Size aSymbolSize;
1891 if (pData->nBits & MenuItemBits::RADIOCHECK)
1893 eSymbol = SymbolType::RADIOCHECKMARK;
1894 aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2);
1896 else
1898 eSymbol = SymbolType::CHECKMARK;
1899 aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2);
1901 aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width()) / 2 );
1902 aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height()) / 2 );
1903 tools::Rectangle aRect(aTmpPos, aSymbolSize);
1904 aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetTextColor(), nSymbolStyle);
1909 // Image:
1910 if (!bLayout && !IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1912 // Don't render an image for a check thing
1913 if (pData->bChecked)
1914 ImplPaintCheckBackground(rRenderContext, *pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
1916 Image aImage = pData->aImage;
1918 aTmpPos = aOuterCheckRect.TopLeft();
1919 aTmpPos.AdjustX((aOuterCheckRect.GetWidth() - aImage.GetSizePixel().Width()) / 2 );
1920 aTmpPos.AdjustY((aOuterCheckRect.GetHeight() - aImage.GetSizePixel().Height()) / 2 );
1921 rRenderContext.DrawImage(aTmpPos, aImage, nImageStyle);
1924 // Text:
1925 if ((pData->eType == MenuItemType::STRING ) || (pData->eType == MenuItemType::STRINGIMAGE))
1927 aTmpPos.setX( aPos.X() + nTextPos );
1928 aTmpPos.setY( aPos.Y() );
1929 aTmpPos.AdjustY(nTextOffsetY );
1930 DrawTextFlags nStyle = nTextStyle | DrawTextFlags::Mnemonic;
1932 const Menu *pMenu = this;
1933 while (!pMenu->IsMenuBar() && pMenu->pStartedFrom)
1934 pMenu = pMenu->pStartedFrom;
1935 if (pMenu->IsMenuBar() && static_cast<MenuBarWindow*>(pMenu->pWindow.get())->GetMBWHideAccel())
1936 nStyle |= DrawTextFlags::HideMnemonic;
1938 if (pData->bIsTemporary)
1939 nStyle |= DrawTextFlags::Disable;
1940 MetricVector* pVector = bLayout ? &mpLayoutData->m_aUnicodeBoundRects : nullptr;
1941 OUString* pDisplayText = bLayout ? &mpLayoutData->m_aDisplayText : nullptr;
1942 if (bLayout)
1944 mpLayoutData->m_aLineIndices.push_back(mpLayoutData->m_aDisplayText.getLength());
1945 mpLayoutData->m_aLineItemIds.push_back(pData->nId);
1947 // #i47946# with NWF painted menus the background is transparent
1948 // since DrawCtrlText can depend on the background (e.g. for
1949 // DrawTextFlags::Disable), temporarily set a background which
1950 // hopefully matches the NWF background since it is read
1951 // from the system style settings
1952 bool bSetTmpBackground = !rRenderContext.IsBackground()
1953 && rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire);
1954 if (bSetTmpBackground)
1956 Color aBg = IsMenuBar() ? rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor()
1957 : rRenderContext.GetSettings().GetStyleSettings().GetMenuColor();
1958 rRenderContext.SetBackground(Wallpaper(aBg));
1960 // how much space is there for the text?
1961 long nMaxItemTextWidth = aOutSz.Width() - aTmpPos.X() - nExtra - nOuterSpaceX;
1962 if (!IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled())
1964 OUString aAccText = pData->aAccelKey.GetName();
1965 nMaxItemTextWidth -= rRenderContext.GetTextWidth(aAccText) + 3 * nExtra;
1967 if (!IsMenuBar() && pData->pSubMenu)
1969 nMaxItemTextWidth -= nFontHeight - nExtra;
1972 OUString aItemText(pData->aText);
1973 pData->bHiddenOnGUI = false;
1975 if (IsMenuBar()) // In case of menubar if we are out of bounds we shouldn't paint the item
1977 if (nMaxItemTextWidth < rRenderContext.GetTextWidth(aItemText))
1979 aItemText = "";
1980 pData->bHiddenOnGUI = true;
1981 bHiddenItems = true;
1984 else
1986 aItemText = getShortenedString(aItemText, rRenderContext, nMaxItemTextWidth);
1987 pData->bHiddenOnGUI = false;
1990 const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(&rRenderContext);
1991 if (aItemText != pData->aText)
1992 // Can't use pre-computed glyphs, item text was
1993 // changed.
1994 pGlyphs = nullptr;
1995 rRenderContext.DrawCtrlText(aTmpPos, aItemText, 0, aItemText.getLength(),
1996 nStyle, pVector, pDisplayText, pGlyphs);
1997 if (bSetTmpBackground)
1998 rRenderContext.SetBackground();
2001 // Accel
2002 if (!bLayout && !IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled())
2004 OUString aAccText = pData->aAccelKey.GetName();
2005 aTmpPos.setX( aOutSz.Width() - rRenderContext.GetTextWidth(aAccText) );
2006 aTmpPos.AdjustX( -(4 * nExtra) );
2008 aTmpPos.AdjustX( -nOuterSpaceX );
2009 aTmpPos.setY( aPos.Y() );
2010 aTmpPos.AdjustY(nTextOffsetY );
2011 rRenderContext.DrawCtrlText(aTmpPos, aAccText, 0, aAccText.getLength(), nTextStyle);
2014 // SubMenu?
2015 if (!bLayout && !IsMenuBar() && pData->pSubMenu)
2017 bool bNativeOk = false;
2018 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
2020 ControlState nState = ControlState::NONE;
2021 Size aTmpSz(0, 0);
2022 long aSpacing = 0;
2024 if (!ImplGetNativeSubmenuArrowSize(rRenderContext, aTmpSz, aSpacing))
2026 aTmpSz = Size(nFontHeight, nFontHeight);
2027 aSpacing = nOuterSpaceX;
2030 if (pData->bEnabled && pWindow->IsEnabled())
2031 nState |= ControlState::ENABLED;
2032 if (bHighlighted)
2033 nState |= ControlState::SELECTED;
2035 aTmpPos.setX( aOutSz.Width() - aTmpSz.Width() - aSpacing - nOuterSpaceX );
2036 aTmpPos.setY( aPos.Y() + ( pData->aSz.Height() - aTmpSz.Height() ) / 2 );
2037 aTmpPos.AdjustY(nExtra / 2 );
2039 tools::Rectangle aItemRect(aTmpPos, aTmpSz);
2040 MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
2041 bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::SubmenuArrow,
2042 aItemRect, nState, aVal, OUString());
2044 if (!bNativeOk)
2046 aTmpPos.setX( aOutSz.Width() - nFontHeight + nExtra - nOuterSpaceX );
2047 aTmpPos.setY( aPos.Y() );
2048 aTmpPos.AdjustY(nExtra/2 );
2049 aTmpPos.AdjustY((pData->aSz.Height() / 2) - (nFontHeight / 4) );
2050 if (pData->nBits & MenuItemBits::POPUPSELECT)
2052 rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
2053 Point aTmpPos2(aPos);
2054 aTmpPos2.setX( aOutSz.Width() - nFontHeight - nFontHeight/4 );
2055 aDecoView.DrawFrame(tools::Rectangle(aTmpPos2, Size(nFontHeight + nFontHeight / 4,
2056 pData->aSz.Height())),
2057 DrawFrameStyle::Group);
2059 aDecoView.DrawSymbol(tools::Rectangle(aTmpPos, Size(nFontHeight / 2, nFontHeight / 2)),
2060 SymbolType::SPIN_RIGHT, rRenderContext.GetTextColor(), nSymbolStyle);
2064 if (pThisItemOnly && bHighlighted)
2066 // This restores the normal menu or menu bar text
2067 // color for when it is no longer highlighted.
2068 if (IsMenuBar())
2069 rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
2070 else
2071 rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
2074 if( bLayout )
2076 if (!IsMenuBar())
2077 mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, Size(aOutSz.Width(), pData->aSz.Height()));
2078 else
2079 mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, pData->aSz);
2083 if (!IsMenuBar())
2084 aTopLeft.AdjustY(pData->aSz.Height() );
2085 else
2086 aTopLeft.AdjustX(pData->aSz.Width() );
2089 // draw "more" (">>") indicator if some items have been hidden as they go out of visible area
2090 if (bHiddenItems)
2092 sal_Int32 nSize = nFontHeight;
2093 tools::Rectangle aRectangle(Point(aOutSz.Width() - nSize, (aOutSz.Height() / 2) - (nSize / 2)), Size(nSize, nSize));
2094 lclDrawMoreIndicator(rRenderContext, aRectangle);
2098 Menu* Menu::ImplGetStartMenu()
2100 Menu* pStart = this;
2101 while ( pStart && pStart->pStartedFrom && ( pStart->pStartedFrom != pStart ) )
2102 pStart = pStart->pStartedFrom;
2103 return pStart;
2106 void Menu::ImplCallHighlight(sal_uInt16 nItem)
2108 ImplMenuDelData aDelData( this );
2110 nSelectedId = 0;
2111 sSelectedIdent.clear();
2112 MenuItemData* pData = pItemList->GetDataFromPos(nItem);
2113 if (pData)
2115 nSelectedId = pData->nId;
2116 sSelectedIdent = pData->sIdent;
2118 ImplCallEventListeners( VclEventId::MenuHighlight, GetItemPos( GetCurItemId() ) );
2120 if( !aDelData.isDeleted() )
2122 nSelectedId = 0;
2123 sSelectedIdent.clear();
2127 IMPL_LINK_NOARG(Menu, ImplCallSelect, void*, void)
2129 nEventId = nullptr;
2130 Select();
2133 Menu* Menu::ImplFindSelectMenu()
2135 Menu* pSelMenu = nEventId ? this : nullptr;
2137 for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
2139 MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
2141 if ( pData->pSubMenu )
2142 pSelMenu = pData->pSubMenu->ImplFindSelectMenu();
2145 return pSelMenu;
2148 Menu* Menu::ImplFindMenu( sal_uInt16 nItemId )
2150 Menu* pSelMenu = nullptr;
2152 for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
2154 MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
2156 if( pData->nId == nItemId )
2157 pSelMenu = this;
2158 else if ( pData->pSubMenu )
2159 pSelMenu = pData->pSubMenu->ImplFindMenu( nItemId );
2162 return pSelMenu;
2165 void Menu::RemoveDisabledEntries( bool bCheckPopups, bool bRemoveEmptyPopups )
2167 for ( sal_uInt16 n = 0; n < GetItemCount(); n++ )
2169 bool bRemove = false;
2170 MenuItemData* pItem = pItemList->GetDataFromPos( n );
2171 if ( pItem->eType == MenuItemType::SEPARATOR )
2173 if ( !n || ( GetItemType( n-1 ) == MenuItemType::SEPARATOR ) )
2174 bRemove = true;
2176 else
2177 bRemove = !pItem->bEnabled;
2179 if ( bCheckPopups && pItem->pSubMenu )
2181 pItem->pSubMenu->RemoveDisabledEntries();
2182 if ( bRemoveEmptyPopups && !pItem->pSubMenu->GetItemCount() )
2183 bRemove = true;
2186 if ( bRemove )
2187 RemoveItem( n-- );
2190 if ( GetItemCount() )
2192 sal_uInt16 nLast = GetItemCount() - 1;
2193 MenuItemData* pItem = pItemList->GetDataFromPos( nLast );
2194 if ( pItem->eType == MenuItemType::SEPARATOR )
2195 RemoveItem( nLast );
2197 mpLayoutData.reset();
2200 void Menu::UpdateNativeMenu()
2202 if ( ImplGetSalMenu() )
2203 ImplGetSalMenu()->Update();
2206 void Menu::MenuBarKeyInput(const KeyEvent&)
2210 void Menu::ImplKillLayoutData() const
2212 mpLayoutData.reset();
2215 void Menu::ImplFillLayoutData() const
2217 if (pWindow && pWindow->IsReallyVisible())
2219 mpLayoutData.reset(new MenuLayoutData);
2220 if (IsMenuBar())
2222 ImplPaint(*pWindow, pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME
2224 else
2226 MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get());
2227 ImplPaint(*pWindow, pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(),
2228 nullptr, false, true); //FIXME
2233 tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, long nIndex ) const
2235 long nItemIndex = -1;
2236 if( ! mpLayoutData )
2237 ImplFillLayoutData();
2238 if( mpLayoutData )
2240 for( size_t i = 0; i < mpLayoutData->m_aLineItemIds.size(); i++ )
2242 if( mpLayoutData->m_aLineItemIds[i] == nItemID )
2244 nItemIndex = mpLayoutData->m_aLineIndices[i];
2245 break;
2249 return (mpLayoutData && nItemIndex != -1) ? mpLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle();
2252 long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const
2254 long nIndex = -1;
2255 rItemID = 0;
2256 if( ! mpLayoutData )
2257 ImplFillLayoutData();
2258 if( mpLayoutData )
2260 nIndex = mpLayoutData->GetIndexForPoint( rPoint );
2261 for( size_t i = 0; i < mpLayoutData->m_aLineIndices.size(); i++ )
2263 if( mpLayoutData->m_aLineIndices[i] <= nIndex &&
2264 (i == mpLayoutData->m_aLineIndices.size()-1 || mpLayoutData->m_aLineIndices[i+1] > nIndex) )
2266 // make index relative to item
2267 nIndex -= mpLayoutData->m_aLineIndices[i];
2268 rItemID = mpLayoutData->m_aLineItemIds[i];
2269 break;
2273 return nIndex;
2276 tools::Rectangle Menu::GetBoundingRectangle( sal_uInt16 nPos ) const
2278 tools::Rectangle aRet;
2280 if (!mpLayoutData )
2281 ImplFillLayoutData();
2282 if (mpLayoutData)
2284 std::map< sal_uInt16, tools::Rectangle >::const_iterator it = mpLayoutData->m_aVisibleItemBoundRects.find( nPos );
2285 if( it != mpLayoutData->m_aVisibleItemBoundRects.end() )
2286 aRet = it->second;
2288 return aRet;
2291 OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const
2293 MenuItemData* pData = pItemList->GetData( nItemId );
2295 if ( pData )
2296 return pData->aAccessibleName;
2298 return OUString();
2301 void Menu::ImplClearSalMenu()
2303 mpSalMenu.reset();
2306 void Menu::GetSystemMenuData( SystemMenuData* pData ) const
2308 Menu* pMenu = const_cast<Menu*>(this);
2309 if( pData && pMenu->ImplGetSalMenu() )
2311 pMenu->ImplGetSalMenu()->GetSystemMenuData( pData );
2315 bool Menu::IsHighlighted( sal_uInt16 nItemPos ) const
2317 bool bRet = false;
2319 if( pWindow )
2321 if (IsMenuBar())
2322 bRet = ( nItemPos == static_cast< MenuBarWindow * > (pWindow.get())->GetHighlightedItem() );
2323 else
2324 bRet = ( nItemPos == static_cast< MenuFloatingWindow * > (pWindow.get())->GetHighlightedItem() );
2327 return bRet;
2330 void Menu::HighlightItem( sal_uInt16 nItemPos )
2332 if ( pWindow )
2334 if (IsMenuBar())
2336 MenuBarWindow* pMenuWin = static_cast< MenuBarWindow* >( pWindow.get() );
2337 pMenuWin->SetAutoPopup( false );
2338 pMenuWin->ChangeHighlightItem( nItemPos, false );
2340 else
2342 static_cast< MenuFloatingWindow* >( pWindow.get() )->ChangeHighlightItem( nItemPos, false );
2347 MenuBarWindow* MenuBar::getMenuBarWindow()
2349 // so far just a dynamic_cast, hopefully to be turned into something saner
2350 // at some stage
2351 MenuBarWindow *pWin = dynamic_cast<MenuBarWindow*>(pWindow.get());
2352 //either there is no window (fdo#87663) or it is a MenuBarWindow
2353 assert(!pWindow || pWin);
2354 return pWin;
2357 MenuBar::MenuBar()
2358 : Menu(),
2359 mbCloseBtnVisible(false),
2360 mbFloatBtnVisible(false),
2361 mbHideBtnVisible(false),
2362 mbDisplayable(true)
2364 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
2367 MenuBar::MenuBar( const MenuBar& rMenu )
2368 : Menu(),
2369 mbCloseBtnVisible(false),
2370 mbFloatBtnVisible(false),
2371 mbHideBtnVisible(false),
2372 mbDisplayable(true)
2374 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
2375 *this = rMenu;
2378 MenuBar::~MenuBar()
2380 disposeOnce();
2383 void MenuBar::dispose()
2385 ImplDestroy( this, true );
2386 Menu::dispose();
2389 void MenuBar::ClosePopup(Menu *pMenu)
2391 MenuBarWindow* pMenuWin = getMenuBarWindow();
2392 if (!pMenuWin)
2393 return;
2394 pMenuWin->PopupClosed(pMenu);
2397 void MenuBar::MenuBarKeyInput(const KeyEvent& rEvent)
2399 pWindow->KeyInput(rEvent);
2402 void MenuBar::ShowCloseButton(bool bShow)
2404 ShowButtons( bShow, mbFloatBtnVisible, mbHideBtnVisible );
2407 void MenuBar::ShowButtons( bool bClose, bool bFloat, bool bHide )
2409 if ((bClose != mbCloseBtnVisible) ||
2410 (bFloat != mbFloatBtnVisible) ||
2411 (bHide != mbHideBtnVisible))
2413 mbCloseBtnVisible = bClose;
2414 mbFloatBtnVisible = bFloat;
2415 mbHideBtnVisible = bHide;
2416 MenuBarWindow* pMenuWin = getMenuBarWindow();
2417 if (pMenuWin)
2418 pMenuWin->ShowButtons(bClose, bFloat, bHide);
2422 void MenuBar::LayoutChanged()
2424 MenuBarWindow* pMenuWin = getMenuBarWindow();
2425 if (pMenuWin)
2426 pMenuWin->LayoutChanged();
2429 void MenuBar::SetDisplayable( bool bDisplayable )
2431 if( bDisplayable != mbDisplayable )
2433 if ( ImplGetSalMenu() )
2434 ImplGetSalMenu()->ShowMenuBar( bDisplayable );
2436 mbDisplayable = bDisplayable;
2437 LayoutChanged();
2441 VclPtr<vcl::Window> MenuBar::ImplCreate(vcl::Window* pParent, vcl::Window* pWindow, MenuBar* pMenu)
2443 VclPtr<MenuBarWindow> pMenuBarWindow = dynamic_cast<MenuBarWindow*>(pWindow);
2444 if (!pMenuBarWindow)
2446 pWindow = pMenuBarWindow = VclPtr<MenuBarWindow>::Create( pParent );
2449 pMenu->pStartedFrom = nullptr;
2450 pMenu->pWindow = pWindow;
2451 pMenuBarWindow->SetMenu(pMenu);
2452 long nHeight = pWindow ? pMenu->ImplCalcSize(pWindow).Height() : 0;
2454 // depending on the native implementation or the displayable flag
2455 // the menubar windows is suppressed (ie, height=0)
2456 if (!pMenu->IsDisplayable() || (pMenu->ImplGetSalMenu() && pMenu->ImplGetSalMenu()->VisibleMenuBar()))
2458 nHeight = 0;
2461 pMenuBarWindow->SetHeight(nHeight);
2462 return pWindow;
2465 void MenuBar::ImplDestroy( MenuBar* pMenu, bool bDelete )
2467 vcl::Window *pWindow = pMenu->ImplGetWindow();
2468 if (pWindow && bDelete)
2470 MenuBarWindow* pMenuWin = pMenu->getMenuBarWindow();
2471 if (pMenuWin)
2472 pMenuWin->KillActivePopup();
2473 pWindow->disposeOnce();
2475 pMenu->pWindow = nullptr;
2478 bool MenuBar::ImplHandleKeyEvent( const KeyEvent& rKEvent )
2480 // No keyboard processing when our menubar is invisible
2481 if (!IsDisplayable())
2482 return false;
2484 // No keyboard processing when system handles the menu.
2485 SalMenu *pNativeMenu = ImplGetSalMenu();
2486 if (pNativeMenu && pNativeMenu->VisibleMenuBar())
2488 // Except when the event is the F6 cycle pane event and we can put our
2489 // focus into it (i.e. the gtk3 menubar case but not the mac/unity case
2490 // where it's not part of the application window)
2491 if (!TaskPaneList::IsCycleKey(rKEvent.GetKeyCode()))
2492 return false;
2493 if (!pNativeMenu->CanGetFocus())
2494 return false;
2497 bool bDone = false;
2498 // check for enabled, if this method is called from another window...
2499 vcl::Window* pWin = ImplGetWindow();
2500 if (pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode())
2502 MenuBarWindow* pMenuWin = getMenuBarWindow();
2503 bDone = pMenuWin && pMenuWin->HandleKeyEvent(rKEvent, false/*bFromMenu*/);
2505 return bDone;
2508 bool MenuBar::ImplHandleCmdEvent( const CommandEvent& rCEvent )
2510 // No keyboard processing when system handles the menu or our menubar is invisible
2511 if( !IsDisplayable() ||
2512 ( ImplGetSalMenu() && ImplGetSalMenu()->VisibleMenuBar() ) )
2513 return false;
2515 // check for enabled, if this method is called from another window...
2516 MenuBarWindow* pWin = static_cast<MenuBarWindow*>(ImplGetWindow());
2517 if ( pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && ! pWin->IsInModalMode() )
2519 if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel)
2521 const CommandModKeyData* pCData = rCEvent.GetModKeyData ();
2522 if (pWin->m_nHighlightedItem == ITEMPOS_INVALID)
2524 if (pCData && pCData->IsMod2() && pCData->IsDown())
2525 pWin->SetMBWHideAccel(false);
2526 else
2527 pWin->SetMBWHideAccel(true);
2528 pWin->Invalidate(InvalidateFlags::Update);
2530 return true;
2533 return false;
2536 void MenuBar::SelectItem(sal_uInt16 nId)
2538 if (pWindow)
2540 pWindow->GrabFocus();
2541 nId = GetItemPos( nId );
2543 MenuBarWindow* pMenuWin = getMenuBarWindow();
2544 if (pMenuWin)
2546 // #99705# popup the selected menu
2547 pMenuWin->SetAutoPopup( true );
2548 if (ITEMPOS_INVALID != pMenuWin->GetHighlightedItem())
2550 pMenuWin->KillActivePopup();
2551 pMenuWin->ChangeHighlightItem( ITEMPOS_INVALID, false );
2553 if (nId != ITEMPOS_INVALID)
2554 pMenuWin->ChangeHighlightItem( nId, false );
2559 // handler for native menu selection and command events
2560 bool Menu::HandleMenuActivateEvent( Menu *pMenu ) const
2562 if( pMenu )
2564 ImplMenuDelData aDelData( this );
2566 pMenu->pStartedFrom = const_cast<Menu*>(this);
2567 pMenu->bInCallback = true;
2568 pMenu->Activate();
2570 if( !aDelData.isDeleted() )
2571 pMenu->bInCallback = false;
2573 return true;
2576 bool Menu::HandleMenuDeActivateEvent( Menu *pMenu ) const
2578 if( pMenu )
2580 ImplMenuDelData aDelData( this );
2582 pMenu->pStartedFrom = const_cast<Menu*>(this);
2583 pMenu->bInCallback = true;
2584 pMenu->Deactivate();
2585 if( !aDelData.isDeleted() )
2586 pMenu->bInCallback = false;
2588 return true;
2591 bool MenuBar::HandleMenuHighlightEvent( Menu *pMenu, sal_uInt16 nHighlightEventId ) const
2593 if( !pMenu )
2594 pMenu = const_cast<MenuBar*>(this)->ImplFindMenu(nHighlightEventId);
2595 if( pMenu )
2597 ImplMenuDelData aDelData( pMenu );
2599 if( mnHighlightedItemPos != ITEMPOS_INVALID )
2600 pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, mnHighlightedItemPos );
2602 if( !aDelData.isDeleted() )
2604 pMenu->mnHighlightedItemPos = pMenu->GetItemPos( nHighlightEventId );
2605 pMenu->nSelectedId = nHighlightEventId;
2606 pMenu->sSelectedIdent = pMenu->GetItemIdent( nHighlightEventId );
2607 pMenu->pStartedFrom = const_cast<MenuBar*>(this);
2608 pMenu->ImplCallHighlight( pMenu->mnHighlightedItemPos );
2610 return true;
2612 else
2613 return false;
2616 bool Menu::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const
2618 if( !pMenu )
2619 pMenu = const_cast<Menu*>(this)->ImplFindMenu(nCommandEventId);
2620 if( pMenu )
2622 pMenu->nSelectedId = nCommandEventId;
2623 pMenu->sSelectedIdent = pMenu->GetItemIdent(nCommandEventId);
2624 pMenu->pStartedFrom = const_cast<Menu*>(this);
2625 pMenu->ImplSelect();
2626 return true;
2628 else
2629 return false;
2632 sal_uInt16 MenuBar::AddMenuBarButton( const Image& i_rImage, const Link<MenuBar::MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip )
2634 MenuBarWindow* pMenuWin = getMenuBarWindow();
2635 return pMenuWin ? pMenuWin->AddMenuBarButton(i_rImage, i_rLink, i_rToolTip) : 0;
2638 void MenuBar::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBar::MenuBarButtonCallbackArg&,bool>& rLink )
2640 MenuBarWindow* pMenuWin = getMenuBarWindow();
2641 if (!pMenuWin)
2642 return;
2643 pMenuWin->SetMenuBarButtonHighlightHdl(nId, rLink);
2646 void MenuBar::RemoveMenuBarButton( sal_uInt16 nId )
2648 MenuBarWindow* pMenuWin = getMenuBarWindow();
2649 if (!pMenuWin)
2650 return;
2651 pMenuWin->RemoveMenuBarButton(nId);
2654 tools::Rectangle MenuBar::GetMenuBarButtonRectPixel( sal_uInt16 nId )
2656 MenuBarWindow* pMenuWin = getMenuBarWindow();
2657 return pMenuWin ? pMenuWin->GetMenuBarButtonRectPixel(nId) : tools::Rectangle();
2660 bool MenuBar::HandleMenuButtonEvent( sal_uInt16 i_nButtonId )
2662 MenuBarWindow* pMenuWin = getMenuBarWindow();
2663 return pMenuWin && pMenuWin->HandleMenuButtonEvent(i_nButtonId);
2666 int MenuBar::GetMenuBarHeight() const
2668 MenuBar* pMenuBar = const_cast<MenuBar*>(this);
2669 const SalMenu *pNativeMenu = pMenuBar->ImplGetSalMenu();
2670 int nMenubarHeight;
2671 if (pNativeMenu)
2672 nMenubarHeight = pNativeMenu->GetMenuBarHeight();
2673 else
2675 vcl::Window* pMenubarWin = GetWindow();
2676 nMenubarHeight = pMenubarWin ? pMenubarWin->GetOutputHeightPixel() : 0;
2678 return nMenubarHeight;
2681 // bool PopupMenu::bAnyPopupInExecute = false;
2683 MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const {
2684 return static_cast<MenuFloatingWindow *>(Menu::ImplGetWindow());
2687 PopupMenu::PopupMenu()
2688 : mpLOKNotifier(nullptr)
2690 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
2693 PopupMenu::PopupMenu( const PopupMenu& rMenu )
2694 : Menu(),
2695 mpLOKNotifier(nullptr)
2697 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
2698 *this = rMenu;
2701 PopupMenu::~PopupMenu()
2703 disposeOnce();
2706 void PopupMenu::ClosePopup(Menu* pMenu)
2708 MenuFloatingWindow* p = dynamic_cast<MenuFloatingWindow*>(ImplGetWindow());
2709 PopupMenu *pPopup = dynamic_cast<PopupMenu*>(pMenu);
2710 if (p && pPopup)
2711 p->KillActivePopup(pPopup);
2714 bool PopupMenu::IsInExecute()
2716 return GetActivePopupMenu() != nullptr;
2719 PopupMenu* PopupMenu::GetActivePopupMenu()
2721 ImplSVData* pSVData = ImplGetSVData();
2722 return pSVData->maAppData.mpActivePopupMenu;
2725 void PopupMenu::EndExecute()
2727 if ( ImplGetWindow() )
2728 ImplGetFloatingWindow()->EndExecute( 0 );
2731 void PopupMenu::SelectItem(sal_uInt16 nId)
2733 if ( ImplGetWindow() )
2735 if( nId != ITEMPOS_INVALID )
2737 size_t nPos = 0;
2738 MenuItemData* pData = GetItemList()->GetData( nId, nPos );
2739 if (pData && pData->pSubMenu)
2740 ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true );
2741 else
2742 ImplGetFloatingWindow()->EndExecute( nId );
2744 else
2746 MenuFloatingWindow* pFloat = ImplGetFloatingWindow();
2747 pFloat->GrabFocus();
2749 for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ )
2751 MenuItemData* pData = GetItemList()->GetDataFromPos( nPos );
2752 if( pData->pSubMenu )
2754 pFloat->KillActivePopup();
2757 pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false );
2762 void PopupMenu::SetSelectedEntry( sal_uInt16 nId )
2764 nSelectedId = nId;
2765 sSelectedIdent = GetItemIdent(nId);
2768 sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos )
2770 return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown );
2773 sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags )
2775 ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 );
2777 FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE;
2778 if ( nFlags & PopupMenuFlags::ExecuteDown )
2779 nPopupModeFlags = FloatWinPopupFlags::Down;
2780 else if ( nFlags & PopupMenuFlags::ExecuteUp )
2781 nPopupModeFlags = FloatWinPopupFlags::Up;
2782 else if ( nFlags & PopupMenuFlags::ExecuteLeft )
2783 nPopupModeFlags = FloatWinPopupFlags::Left;
2784 else if ( nFlags & PopupMenuFlags::ExecuteRight )
2785 nPopupModeFlags = FloatWinPopupFlags::Right;
2786 else
2787 nPopupModeFlags = FloatWinPopupFlags::Down;
2789 if (nFlags & PopupMenuFlags::NoMouseUpClose ) // allow popup menus to stay open on mouse button up
2790 nPopupModeFlags |= FloatWinPopupFlags::NoMouseUpClose; // useful if the menu was opened on mousebutton down (eg toolbox configuration)
2792 if (nFlags & PopupMenuFlags::NoHorzPlacement)
2793 nPopupModeFlags |= FloatWinPopupFlags::NoHorzPlacement;
2795 return ImplExecute( pExecWindow, rRect, nPopupModeFlags, nullptr, false );
2798 void PopupMenu::ImplFlushPendingSelect()
2800 // is there still Select?
2801 Menu* pSelect = ImplFindSelectMenu();
2802 if (pSelect)
2804 // Select should be called prior to leaving execute in a popup menu!
2805 Application::RemoveUserEvent( pSelect->nEventId );
2806 pSelect->nEventId = nullptr;
2807 pSelect->Select();
2811 sal_uInt16 PopupMenu::ImplExecute( const VclPtr<vcl::Window>& pW, const tools::Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst )
2813 if ( !pSFrom && ( PopupMenu::IsInExecute() || !GetItemCount() ) )
2814 return 0;
2816 // set the flag to hide or show accelerators in the menu depending on whether the menu was launched by mouse or keyboard shortcut
2817 if( pSFrom && pSFrom->IsMenuBar())
2819 auto pMenuBarWindow = static_cast<MenuBarWindow*>(pSFrom->pWindow.get());
2820 pMenuBarWindow->SetMBWHideAccel( !(pMenuBarWindow->GetMBWMenuKey()) );
2823 mpLayoutData.reset();
2825 ImplSVData* pSVData = ImplGetSVData();
2827 pStartedFrom = pSFrom;
2828 nSelectedId = 0;
2829 sSelectedIdent.clear();
2830 bCanceled = false;
2832 VclPtr<vcl::Window> xFocusId;
2833 bool bRealExecute = false;
2834 if ( !pStartedFrom )
2836 pSVData->maWinData.mbNoDeactivate = true;
2837 xFocusId = Window::SaveFocus();
2838 bRealExecute = true;
2840 else
2842 // assure that only one menu is open at a time
2843 if (pStartedFrom->IsMenuBar() && pSVData->maWinData.mpFirstFloat)
2844 pSVData->maWinData.mpFirstFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
2847 SAL_WARN_IF( ImplGetWindow(), "vcl", "Win?!" );
2848 tools::Rectangle aRect( rRect );
2849 aRect.SetPos( pW->OutputToScreenPixel( aRect.TopLeft() ) );
2851 if (bRealExecute)
2852 nPopupModeFlags |= FloatWinPopupFlags::NewLevel;
2853 nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose;
2855 bInCallback = true; // set it here, if Activate overridden
2856 Activate();
2857 bInCallback = false;
2859 if ( pW->IsDisposed() )
2860 return 0; // Error
2862 if ( bCanceled || bKilled )
2863 return 0;
2865 if ( !GetItemCount() )
2866 return 0;
2868 // The flag MenuFlags::HideDisabledEntries is inherited.
2869 if ( pSFrom )
2871 if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries )
2872 nMenuFlags |= MenuFlags::HideDisabledEntries;
2873 else
2874 nMenuFlags &= ~MenuFlags::HideDisabledEntries;
2876 else
2877 // #102790# context menus shall never show disabled entries
2878 nMenuFlags |= MenuFlags::HideDisabledEntries;
2880 sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount();
2881 if ( !nVisibleEntries )
2883 OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
2885 MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, OString());
2886 size_t nPos = 0;
2887 pData = pItemList->GetData( pData->nId, nPos );
2888 assert(pData);
2889 if (pData)
2891 pData->bIsTemporary = true;
2893 ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos);
2896 VclPtrInstance<MenuFloatingWindow> pWin( this, pW, WB_BORDER | WB_SYSTEMWINDOW );
2897 if (comphelper::LibreOfficeKit::isActive() && mpLOKNotifier)
2898 pWin->SetLOKNotifier(mpLOKNotifier);
2900 if( pSVData->maNWFData.mbFlatMenu )
2901 pWin->SetBorderStyle( WindowBorderStyle::NOBORDER );
2902 else
2903 pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU );
2904 pWindow = pWin;
2906 Size aSz = ImplCalcSize( pWin );
2908 tools::Rectangle aDesktopRect(pWin->GetDesktopRectPixel());
2909 if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() )
2911 vcl::Window* pDeskW = pWindow->GetWindow( GetWindowType::RealParent );
2912 if( ! pDeskW )
2913 pDeskW = pWindow;
2914 Point aDesktopTL( pDeskW->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) );
2915 aDesktopRect = Application::GetScreenPosSizePixel(
2916 Application::GetBestScreen( tools::Rectangle( aDesktopTL, aRect.GetSize() ) ));
2919 long nMaxHeight = aDesktopRect.GetHeight();
2921 //rhbz#1021915. If a menu won't fit in the desired location the default
2922 //mode is to place it somewhere it will fit. e.g. above, left, right. For
2923 //some cases, e.g. menubars, it's desirable to limit the options to
2924 //above/below and force the menu to scroll if it won't fit
2925 if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement)
2927 vcl::Window* pRef = pWin;
2928 if ( pRef->GetParent() )
2929 pRef = pRef->GetParent();
2931 tools::Rectangle devRect( pRef->OutputToAbsoluteScreenPixel( aRect.TopLeft() ),
2932 pRef->OutputToAbsoluteScreenPixel( aRect.BottomRight() ) );
2934 long nHeightAbove = devRect.Top() - aDesktopRect.Top();
2935 long nHeightBelow = aDesktopRect.Bottom() - devRect.Bottom();
2936 nMaxHeight = std::min(nMaxHeight, std::max(nHeightAbove, nHeightBelow));
2939 if (pStartedFrom && pStartedFrom->IsMenuBar())
2940 nMaxHeight -= pW->GetSizePixel().Height();
2941 sal_Int32 nLeft, nTop, nRight, nBottom;
2942 pWindow->GetBorder( nLeft, nTop, nRight, nBottom );
2943 nMaxHeight -= nTop+nBottom;
2944 if ( aSz.Height() > nMaxHeight )
2946 pWin->EnableScrollMenu( true );
2947 sal_uInt16 nStart = ImplGetFirstVisible();
2948 sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart );
2949 aSz.setHeight( ImplCalcHeight( nEntries ) );
2952 // tdf#126054 hold this until after function completes
2953 VclPtr<PopupMenu> m_xThis(this);
2955 pWin->SetFocusId( xFocusId );
2956 pWin->SetOutputSizePixel( aSz );
2957 if ( GetItemCount() )
2959 SalMenu* pMenu = ImplGetSalMenu();
2960 if( pMenu && bRealExecute && pMenu->ShowNativePopupMenu( pWin, aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus ) )
2962 pWin->StopExecute();
2963 pWin->doShutdown();
2964 pWindow->SetParentToDefaultWindow();
2965 pWindow.disposeAndClear();
2966 ImplClosePopupToolBox(pW);
2967 ImplFlushPendingSelect();
2968 return nSelectedId;
2970 else
2972 pWin->StartPopupMode( aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus );
2974 if( pSFrom )
2976 sal_uInt16 aPos;
2977 if (pSFrom->IsMenuBar())
2978 aPos = static_cast<MenuBarWindow *>(pSFrom->pWindow.get())->GetHighlightedItem();
2979 else
2980 aPos = static_cast<MenuFloatingWindow *>(pSFrom->pWindow.get())->GetHighlightedItem();
2982 pWin->SetPosInParent( aPos ); // store position to be sent in SUBMENUDEACTIVATE
2983 pSFrom->ImplCallEventListeners( VclEventId::MenuSubmenuActivate, aPos );
2986 if ( bPreSelectFirst )
2988 size_t nCount = pItemList->size();
2989 for ( size_t n = 0; n < nCount; n++ )
2991 MenuItemData* pData = pItemList->GetDataFromPos( n );
2992 if ( ( pData->bEnabled
2993 || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus()
2995 && ( pData->eType != MenuItemType::SEPARATOR )
2996 && ImplIsVisible( n )
2997 && ImplIsSelectable( n )
3000 pWin->ChangeHighlightItem( n, false );
3001 break;
3005 if ( bRealExecute )
3007 pWin->Execute();
3008 if (pWin->IsDisposed())
3009 return 0;
3011 xFocusId = pWin->GetFocusId();
3012 assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End");
3013 pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId);
3015 if ( nSelectedId ) // then clean up .. ( otherwise done by TH )
3017 PopupMenu* pSub = pWin->GetActivePopup();
3018 while ( pSub )
3020 pSub->ImplGetFloatingWindow()->EndPopupMode();
3021 pSub = pSub->ImplGetFloatingWindow()->GetActivePopup();
3024 pWin->doShutdown();
3025 pWindow->SetParentToDefaultWindow();
3026 pWindow.disposeAndClear();
3027 ImplClosePopupToolBox(pW);
3028 ImplFlushPendingSelect();
3031 return bRealExecute ? nSelectedId : 0;
3034 sal_uInt16 PopupMenu::ImplCalcVisEntries( long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const
3036 nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight();
3038 long nHeight = 0;
3039 size_t nEntries = pItemList->size();
3040 sal_uInt16 nVisEntries = 0;
3042 if ( pLastVisible )
3043 *pLastVisible = 0;
3045 for ( size_t n = nStartEntry; n < nEntries; n++ )
3047 if ( ImplIsVisible( n ) )
3049 MenuItemData* pData = pItemList->GetDataFromPos( n );
3050 nHeight += pData->aSz.Height();
3051 if ( nHeight > nMaxHeight )
3052 break;
3054 if ( pLastVisible )
3055 *pLastVisible = n;
3056 nVisEntries++;
3059 return nVisEntries;
3062 long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const
3064 long nHeight = 0;
3066 sal_uInt16 nFound = 0;
3067 for ( size_t n = 0; ( nFound < nEntries ) && ( n < pItemList->size() ); n++ )
3069 if ( ImplIsVisible( static_cast<sal_uInt16>(n) ) )
3071 MenuItemData* pData = pItemList->GetDataFromPos( n );
3072 nHeight += pData->aSz.Height();
3073 nFound++;
3077 nHeight += 2*ImplGetFloatingWindow()->GetScrollerHeight();
3079 return nHeight;
3082 ImplMenuDelData::ImplMenuDelData( const Menu* pMenu )
3083 : mpNext( nullptr )
3084 , mpMenu( nullptr )
3086 if( pMenu )
3087 const_cast< Menu* >( pMenu )->ImplAddDel( *this );
3090 ImplMenuDelData::~ImplMenuDelData()
3092 if( mpMenu )
3093 const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this );
3096 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */