tdf#164393 Change themes UI as per UX feedback
[LibreOffice.git] / vcl / source / window / menu.cxx
blob7b3c2a78be62fe30cffe353a972d07ba4d41a053
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 <accessibility/accessiblemenubasecomponent.hxx>
21 #include <accessibility/vclxaccessiblemenubar.hxx>
22 #include <accessibility/vclxaccessiblepopupmenu.hxx>
24 #include <comphelper/diagnose_ex.hxx>
25 #include <sal/log.hxx>
27 #include <comphelper/types.hxx>
28 #include <comphelper/lok.hxx>
29 #include <vcl/dialoghelper.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/mnemonic.hxx>
32 #include <vcl/image.hxx>
33 #include <vcl/event.hxx>
34 #include <vcl/help.hxx>
35 #include <vcl/toolkit/floatwin.hxx>
36 #include <vcl/decoview.hxx>
37 #include <vcl/menu.hxx>
38 #include <vcl/taskpanelist.hxx>
39 #include <vcl/settings.hxx>
40 #include <vcl/commandinfoprovider.hxx>
42 #include <salinst.hxx>
43 #include <svdata.hxx>
44 #include <strings.hrc>
45 #include <window.h>
46 #include <salmenu.hxx>
47 #include <salframe.hxx>
49 #include "menubarwindow.hxx"
50 #include "menufloatingwindow.hxx"
51 #include "menuitemlist.hxx"
53 #include <com/sun/star/uno/Reference.h>
54 #include <com/sun/star/lang/XComponent.hpp>
55 #include <com/sun/star/accessibility/XAccessible.hpp>
56 #include <vcl/toolkit/unowrap.hxx>
57 #include <rtl/ustrbuf.hxx>
59 #include <configsettings.hxx>
61 #include <map>
62 #include <string_view>
63 #include <vector>
65 #include <officecfg/Office/Common.hxx>
67 namespace vcl
70 struct MenuLayoutData : public ControlLayoutData
72 std::vector< sal_uInt16 > m_aLineItemIds;
73 std::map< sal_uInt16, tools::Rectangle > m_aVisibleItemBoundRects;
78 using namespace vcl;
80 #define EXTRAITEMHEIGHT 4
81 #define SPACE_AROUND_TITLE 4
83 static bool ImplAccelDisabled()
85 // display of accelerator strings may be suppressed via configuration
86 static int nAccelDisabled = -1;
88 if( nAccelDisabled == -1 )
90 OUString aStr =
91 vcl::SettingsConfigItem::get()->
92 getValue( u"Menu"_ustr, u"SuppressAccelerators"_ustr );
93 nAccelDisabled = aStr.equalsIgnoreAsciiCase("true") ? 1 : 0;
95 return nAccelDisabled == 1;
98 static void ImplSetMenuItemData( MenuItemData* pData )
100 // convert data
101 if ( !pData->aImage )
102 pData->eType = MenuItemType::STRING;
103 else if ( pData->aText.isEmpty() )
104 pData->eType = MenuItemType::IMAGE;
105 else
106 pData->eType = MenuItemType::STRINGIMAGE;
109 namespace {
111 void ImplClosePopupToolBox( const VclPtr<vcl::Window>& pWin )
113 if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin ) )
115 SystemWindow* pFloatingWindow = ImplGetDockingManager()->GetFloatingWindow(pWin);
116 if (pFloatingWindow)
117 static_cast<FloatingWindow*>(pFloatingWindow)->EndPopupMode( FloatWinPopupEndFlags::CloseAll );
121 // TODO: Move to common code with the same function in toolbox
122 // Draw the ">>" - more indicator at the coordinates
123 void lclDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
125 rRenderContext.Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
126 rRenderContext.SetLineColor();
128 if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
129 rRenderContext.SetFillColor(COL_WHITE);
130 else
131 rRenderContext.SetFillColor(COL_BLACK);
132 float fScaleFactor = rRenderContext.GetDPIScaleFactor();
134 int linewidth = 1 * fScaleFactor;
135 int space = 4 * fScaleFactor;
137 tools::Long width = 8 * fScaleFactor;
138 tools::Long height = 5 * fScaleFactor;
140 //Keep odd b/c drawing code works better
141 if ( height % 2 == 0 )
142 height--;
144 tools::Long heightOrig = height;
146 tools::Long x = rRect.Left() + (rRect.getOpenWidth() - width)/2 + 1;
147 tools::Long y = rRect.Top() + (rRect.getOpenHeight() - height)/2 + 1;
148 while( height >= 1)
150 rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
151 x += space;
152 rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
153 x -= space;
154 y++;
155 if( height <= heightOrig / 2 + 1) x--;
156 else x++;
157 height--;
159 rRenderContext.Pop();
162 } // end anonymous namespace
165 Menu::Menu()
166 : mpFirstDel(nullptr),
167 pItemList(new MenuItemList),
168 pStartedFrom(nullptr),
169 m_pWindow(nullptr),
170 nTitleHeight(0),
171 nEventId(nullptr),
172 mnHighlightedItemPos(ITEMPOS_INVALID),
173 nMenuFlags(MenuFlags::NONE),
174 nSelectedId(0),
175 nImgOrChkPos(0),
176 nTextPos(0),
177 bCanceled(false),
178 bInCallback(false),
179 bKilled(false)
183 Menu::~Menu()
185 disposeOnce();
188 void Menu::dispose()
190 ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID );
192 m_pWindow.disposeAndClear();
194 // dispose accessible components
195 comphelper::disposeComponent(mxAccessible);
197 if ( nEventId )
198 Application::RemoveUserEvent( nEventId );
200 // Notify deletion of this menu
201 ImplMenuDelData* pDelData = mpFirstDel;
202 while ( pDelData )
204 pDelData->mpMenu = nullptr;
205 pDelData = pDelData->mpNext;
208 bKilled = true;
210 // tdf#140225 when clearing pItemList, keep SalMenu in sync with
211 // their removal during menu teardown
212 for (size_t n = pItemList->size(); n;)
214 --n;
215 if (mpSalMenu)
216 mpSalMenu->RemoveItem(n);
217 pItemList->Remove(n);
220 assert(!pItemList->size());
222 mpLayoutData.reset();
224 // Native-support: destroy SalMenu
225 mpSalMenu.reset();
227 pStartedFrom.clear();
228 m_pWindow.clear();
229 VclReferenceBase::dispose();
232 void Menu::CreateAutoMnemonics()
234 MnemonicGenerator aMnemonicGenerator;
235 size_t n;
236 for ( n = 0; n < pItemList->size(); n++ )
238 MenuItemData* pData = pItemList->GetDataFromPos( n );
239 if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
240 aMnemonicGenerator.RegisterMnemonic( pData->aText );
242 for ( n = 0; n < pItemList->size(); n++ )
244 MenuItemData* pData = pItemList->GetDataFromPos( n );
245 if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
246 pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText );
250 void Menu::Activate()
252 bInCallback = true;
254 ImplMenuDelData aDelData( this );
256 ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID );
258 if( !aDelData.isDeleted() )
260 if ( !aActivateHdl.Call( this ) )
262 if( !aDelData.isDeleted() )
264 Menu* pStartMenu = ImplGetStartMenu();
265 if ( pStartMenu && ( pStartMenu != this ) )
267 pStartMenu->bInCallback = true;
268 // MT 11/01: Call EventListener here? I don't know...
269 pStartMenu->aActivateHdl.Call( this );
270 pStartMenu->bInCallback = false;
274 bInCallback = false;
277 if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics))
278 CreateAutoMnemonics();
281 void Menu::Deactivate()
283 for ( size_t n = pItemList->size(); n; )
285 MenuItemData* pData = pItemList->GetDataFromPos( --n );
286 if ( pData->bIsTemporary )
288 if ( ImplGetSalMenu() )
289 ImplGetSalMenu()->RemoveItem( n );
291 pItemList->Remove( n );
295 bInCallback = true;
297 ImplMenuDelData aDelData( this );
299 Menu* pStartMenu = ImplGetStartMenu();
300 ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID );
302 if( !aDelData.isDeleted() )
304 if ( !aDeactivateHdl.Call( this ) )
306 if( !aDelData.isDeleted() )
308 if ( pStartMenu && ( pStartMenu != this ) )
310 pStartMenu->bInCallback = true;
311 pStartMenu->aDeactivateHdl.Call( this );
312 pStartMenu->bInCallback = false;
318 if( !aDelData.isDeleted() )
320 bInCallback = false;
324 void Menu::ImplSelect()
326 MenuItemData* pData = GetItemList()->GetData( nSelectedId );
327 if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) )
329 bool bChecked = IsItemChecked( nSelectedId );
330 if ( pData->nBits & MenuItemBits::RADIOCHECK )
332 if ( !bChecked )
333 CheckItem( nSelectedId );
335 else
336 CheckItem( nSelectedId, !bChecked );
339 // call select
340 ImplSVData* pSVData = ImplGetSVData();
341 pSVData->maAppData.mpActivePopupMenu = nullptr; // if new execute in select()
342 nEventId = Application::PostUserEvent( LINK( this, Menu, ImplCallSelect ) );
345 void Menu::Select()
347 ImplMenuDelData aDelData( this );
349 ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) );
350 if (aDelData.isDeleted())
351 return;
352 if (aSelectHdl.Call(this))
353 return;
354 if (aDelData.isDeleted())
355 return;
356 Menu* pStartMenu = ImplGetStartMenu();
357 if (!pStartMenu || (pStartMenu == this))
358 return;
359 pStartMenu->nSelectedId = nSelectedId;
360 pStartMenu->sSelectedIdent = sSelectedIdent;
361 pStartMenu->aSelectHdl.Call( this );
364 #if defined(MACOSX)
365 void Menu::ImplSelectWithStart( Menu* pSMenu )
367 auto pOldStartedFrom = pStartedFrom;
368 pStartedFrom = pSMenu;
369 auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>();
370 Select();
371 if( pOldStartedFrom )
372 pOldStartedFrom->pStartedFrom = pOldStartedStarted;
373 pStartedFrom = pOldStartedFrom;
375 #endif
377 void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos )
379 ImplMenuDelData aDelData( this );
381 VclMenuEvent aEvent( this, nEvent, nPos );
383 // This is needed by atk accessibility bridge
384 if ( nEvent == VclEventId::MenuHighlight )
386 Application::ImplCallEventListeners( aEvent );
389 if ( !aDelData.isDeleted() )
391 // Copy the list, because this can be destroyed when calling a Link...
392 std::list<Link<VclMenuEvent&,void>> aCopy( maEventListeners );
393 for ( const auto& rLink : aCopy )
395 if( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() )
396 rLink.Call( aEvent );
401 void Menu::AddEventListener( const Link<VclMenuEvent&,void>& rEventListener )
403 maEventListeners.push_back( rEventListener );
406 void Menu::RemoveEventListener( const Link<VclMenuEvent&,void>& rEventListener )
408 maEventListeners.remove( rEventListener );
411 MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits,
412 const OUString& rStr, Menu* pMenu,
413 size_t nPos, const OUString &rIdent)
415 // put Item in MenuItemList
416 MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING,
417 nBits, rStr, pMenu, nPos, rIdent);
419 // update native menu
420 if (ImplGetSalMenu() && pData->pSalMenuItem)
421 ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos);
423 return pData;
426 void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits,
427 const OUString &rIdent, sal_uInt16 nPos)
429 SAL_WARN_IF( !nItemId, "vcl", "Menu::InsertItem(): ItemId == 0" );
430 SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl",
431 "Menu::InsertItem(): ItemId already exists" );
433 // if Position > ItemCount, append
434 if ( nPos >= pItemList->size() )
435 nPos = MENU_APPEND;
437 // put Item in MenuItemList
438 NbcInsertItem(nItemId, nItemBits, rStr, this, nPos, rIdent);
440 vcl::Window* pWin = GetWindow();
441 mpLayoutData.reset();
442 if ( pWin )
444 ImplCalcSize( pWin );
445 if ( pWin->IsVisible() )
446 pWin->Invalidate();
448 ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
451 void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage,
452 MenuItemBits nItemBits, const OUString &rIdent, sal_uInt16 nPos)
454 InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos);
455 SetItemImage( nItemId, rImage );
458 void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr,
459 const Image& rImage, MenuItemBits nItemBits,
460 const OUString &rIdent, sal_uInt16 nPos)
462 InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
463 SetItemImage( nItemId, rImage );
466 void Menu::InsertSeparator(const OUString &rIdent, sal_uInt16 nPos)
468 // do nothing if it's a menu bar
469 if (IsMenuBar())
470 return;
472 // if position > ItemCount, append
473 if ( nPos >= pItemList->size() )
474 nPos = MENU_APPEND;
476 // put separator in item list
477 pItemList->InsertSeparator(rIdent, nPos);
479 // update native menu
480 size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1;
481 MenuItemData *pData = pItemList->GetDataFromPos( itemPos );
482 if( ImplGetSalMenu() && pData && pData->pSalMenuItem )
483 ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos );
485 mpLayoutData.reset();
487 ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
490 void Menu::RemoveItem( sal_uInt16 nPos )
492 bool bRemove = false;
494 if ( nPos < GetItemCount() )
496 // update native menu
497 if( ImplGetSalMenu() )
498 ImplGetSalMenu()->RemoveItem( nPos );
500 pItemList->Remove( nPos );
501 bRemove = true;
504 vcl::Window* pWin = GetWindow();
505 if ( pWin )
507 ImplCalcSize( pWin );
508 if ( pWin->IsVisible() )
509 pWin->Invalidate();
511 mpLayoutData.reset();
513 if ( bRemove )
514 ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos );
517 static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos )
519 MenuItemType eType = rMenu.GetItemType( nPos );
521 if ( eType == MenuItemType::DONTKNOW )
522 return;
524 if ( eType == MenuItemType::SEPARATOR )
525 pThis->InsertSeparator( {}, nNewPos );
526 else
528 sal_uInt16 nId = rMenu.GetItemId( nPos );
530 SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl",
531 "Menu::CopyItem(): ItemId already exists" );
533 MenuItemData* pData = rMenu.GetItemList()->GetData( nId );
535 if (!pData)
536 return;
538 if ( eType == MenuItemType::STRINGIMAGE )
539 pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
540 else if ( eType == MenuItemType::STRING )
541 pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos );
542 else
543 pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
545 if ( rMenu.IsItemChecked( nId ) )
546 pThis->CheckItem( nId );
547 if ( !rMenu.IsItemEnabled( nId ) )
548 pThis->EnableItem( nId, false );
549 pThis->SetHelpId( nId, pData->aHelpId );
550 pThis->SetHelpText( nId, pData->aHelpText );
551 pThis->SetAccelKey( nId, pData->aAccelKey );
552 pThis->SetItemCommand( nId, pData->aCommandStr );
553 pThis->SetHelpCommand( nId, pData->aHelpCommandStr );
555 PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId );
556 if ( pSubMenu )
558 // create auto-copy
559 VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu );
560 pThis->SetPopupMenu( nId, pNewMenu );
565 void Menu::Clear()
567 for ( sal_uInt16 i = GetItemCount(); i; i-- )
568 RemoveItem( 0 );
571 sal_uInt16 Menu::GetItemCount() const
573 return static_cast<sal_uInt16>(pItemList->size());
576 bool Menu::HasValidEntries(bool bCheckPopups) const
578 bool bValidEntries = false;
579 sal_uInt16 nCount = GetItemCount();
580 for (sal_uInt16 n = 0; !bValidEntries && (n < nCount); n++)
582 MenuItemData* pItem = pItemList->GetDataFromPos(n);
583 if (pItem->bEnabled && (pItem->eType != MenuItemType::SEPARATOR))
585 if (bCheckPopups && pItem->pSubMenu)
586 bValidEntries = pItem->pSubMenu->HasValidEntries(true);
587 else
588 bValidEntries = true;
591 return bValidEntries;
594 sal_uInt16 Menu::ImplGetVisibleItemCount() const
596 sal_uInt16 nItems = 0;
597 for ( size_t n = pItemList->size(); n; )
599 if ( ImplIsVisible( --n ) )
600 nItems++;
602 return nItems;
605 sal_uInt16 Menu::ImplGetFirstVisible() const
607 for ( size_t n = 0; n < pItemList->size(); n++ )
609 if ( ImplIsVisible( n ) )
610 return n;
612 return ITEMPOS_INVALID;
615 sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const
617 for ( size_t n = nPos; n; )
619 if (ImplIsVisible(--n))
620 return n;
622 return ITEMPOS_INVALID;
625 sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const
627 for ( size_t n = nPos+1; n < pItemList->size(); n++ )
629 if ( ImplIsVisible( n ) )
630 return n;
632 return ITEMPOS_INVALID;
635 sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const
637 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
639 if ( pData )
640 return pData->nId;
641 else
642 return 0;
645 sal_uInt16 Menu::GetItemId(std::u16string_view rIdent) const
647 for (size_t n = 0; n < pItemList->size(); ++n)
649 MenuItemData* pData = pItemList->GetDataFromPos(n);
650 if (pData && pData->sIdent == rIdent)
651 return pData->nId;
653 return MENU_ITEM_NOTFOUND;
656 sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const
658 size_t nPos;
659 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
661 if ( pData )
662 return static_cast<sal_uInt16>(nPos);
663 else
664 return MENU_ITEM_NOTFOUND;
667 MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const
669 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
671 if ( pData )
672 return pData->eType;
673 else
674 return MenuItemType::DONTKNOW;
677 OUString Menu::GetItemIdent(sal_uInt16 nId) const
679 const MenuItemData* pData = pItemList->GetData(nId);
680 return pData ? pData->sIdent : OUString();
683 void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
685 size_t nPos;
686 MenuItemData* pData = pItemList->GetData(nItemId, nPos);
688 if (pData && (pData->nBits != nBits))
690 // these two menu item bits are relevant for (accessible) role
691 const MenuItemBits nRoleMask = MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK;
692 const bool bRoleBitsChanged = (pData->nBits & nRoleMask) != (nBits & nRoleMask);
694 pData->nBits = nBits;
696 // update native menu
697 if (ImplGetSalMenu())
698 ImplGetSalMenu()->SetItemBits(nPos, nBits);
700 if (bRoleBitsChanged)
701 ImplCallEventListeners(VclEventId::MenuItemRoleChanged, nPos);
705 MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const
707 MenuItemBits nBits = MenuItemBits::NONE;
708 MenuItemData* pData = pItemList->GetData( nItemId );
709 if ( pData )
710 nBits = pData->nBits;
711 return nBits;
714 void Menu::SetUserValue(sal_uInt16 nItemId, void* nUserValue, MenuUserDataReleaseFunction aFunc)
716 MenuItemData* pData = pItemList->GetData(nItemId);
717 if (pData)
719 if (pData->aUserValueReleaseFunc)
720 pData->aUserValueReleaseFunc(pData->nUserValue);
721 pData->aUserValueReleaseFunc = aFunc;
722 pData->nUserValue = nUserValue;
726 void* Menu::GetUserValue( sal_uInt16 nItemId ) const
728 MenuItemData* pData = pItemList->GetData( nItemId );
729 return pData ? pData->nUserValue : nullptr;
732 void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu )
734 size_t nPos;
735 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
737 // Item does not exist -> return NULL
738 if ( !pData )
739 return;
741 // same menu, nothing to do
742 if ( pData->pSubMenu.get() == pMenu )
743 return;
745 // remove old menu
746 auto oldSubMenu = pData->pSubMenu;
748 // data exchange
749 pData->pSubMenu = pMenu;
751 // #112023# Make sure pStartedFrom does not point to invalid (old) data
752 if ( pData->pSubMenu )
753 pData->pSubMenu->pStartedFrom = nullptr;
755 // set native submenu
756 if( ImplGetSalMenu() && pData->pSalMenuItem )
758 if( pMenu )
759 ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos );
760 else
761 ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos );
764 oldSubMenu.disposeAndClear();
766 ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos );
769 PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const
771 MenuItemData* pData = pItemList->GetData( nItemId );
773 if ( pData )
774 return pData->pSubMenu.get();
775 else
776 return nullptr;
779 void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode )
781 size_t nPos;
782 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
784 if ( !pData )
785 return;
787 if ( pData->aAccelKey == rKeyCode )
788 return;
790 pData->aAccelKey = rKeyCode;
792 // update native menu
793 if( ImplGetSalMenu() && pData->pSalMenuItem )
794 ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() );
797 KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const
799 MenuItemData* pData = pItemList->GetData( nItemId );
801 if ( pData )
802 return pData->aAccelKey;
803 else
804 return KeyCode();
807 KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const
809 KeyEvent aRet;
810 MenuItemData* pData = pItemList->GetData( nItemId );
811 if( pData )
813 sal_Int32 nPos = pData->aText.indexOf( '~' );
814 if( nPos != -1 && nPos < pData->aText.getLength()-1 )
816 sal_uInt16 nCode = 0;
817 sal_Unicode cAccel = pData->aText[nPos+1];
818 if( cAccel >= 'a' && cAccel <= 'z' )
819 nCode = KEY_A + (cAccel-'a');
820 else if( cAccel >= 'A' && cAccel <= 'Z' )
821 nCode = KEY_A + (cAccel-'A');
822 else if( cAccel >= '0' && cAccel <= '9' )
823 nCode = KEY_0 + (cAccel-'0');
825 aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) );
829 return aRet;
832 void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck )
834 size_t nPos;
835 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
837 if ( !pData || pData->bChecked == bCheck )
838 return;
840 // if radio-check, then uncheck previous
841 if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) &&
842 (pData->nBits & MenuItemBits::RADIOCHECK) )
844 MenuItemData* pGroupData;
845 sal_uInt16 nGroupPos;
846 sal_uInt16 nItemCount = GetItemCount();
847 bool bFound = false;
849 nGroupPos = nPos;
850 while ( nGroupPos )
852 pGroupData = pItemList->GetDataFromPos( nGroupPos-1 );
853 if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
855 if ( IsItemChecked( pGroupData->nId ) )
857 CheckItem( pGroupData->nId, false );
858 bFound = true;
859 break;
862 else
863 break;
864 nGroupPos--;
867 if ( !bFound )
869 nGroupPos = nPos+1;
870 while ( nGroupPos < nItemCount )
872 pGroupData = pItemList->GetDataFromPos( nGroupPos );
873 if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
875 if ( IsItemChecked( pGroupData->nId ) )
877 CheckItem( pGroupData->nId, false );
878 break;
881 else
882 break;
883 nGroupPos++;
888 pData->bChecked = bCheck;
890 // update native menu
891 if( ImplGetSalMenu() )
892 ImplGetSalMenu()->CheckItem( nPos, bCheck );
894 ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos );
897 void Menu::CheckItem( std::u16string_view rIdent , bool bCheck )
899 CheckItem( GetItemId( rIdent ), bCheck );
902 bool Menu::IsItemCheckable(sal_uInt16 nItemId) const
904 size_t nPos;
905 MenuItemData* pData = pItemList->GetData(nItemId, nPos);
907 if (!pData)
908 return false;
910 return pData->HasCheck();
913 bool Menu::IsItemChecked( sal_uInt16 nItemId ) const
915 size_t nPos;
916 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
918 if ( !pData )
919 return false;
921 return pData->bChecked;
924 void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable )
926 size_t nPos;
927 MenuItemData* pItemData = pItemList->GetData( nItemId, nPos );
929 if ( !(pItemData && ( pItemData->bEnabled != bEnable )) )
930 return;
932 pItemData->bEnabled = bEnable;
934 vcl::Window* pWin = GetWindow();
935 if ( pWin && pWin->IsVisible() )
937 SAL_WARN_IF(!IsMenuBar(), "vcl", "Menu::EnableItem - Popup visible!" );
938 tools::Long nX = 0;
939 size_t nCount = pItemList->size();
940 for ( size_t n = 0; n < nCount; n++ )
942 MenuItemData* pData = pItemList->GetDataFromPos( n );
943 if ( n == nPos )
945 pWin->Invalidate( tools::Rectangle( Point( nX, 0 ), Size( pData->aSz.Width(), pData->aSz.Height() ) ) );
946 break;
948 nX += pData->aSz.Width();
951 // update native menu
952 if( ImplGetSalMenu() )
953 ImplGetSalMenu()->EnableItem( nPos, bEnable );
955 ImplCallEventListeners( bEnable ? VclEventId::MenuEnable : VclEventId::MenuDisable, nPos );
958 bool Menu::IsItemEnabled( sal_uInt16 nItemId ) const
960 size_t nPos;
961 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
963 if ( !pData )
964 return false;
966 return pData->bEnabled;
969 void Menu::ShowItem( sal_uInt16 nItemId, bool bVisible )
971 size_t nPos;
972 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
974 SAL_WARN_IF(IsMenuBar() && !bVisible , "vcl", "Menu::ShowItem - ignored for menu bar entries!");
975 if (IsMenuBar() || !pData || (pData->bVisible == bVisible))
976 return;
978 vcl::Window* pWin = GetWindow();
979 if ( pWin && pWin->IsVisible() )
981 SAL_WARN( "vcl", "Menu::ShowItem - ignored for visible popups!" );
982 return;
984 pData->bVisible = bVisible;
986 // update native menu
987 if( ImplGetSalMenu() )
988 ImplGetSalMenu()->ShowItem( nPos, bVisible );
991 void Menu::SetItemText( sal_uInt16 nItemId, const OUString& rStr )
993 size_t nPos;
994 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
996 if ( !pData )
997 return;
999 if ( rStr == pData->aText )
1000 return;
1002 pData->aText = rStr;
1003 // Clear layout for aText.
1004 pData->aTextGlyphs.Invalidate();
1005 ImplSetMenuItemData( pData );
1006 // update native menu
1007 if( ImplGetSalMenu() && pData->pSalMenuItem )
1008 ImplGetSalMenu()->SetItemText( nPos, pData->pSalMenuItem.get(), rStr );
1010 vcl::Window* pWin = GetWindow();
1011 mpLayoutData.reset();
1012 if (pWin && IsMenuBar())
1014 ImplCalcSize( pWin );
1015 if ( pWin->IsVisible() )
1016 pWin->Invalidate();
1019 ImplCallEventListeners( VclEventId::MenuItemTextChanged, nPos );
1022 OUString Menu::GetItemText( sal_uInt16 nItemId ) const
1024 size_t nPos;
1025 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1027 if ( pData )
1028 return pData->aText;
1030 return OUString();
1033 void Menu::SetItemImage( sal_uInt16 nItemId, const Image& rImage )
1035 size_t nPos;
1036 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1038 if ( !pData )
1039 return;
1041 pData->aImage = rImage;
1042 ImplSetMenuItemData( pData );
1044 // update native menu
1045 if( ImplGetSalMenu() && pData->pSalMenuItem )
1046 ImplGetSalMenu()->SetItemImage( nPos, pData->pSalMenuItem.get(), rImage );
1049 Image Menu::GetItemImage( sal_uInt16 nItemId ) const
1051 MenuItemData* pData = pItemList->GetData( nItemId );
1053 if ( pData )
1054 return pData->aImage;
1055 else
1056 return Image();
1059 void Menu::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
1061 size_t nPos;
1062 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1064 if ( pData )
1065 pData->aCommandStr = rCommand;
1068 OUString Menu::GetItemCommand( sal_uInt16 nItemId ) const
1070 MenuItemData* pData = pItemList->GetData( nItemId );
1072 if (pData)
1073 return pData->aCommandStr;
1075 return OUString();
1078 void Menu::SetHelpCommand( sal_uInt16 nItemId, const OUString& rStr )
1080 MenuItemData* pData = pItemList->GetData( nItemId );
1082 if ( pData )
1083 pData->aHelpCommandStr = rStr;
1086 OUString Menu::GetHelpCommand( sal_uInt16 nItemId ) const
1088 MenuItemData* pData = pItemList->GetData( nItemId );
1090 if ( pData )
1091 return pData->aHelpCommandStr;
1093 return OUString();
1096 void Menu::SetHelpText( sal_uInt16 nItemId, const OUString& rStr )
1098 MenuItemData* pData = pItemList->GetData( nItemId );
1100 if ( pData )
1101 pData->aHelpText = rStr;
1104 OUString Menu::ImplGetHelpText( sal_uInt16 nItemId ) const
1106 MenuItemData* pData = pItemList->GetData( nItemId );
1108 if (!pData)
1109 return OUString();
1111 if ( pData->aHelpText.isEmpty() &&
1112 (( !pData->aHelpId.isEmpty() ) || ( !pData->aCommandStr.isEmpty() )))
1114 Help* pHelp = Application::GetHelp();
1115 if ( pHelp )
1117 if (!pData->aCommandStr.isEmpty())
1118 pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr );
1119 if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty())
1120 pData->aHelpText = pHelp->GetHelpText( pData->aHelpId );
1124 //Fallback to Menu::GetAccessibleDescription without reentry to GetHelpText()
1125 if (pData->aHelpText.isEmpty())
1126 return pData->aAccessibleDescription;
1127 return pData->aHelpText;
1130 OUString Menu::GetHelpText( sal_uInt16 nItemId ) const
1132 return ImplGetHelpText( nItemId );
1135 void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr )
1137 MenuItemData* pData = pItemList->GetData( nItemId );
1139 if ( pData )
1140 pData->aTipHelpText = rStr;
1143 OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const
1145 MenuItemData* pData = pItemList->GetData( nItemId );
1147 if ( pData )
1148 return pData->aTipHelpText;
1150 return OUString();
1153 void Menu::SetHelpId( sal_uInt16 nItemId, const OUString& rHelpId )
1155 MenuItemData* pData = pItemList->GetData( nItemId );
1157 if ( pData )
1158 pData->aHelpId = rHelpId;
1161 OUString Menu::GetHelpId( sal_uInt16 nItemId ) const
1163 OUString aRet;
1165 MenuItemData* pData = pItemList->GetData( nItemId );
1167 if ( pData )
1169 if ( !pData->aHelpId.isEmpty() )
1170 aRet = pData->aHelpId;
1171 else
1172 aRet = pData->aCommandStr;
1175 return aRet;
1178 Menu& Menu::operator=( const Menu& rMenu )
1180 if(this == &rMenu)
1181 return *this;
1183 // clean up
1184 Clear();
1186 // copy items
1187 sal_uInt16 nCount = rMenu.GetItemCount();
1188 for ( sal_uInt16 i = 0; i < nCount; i++ )
1189 ImplCopyItem( this, rMenu, i, MENU_APPEND );
1191 aActivateHdl = rMenu.aActivateHdl;
1192 aDeactivateHdl = rMenu.aDeactivateHdl;
1193 aSelectHdl = rMenu.aSelectHdl;
1194 aTitleText = rMenu.aTitleText;
1195 nTitleHeight = rMenu.nTitleHeight;
1197 return *this;
1200 // Returns true if the item is completely hidden on the GUI and shouldn't
1201 // be possible to interact with
1202 bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const
1204 MenuItemData* pData = pItemList->GetDataFromPos(nPos);
1205 if (pData)
1207 MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 );
1208 if (pPreviousData && pPreviousData->bHiddenOnGUI)
1210 return true;
1213 return false;
1216 bool Menu::ImplIsVisible( sal_uInt16 nPos ) const
1218 bool bVisible = true;
1220 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
1221 // check general visibility first
1222 if( pData && !pData->bVisible )
1223 bVisible = false;
1225 if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR )
1227 if( nPos == 0 ) // no separator should be shown at the very beginning
1228 bVisible = false;
1229 else
1231 // always avoid adjacent separators
1232 size_t nCount = pItemList->size();
1233 size_t n;
1234 MenuItemData* pNextData = nullptr;
1235 // search next visible item
1236 for( n = nPos + 1; n < nCount; n++ )
1238 pNextData = pItemList->GetDataFromPos( n );
1239 if( pNextData && pNextData->bVisible )
1241 if( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) )
1242 break;
1245 if( n == nCount ) // no next visible item
1246 bVisible = false;
1247 // check for separator
1248 if( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR )
1249 bVisible = false;
1251 if( bVisible )
1253 for( n = nPos; n > 0; n-- )
1255 pNextData = pItemList->GetDataFromPos( n-1 );
1256 if( pNextData && pNextData->bVisible )
1258 if( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) )
1259 break;
1262 if( n == 0 ) // no previous visible item
1263 bVisible = false;
1268 // not allowed for menubar, as I do not know
1269 // whether a menu-entry will disappear or will appear
1270 if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) &&
1271 !(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries))
1273 if( !pData ) // e.g. nPos == ITEMPOS_INVALID
1274 bVisible = false;
1275 else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above
1277 // tdf#86850 Always display clipboard functions
1278 if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" ||
1279 pData->sIdent == ".uno:Cut" || pData->sIdent == ".uno:Copy" || pData->sIdent == ".uno:Paste" )
1280 bVisible = true;
1281 else
1282 // bVisible = pData->bEnabled && ( !pData->pSubMenu || pData->pSubMenu->HasValidEntries( true ) );
1283 bVisible = pData->bEnabled; // do not check submenus as they might be filled at Activate().
1287 return bVisible;
1290 bool Menu::IsItemPosVisible( sal_uInt16 nItemPos ) const
1292 return IsMenuVisible() && ImplIsVisible( nItemPos );
1295 bool Menu::IsMenuVisible() const
1297 return m_pWindow && m_pWindow->IsReallyVisible();
1300 bool Menu::ImplIsSelectable( sal_uInt16 nPos ) const
1302 bool bSelectable = true;
1304 MenuItemData* pData = pItemList->GetDataFromPos( nPos );
1305 // check general visibility first
1306 if ( pData && ( pData->nBits & MenuItemBits::NOSELECT ) )
1307 bSelectable = false;
1309 return bSelectable;
1312 css::uno::Reference<css::accessibility::XAccessible> Menu::CreateAccessible()
1314 rtl::Reference<OAccessibleMenuBaseComponent> xAccessible;
1315 if (IsMenuBar())
1316 xAccessible = new VCLXAccessibleMenuBar(this);
1317 else
1318 xAccessible = new VCLXAccessiblePopupMenu(this);
1319 xAccessible->SetStates();
1320 return xAccessible;
1323 css::uno::Reference<css::accessibility::XAccessible> Menu::GetAccessible()
1325 // Since PopupMenu are sometimes shared by different instances of MenuBar, the mxAccessible member gets
1326 // overwritten and may contain a disposed object when the initial menubar gets set again. So use the
1327 // mxAccessible member only for sub menus.
1328 if (pStartedFrom && pStartedFrom != this)
1330 for ( sal_uInt16 i = 0, nCount = pStartedFrom->GetItemCount(); i < nCount; ++i )
1332 sal_uInt16 nItemId = pStartedFrom->GetItemId( i );
1333 if ( static_cast< Menu* >( pStartedFrom->GetPopupMenu( nItemId ) ) == this )
1335 css::uno::Reference<css::accessibility::XAccessible> xParent = pStartedFrom->GetAccessible();
1336 if ( xParent.is() )
1338 css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( xParent->getAccessibleContext() );
1339 if (xParentContext.is())
1340 return xParentContext->getAccessibleChild( i );
1345 else if ( !mxAccessible.is() )
1346 mxAccessible = CreateAccessible();
1348 return mxAccessible;
1351 void Menu::SetAccessible(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible )
1353 mxAccessible = rxAccessible;
1356 Size Menu::ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, tools::Long& rCheckHeight, tools::Long& rRadioHeight ) const
1358 tools::Long nCheckWidth = 0, nRadioWidth = 0;
1359 rCheckHeight = rRadioHeight = 0;
1361 if (!IsMenuBar())
1363 ImplControlValue aVal;
1364 tools::Rectangle aNativeBounds;
1365 tools::Rectangle aNativeContent;
1367 tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
1368 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark))
1370 if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark,
1371 aCtrlRegion, ControlState::ENABLED, aVal,
1372 aNativeBounds, aNativeContent))
1374 rCheckHeight = aNativeBounds.GetHeight() - 1;
1375 nCheckWidth = aNativeContent.GetWidth() - 1;
1378 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
1380 if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
1381 aCtrlRegion, ControlState::ENABLED, aVal,
1382 aNativeBounds, aNativeContent))
1384 rRadioHeight = aNativeBounds.GetHeight() - 1;
1385 nRadioWidth = aNativeContent.GetWidth() - 1;
1389 return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight));
1392 bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, tools::Long& rArrowSpacing)
1394 ImplControlValue aVal;
1395 tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
1396 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
1398 tools::Rectangle aNativeContent;
1399 tools::Rectangle aNativeBounds;
1400 if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::SubmenuArrow,
1401 aCtrlRegion, ControlState::ENABLED,
1402 aVal, aNativeBounds, aNativeContent))
1404 Size aSize(aNativeContent.GetWidth(), aNativeContent.GetHeight());
1405 rArrowSize = aSize;
1406 rArrowSpacing = aNativeBounds.GetWidth() - aNativeContent.GetWidth();
1407 return true;
1410 return false;
1413 void Menu::ImplAddDel( ImplMenuDelData& rDel )
1415 SAL_WARN_IF( rDel.mpMenu, "vcl", "Menu::ImplAddDel(): cannot add ImplMenuDelData twice !" );
1416 if( !rDel.mpMenu )
1418 rDel.mpMenu = this;
1419 rDel.mpNext = mpFirstDel;
1420 mpFirstDel = &rDel;
1424 void Menu::ImplRemoveDel( ImplMenuDelData& rDel )
1426 rDel.mpMenu = nullptr;
1427 if ( mpFirstDel == &rDel )
1429 mpFirstDel = rDel.mpNext;
1431 else
1433 ImplMenuDelData* pData = mpFirstDel;
1434 while ( pData && (pData->mpNext != &rDel) )
1435 pData = pData->mpNext;
1437 SAL_WARN_IF( !pData, "vcl", "Menu::ImplRemoveDel(): ImplMenuDelData not registered !" );
1438 if( pData )
1439 pData->mpNext = rDel.mpNext;
1443 Size Menu::ImplCalcSize( vcl::Window* pWin )
1445 // | Check/Radio/Image| Text| Accel/Popup|
1447 // for symbols: nFontHeight x nFontHeight
1448 tools::Long nFontHeight = pWin->GetTextHeight();
1449 tools::Long nExtra = nFontHeight/4;
1451 tools::Long nMinMenuItemHeight = nFontHeight;
1452 tools::Long nCheckHeight = 0, nRadioHeight = 0;
1453 Size aMarkSize = ImplGetNativeCheckAndRadioSize(*pWin->GetOutDev(), nCheckHeight, nRadioHeight);
1454 if( aMarkSize.Height() > nMinMenuItemHeight )
1455 nMinMenuItemHeight = aMarkSize.Height();
1457 tools::Long aMaxImgWidth = 0;
1459 const StyleSettings& rSettings = pWin->GetSettings().GetStyleSettings();
1460 if ( rSettings.GetUseImagesInMenus() )
1462 if ( 16 > nMinMenuItemHeight )
1463 nMinMenuItemHeight = 16;
1464 for ( size_t i = pItemList->size(); i; )
1466 MenuItemData* pData = pItemList->GetDataFromPos( --i );
1467 if ( ImplIsVisible( i )
1468 && ( ( pData->eType == MenuItemType::IMAGE )
1469 || ( pData->eType == MenuItemType::STRINGIMAGE )
1473 Size aImgSz = pData->aImage.GetSizePixel();
1474 if ( aImgSz.Width() > aMaxImgWidth )
1475 aMaxImgWidth = aImgSz.Width();
1476 if ( aImgSz.Height() > nMinMenuItemHeight )
1477 nMinMenuItemHeight = aImgSz.Height();
1478 break;
1483 Size aSz;
1484 tools::Long nMaxWidth = 0;
1486 for ( size_t n = pItemList->size(); n; )
1488 MenuItemData* pData = pItemList->GetDataFromPos( --n );
1490 pData->aSz.setHeight( 0 );
1491 pData->aSz.setWidth( 0 );
1493 if ( ImplIsVisible( n ) )
1495 tools::Long nWidth = 0;
1497 // Separator
1498 if (!IsMenuBar()&& (pData->eType == MenuItemType::SEPARATOR))
1500 pData->aSz.setHeight( 4 );
1503 // Image:
1504 if (!IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1506 tools::Long aImgHeight = pData->aImage.GetSizePixel().Height();
1508 aImgHeight += 4; // add a border for native marks
1509 if (aImgHeight > pData->aSz.Height())
1510 pData->aSz.setHeight(aImgHeight);
1513 // Check Buttons:
1514 if (!IsMenuBar() && pData->HasCheck())
1516 // checks / images take the same place
1517 if( ( pData->eType != MenuItemType::IMAGE ) && ( pData->eType != MenuItemType::STRINGIMAGE ) )
1519 nWidth += aMarkSize.Width() + nExtra * 2;
1520 if (aMarkSize.Height() > pData->aSz.Height())
1521 pData->aSz.setHeight(aMarkSize.Height());
1525 // Text:
1526 if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) )
1528 const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin->GetOutDev());
1529 tools::Long nTextWidth = pWin->GetOutDev()->GetCtrlTextWidth(pData->aText, pGlyphs);
1530 tools::Long nTextHeight = pWin->GetTextHeight() + EXTRAITEMHEIGHT;
1532 if (IsMenuBar())
1534 if ( nTextHeight > pData->aSz.Height() )
1535 pData->aSz.setHeight( nTextHeight );
1537 pData->aSz.setWidth( nTextWidth + 4*nExtra );
1538 aSz.AdjustWidth(pData->aSz.Width() );
1540 else
1541 pData->aSz.setHeight( std::max( std::max( nTextHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
1543 nWidth += nTextWidth;
1546 // Accel
1547 if (!IsMenuBar()&& pData->aAccelKey.GetCode() && !ImplAccelDisabled())
1549 OUString aName = pData->aAccelKey.GetName();
1550 tools::Long nAccWidth = pWin->GetTextWidth( aName );
1551 nAccWidth += nExtra;
1552 nWidth += nAccWidth;
1555 // SubMenu?
1556 if (!IsMenuBar() && pData->pSubMenu)
1558 if ( nFontHeight > nWidth )
1559 nWidth += nFontHeight;
1561 pData->aSz.setHeight( std::max( std::max( nFontHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
1564 if (!IsMenuBar())
1565 aSz.AdjustHeight(pData->aSz.Height() );
1567 if ( nWidth > nMaxWidth )
1568 nMaxWidth = nWidth;
1573 // Additional space for title
1574 nTitleHeight = 0;
1575 if (!IsMenuBar() && aTitleText.getLength() > 0) {
1576 // Set expected font
1577 pWin->GetOutDev()->Push(PushFlags::FONT);
1578 vcl::Font aFont = pWin->GetFont();
1579 aFont.SetWeight(WEIGHT_BOLD);
1580 pWin->SetFont(aFont);
1582 // Compute text bounding box
1583 tools::Rectangle aTextBoundRect;
1584 pWin->GetOutDev()->GetTextBoundRect(aTextBoundRect, aTitleText);
1586 // Vertically, one height of char + extra space for decoration
1587 nTitleHeight = aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ;
1588 aSz.AdjustHeight(nTitleHeight );
1590 tools::Long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE;
1591 pWin->GetOutDev()->Pop();
1592 if ( nWidth > nMaxWidth )
1593 nMaxWidth = nWidth;
1596 if (!IsMenuBar())
1598 // popup menus should not be wider than half the screen
1599 // except on rather small screens
1600 // TODO: move GetScreenNumber from SystemWindow to Window ?
1601 // currently we rely on internal privileges
1602 unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->GetUnmirroredGeometry().screen();
1603 tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) );
1604 tools::Long nScreenWidth = aDispRect.GetWidth() >= 800 ? aDispRect.GetWidth() : 800;
1605 if( nMaxWidth > nScreenWidth/2 )
1606 nMaxWidth = nScreenWidth/2;
1608 sal_uInt16 gfxExtra = static_cast<sal_uInt16>(std::max( nExtra, tools::Long(7) )); // #107710# increase space between checkmarks/images/text
1609 nImgOrChkPos = static_cast<sal_uInt16>(nExtra);
1610 tools::Long nImgOrChkWidth = 0;
1611 if( aMarkSize.Height() > 0 ) // NWF case
1612 nImgOrChkWidth = aMarkSize.Height() + nExtra;
1613 else // non NWF case
1614 nImgOrChkWidth = nFontHeight/2 + gfxExtra;
1615 nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgWidth + gfxExtra );
1616 nTextPos = static_cast<sal_uInt16>(nImgOrChkPos + nImgOrChkWidth);
1617 nTextPos = nTextPos + gfxExtra;
1619 aSz.setWidth( nTextPos + nMaxWidth + nExtra );
1620 aSz.AdjustWidth(4*nExtra ); // a _little_ more ...
1622 aSz.AdjustWidth(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderX );
1623 aSz.AdjustHeight(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
1625 else
1627 nTextPos = static_cast<sal_uInt16>(2*nExtra);
1628 aSz.setHeight( nFontHeight+6 );
1630 // get menubar height from native methods if supported
1631 if (m_pWindow->IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
1633 ImplControlValue aVal;
1634 tools::Rectangle aNativeBounds;
1635 tools::Rectangle aNativeContent;
1636 Point tmp( 0, 0 );
1637 tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) );
1638 if (m_pWindow->GetNativeControlRegion(ControlType::Menubar,
1639 ControlPart::Entire,
1640 aCtrlRegion,
1641 ControlState::ENABLED,
1642 aVal,
1643 aNativeBounds,
1644 aNativeContent)
1647 int nNativeHeight = aNativeBounds.GetHeight();
1648 if( nNativeHeight > aSz.Height() )
1649 aSz.setHeight( nNativeHeight );
1653 // account for the size of the close button, which actually is a toolbox
1654 // due to NWF this is variable
1655 tools::Long nCloseButtonHeight = static_cast<MenuBarWindow*>(m_pWindow.get())->MinCloseButtonSize().Height();
1656 if (aSz.Height() < nCloseButtonHeight)
1657 aSz.setHeight( nCloseButtonHeight );
1660 return aSz;
1663 static void ImplPaintCheckBackground(vcl::RenderContext & rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight)
1665 bool bNativeOk = false;
1666 if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button))
1668 ImplControlValue aControlValue;
1669 aControlValue.setTristateVal(ButtonValue::On);
1670 tools::Rectangle r = i_rRect;
1671 r.AdjustBottom(1);
1673 bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
1675 ControlState::PRESSED | ControlState::ENABLED,
1676 aControlValue,
1677 OUString());
1680 if (!bNativeOk)
1682 const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
1683 Color aColor( i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor() );
1684 RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true, false, nullptr, 2, &aColor);
1688 static OUString getShortenedString( const OUString& i_rLong, vcl::RenderContext const & rRenderContext, tools::Long i_nMaxWidth )
1690 sal_Int32 nPos = -1;
1691 OUString aNonMnem(removeMnemonicFromString(i_rLong, nPos));
1692 aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis);
1693 // re-insert mnemonic
1694 if (nPos != -1)
1696 if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos])
1697 aNonMnem = OUString::Concat(aNonMnem.subView(0, nPos)) + "~" + aNonMnem.subView(nPos);
1699 return aNonMnem;
1702 void Menu::ImplPaintMenuTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) const
1704 // Save previous graphical settings, set new one
1705 rRenderContext.Push(PushFlags::FONT | PushFlags::FILLCOLOR);
1706 Wallpaper aOldBackground = rRenderContext.GetBackground();
1708 Color aBackgroundColor = rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor();
1709 rRenderContext.SetBackground(Wallpaper(aBackgroundColor));
1710 rRenderContext.SetFillColor(aBackgroundColor);
1711 vcl::Font aFont = rRenderContext.GetFont();
1712 aFont.SetWeight(WEIGHT_BOLD);
1713 rRenderContext.SetFont(aFont);
1715 // Draw background rectangle
1716 tools::Rectangle aBgRect(rRect);
1717 int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
1718 aBgRect.Move(SPACE_AROUND_TITLE, SPACE_AROUND_TITLE);
1719 aBgRect.setWidth(aBgRect.getOpenWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX);
1720 aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE);
1721 rRenderContext.DrawRect(aBgRect);
1723 // Draw the text centered
1724 Point aTextTopLeft(aBgRect.TopLeft());
1725 tools::Rectangle aTextBoundRect;
1726 rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText );
1727 aTextTopLeft.AdjustX((aBgRect.getOpenWidth() - aTextBoundRect.GetSize().Width()) / 2 );
1728 aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2
1729 - aTextBoundRect.Top() );
1730 rRenderContext.DrawText(aTextTopLeft, aTitleText, 0, aTitleText.getLength());
1732 // Restore
1733 rRenderContext.Pop();
1734 rRenderContext.SetBackground(aOldBackground);
1737 void Menu::ImplPaint(vcl::RenderContext& rRenderContext, Size const & rSize,
1738 sal_uInt16 nBorder, tools::Long nStartY, MenuItemData const * pThisItemOnly,
1739 bool bHighlighted, bool bLayout, bool bRollover) const
1741 // for symbols: nFontHeight x nFontHeight
1742 tools::Long nFontHeight = rRenderContext.GetTextHeight();
1743 tools::Long nExtra = nFontHeight / 4;
1745 tools::Long nCheckHeight = 0, nRadioHeight = 0;
1746 ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight);
1748 DecorationView aDecoView(&rRenderContext);
1749 const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
1751 Point aTopLeft, aTmpPos;
1753 int nOuterSpaceX = 0;
1754 if (!IsMenuBar())
1756 nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
1757 aTopLeft.AdjustX(nOuterSpaceX );
1758 aTopLeft.AdjustY(ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
1761 // for the computations, use size of the underlying window, not of RenderContext
1762 Size aOutSz(rSize);
1764 size_t nCount = pItemList->size();
1765 if (bLayout)
1766 mpLayoutData->m_aVisibleItemBoundRects.clear();
1768 // Paint title
1769 if (!pThisItemOnly && !IsMenuBar() && nTitleHeight > 0)
1770 ImplPaintMenuTitle(rRenderContext, tools::Rectangle(aTopLeft, aOutSz));
1772 bool bHiddenItems = false; // are any items on the GUI hidden
1774 for (size_t n = 0; n < nCount; n++)
1776 MenuItemData* pData = pItemList->GetDataFromPos( n );
1777 if (ImplIsVisible(n) && (!pThisItemOnly || (pData == pThisItemOnly)))
1779 if (pThisItemOnly)
1781 if (IsMenuBar())
1783 if (!ImplGetSVData()->maNWFData.mbRolloverMenubar)
1785 if (bRollover)
1786 rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
1787 else if (bHighlighted)
1788 rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
1790 else
1792 if (bHighlighted)
1793 rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
1794 else if (bRollover)
1795 rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
1797 if (!bRollover && !bHighlighted)
1798 rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
1800 else if (bHighlighted)
1801 rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor());
1804 Point aPos(aTopLeft);
1805 aPos.AdjustY(nBorder );
1806 aPos.AdjustY(nStartY );
1808 if (aPos.Y() >= 0)
1810 tools::Long nTextOffsetY = (pData->aSz.Height() - nFontHeight) / 2;
1811 if (IsMenuBar())
1812 nTextOffsetY += (aOutSz.Height()-pData->aSz.Height()) / 2;
1813 DrawTextFlags nTextStyle = DrawTextFlags::NONE;
1814 DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
1815 DrawImageFlags nImageStyle = DrawImageFlags::NONE;
1817 // submenus without items are not disabled when no items are
1818 // contained. The application itself should check for this!
1819 // Otherwise it could happen entries are disabled due to
1820 // asynchronous loading
1821 if (!pData->bEnabled || !m_pWindow->IsEnabled())
1823 nTextStyle |= DrawTextFlags::Disable;
1824 nSymbolStyle |= DrawSymbolFlags::Disable;
1825 nImageStyle |= DrawImageFlags::Disable;
1828 // Separator
1829 if (!bLayout && !IsMenuBar() && (pData->eType == MenuItemType::SEPARATOR))
1831 bool bNativeOk = false;
1832 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
1834 ControlState nState = ControlState::NONE;
1835 if (pData->bEnabled && m_pWindow->IsEnabled())
1836 nState |= ControlState::ENABLED;
1837 if (bHighlighted)
1838 nState |= ControlState::SELECTED;
1839 Size aSz(pData->aSz);
1840 aSz.setWidth( aOutSz.Width() - 2*nOuterSpaceX );
1841 tools::Rectangle aItemRect(aPos, aSz);
1842 MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
1843 bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
1844 aItemRect, nState, aVal, OUString());
1846 if (!bNativeOk)
1848 aTmpPos.setY( aPos.Y() + ((pData->aSz.Height() - 2) / 2) );
1849 aTmpPos.setX( aPos.X() + 2 + nOuterSpaceX );
1850 rRenderContext.SetLineColor(rSettings.GetShadowColor());
1851 rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
1852 aTmpPos.AdjustY( 1 );
1853 rRenderContext.SetLineColor(rSettings.GetLightColor());
1854 rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
1855 rRenderContext.SetLineColor();
1859 tools::Rectangle aOuterCheckRect(Point(aPos.X()+nImgOrChkPos, aPos.Y()),
1860 Size(pData->aSz.Height(), pData->aSz.Height()));
1862 // CheckMark
1863 if (!bLayout && !IsMenuBar() && pData->HasCheck())
1865 // draw selection transparent marker if checked
1866 // onto that either a checkmark or the item image
1867 // will be painted
1868 // however do not do this if native checks will be painted since
1869 // the selection color too often does not fit the theme's check and/or radio
1871 if( (pData->eType != MenuItemType::IMAGE) && (pData->eType != MenuItemType::STRINGIMAGE))
1873 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup,
1874 (pData->nBits & MenuItemBits::RADIOCHECK)
1875 ? ControlPart::MenuItemCheckMark
1876 : ControlPart::MenuItemRadioMark))
1878 ControlPart nPart = ((pData->nBits & MenuItemBits::RADIOCHECK)
1879 ? ControlPart::MenuItemRadioMark
1880 : ControlPart::MenuItemCheckMark);
1882 ControlState nState = ControlState::NONE;
1884 if (pData->bChecked)
1885 nState |= ControlState::PRESSED;
1887 if (pData->bEnabled && m_pWindow->IsEnabled())
1888 nState |= ControlState::ENABLED;
1890 if (bHighlighted)
1891 nState |= ControlState::SELECTED;
1893 tools::Long nCtrlHeight = (pData->nBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
1894 aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 );
1895 aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 );
1897 tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight));
1898 Size aSz(pData->aSz);
1899 aSz.setWidth( aOutSz.Width() - 2 * nOuterSpaceX );
1900 tools::Rectangle aItemRect(aPos, aSz);
1901 MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
1902 rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect,
1903 nState, aVal, OUString());
1905 else if (pData->bChecked) // by default do nothing for unchecked items
1907 ImplPaintCheckBackground(rRenderContext, *m_pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
1909 SymbolType eSymbol;
1910 Size aSymbolSize;
1911 if (pData->nBits & MenuItemBits::RADIOCHECK)
1913 eSymbol = SymbolType::RADIOCHECKMARK;
1914 aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2);
1916 else
1918 eSymbol = SymbolType::CHECKMARK;
1919 aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2);
1921 aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width()) / 2 );
1922 aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height()) / 2 );
1923 tools::Rectangle aRect(aTmpPos, aSymbolSize);
1924 aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetTextColor(), nSymbolStyle);
1929 // Image:
1930 if (!bLayout && !IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1932 // Don't render an image for a check thing
1933 if (pData->bChecked)
1934 ImplPaintCheckBackground(rRenderContext, *m_pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
1936 Image aImage = pData->aImage;
1938 aTmpPos = aOuterCheckRect.TopLeft();
1939 aTmpPos.AdjustX((aOuterCheckRect.GetWidth() - aImage.GetSizePixel().Width()) / 2 );
1940 aTmpPos.AdjustY((aOuterCheckRect.GetHeight() - aImage.GetSizePixel().Height()) / 2 );
1941 rRenderContext.DrawImage(aTmpPos, aImage, nImageStyle);
1944 // Text:
1945 if ((pData->eType == MenuItemType::STRING ) || (pData->eType == MenuItemType::STRINGIMAGE))
1947 aTmpPos.setX( aPos.X() + nTextPos );
1948 aTmpPos.setY( aPos.Y() );
1949 aTmpPos.AdjustY(nTextOffsetY );
1950 DrawTextFlags nStyle = nTextStyle;
1952 const Menu *pMenu = this;
1953 while (!pMenu->IsMenuBar() && pMenu->pStartedFrom)
1954 pMenu = pMenu->pStartedFrom;
1955 if (!pMenu->IsMenuBar() || !static_cast<MenuBarWindow*>(pMenu->m_pWindow.get())->GetMBWHideAccel())
1956 nStyle |= DrawTextFlags::Mnemonic;
1958 if (pData->bIsTemporary)
1959 nStyle |= DrawTextFlags::Disable;
1960 std::vector< tools::Rectangle >* pVector = bLayout ? &mpLayoutData->m_aUnicodeBoundRects : nullptr;
1961 OUString* pDisplayText = bLayout ? &mpLayoutData->m_aDisplayText : nullptr;
1962 if (bLayout)
1964 mpLayoutData->m_aLineIndices.push_back(mpLayoutData->m_aDisplayText.getLength());
1965 mpLayoutData->m_aLineItemIds.push_back(pData->nId);
1967 // #i47946# with NWF painted menus the background is transparent
1968 // since DrawCtrlText can depend on the background (e.g. for
1969 // DrawTextFlags::Disable), temporarily set a background which
1970 // hopefully matches the NWF background since it is read
1971 // from the system style settings
1972 bool bSetTmpBackground = !rRenderContext.IsBackground()
1973 && rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire);
1974 if (bSetTmpBackground)
1976 Color aBg = IsMenuBar() ? rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor()
1977 : rRenderContext.GetSettings().GetStyleSettings().GetMenuColor();
1978 rRenderContext.SetBackground(Wallpaper(aBg));
1980 // how much space is there for the text?
1981 tools::Long nMaxItemTextWidth = aOutSz.Width() - aTmpPos.X() - nExtra - nOuterSpaceX;
1982 if (!IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled())
1984 OUString aAccText = pData->aAccelKey.GetName();
1985 nMaxItemTextWidth -= rRenderContext.GetTextWidth(aAccText) + 3 * nExtra;
1987 if (!IsMenuBar() && pData->pSubMenu)
1989 nMaxItemTextWidth -= nFontHeight - nExtra;
1992 OUString aItemText(pData->aText);
1993 pData->bHiddenOnGUI = false;
1995 if (IsMenuBar()) // In case of menubar if we are out of bounds we shouldn't paint the item
1997 if (nMaxItemTextWidth < rRenderContext.GetTextWidth(aItemText))
1999 aItemText = "";
2000 pData->bHiddenOnGUI = true;
2001 bHiddenItems = true;
2004 else
2006 aItemText = getShortenedString(aItemText, rRenderContext, nMaxItemTextWidth);
2007 pData->bHiddenOnGUI = false;
2010 const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(&rRenderContext);
2011 if (aItemText != pData->aText)
2012 // Can't use pre-computed glyphs, item text was
2013 // changed.
2014 pGlyphs = nullptr;
2015 rRenderContext.DrawCtrlText(aTmpPos, aItemText, 0, aItemText.getLength(),
2016 nStyle, pVector, pDisplayText, pGlyphs);
2017 if (bSetTmpBackground)
2018 rRenderContext.SetBackground();
2021 // Accel
2022 if (!bLayout && !IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled())
2024 OUString aAccText = pData->aAccelKey.GetName();
2025 aTmpPos.setX( aOutSz.Width() - rRenderContext.GetTextWidth(aAccText) );
2026 aTmpPos.AdjustX( -(4 * nExtra) );
2028 aTmpPos.AdjustX( -nOuterSpaceX );
2029 aTmpPos.setY( aPos.Y() );
2030 aTmpPos.AdjustY(nTextOffsetY );
2031 rRenderContext.DrawCtrlText(aTmpPos, aAccText, 0, aAccText.getLength(), nTextStyle);
2034 // SubMenu?
2035 if (!bLayout && !IsMenuBar() && pData->pSubMenu)
2037 bool bNativeOk = false;
2038 if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
2040 ControlState nState = ControlState::NONE;
2041 Size aTmpSz(0, 0);
2042 tools::Long aSpacing = 0;
2044 if (!ImplGetNativeSubmenuArrowSize(rRenderContext, aTmpSz, aSpacing))
2046 aTmpSz = Size(nFontHeight, nFontHeight);
2047 aSpacing = nOuterSpaceX;
2050 if (pData->bEnabled && m_pWindow->IsEnabled())
2051 nState |= ControlState::ENABLED;
2052 if (bHighlighted)
2053 nState |= ControlState::SELECTED;
2055 aTmpPos.setX( aOutSz.Width() - aTmpSz.Width() - aSpacing - nOuterSpaceX );
2056 aTmpPos.setY( aPos.Y() + ( pData->aSz.Height() - aTmpSz.Height() ) / 2 );
2057 aTmpPos.AdjustY(nExtra / 2 );
2059 tools::Rectangle aItemRect(aTmpPos, aTmpSz);
2060 MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
2061 bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::SubmenuArrow,
2062 aItemRect, nState, aVal, OUString());
2064 if (!bNativeOk)
2066 aTmpPos.setX( aOutSz.Width() - nFontHeight + nExtra - nOuterSpaceX );
2067 aTmpPos.setY( aPos.Y() );
2068 aTmpPos.AdjustY(nExtra/2 );
2069 aTmpPos.AdjustY((pData->aSz.Height() / 2) - (nFontHeight / 4) );
2070 if (pData->nBits & MenuItemBits::POPUPSELECT)
2072 rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
2073 Point aTmpPos2(aPos);
2074 aTmpPos2.setX( aOutSz.Width() - nFontHeight - nFontHeight/4 );
2075 aDecoView.DrawFrame(tools::Rectangle(aTmpPos2, Size(nFontHeight + nFontHeight / 4,
2076 pData->aSz.Height())),
2077 DrawFrameStyle::Group);
2079 aDecoView.DrawSymbol(tools::Rectangle(aTmpPos, Size(nFontHeight / 2, nFontHeight / 2)),
2080 SymbolType::SPIN_RIGHT, rRenderContext.GetTextColor(), nSymbolStyle);
2084 if (pThisItemOnly && bHighlighted)
2086 // This restores the normal menu or menu bar text
2087 // color for when it is no longer highlighted.
2088 if (IsMenuBar())
2089 rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
2090 else
2091 rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
2094 if( bLayout )
2096 if (!IsMenuBar())
2097 mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, Size(aOutSz.Width(), pData->aSz.Height()));
2098 else
2099 mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, pData->aSz);
2103 if (!IsMenuBar())
2104 aTopLeft.AdjustY(pData->aSz.Height() );
2105 else
2106 aTopLeft.AdjustX(pData->aSz.Width() );
2109 // draw "more" (">>") indicator if some items have been hidden as they go out of visible area
2110 if (bHiddenItems)
2112 sal_Int32 nSize = nFontHeight;
2113 tools::Rectangle aRectangle(Point(aOutSz.Width() - nSize, (aOutSz.Height() / 2) - (nSize / 2)), Size(nSize, nSize));
2114 lclDrawMoreIndicator(rRenderContext, aRectangle);
2118 Menu* Menu::ImplGetStartMenu()
2120 Menu* pStart = this;
2121 while ( pStart && pStart->pStartedFrom && ( pStart->pStartedFrom != pStart ) )
2122 pStart = pStart->pStartedFrom;
2123 return pStart;
2126 void Menu::ImplCallHighlight(sal_uInt16 nItem)
2128 ImplMenuDelData aDelData( this );
2130 nSelectedId = 0;
2131 sSelectedIdent.clear();
2132 MenuItemData* pData = pItemList->GetDataFromPos(nItem);
2133 if (pData)
2135 nSelectedId = pData->nId;
2136 sSelectedIdent = pData->sIdent;
2138 ImplCallEventListeners( VclEventId::MenuHighlight, GetItemPos( GetCurItemId() ) );
2140 if( !aDelData.isDeleted() )
2142 nSelectedId = 0;
2143 sSelectedIdent.clear();
2147 IMPL_LINK_NOARG(Menu, ImplCallSelect, void*, void)
2149 nEventId = nullptr;
2150 Select();
2153 Menu* Menu::ImplFindSelectMenu()
2155 Menu* pSelMenu = nEventId ? this : nullptr;
2157 for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
2159 MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
2161 if ( pData->pSubMenu )
2162 pSelMenu = pData->pSubMenu->ImplFindSelectMenu();
2165 return pSelMenu;
2168 Menu* Menu::ImplFindMenu( sal_uInt16 nItemId )
2170 Menu* pSelMenu = nullptr;
2172 for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
2174 MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
2176 if( pData->nId == nItemId )
2177 pSelMenu = this;
2178 else if ( pData->pSubMenu )
2179 pSelMenu = pData->pSubMenu->ImplFindMenu( nItemId );
2182 return pSelMenu;
2185 void Menu::RemoveDisabledEntries( bool bRemoveEmptyPopups )
2187 sal_uInt16 n = 0;
2188 while (n < GetItemCount())
2190 bool bRemove = false;
2191 MenuItemData* pItem = pItemList->GetDataFromPos( n );
2192 if ( pItem->eType == MenuItemType::SEPARATOR )
2194 if ( !n || ( GetItemType( n-1 ) == MenuItemType::SEPARATOR ) )
2195 bRemove = true;
2197 else
2198 bRemove = !pItem->bEnabled;
2200 if ( pItem->pSubMenu )
2202 pItem->pSubMenu->RemoveDisabledEntries();
2203 if ( bRemoveEmptyPopups && !pItem->pSubMenu->GetItemCount() )
2204 bRemove = true;
2207 if (bRemove)
2208 RemoveItem(n);
2209 else
2210 ++n;
2213 if ( GetItemCount() )
2215 sal_uInt16 nLast = GetItemCount() - 1;
2216 MenuItemData* pItem = pItemList->GetDataFromPos( nLast );
2217 if ( pItem->eType == MenuItemType::SEPARATOR )
2218 RemoveItem( nLast );
2220 mpLayoutData.reset();
2223 void Menu::UpdateNativeMenu()
2225 if ( ImplGetSalMenu() )
2226 ImplGetSalMenu()->Update();
2229 void Menu::MenuBarKeyInput(const KeyEvent&)
2233 void Menu::ImplKillLayoutData() const
2235 mpLayoutData.reset();
2238 void Menu::ImplFillLayoutData() const
2240 if (!(m_pWindow && m_pWindow->IsReallyVisible()))
2241 return;
2243 mpLayoutData.reset(new MenuLayoutData);
2244 if (IsMenuBar())
2246 ImplPaint(*m_pWindow->GetOutDev(), m_pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME
2248 else
2250 MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(m_pWindow.get());
2251 ImplPaint(*m_pWindow->GetOutDev(), m_pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(),
2252 nullptr, false, true); //FIXME
2256 tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, tools::Long nIndex ) const
2258 tools::Long nItemIndex = -1;
2259 if( ! mpLayoutData )
2260 ImplFillLayoutData();
2261 if( mpLayoutData )
2263 for( size_t i = 0; i < mpLayoutData->m_aLineItemIds.size(); i++ )
2265 if( mpLayoutData->m_aLineItemIds[i] == nItemID )
2267 nItemIndex = mpLayoutData->m_aLineIndices[i];
2268 break;
2272 return (mpLayoutData && nItemIndex != -1) ? mpLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle();
2275 tools::Long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const
2277 rItemID = 0;
2278 if( ! mpLayoutData )
2279 ImplFillLayoutData();
2280 if( mpLayoutData )
2282 // coverity[ tainted_data_return : FALSE ] version 2023.12.2
2283 tools::Long nIndex = mpLayoutData->GetIndexForPoint( rPoint );
2284 for( size_t i = 0; i < mpLayoutData->m_aLineIndices.size(); i++ )
2286 if( mpLayoutData->m_aLineIndices[i] <= nIndex &&
2287 (i == mpLayoutData->m_aLineIndices.size()-1 || mpLayoutData->m_aLineIndices[i+1] > nIndex) )
2289 rItemID = mpLayoutData->m_aLineItemIds[i];
2290 // return index relative to item
2291 return nIndex - mpLayoutData->m_aLineIndices[i];
2295 return -1;
2298 tools::Rectangle Menu::GetBoundingRectangle( sal_uInt16 nPos ) const
2300 tools::Rectangle aRet;
2302 if (!mpLayoutData )
2303 ImplFillLayoutData();
2304 if (mpLayoutData)
2306 std::map< sal_uInt16, tools::Rectangle >::const_iterator it = mpLayoutData->m_aVisibleItemBoundRects.find( nPos );
2307 if( it != mpLayoutData->m_aVisibleItemBoundRects.end() )
2308 aRet = it->second;
2310 return aRet;
2313 void Menu::SetAccessibleName( sal_uInt16 nItemId, const OUString& rStr )
2315 size_t nPos;
2316 MenuItemData* pData = pItemList->GetData( nItemId, nPos );
2318 if (pData && !rStr.equals(pData->aAccessibleName))
2320 pData->aAccessibleName = rStr;
2321 ImplCallEventListeners(VclEventId::MenuAccessibleNameChanged, nPos);
2325 OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const
2327 MenuItemData* pData = pItemList->GetData( nItemId );
2329 if ( pData )
2330 return pData->aAccessibleName;
2332 return OUString();
2335 void Menu::SetAccessibleDescription( sal_uInt16 nItemId, const OUString& rStr )
2337 MenuItemData* pData = pItemList->GetData( nItemId );
2339 if ( pData )
2340 pData->aAccessibleDescription = rStr;
2343 OUString Menu::GetAccessibleDescription( sal_uInt16 nItemId ) const
2345 MenuItemData* pData = pItemList->GetData( nItemId );
2347 if (pData && !pData->aAccessibleDescription.isEmpty())
2348 return pData->aAccessibleDescription;
2350 return GetHelpText(nItemId);
2353 void Menu::GetSystemMenuData(SystemMenuData& rData) const
2355 if (ImplGetSalMenu())
2357 ImplGetSalMenu()->GetSystemMenuData(rData);
2361 bool Menu::IsHighlighted( sal_uInt16 nItemPos ) const
2363 bool bRet = false;
2365 if (m_pWindow)
2367 if (IsMenuBar())
2368 bRet = ( nItemPos == static_cast< MenuBarWindow * > (m_pWindow.get())->GetHighlightedItem() );
2369 else
2370 bRet = ( nItemPos == static_cast< MenuFloatingWindow * > (m_pWindow.get())->GetHighlightedItem() );
2373 return bRet;
2376 void Menu::HighlightItem( sal_uInt16 nItemPos )
2378 if ( !m_pWindow )
2379 return;
2381 if (IsMenuBar())
2383 MenuBarWindow* pMenuWin = static_cast< MenuBarWindow* >( m_pWindow.get() );
2384 pMenuWin->SetAutoPopup( false );
2385 pMenuWin->ChangeHighlightItem( nItemPos, false );
2387 else
2389 static_cast< MenuFloatingWindow* >( m_pWindow.get() )->ChangeHighlightItem( nItemPos, false );
2393 void Menu::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) const
2395 rJsonWriter.put("id", "__MENU__"); // we have single instance of menu at the time per session
2396 rJsonWriter.put("type", "menu");
2397 rJsonWriter.put("count", GetItemCount());
2399 auto aEntries = rJsonWriter.startArray("entries");
2400 for (size_t i = 0; i < GetItemCount(); i++)
2402 auto aEntry = rJsonWriter.startStruct();
2403 sal_uInt16 nId = GetItemId(i);
2404 rJsonWriter.put("row", GetItemIdent(nId));
2406 auto aColumns = rJsonWriter.startArray("columns");
2407 auto aColumn = rJsonWriter.startStruct();
2408 rJsonWriter.put("text", GetItemText(nId));
2414 MenuBarWindow* MenuBar::getMenuBarWindow()
2416 // so far just a dynamic_cast, hopefully to be turned into something saner
2417 // at some stage
2418 MenuBarWindow *pWin = dynamic_cast<MenuBarWindow*>(m_pWindow.get());
2419 //either there is no window (fdo#87663) or it is a MenuBarWindow
2420 assert(!m_pWindow || pWin);
2421 return pWin;
2424 MenuBar::MenuBar()
2425 : mbCloseBtnVisible(false),
2426 mbFloatBtnVisible(false),
2427 mbHideBtnVisible(false),
2428 mbDisplayable(true)
2430 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
2433 MenuBar::MenuBar( const MenuBar& rMenu )
2434 : mbCloseBtnVisible(false),
2435 mbFloatBtnVisible(false),
2436 mbHideBtnVisible(false),
2437 mbDisplayable(true)
2439 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
2440 *this = rMenu;
2443 MenuBar::~MenuBar()
2445 disposeOnce();
2448 void MenuBar::dispose()
2450 ImplDestroy( this, true );
2451 Menu::dispose();
2454 void MenuBar::ClosePopup(PopupMenu *pPopupMenu)
2456 MenuBarWindow* pMenuWin = getMenuBarWindow();
2457 if (!pMenuWin)
2458 return;
2459 pMenuWin->PopupClosed(pPopupMenu);
2462 void MenuBar::MenuBarKeyInput(const KeyEvent& rEvent)
2464 m_pWindow->KeyInput(rEvent);
2467 void MenuBar::ShowCloseButton(bool bShow)
2469 ShowButtons( bShow, mbFloatBtnVisible, mbHideBtnVisible );
2472 void MenuBar::ShowButtons( bool bClose, bool bFloat, bool bHide )
2474 if ((bClose != mbCloseBtnVisible) ||
2475 (bFloat != mbFloatBtnVisible) ||
2476 (bHide != mbHideBtnVisible))
2478 mbCloseBtnVisible = bClose;
2479 mbFloatBtnVisible = bFloat;
2480 mbHideBtnVisible = bHide;
2481 MenuBarWindow* pMenuWin = getMenuBarWindow();
2482 if (pMenuWin)
2483 pMenuWin->ShowButtons(bClose, bFloat, bHide);
2487 void MenuBar::LayoutChanged()
2489 MenuBarWindow* pMenuWin = getMenuBarWindow();
2490 if (pMenuWin)
2491 pMenuWin->LayoutChanged();
2494 void MenuBar::SetDisplayable( bool bDisplayable )
2496 if( bDisplayable != mbDisplayable )
2498 if ( ImplGetSalMenu() )
2499 ImplGetSalMenu()->ShowMenuBar( bDisplayable );
2501 mbDisplayable = bDisplayable;
2502 LayoutChanged();
2506 VclPtr<MenuBarWindow> MenuBar::ImplCreate(vcl::Window* pParent, MenuBarWindow* pWindow, MenuBar* pMenu)
2508 VclPtr<MenuBarWindow> pMenuBarWindow = pWindow;
2509 if (!pMenuBarWindow)
2511 pMenuBarWindow = VclPtr<MenuBarWindow>::Create(pParent);
2514 pMenu->pStartedFrom = nullptr;
2515 pMenu->m_pWindow = pMenuBarWindow;
2516 pMenuBarWindow->SetMenu(pMenu);
2517 tools::Long nHeight = pMenu->ImplCalcSize(pMenuBarWindow).Height();
2519 // depending on the native implementation or the displayable flag
2520 // the menubar windows is suppressed (ie, height=0)
2521 if (!pMenu->IsDisplayable() || (pMenu->ImplGetSalMenu() && pMenu->ImplGetSalMenu()->VisibleMenuBar()))
2523 nHeight = 0;
2526 pMenuBarWindow->SetHeight(nHeight);
2527 return pMenuBarWindow;
2530 void MenuBar::ImplDestroy( MenuBar* pMenu, bool bDelete )
2532 MenuBarWindow* pMenuWin = pMenu->getMenuBarWindow();
2533 if (pMenuWin && bDelete)
2535 pMenuWin->KillActivePopup();
2536 pMenuWin->disposeOnce();
2538 pMenu->m_pWindow = nullptr;
2539 if (pMenu->mpSalMenu) {
2540 pMenu->mpSalMenu->ShowMenuBar(false);
2544 bool MenuBar::ImplHandleKeyEvent( const KeyEvent& rKEvent )
2546 // No keyboard processing when our menubar is invisible
2547 if (!IsDisplayable())
2548 return false;
2550 // No keyboard processing when system handles the menu.
2551 SalMenu *pNativeMenu = ImplGetSalMenu();
2552 if (pNativeMenu && pNativeMenu->VisibleMenuBar())
2554 // Except when the event is the F6 cycle pane event and we can put our
2555 // focus into it (i.e. the gtk3 menubar case but not the mac/unity case
2556 // where it's not part of the application window)
2557 if (!TaskPaneList::IsCycleKey(rKEvent.GetKeyCode()))
2558 return false;
2559 if (!pNativeMenu->CanGetFocus())
2560 return false;
2563 bool bDone = false;
2564 // check for enabled, if this method is called from another window...
2565 vcl::Window* pWin = GetWindow();
2566 if (pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode())
2568 MenuBarWindow* pMenuWin = getMenuBarWindow();
2569 bDone = pMenuWin && pMenuWin->HandleKeyEvent(rKEvent, false/*bFromMenu*/);
2571 return bDone;
2574 bool MenuBar::ImplHandleCmdEvent( const CommandEvent& rCEvent )
2576 // No keyboard processing when system handles the menu or our menubar is invisible
2577 if( !IsDisplayable() ||
2578 ( ImplGetSalMenu() && ImplGetSalMenu()->VisibleMenuBar() ) )
2579 return false;
2581 // check for enabled, if this method is called from another window...
2582 MenuBarWindow* pWin = static_cast<MenuBarWindow*>(GetWindow());
2583 if ( pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && ! pWin->IsInModalMode() )
2585 if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel)
2587 const CommandModKeyData* pCData = rCEvent.GetModKeyData ();
2588 if (pWin->m_nHighlightedItem == ITEMPOS_INVALID)
2590 if (pCData && pCData->IsMod2() && pCData->IsDown())
2591 pWin->SetMBWHideAccel(false);
2592 pWin->Invalidate(InvalidateFlags::Update);
2594 return true;
2597 return false;
2600 void MenuBar::SelectItem(sal_uInt16 nId)
2602 if (!m_pWindow)
2603 return;
2605 m_pWindow->GrabFocus();
2606 nId = GetItemPos( nId );
2608 MenuBarWindow* pMenuWin = getMenuBarWindow();
2609 if (pMenuWin)
2611 // #99705# popup the selected menu
2612 pMenuWin->SetAutoPopup( true );
2613 if (ITEMPOS_INVALID != pMenuWin->GetHighlightedItem())
2615 pMenuWin->KillActivePopup();
2616 pMenuWin->ChangeHighlightItem( ITEMPOS_INVALID, false );
2618 if (nId != ITEMPOS_INVALID)
2619 pMenuWin->ChangeHighlightItem( nId, false );
2623 // handler for native menu selection and command events
2624 bool Menu::HandleMenuActivateEvent( Menu *pMenu ) const
2626 if( pMenu )
2628 ImplMenuDelData aDelData( this );
2630 pMenu->pStartedFrom = const_cast<Menu*>(this);
2631 pMenu->bInCallback = true;
2632 pMenu->Activate();
2634 if( !aDelData.isDeleted() )
2635 pMenu->bInCallback = false;
2637 return true;
2640 bool Menu::HandleMenuDeActivateEvent( Menu *pMenu ) const
2642 if( pMenu )
2644 ImplMenuDelData aDelData( this );
2646 pMenu->pStartedFrom = const_cast<Menu*>(this);
2647 pMenu->bInCallback = true;
2648 pMenu->Deactivate();
2649 if( !aDelData.isDeleted() )
2650 pMenu->bInCallback = false;
2652 return true;
2655 bool MenuBar::HandleMenuHighlightEvent( Menu *pMenu, sal_uInt16 nHighlightEventId ) const
2657 if( !pMenu )
2658 pMenu = const_cast<MenuBar*>(this)->ImplFindMenu(nHighlightEventId);
2659 if( pMenu )
2661 ImplMenuDelData aDelData( pMenu );
2663 if( mnHighlightedItemPos != ITEMPOS_INVALID )
2664 pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, mnHighlightedItemPos );
2666 if( !aDelData.isDeleted() )
2668 pMenu->mnHighlightedItemPos = pMenu->GetItemPos( nHighlightEventId );
2669 pMenu->nSelectedId = nHighlightEventId;
2670 pMenu->sSelectedIdent = pMenu->GetItemIdent( nHighlightEventId );
2671 pMenu->pStartedFrom = const_cast<MenuBar*>(this);
2672 pMenu->ImplCallHighlight( pMenu->mnHighlightedItemPos );
2674 return true;
2676 else
2677 return false;
2680 bool Menu::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const
2682 if( !pMenu )
2683 pMenu = const_cast<Menu*>(this)->ImplFindMenu(nCommandEventId);
2684 if( pMenu )
2686 pMenu->nSelectedId = nCommandEventId;
2687 pMenu->sSelectedIdent = pMenu->GetItemIdent(nCommandEventId);
2688 pMenu->pStartedFrom = const_cast<Menu*>(this);
2689 pMenu->ImplSelect();
2690 return true;
2692 else
2693 return false;
2696 sal_uInt16 MenuBar::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip )
2698 MenuBarWindow* pMenuWin = getMenuBarWindow();
2699 return pMenuWin ? pMenuWin->AddMenuBarButton(i_rImage, i_rLink, i_rToolTip) : 0;
2702 void MenuBar::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink )
2704 MenuBarWindow* pMenuWin = getMenuBarWindow();
2705 if (!pMenuWin)
2706 return;
2707 pMenuWin->SetMenuBarButtonHighlightHdl(nId, rLink);
2710 void MenuBar::RemoveMenuBarButton( sal_uInt16 nId )
2712 MenuBarWindow* pMenuWin = getMenuBarWindow();
2713 if (!pMenuWin)
2714 return;
2715 pMenuWin->RemoveMenuBarButton(nId);
2718 tools::Rectangle MenuBar::GetMenuBarButtonRectPixel( sal_uInt16 nId )
2720 MenuBarWindow* pMenuWin = getMenuBarWindow();
2721 return pMenuWin ? pMenuWin->GetMenuBarButtonRectPixel(nId) : tools::Rectangle();
2724 bool MenuBar::HandleMenuButtonEvent( sal_uInt16 i_nButtonId )
2726 MenuBarWindow* pMenuWin = getMenuBarWindow();
2727 return pMenuWin && pMenuWin->HandleMenuButtonEvent(i_nButtonId);
2730 int MenuBar::GetMenuBarHeight() const
2732 MenuBar* pMenuBar = const_cast<MenuBar*>(this);
2733 const SalMenu *pNativeMenu = pMenuBar->ImplGetSalMenu();
2734 int nMenubarHeight;
2735 if (pNativeMenu)
2736 nMenubarHeight = pNativeMenu->GetMenuBarHeight();
2737 else
2739 vcl::Window* pMenubarWin = GetWindow();
2740 nMenubarHeight = pMenubarWin ? pMenubarWin->GetOutputSizePixel().Height() : 0;
2742 return nMenubarHeight;
2745 MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const {
2746 return static_cast<MenuFloatingWindow*>(GetWindow());
2749 PopupMenu::PopupMenu()
2751 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
2754 PopupMenu::PopupMenu( const PopupMenu& rMenu )
2756 mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
2757 *this = rMenu;
2760 PopupMenu::~PopupMenu()
2762 disposeOnce();
2765 void PopupMenu::ClosePopup(PopupMenu* pPopupMenu)
2767 MenuFloatingWindow* p = ImplGetFloatingWindow();
2768 if (p && pPopupMenu && p->GetActivePopup() == pPopupMenu)
2769 p->KillActivePopup();
2772 namespace vcl
2774 bool IsInPopupMenuExecute()
2776 return PopupMenu::GetActivePopupMenu() != nullptr;
2780 PopupMenu* PopupMenu::GetActivePopupMenu()
2782 ImplSVData* pSVData = ImplGetSVData();
2783 return pSVData->maAppData.mpActivePopupMenu;
2786 void PopupMenu::EndExecute()
2788 if (GetWindow())
2789 ImplGetFloatingWindow()->EndExecute( 0 );
2792 void PopupMenu::SelectItem(sal_uInt16 nId)
2794 if (!GetWindow())
2795 return;
2797 if( nId != ITEMPOS_INVALID )
2799 size_t nPos = 0;
2800 MenuItemData* pData = GetItemList()->GetData( nId, nPos );
2801 if (pData && pData->pSubMenu)
2802 ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true );
2803 else
2804 ImplGetFloatingWindow()->EndExecute( nId );
2806 else
2808 MenuFloatingWindow* pFloat = ImplGetFloatingWindow();
2809 pFloat->GrabFocus();
2811 for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ )
2813 MenuItemData* pData = GetItemList()->GetDataFromPos( nPos );
2814 if( pData->pSubMenu )
2816 pFloat->KillActivePopup();
2819 pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false );
2823 void PopupMenu::SetSelectedEntry( sal_uInt16 nId )
2825 nSelectedId = nId;
2826 sSelectedIdent = GetItemIdent(nId);
2829 sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos )
2831 return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown );
2834 static FloatWinPopupFlags lcl_TranslateFlags(PopupMenuFlags nFlags)
2836 FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE;
2837 if ( nFlags & PopupMenuFlags::ExecuteDown )
2838 nPopupModeFlags = FloatWinPopupFlags::Down;
2839 else if ( nFlags & PopupMenuFlags::ExecuteUp )
2840 nPopupModeFlags = FloatWinPopupFlags::Up;
2841 else if ( nFlags & PopupMenuFlags::ExecuteRight )
2842 nPopupModeFlags = FloatWinPopupFlags::Right;
2843 else
2844 nPopupModeFlags = FloatWinPopupFlags::Down;
2846 if (nFlags & PopupMenuFlags::NoMouseUpClose ) // allow popup menus to stay open on mouse button up
2847 nPopupModeFlags |= FloatWinPopupFlags::NoMouseUpClose; // useful if the menu was opened on mousebutton down (eg toolbox configuration)
2849 return nPopupModeFlags;
2852 sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags )
2854 ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 );
2855 return ImplExecute( pExecWindow, rRect, lcl_TranslateFlags(nFlags), nullptr, false );
2858 void PopupMenu::ImplFlushPendingSelect()
2860 // is there still Select?
2861 Menu* pSelect = ImplFindSelectMenu();
2862 if (pSelect)
2864 // Select should be called prior to leaving execute in a popup menu!
2865 Application::RemoveUserEvent( pSelect->nEventId );
2866 pSelect->nEventId = nullptr;
2867 pSelect->Select();
2871 sal_uInt16 PopupMenu::ImplExecute(const VclPtr<vcl::Window>& pParentWin, const tools::Rectangle& rRect,
2872 FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst)
2874 // tdf#126054 hold this until after function completes
2875 VclPtr<PopupMenu> xThis(this);
2877 bool bRealExecute = false;
2878 const sal_uInt16 nItemCount = GetItemCount();
2879 if (!pSFrom && (vcl::IsInPopupMenuExecute() || !nItemCount))
2880 return 0;
2882 mpLayoutData.reset();
2884 ImplSVData* pSVData = ImplGetSVData();
2886 pStartedFrom = pSFrom;
2887 nSelectedId = 0;
2888 sSelectedIdent.clear();
2889 bCanceled = false;
2891 VclPtr<vcl::Window> xFocusId;
2892 if ( !pStartedFrom )
2894 pSVData->mpWinData->mbNoDeactivate = true;
2895 xFocusId = Window::SaveFocus();
2896 bRealExecute = true;
2898 else
2900 // assure that only one menu is open at a time
2901 if (pStartedFrom->IsMenuBar() && pSVData->mpWinData->mpFirstFloat)
2902 pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
2903 | FloatWinPopupEndFlags::CloseAll);
2906 tools::Rectangle aRect(rRect);
2907 aRect.SetPos(pParentWin->OutputToScreenPixel(aRect.TopLeft()));
2909 nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose | FloatWinPopupFlags::GrabFocus;
2910 if (bRealExecute)
2911 nPopupModeFlags |= FloatWinPopupFlags::NewLevel;
2913 // MenuFlags get clobbered in the Activate function. Restore them after calling.
2914 MenuFlags nMenuFlagsSaved = GetMenuFlags();
2915 bInCallback = true; // set it here, if Activate overridden
2916 Activate();
2917 bInCallback = false;
2918 SetMenuFlags(nMenuFlagsSaved);
2920 if (pParentWin->isDisposed())
2921 return 0;
2923 if ( bCanceled || bKilled )
2924 return 0;
2926 if (!nItemCount)
2927 return 0;
2929 // The flag MenuFlags::HideDisabledEntries is inherited.
2930 if ( pSFrom )
2932 if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries )
2933 nMenuFlags |= MenuFlags::HideDisabledEntries;
2934 else
2935 nMenuFlags &= ~MenuFlags::HideDisabledEntries;
2938 sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount();
2939 if ( !nVisibleEntries )
2941 OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
2943 MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, {});
2944 size_t nPos = 0;
2945 pData = pItemList->GetData( pData->nId, nPos );
2946 assert(pData);
2947 if (pData)
2949 pData->bIsTemporary = true;
2951 ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos);
2954 VclPtr<MenuFloatingWindow> pWin = VclPtrInstance<MenuFloatingWindow>(this, pParentWin, WB_BORDER | WB_SYSTEMWINDOW);
2955 if (comphelper::LibreOfficeKit::isActive() && get_id() == "editviewspellmenu")
2957 VclPtr<vcl::Window> xNotifierParent = pParentWin->GetParentWithLOKNotifier();
2958 assert(xNotifierParent && xNotifierParent->GetLOKNotifier() && "editview menu without LOKNotifier");
2959 pWin->SetLOKNotifier(xNotifierParent->GetLOKNotifier());
2962 if( pSVData->maNWFData.mbFlatMenu )
2963 pWin->SetBorderStyle( WindowBorderStyle::NOBORDER );
2964 else
2965 pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU );
2967 m_pWindow.disposeAndClear();
2968 m_pWindow = pWin;
2970 Size aSz = ImplCalcSize( pWin );
2972 AbsoluteScreenPixelRectangle aDesktopRect(pWin->GetDesktopRectPixel());
2973 if( Application::GetScreenCount() > 1 )
2975 vcl::Window* pDeskW = m_pWindow->GetWindow( GetWindowType::RealParent );
2976 if( ! pDeskW )
2977 pDeskW = m_pWindow;
2978 AbsoluteScreenPixelPoint aDesktopTL(pDeskW->OutputToAbsoluteScreenPixel(aRect.TopLeft()));
2979 aDesktopRect = Application::GetScreenPosSizePixel(
2980 Application::GetBestScreen(AbsoluteScreenPixelRectangle(aDesktopTL, aRect.GetSize())));
2983 tools::Long nMaxHeight = aDesktopRect.GetHeight();
2985 //rhbz#1021915. If a menu won't fit in the desired location the default
2986 //mode is to place it somewhere it will fit. e.g. above, left, right. For
2987 //some cases, e.g. menubars, it's desirable to limit the options to
2988 //above/below and force the menu to scroll if it won't fit
2989 if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement)
2991 vcl::Window* pRef = pWin;
2992 if ( pRef->GetParent() )
2993 pRef = pRef->GetParent();
2995 AbsoluteScreenPixelRectangle devRect(pRef->OutputToAbsoluteScreenPixel(aRect.TopLeft()),
2996 pRef->OutputToAbsoluteScreenPixel(aRect.BottomRight()));
2998 tools::Long nHeightAbove = devRect.Top() - aDesktopRect.Top();
2999 tools::Long nHeightBelow = aDesktopRect.Bottom() - devRect.Bottom();
3000 nMaxHeight = std::min(nMaxHeight, std::max(nHeightAbove, nHeightBelow));
3003 // In certain cases this might be misdetected with a height of 0, leading to menus not being displayed.
3004 // So assume that the available screen size matches at least the system requirements. With menu origin
3005 // in the middle, nMaxHeight will be at least half of screen height.
3006 SAL_WARN_IF(nMaxHeight < 768 / 2, "vcl",
3007 "Available height misdetected as " << nMaxHeight
3008 << "px. Setting to 384px instead.");
3009 nMaxHeight = std::max(nMaxHeight, tools::Long(768 / 2));
3011 if (pStartedFrom && pStartedFrom->IsMenuBar())
3012 nMaxHeight -= pParentWin->GetSizePixel().Height();
3013 sal_Int32 nLeft, nTop, nRight, nBottom;
3014 m_pWindow->GetBorder( nLeft, nTop, nRight, nBottom );
3015 nMaxHeight -= nTop+nBottom;
3016 if ( aSz.Height() > nMaxHeight )
3018 pWin->EnableScrollMenu( true );
3019 sal_uInt16 nStart = ImplGetFirstVisible();
3020 sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart );
3021 aSz.setHeight( ImplCalcHeight( nEntries ) );
3024 pWin->SetFocusId( xFocusId );
3025 pWin->SetOutputSizePixel( aSz );
3027 const bool bNative = Run(pWin, bRealExecute, bPreSelectFirst, nPopupModeFlags, pSFrom, aRect);
3028 FinishRun(pWin, pParentWin, bRealExecute, bNative);
3029 return nSelectedId;
3032 bool PopupMenu::Run(const VclPtr<MenuFloatingWindow>& pWin, const bool bRealExecute, const bool bPreSelectFirst,
3033 const FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, const tools::Rectangle& rRect)
3035 SalMenu* pMenu = ImplGetSalMenu();
3036 if (pMenu && bRealExecute && pMenu->ShowNativePopupMenu(pWin, rRect, nPopupModeFlags))
3037 return true;
3039 pWin->StartPopupMode(rRect, nPopupModeFlags);
3040 if (pSFrom)
3042 sal_uInt16 aPos;
3043 if (pSFrom->IsMenuBar())
3044 aPos = static_cast<MenuBarWindow *>(pSFrom->m_pWindow.get())->GetHighlightedItem();
3045 else
3046 aPos = static_cast<MenuFloatingWindow *>(pSFrom->m_pWindow.get())->GetHighlightedItem();
3048 pWin->SetPosInParent(aPos); // store position to be sent in SUBMENUDEACTIVATE
3049 pSFrom->ImplCallEventListeners(VclEventId::MenuSubmenuActivate, aPos);
3052 if ( bPreSelectFirst )
3054 for (size_t n = 0; n < static_cast<size_t>(GetItemCount()); n++)
3056 MenuItemData* pData = pItemList->GetDataFromPos( n );
3057 if ( ( pData->bEnabled
3058 || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus()
3060 && ( pData->eType != MenuItemType::SEPARATOR )
3061 && ImplIsVisible( n )
3062 && ImplIsSelectable( n )
3065 pWin->ChangeHighlightItem(n, false);
3066 break;
3071 if (bRealExecute)
3072 pWin->Execute();
3074 return false;
3077 void PopupMenu::FinishRun(const VclPtr<MenuFloatingWindow>& pWin, const VclPtr<vcl::Window>& pParentWin, const bool bRealExecute, const bool bIsNativeMenu)
3079 if (!bRealExecute || pWin->isDisposed())
3080 return;
3082 if (!bIsNativeMenu)
3084 VclPtr<vcl::Window> xFocusId = pWin->GetFocusId();
3085 assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End");
3086 pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId);
3088 if (nSelectedId) // then clean up .. ( otherwise done by TH )
3090 PopupMenu* pSub = pWin->GetActivePopup();
3091 while ( pSub )
3093 pSub->ImplGetFloatingWindow()->EndPopupMode();
3094 pSub = pSub->ImplGetFloatingWindow()->GetActivePopup();
3098 else
3099 pWin->StopExecute();
3101 pWin->doShutdown();
3102 ImplClosePopupToolBox(pParentWin);
3103 ImplFlushPendingSelect();
3106 sal_uInt16 PopupMenu::ImplCalcVisEntries( tools::Long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const
3108 nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight();
3110 tools::Long nHeight = 0;
3111 size_t nEntries = pItemList->size();
3112 sal_uInt16 nVisEntries = 0;
3114 if ( pLastVisible )
3115 *pLastVisible = 0;
3117 for ( size_t n = nStartEntry; n < nEntries; n++ )
3119 if ( ImplIsVisible( n ) )
3121 MenuItemData* pData = pItemList->GetDataFromPos( n );
3122 nHeight += pData->aSz.Height();
3123 if ( nHeight > nMaxHeight )
3124 break;
3126 if ( pLastVisible )
3127 *pLastVisible = n;
3128 nVisEntries++;
3131 return nVisEntries;
3134 tools::Long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const
3136 tools::Long nHeight = 0;
3138 sal_uInt16 nFound = 0;
3139 for ( size_t n = 0; ( nFound < nEntries ) && ( n < pItemList->size() ); n++ )
3141 if ( ImplIsVisible( static_cast<sal_uInt16>(n) ) )
3143 MenuItemData* pData = pItemList->GetDataFromPos( n );
3144 nHeight += pData->aSz.Height();
3145 nFound++;
3149 nHeight += 2*ImplGetFloatingWindow()->GetScrollerHeight();
3151 return nHeight;
3154 css::uno::Reference<css::awt::XPopupMenu> PopupMenu::CreateMenuInterface()
3156 UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
3157 if ( pWrapper )
3158 return pWrapper->CreateMenuInterface(this);
3159 return nullptr;
3162 ImplMenuDelData::ImplMenuDelData( const Menu* pMenu )
3163 : mpNext( nullptr )
3164 , mpMenu( nullptr )
3166 if( pMenu )
3167 const_cast< Menu* >( pMenu )->ImplAddDel( *this );
3170 ImplMenuDelData::~ImplMenuDelData()
3172 if( mpMenu )
3173 const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this );
3176 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */