xref: /core/vcl/source/window/menu.cxx (revision 72e538b9bdcfc193ccef8c134bd3f4d79bdcc14c)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <accessibility/accessiblemenubasecomponent.hxx>
21 #include <accessibility/vclxaccessiblemenubar.hxx>
22 #include <accessibility/vclxaccessiblepopupmenu.hxx>
23 
24 #include <comphelper/diagnose_ex.hxx>
25 #include <sal/log.hxx>
26 
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>
41 
42 #include <salinst.hxx>
43 #include <svdata.hxx>
44 #include <strings.hrc>
45 #include <window.h>
46 #include <salmenu.hxx>
47 #include <salframe.hxx>
48 
49 #include "menubarwindow.hxx"
50 #include "menufloatingwindow.hxx"
51 #include "menuitemlist.hxx"
52 
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>
58 
59 #include <map>
60 #include <string_view>
61 #include <vector>
62 
63 #include <officecfg/Office/Common.hxx>
64 #include <officecfg/VCL.hxx>
65 
66 namespace vcl
67 {
68 
69 struct MenuLayoutData : public ControlLayoutData
70 {
71     std::vector< sal_uInt16 >               m_aLineItemIds;
72     std::map< sal_uInt16, tools::Rectangle >       m_aVisibleItemBoundRects;
73 };
74 
75 }
76 
77 using namespace vcl;
78 
79 constexpr auto EXTRAITEMHEIGHT = 4;
80 constexpr auto SPACE_AROUND_TITLE = 4;
81 
ImplSetMenuItemData(MenuItemData * pData)82 static void ImplSetMenuItemData( MenuItemData* pData )
83 {
84     // convert data
85     if ( !pData->aImage )
86         pData->eType = MenuItemType::STRING;
87     else if ( pData->aText.isEmpty() )
88         pData->eType = MenuItemType::IMAGE;
89     else
90         pData->eType = MenuItemType::STRINGIMAGE;
91 }
92 
93 namespace {
94 
ImplClosePopupToolBox(const VclPtr<vcl::Window> & pWin)95 void ImplClosePopupToolBox( const VclPtr<vcl::Window>& pWin )
96 {
97     if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin ) )
98     {
99         SystemWindow* pFloatingWindow = ImplGetDockingManager()->GetFloatingWindow(pWin);
100         if (pFloatingWindow)
101             static_cast<FloatingWindow*>(pFloatingWindow)->EndPopupMode( FloatWinPopupEndFlags::CloseAll );
102     }
103 }
104 
105 // TODO: Move to common code with the same function in toolbox
106 // Draw the ">>" - more indicator at the coordinates
lclDrawMoreIndicator(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)107 void lclDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
108 {
109     auto popIt = rRenderContext.ScopedPush(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
110     rRenderContext.SetLineColor();
111 
112     if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark())
113         rRenderContext.SetFillColor(COL_WHITE);
114     else
115         rRenderContext.SetFillColor(COL_BLACK);
116     float fScaleFactor = rRenderContext.GetDPIScaleFactor();
117 
118     int linewidth = 1 * fScaleFactor;
119     int space = 4 * fScaleFactor;
120 
121     tools::Long width = 8 * fScaleFactor;
122     tools::Long height = 5 * fScaleFactor;
123 
124     //Keep odd b/c drawing code works better
125     if ( height % 2 == 0 )
126         height--;
127 
128     tools::Long heightOrig = height;
129 
130     tools::Long x = rRect.Left() + (rRect.getOpenWidth() - width)/2 + 1;
131     tools::Long y = rRect.Top() + (rRect.getOpenHeight() - height)/2 + 1;
132     while( height >= 1)
133     {
134         rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
135         x += space;
136         rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) );
137         x -= space;
138         y++;
139         if( height <= heightOrig / 2 + 1) x--;
140         else            x++;
141         height--;
142     }
143 }
144 
145 } // end anonymous namespace
146 
147 
Menu()148 Menu::Menu()
149     : mpFirstDel(nullptr),
150       pItemList(new MenuItemList),
151       pStartedFrom(nullptr),
152       m_pWindow(nullptr),
153       nTitleHeight(0),
154       nEventId(nullptr),
155       mnHighlightedItemPos(ITEMPOS_INVALID),
156       nMenuFlags(MenuFlags::NONE),
157       nSelectedId(0),
158       nImgOrChkPos(0),
159       nTextPos(0),
160       bCanceled(false),
161       bInCallback(false),
162       bKilled(false)
163 {
164 }
165 
~Menu()166 Menu::~Menu()
167 {
168     disposeOnce();
169 }
170 
dispose()171 void Menu::dispose()
172 {
173     ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID );
174 
175     m_pWindow.disposeAndClear();
176 
177     if (mpAccessible.is())
178         mpAccessible->dispose();
179     mpAccessible.clear();
180 
181     if ( nEventId )
182         Application::RemoveUserEvent( nEventId );
183 
184     // Notify deletion of this menu
185     ImplMenuDelData* pDelData = mpFirstDel;
186     while ( pDelData )
187     {
188         pDelData->mpMenu = nullptr;
189         pDelData = pDelData->mpNext;
190     }
191 
192     bKilled = true;
193 
194     // tdf#140225 when clearing pItemList, keep SalMenu in sync with
195     // their removal during menu teardown
196     for (size_t n = pItemList->size(); n;)
197     {
198         --n;
199         if (mpSalMenu)
200             mpSalMenu->RemoveItem(n);
201         pItemList->Remove(n);
202     }
203 
204     assert(!pItemList->size());
205 
206     mpLayoutData.reset();
207 
208     // Native-support: destroy SalMenu
209     mpSalMenu.reset();
210 
211     pStartedFrom.reset();
212     m_pWindow.reset();
213     VclReferenceBase::dispose();
214 }
215 
CreateAutoMnemonics()216 void Menu::CreateAutoMnemonics()
217 {
218     MnemonicGenerator aMnemonicGenerator;
219     size_t n;
220     for ( n = 0; n < pItemList->size(); n++ )
221     {
222         MenuItemData* pData = pItemList->GetDataFromPos( n );
223         if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
224             aMnemonicGenerator.RegisterMnemonic( pData->aText );
225     }
226     for ( n = 0; n < pItemList->size(); n++ )
227     {
228         MenuItemData* pData = pItemList->GetDataFromPos( n );
229         if ( ! (pData->nBits & MenuItemBits::NOSELECT ) )
230             pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText );
231     }
232 }
233 
Activate()234 void Menu::Activate()
235 {
236     bInCallback = true;
237 
238     ImplMenuDelData aDelData( this );
239 
240     ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID );
241 
242     if( !aDelData.isDeleted() )
243     {
244         if ( !aActivateHdl.Call( this ) )
245         {
246             if( !aDelData.isDeleted() )
247             {
248                 Menu* pStartMenu = ImplGetStartMenu();
249                 if ( pStartMenu && ( pStartMenu != this ) )
250                 {
251                     pStartMenu->bInCallback = true;
252                     // MT 11/01: Call EventListener here? I don't know...
253                     pStartMenu->aActivateHdl.Call( this );
254                     pStartMenu->bInCallback = false;
255                 }
256             }
257         }
258         bInCallback = false;
259     }
260 
261     if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics))
262         CreateAutoMnemonics();
263 }
264 
Deactivate()265 void Menu::Deactivate()
266 {
267     for ( size_t n = pItemList->size(); n; )
268     {
269         MenuItemData* pData = pItemList->GetDataFromPos( --n );
270         if ( pData->bIsTemporary )
271         {
272             if ( ImplGetSalMenu() )
273                 ImplGetSalMenu()->RemoveItem( n );
274 
275             pItemList->Remove( n );
276         }
277     }
278 
279     bInCallback = true;
280 
281     ImplMenuDelData aDelData( this );
282 
283     Menu* pStartMenu = ImplGetStartMenu();
284     ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID );
285 
286     if( !aDelData.isDeleted() )
287     {
288         if ( !aDeactivateHdl.Call( this ) )
289         {
290             if( !aDelData.isDeleted() )
291             {
292                 if ( pStartMenu && ( pStartMenu != this ) )
293                 {
294                     pStartMenu->bInCallback = true;
295                     pStartMenu->aDeactivateHdl.Call( this );
296                     pStartMenu->bInCallback = false;
297                 }
298             }
299         }
300     }
301 
302     if( !aDelData.isDeleted() )
303     {
304         bInCallback = false;
305     }
306 }
307 
ImplSelect()308 void Menu::ImplSelect()
309 {
310     MenuItemData* pData = GetItemList()->GetData( nSelectedId );
311     if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) )
312     {
313         bool bChecked = IsItemChecked( nSelectedId );
314         if ( pData->nBits & MenuItemBits::RADIOCHECK )
315         {
316             if ( !bChecked )
317                 CheckItem( nSelectedId );
318         }
319         else
320             CheckItem( nSelectedId, !bChecked );
321     }
322 
323     // call select
324     ImplSVData* pSVData = ImplGetSVData();
325     pSVData->maAppData.mpActivePopupMenu = nullptr;        // if new execute in select()
326     nEventId = Application::PostUserEvent( LINK( this, Menu, ImplCallSelect ) );
327 }
328 
Select()329 void Menu::Select()
330 {
331     ImplMenuDelData aDelData( this );
332 
333     ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) );
334     if (aDelData.isDeleted())
335         return;
336     if (aSelectHdl.Call(this))
337         return;
338     if (aDelData.isDeleted())
339         return;
340     Menu* pStartMenu = ImplGetStartMenu();
341     if (!pStartMenu || (pStartMenu == this))
342         return;
343     pStartMenu->nSelectedId = nSelectedId;
344     pStartMenu->sSelectedIdent = sSelectedIdent;
345     pStartMenu->aSelectHdl.Call( this );
346 }
347 
348 #if defined(MACOSX)
ImplSelectWithStart(Menu * pSMenu)349 void Menu::ImplSelectWithStart( Menu* pSMenu )
350 {
351     auto pOldStartedFrom = pStartedFrom;
352     pStartedFrom = pSMenu;
353     auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>();
354     Select();
355     if( pOldStartedFrom )
356         pOldStartedFrom->pStartedFrom = pOldStartedStarted;
357     pStartedFrom = pOldStartedFrom;
358 }
359 #endif
360 
ImplCallEventListeners(VclEventId nEvent,sal_uInt16 nPos)361 void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos )
362 {
363     ImplMenuDelData aDelData( this );
364 
365     VclMenuEvent aEvent( this, nEvent, nPos );
366 
367     // This is needed by atk accessibility bridge
368     if ( nEvent == VclEventId::MenuHighlight )
369     {
370         Application::ImplCallEventListeners( aEvent );
371     }
372 
373     if ( !aDelData.isDeleted() )
374     {
375         // Copy the list, because this can be destroyed when calling a Link...
376         std::list<Link<VclMenuEvent&,void>> aCopy( maEventListeners );
377         for ( const auto& rLink : aCopy )
378         {
379             if( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() )
380                 rLink.Call( aEvent );
381         }
382     }
383 }
384 
AddEventListener(const Link<VclMenuEvent &,void> & rEventListener)385 void Menu::AddEventListener( const Link<VclMenuEvent&,void>& rEventListener )
386 {
387     maEventListeners.push_back( rEventListener );
388 }
389 
RemoveEventListener(const Link<VclMenuEvent &,void> & rEventListener)390 void Menu::RemoveEventListener( const Link<VclMenuEvent&,void>& rEventListener )
391 {
392     maEventListeners.remove( rEventListener );
393 }
394 
NbcInsertItem(sal_uInt16 nId,MenuItemBits nBits,const OUString & rStr,Menu * pMenu,size_t nPos,const OUString & rIdent)395 MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits,
396                                   const OUString& rStr, Menu* pMenu,
397                                   size_t nPos, const OUString &rIdent)
398 {
399     // put Item in MenuItemList
400     MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING,
401                              nBits, rStr, pMenu, nPos, rIdent);
402 
403     // update native menu
404     if (ImplGetSalMenu() && pData->pSalMenuItem)
405         ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos);
406 
407     return pData;
408 }
409 
InsertItem(sal_uInt16 nItemId,const OUString & rStr,MenuItemBits nItemBits,const OUString & rIdent,sal_uInt16 nPos)410 void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits,
411     const OUString &rIdent, sal_uInt16 nPos)
412 {
413     SAL_WARN_IF( !nItemId, "vcl", "Menu::InsertItem(): ItemId == 0" );
414     SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl",
415                 "Menu::InsertItem(): ItemId already exists" );
416 
417     // if Position > ItemCount, append
418     if ( nPos >= pItemList->size() )
419         nPos = MENU_APPEND;
420 
421     // put Item in MenuItemList
422     NbcInsertItem(nItemId, nItemBits, rStr, this, nPos, rIdent);
423 
424     vcl::Window* pWin = GetWindow();
425     mpLayoutData.reset();
426     if ( pWin )
427     {
428         ImplCalcSize( pWin );
429         if ( pWin->IsVisible() )
430             pWin->Invalidate();
431     }
432     ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
433 }
434 
InsertItem(sal_uInt16 nItemId,const Image & rImage,MenuItemBits nItemBits,const OUString & rIdent,sal_uInt16 nPos)435 void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage,
436     MenuItemBits nItemBits, const OUString &rIdent, sal_uInt16 nPos)
437 {
438     InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos);
439     SetItemImage( nItemId, rImage );
440 }
441 
InsertItem(sal_uInt16 nItemId,const OUString & rStr,const Image & rImage,MenuItemBits nItemBits,const OUString & rIdent,sal_uInt16 nPos)442 void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr,
443     const Image& rImage, MenuItemBits nItemBits,
444     const OUString &rIdent, sal_uInt16 nPos)
445 {
446     InsertItem(nItemId, rStr, nItemBits, rIdent, nPos);
447     SetItemImage( nItemId, rImage );
448 }
449 
InsertSeparator(const OUString & rIdent,sal_uInt16 nPos)450 void Menu::InsertSeparator(const OUString &rIdent, sal_uInt16 nPos)
451 {
452     // do nothing if it's a menu bar
453     if (IsMenuBar())
454         return;
455 
456     // if position > ItemCount, append
457     if ( nPos >= pItemList->size() )
458         nPos = MENU_APPEND;
459 
460     // put separator in item list
461     pItemList->InsertSeparator(rIdent, nPos);
462 
463     // update native menu
464     size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1;
465     MenuItemData *pData = pItemList->GetDataFromPos( itemPos );
466     if( ImplGetSalMenu() && pData && pData->pSalMenuItem )
467         ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos );
468 
469     mpLayoutData.reset();
470 
471     ImplCallEventListeners( VclEventId::MenuInsertItem, nPos );
472 }
473 
RemoveItem(sal_uInt16 nPos)474 void Menu::RemoveItem( sal_uInt16 nPos )
475 {
476     bool bRemove = false;
477 
478     if ( nPos < GetItemCount() )
479     {
480         // update native menu
481         if( ImplGetSalMenu() )
482             ImplGetSalMenu()->RemoveItem( nPos );
483 
484         pItemList->Remove( nPos );
485         bRemove = true;
486     }
487 
488     vcl::Window* pWin = GetWindow();
489     if ( pWin )
490     {
491         ImplCalcSize( pWin );
492         if ( pWin->IsVisible() )
493             pWin->Invalidate();
494     }
495     mpLayoutData.reset();
496 
497     if ( bRemove )
498         ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos );
499 }
500 
ImplCopyItem(Menu * pThis,const Menu & rMenu,sal_uInt16 nPos,sal_uInt16 nNewPos)501 static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos )
502 {
503     MenuItemType eType = rMenu.GetItemType( nPos );
504 
505     if ( eType == MenuItemType::DONTKNOW )
506         return;
507 
508     if ( eType == MenuItemType::SEPARATOR )
509         pThis->InsertSeparator( {}, nNewPos );
510     else
511     {
512         sal_uInt16 nId = rMenu.GetItemId( nPos );
513 
514         SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl",
515                     "Menu::CopyItem(): ItemId already exists" );
516 
517         MenuItemData* pData = rMenu.GetItemList()->GetData( nId );
518 
519         if (!pData)
520             return;
521 
522         if ( eType == MenuItemType::STRINGIMAGE )
523             pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
524         else if ( eType == MenuItemType::STRING )
525             pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos );
526         else
527             pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos );
528 
529         if ( rMenu.IsItemChecked( nId ) )
530             pThis->CheckItem( nId );
531         if ( !rMenu.IsItemEnabled( nId ) )
532             pThis->EnableItem( nId, false );
533         pThis->SetHelpId( nId, pData->aHelpId );
534         pThis->SetHelpText( nId, pData->aHelpText );
535         pThis->SetAccelKey( nId, pData->aAccelKey );
536         pThis->SetItemCommand( nId, pData->aCommandStr );
537         pThis->SetHelpCommand( nId, pData->aHelpCommandStr );
538 
539         PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId );
540         if ( pSubMenu )
541         {
542             // create auto-copy
543             VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu );
544             pThis->SetPopupMenu( nId, pNewMenu );
545         }
546     }
547 }
548 
Clear()549 void Menu::Clear()
550 {
551     for ( sal_uInt16 i = GetItemCount(); i; i-- )
552         RemoveItem( 0 );
553 }
554 
GetItemCount() const555 sal_uInt16 Menu::GetItemCount() const
556 {
557     return static_cast<sal_uInt16>(pItemList->size());
558 }
559 
HasValidEntries(bool bCheckPopups) const560 bool Menu::HasValidEntries(bool bCheckPopups) const
561 {
562     bool bValidEntries = false;
563     sal_uInt16 nCount = GetItemCount();
564     for (sal_uInt16 n = 0; !bValidEntries && (n < nCount); n++)
565     {
566         MenuItemData* pItem = pItemList->GetDataFromPos(n);
567         if (pItem->bEnabled && (pItem->eType != MenuItemType::SEPARATOR))
568         {
569             if (bCheckPopups && pItem->pSubMenu)
570                 bValidEntries = pItem->pSubMenu->HasValidEntries(true);
571             else
572                 bValidEntries = true;
573         }
574     }
575     return bValidEntries;
576 }
577 
ImplGetVisibleItemCount() const578 sal_uInt16 Menu::ImplGetVisibleItemCount() const
579 {
580     sal_uInt16 nItems = 0;
581     for ( size_t n = pItemList->size(); n; )
582     {
583         if ( ImplIsVisible( --n ) )
584             nItems++;
585     }
586     return nItems;
587 }
588 
ImplGetFirstVisible() const589 sal_uInt16 Menu::ImplGetFirstVisible() const
590 {
591     for ( size_t n = 0; n < pItemList->size(); n++ )
592     {
593         if ( ImplIsVisible( n ) )
594             return n;
595     }
596     return ITEMPOS_INVALID;
597 }
598 
ImplGetPrevVisible(sal_uInt16 nPos) const599 sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const
600 {
601     for ( size_t n = nPos; n; )
602     {
603         if (ImplIsVisible(--n))
604             return n;
605     }
606     return ITEMPOS_INVALID;
607 }
608 
ImplGetNextVisible(sal_uInt16 nPos) const609 sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const
610 {
611     for ( size_t n = nPos+1; n < pItemList->size(); n++ )
612     {
613         if ( ImplIsVisible( n ) )
614             return n;
615     }
616     return ITEMPOS_INVALID;
617 }
618 
GetItemId(sal_uInt16 nPos) const619 sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const
620 {
621     MenuItemData* pData = pItemList->GetDataFromPos( nPos );
622 
623     if ( pData )
624         return pData->nId;
625     else
626         return 0;
627 }
628 
GetItemId(std::u16string_view rIdent) const629 sal_uInt16 Menu::GetItemId(std::u16string_view rIdent) const
630 {
631     for (size_t n = 0; n < pItemList->size(); ++n)
632     {
633         MenuItemData* pData = pItemList->GetDataFromPos(n);
634         if (pData && pData->sIdent == rIdent)
635             return pData->nId;
636     }
637     return MENU_ITEM_NOTFOUND;
638 }
639 
GetItemPos(sal_uInt16 nItemId) const640 sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const
641 {
642     size_t          nPos;
643     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
644 
645     if ( pData )
646         return static_cast<sal_uInt16>(nPos);
647     else
648         return MENU_ITEM_NOTFOUND;
649 }
650 
GetItemType(sal_uInt16 nPos) const651 MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const
652 {
653     MenuItemData* pData = pItemList->GetDataFromPos( nPos );
654 
655     if ( pData )
656         return pData->eType;
657     else
658         return MenuItemType::DONTKNOW;
659 }
660 
GetItemIdent(sal_uInt16 nId) const661 OUString Menu::GetItemIdent(sal_uInt16 nId) const
662 {
663     const MenuItemData* pData = pItemList->GetData(nId);
664     return pData ? pData->sIdent : OUString();
665 }
666 
SetItemBits(sal_uInt16 nItemId,MenuItemBits nBits)667 void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits )
668 {
669     size_t        nPos;
670     MenuItemData* pData = pItemList->GetData(nItemId, nPos);
671 
672     if (pData && (pData->nBits != nBits))
673     {
674         // these two menu item bits are relevant for (accessible) role
675         const MenuItemBits nRoleMask = MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK;
676         const bool bRoleBitsChanged = (pData->nBits & nRoleMask) != (nBits & nRoleMask);
677 
678         pData->nBits = nBits;
679 
680         // update native menu
681         if (ImplGetSalMenu())
682             ImplGetSalMenu()->SetItemBits(nPos, nBits);
683 
684         if (bRoleBitsChanged)
685             ImplCallEventListeners(VclEventId::MenuItemRoleChanged, nPos);
686     }
687 }
688 
GetItemBits(sal_uInt16 nItemId) const689 MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const
690 {
691     MenuItemBits nBits = MenuItemBits::NONE;
692     MenuItemData* pData = pItemList->GetData( nItemId );
693     if ( pData )
694         nBits = pData->nBits;
695     return nBits;
696 }
697 
SetUserValue(sal_uInt16 nItemId,void * nUserValue,MenuUserDataReleaseFunction aFunc)698 void Menu::SetUserValue(sal_uInt16 nItemId, void* nUserValue, MenuUserDataReleaseFunction aFunc)
699 {
700     MenuItemData* pData = pItemList->GetData(nItemId);
701     if (pData)
702     {
703         if (pData->aUserValueReleaseFunc)
704             pData->aUserValueReleaseFunc(pData->nUserValue);
705         pData->aUserValueReleaseFunc = aFunc;
706         pData->nUserValue = nUserValue;
707     }
708 }
709 
GetUserValue(sal_uInt16 nItemId) const710 void* Menu::GetUserValue( sal_uInt16 nItemId ) const
711 {
712     MenuItemData* pData = pItemList->GetData( nItemId );
713     return pData ? pData->nUserValue : nullptr;
714 }
715 
SetPopupMenu(sal_uInt16 nItemId,PopupMenu * pMenu)716 void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu )
717 {
718     size_t          nPos;
719     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
720 
721     // Item does not exist -> return NULL
722     if ( !pData )
723         return;
724 
725     // same menu, nothing to do
726     if ( pData->pSubMenu.get() == pMenu )
727         return;
728 
729     // remove old menu
730     auto oldSubMenu = pData->pSubMenu;
731 
732     // data exchange
733     pData->pSubMenu = pMenu;
734 
735     // #112023# Make sure pStartedFrom does not point to invalid (old) data
736     if ( pData->pSubMenu )
737         pData->pSubMenu->pStartedFrom = nullptr;
738 
739     // set native submenu
740     if( ImplGetSalMenu() && pData->pSalMenuItem )
741     {
742         if( pMenu )
743             ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos );
744         else
745             ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos );
746     }
747 
748     oldSubMenu.disposeAndClear();
749 
750     ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos );
751 }
752 
GetPopupMenu(sal_uInt16 nItemId) const753 PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const
754 {
755     MenuItemData* pData = pItemList->GetData( nItemId );
756 
757     if ( pData )
758         return pData->pSubMenu.get();
759     else
760         return nullptr;
761 }
762 
SetAccelKey(sal_uInt16 nItemId,const KeyCode & rKeyCode)763 void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode )
764 {
765     size_t          nPos;
766     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
767 
768     if ( !pData )
769         return;
770 
771     if ( pData->aAccelKey == rKeyCode )
772         return;
773 
774     pData->aAccelKey = rKeyCode;
775 
776     // update native menu
777     if( ImplGetSalMenu() && pData->pSalMenuItem )
778         ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() );
779 }
780 
GetAccelKey(sal_uInt16 nItemId) const781 KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const
782 {
783     MenuItemData* pData = pItemList->GetData( nItemId );
784 
785     if ( pData )
786         return pData->aAccelKey;
787     else
788         return KeyCode();
789 }
790 
GetActivationKey(sal_uInt16 nItemId) const791 KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const
792 {
793     KeyEvent aRet;
794     MenuItemData* pData = pItemList->GetData( nItemId );
795     if( pData )
796     {
797         sal_Int32 nPos = pData->aText.indexOf( '~' );
798         if( nPos != -1 && nPos < pData->aText.getLength()-1 )
799         {
800             sal_uInt16 nCode = 0;
801             sal_Unicode cAccel = pData->aText[nPos+1];
802             if( cAccel >= 'a' && cAccel <= 'z' )
803                 nCode = KEY_A + (cAccel-'a');
804             else if( cAccel >= 'A' && cAccel <= 'Z' )
805                 nCode = KEY_A + (cAccel-'A');
806             else if( cAccel >= '0' && cAccel <= '9' )
807                 nCode = KEY_0 + (cAccel-'0');
808 
809             aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) );
810         }
811 
812     }
813     return aRet;
814 }
815 
CheckItem(sal_uInt16 nItemId,bool bCheck)816 void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck )
817 {
818     size_t          nPos;
819     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
820 
821     if ( !pData || pData->bChecked == bCheck )
822         return;
823 
824     // if radio-check, then uncheck previous
825     if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) &&
826          (pData->nBits & MenuItemBits::RADIOCHECK) )
827     {
828         MenuItemData*   pGroupData;
829         sal_uInt16          nGroupPos;
830         sal_uInt16          nItemCount = GetItemCount();
831         bool            bFound = false;
832 
833         nGroupPos = nPos;
834         while ( nGroupPos )
835         {
836             pGroupData = pItemList->GetDataFromPos( nGroupPos-1 );
837             if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
838             {
839                 if ( IsItemChecked( pGroupData->nId ) )
840                 {
841                     CheckItem( pGroupData->nId, false );
842                     bFound = true;
843                     break;
844                 }
845             }
846             else
847                 break;
848             nGroupPos--;
849         }
850 
851         if ( !bFound )
852         {
853             nGroupPos = nPos+1;
854             while ( nGroupPos < nItemCount )
855             {
856                 pGroupData = pItemList->GetDataFromPos( nGroupPos );
857                 if ( pGroupData->nBits & MenuItemBits::RADIOCHECK )
858                 {
859                     if ( IsItemChecked( pGroupData->nId ) )
860                     {
861                         CheckItem( pGroupData->nId, false );
862                         break;
863                     }
864                 }
865                 else
866                     break;
867                 nGroupPos++;
868             }
869         }
870     }
871 
872     pData->bChecked = bCheck;
873 
874     // update native menu
875     if( ImplGetSalMenu() )
876         ImplGetSalMenu()->CheckItem( nPos, bCheck );
877 
878     ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos );
879 }
880 
CheckItem(std::u16string_view rIdent,bool bCheck)881 void Menu::CheckItem( std::u16string_view rIdent , bool bCheck )
882 {
883     CheckItem( GetItemId( rIdent ), bCheck );
884 }
885 
IsItemCheckable(sal_uInt16 nItemId) const886 bool Menu::IsItemCheckable(sal_uInt16 nItemId) const
887 {
888     size_t nPos;
889     MenuItemData* pData = pItemList->GetData(nItemId, nPos);
890 
891     if (!pData)
892         return false;
893 
894     return pData->HasCheck();
895 }
896 
IsItemChecked(sal_uInt16 nItemId) const897 bool Menu::IsItemChecked( sal_uInt16 nItemId ) const
898 {
899     size_t          nPos;
900     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
901 
902     if ( !pData )
903         return false;
904 
905     return pData->bChecked;
906 }
907 
EnableItem(sal_uInt16 nItemId,bool bEnable)908 void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable )
909 {
910     size_t          nPos;
911     MenuItemData*   pItemData = pItemList->GetData( nItemId, nPos );
912 
913     if ( !(pItemData && ( pItemData->bEnabled != bEnable )) )
914         return;
915 
916     pItemData->bEnabled = bEnable;
917 
918     vcl::Window* pWin = GetWindow();
919     if ( pWin && pWin->IsVisible() )
920     {
921         SAL_WARN_IF(!IsMenuBar(), "vcl", "Menu::EnableItem - Popup visible!" );
922         tools::Long nX = 0;
923         size_t nCount = pItemList->size();
924         for ( size_t n = 0; n < nCount; n++ )
925         {
926             MenuItemData* pData = pItemList->GetDataFromPos( n );
927             if ( n == nPos )
928             {
929                 pWin->Invalidate( tools::Rectangle( Point( nX, 0 ), Size( pData->aSz.Width(), pData->aSz.Height() ) ) );
930                 break;
931             }
932             nX += pData->aSz.Width();
933         }
934     }
935     // update native menu
936     if( ImplGetSalMenu() )
937         ImplGetSalMenu()->EnableItem( nPos, bEnable );
938 
939     ImplCallEventListeners( bEnable ? VclEventId::MenuEnable : VclEventId::MenuDisable, nPos );
940 }
941 
IsItemEnabled(sal_uInt16 nItemId) const942 bool Menu::IsItemEnabled( sal_uInt16 nItemId ) const
943 {
944     size_t          nPos;
945     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
946 
947     if ( !pData )
948         return false;
949 
950     return pData->bEnabled;
951 }
952 
ShowItem(sal_uInt16 nItemId,bool bVisible)953 void Menu::ShowItem( sal_uInt16 nItemId, bool bVisible )
954 {
955     size_t          nPos;
956     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
957 
958     SAL_WARN_IF(IsMenuBar() && !bVisible , "vcl", "Menu::ShowItem - ignored for menu bar entries!");
959     if (IsMenuBar() || !pData || (pData->bVisible == bVisible))
960         return;
961 
962     vcl::Window* pWin = GetWindow();
963     if ( pWin && pWin->IsVisible() )
964     {
965         SAL_WARN( "vcl", "Menu::ShowItem - ignored for visible popups!" );
966         return;
967     }
968     pData->bVisible = bVisible;
969 
970     // update native menu
971     if( ImplGetSalMenu() )
972         ImplGetSalMenu()->ShowItem( nPos, bVisible );
973 }
974 
SetItemText(sal_uInt16 nItemId,const OUString & rStr)975 void Menu::SetItemText( sal_uInt16 nItemId, const OUString& rStr )
976 {
977     size_t          nPos;
978     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
979 
980     if ( !pData )
981         return;
982 
983     if ( rStr == pData->aText )
984         return;
985 
986     pData->aText = rStr;
987     // Clear layout for aText.
988     pData->aTextGlyphs.Invalidate();
989     ImplSetMenuItemData( pData );
990     // update native menu
991     if( ImplGetSalMenu() && pData->pSalMenuItem )
992         ImplGetSalMenu()->SetItemText( nPos, pData->pSalMenuItem.get(), rStr );
993 
994     vcl::Window* pWin = GetWindow();
995     mpLayoutData.reset();
996     if (pWin && IsMenuBar())
997     {
998         ImplCalcSize( pWin );
999         if ( pWin->IsVisible() )
1000             pWin->Invalidate();
1001     }
1002 
1003     ImplCallEventListeners( VclEventId::MenuItemTextChanged, nPos );
1004 }
1005 
GetItemText(sal_uInt16 nItemId) const1006 OUString Menu::GetItemText( sal_uInt16 nItemId ) const
1007 {
1008     size_t          nPos;
1009     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
1010 
1011     if ( pData )
1012         return pData->aText;
1013 
1014     return OUString();
1015 }
1016 
SetItemImage(sal_uInt16 nItemId,const Image & rImage)1017 void Menu::SetItemImage( sal_uInt16 nItemId, const Image& rImage )
1018 {
1019     size_t          nPos;
1020     MenuItemData*   pData = pItemList->GetData( nItemId, nPos );
1021 
1022     if ( !pData )
1023         return;
1024 
1025     pData->aImage = rImage;
1026     ImplSetMenuItemData( pData );
1027 
1028     // update native menu
1029     if( ImplGetSalMenu() && pData->pSalMenuItem )
1030         ImplGetSalMenu()->SetItemImage( nPos, pData->pSalMenuItem.get(), rImage );
1031 }
1032 
GetItemImage(sal_uInt16 nItemId) const1033 Image Menu::GetItemImage( sal_uInt16 nItemId ) const
1034 {
1035     MenuItemData* pData = pItemList->GetData( nItemId );
1036 
1037     if ( pData )
1038         return pData->aImage;
1039     else
1040         return Image();
1041 }
1042 
SetItemCommand(sal_uInt16 nItemId,const OUString & rCommand)1043 void Menu::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand )
1044 {
1045     size_t        nPos;
1046     MenuItemData* pData = pItemList->GetData( nItemId, nPos );
1047 
1048     if ( pData )
1049         pData->aCommandStr = rCommand;
1050 }
1051 
GetItemCommand(sal_uInt16 nItemId) const1052 OUString Menu::GetItemCommand( sal_uInt16 nItemId ) const
1053 {
1054     MenuItemData* pData = pItemList->GetData( nItemId );
1055 
1056     if (pData)
1057         return pData->aCommandStr;
1058 
1059     return OUString();
1060 }
1061 
SetHelpCommand(sal_uInt16 nItemId,const OUString & rStr)1062 void Menu::SetHelpCommand( sal_uInt16 nItemId, const OUString& rStr )
1063 {
1064     MenuItemData* pData = pItemList->GetData( nItemId );
1065 
1066     if ( pData )
1067         pData->aHelpCommandStr = rStr;
1068 }
1069 
GetHelpCommand(sal_uInt16 nItemId) const1070 OUString Menu::GetHelpCommand( sal_uInt16 nItemId ) const
1071 {
1072     MenuItemData* pData = pItemList->GetData( nItemId );
1073 
1074     if ( pData )
1075         return pData->aHelpCommandStr;
1076 
1077     return OUString();
1078 }
1079 
SetHelpText(sal_uInt16 nItemId,const OUString & rStr)1080 void Menu::SetHelpText( sal_uInt16 nItemId, const OUString& rStr )
1081 {
1082     MenuItemData* pData = pItemList->GetData( nItemId );
1083 
1084     if ( pData )
1085         pData->aHelpText = rStr;
1086 }
1087 
ImplGetHelpText(sal_uInt16 nItemId) const1088 OUString Menu::ImplGetHelpText( sal_uInt16 nItemId ) const
1089 {
1090     MenuItemData* pData = pItemList->GetData( nItemId );
1091 
1092     if (!pData)
1093         return OUString();
1094 
1095     if ( pData->aHelpText.isEmpty() &&
1096          (( !pData->aHelpId.isEmpty()  ) || ( !pData->aCommandStr.isEmpty() )))
1097     {
1098         Help* pHelp = Application::GetHelp();
1099         if ( pHelp )
1100         {
1101             if (!pData->aCommandStr.isEmpty())
1102                 pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr );
1103             if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty())
1104                 pData->aHelpText = pHelp->GetHelpText( pData->aHelpId );
1105         }
1106     }
1107 
1108     //Fallback to Menu::GetAccessibleDescription without reentry to GetHelpText()
1109     if (pData->aHelpText.isEmpty())
1110         return pData->aAccessibleDescription;
1111     return pData->aHelpText;
1112 }
1113 
GetHelpText(sal_uInt16 nItemId) const1114 OUString Menu::GetHelpText( sal_uInt16 nItemId ) const
1115 {
1116     return ImplGetHelpText( nItemId );
1117 }
1118 
SetTipHelpText(sal_uInt16 nItemId,const OUString & rStr)1119 void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr )
1120 {
1121     MenuItemData* pData = pItemList->GetData( nItemId );
1122 
1123     if ( pData )
1124     {
1125         pData->aTipHelpText = rStr;
1126 
1127         if (ImplGetSalMenu() && pData->pSalMenuItem)
1128             ImplGetSalMenu()->SetItemTooltip(pData->pSalMenuItem.get(), rStr);
1129     }
1130 }
1131 
GetTipHelpText(sal_uInt16 nItemId) const1132 OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const
1133 {
1134     MenuItemData* pData = pItemList->GetData( nItemId );
1135 
1136     if ( pData )
1137         return pData->aTipHelpText;
1138 
1139     return OUString();
1140 }
1141 
SetHelpId(sal_uInt16 nItemId,const OUString & rHelpId)1142 void Menu::SetHelpId( sal_uInt16 nItemId, const OUString& rHelpId )
1143 {
1144     MenuItemData* pData = pItemList->GetData( nItemId );
1145 
1146     if ( pData )
1147         pData->aHelpId = rHelpId;
1148 }
1149 
GetHelpId(sal_uInt16 nItemId) const1150 OUString Menu::GetHelpId( sal_uInt16 nItemId ) const
1151 {
1152     OUString aRet;
1153 
1154     MenuItemData* pData = pItemList->GetData( nItemId );
1155 
1156     if ( pData )
1157     {
1158         if ( !pData->aHelpId.isEmpty() )
1159             aRet = pData->aHelpId;
1160         else
1161             aRet = pData->aCommandStr;
1162     }
1163 
1164     return aRet;
1165 }
1166 
operator =(const Menu & rMenu)1167 Menu& Menu::operator=( const Menu& rMenu )
1168 {
1169     if(this == &rMenu)
1170         return *this;
1171 
1172     // clean up
1173     Clear();
1174 
1175     // copy items
1176     sal_uInt16 nCount = rMenu.GetItemCount();
1177     for ( sal_uInt16 i = 0; i < nCount; i++ )
1178         ImplCopyItem( this, rMenu, i, MENU_APPEND );
1179 
1180     aActivateHdl = rMenu.aActivateHdl;
1181     aDeactivateHdl = rMenu.aDeactivateHdl;
1182     aSelectHdl = rMenu.aSelectHdl;
1183     aTitleText = rMenu.aTitleText;
1184     nTitleHeight = rMenu.nTitleHeight;
1185 
1186     return *this;
1187 }
1188 
1189 // Returns true if the item is completely hidden on the GUI and shouldn't
1190 // be possible to interact with
ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const1191 bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const
1192 {
1193     MenuItemData* pData = pItemList->GetDataFromPos(nPos);
1194     if (pData)
1195     {
1196         MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 );
1197         if (pPreviousData && pPreviousData->bHiddenOnGUI)
1198         {
1199             return true;
1200         }
1201     }
1202     return false;
1203 }
1204 
ImplIsVisible(sal_uInt16 nPos) const1205 bool Menu::ImplIsVisible( sal_uInt16 nPos ) const
1206 {
1207     bool bVisible = true;
1208 
1209     MenuItemData* pData = pItemList->GetDataFromPos( nPos );
1210     // check general visibility first
1211     if( pData && !pData->bVisible )
1212         bVisible = false;
1213 
1214     if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR )
1215     {
1216         if( nPos == 0 ) // no separator should be shown at the very beginning
1217             bVisible = false;
1218         else
1219         {
1220             // always avoid adjacent separators
1221             size_t nCount = pItemList->size();
1222             size_t n;
1223             MenuItemData* pNextData = nullptr;
1224             // search next visible item
1225             for( n = nPos + 1; n < nCount; n++ )
1226             {
1227                 pNextData = pItemList->GetDataFromPos( n );
1228                 if( pNextData && pNextData->bVisible )
1229                 {
1230                     if( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) )
1231                         break;
1232                 }
1233             }
1234             if( n == nCount ) // no next visible item
1235                 bVisible = false;
1236             // check for separator
1237             if( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR )
1238                 bVisible = false;
1239 
1240             if( bVisible )
1241             {
1242                 for( n = nPos; n > 0; n-- )
1243                 {
1244                     pNextData = pItemList->GetDataFromPos( n-1 );
1245                     if( pNextData && pNextData->bVisible )
1246                     {
1247                         if( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) )
1248                             break;
1249                     }
1250                 }
1251                 if( n == 0 ) // no previous visible item
1252                     bVisible = false;
1253             }
1254         }
1255     }
1256 
1257     // not allowed for menubar, as I do not know
1258     // whether a menu-entry will disappear or will appear
1259     if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) &&
1260         !(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries))
1261     {
1262         if( !pData ) // e.g. nPos == ITEMPOS_INVALID
1263             bVisible = false;
1264         else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above
1265         {
1266             // tdf#86850 Always display clipboard functions
1267             if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" ||
1268                  pData->sIdent == ".uno:Cut" || pData->sIdent == ".uno:Copy" || pData->sIdent == ".uno:Paste" )
1269                 bVisible = true;
1270             else
1271                 // bVisible = pData->bEnabled && ( !pData->pSubMenu || pData->pSubMenu->HasValidEntries( true ) );
1272                 bVisible = pData->bEnabled; // do not check submenus as they might be filled at Activate().
1273         }
1274     }
1275 
1276     return bVisible;
1277 }
1278 
IsItemPosVisible(sal_uInt16 nItemPos) const1279 bool Menu::IsItemPosVisible( sal_uInt16 nItemPos ) const
1280 {
1281     return IsMenuVisible() && ImplIsVisible( nItemPos );
1282 }
1283 
IsMenuVisible() const1284 bool Menu::IsMenuVisible() const
1285 {
1286     return m_pWindow && m_pWindow->IsReallyVisible();
1287 }
1288 
ImplIsSelectable(sal_uInt16 nPos) const1289 bool Menu::ImplIsSelectable( sal_uInt16 nPos ) const
1290 {
1291     bool bSelectable = true;
1292 
1293     MenuItemData* pData = pItemList->GetDataFromPos( nPos );
1294     // check general visibility first
1295     if ( pData && ( pData->nBits & MenuItemBits::NOSELECT ) )
1296         bSelectable = false;
1297 
1298     return bSelectable;
1299 }
1300 
CreateAccessible()1301 rtl::Reference<comphelper::OAccessible> Menu::CreateAccessible()
1302 {
1303     rtl::Reference<OAccessibleMenuBaseComponent> xAccessible;
1304     if (IsMenuBar())
1305         xAccessible = new VCLXAccessibleMenuBar(this);
1306     else
1307         xAccessible = new VCLXAccessiblePopupMenu(this);
1308     xAccessible->SetStates();
1309     return xAccessible;
1310 }
1311 
GetAccessible()1312 rtl::Reference<comphelper::OAccessible> Menu::GetAccessible()
1313 {
1314     // Since PopupMenu are sometimes shared by different instances of MenuBar, the mpAccessible member gets
1315     // overwritten and may contain a disposed object when the initial menubar gets set again. So use the
1316     // mpAccessible member only for sub menus.
1317     if (pStartedFrom && pStartedFrom != this)
1318     {
1319         for ( sal_uInt16 i = 0, nCount = pStartedFrom->GetItemCount(); i < nCount; ++i )
1320         {
1321             sal_uInt16 nItemId = pStartedFrom->GetItemId( i );
1322             if ( static_cast< Menu* >( pStartedFrom->GetPopupMenu( nItemId ) ) == this )
1323             {
1324                 rtl::Reference<comphelper::OAccessible> pParent = pStartedFrom->GetAccessible();
1325                 if (pParent.is())
1326                 {
1327                     css::uno::Reference<css::accessibility::XAccessible> xAcc = pParent->getAccessibleChild(i);
1328                     if (!xAcc)
1329                         return {};
1330 
1331                     rtl::Reference<comphelper::OAccessible> pAccessible = dynamic_cast<comphelper::OAccessible*>(xAcc.get());
1332                     assert(pAccessible.is() && "Accessible is not an OAccessible");
1333                     return pAccessible;
1334                 }
1335             }
1336         }
1337     }
1338     else if (!mpAccessible.is())
1339         mpAccessible = CreateAccessible();
1340 
1341     return mpAccessible;
1342 }
1343 
SetAccessible(const rtl::Reference<comphelper::OAccessible> & rAccessible)1344 void Menu::SetAccessible(const rtl::Reference<comphelper::OAccessible>& rAccessible)
1345 {
1346     mpAccessible = rAccessible;
1347 }
1348 
ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext,tools::Long & rCheckHeight,tools::Long & rRadioHeight) const1349 Size Menu::ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, tools::Long& rCheckHeight, tools::Long& rRadioHeight ) const
1350 {
1351     tools::Long nCheckWidth = 0, nRadioWidth = 0;
1352     rCheckHeight = rRadioHeight = 0;
1353 
1354     if (!IsMenuBar())
1355     {
1356         ImplControlValue aVal;
1357         tools::Rectangle aNativeBounds;
1358         tools::Rectangle aNativeContent;
1359 
1360         tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
1361         if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark))
1362         {
1363             if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark,
1364                                               aCtrlRegion, ControlState::ENABLED, aVal,
1365                                               aNativeBounds, aNativeContent))
1366             {
1367                 rCheckHeight = aNativeBounds.GetHeight() - 1;
1368                 nCheckWidth = aNativeContent.GetWidth() - 1;
1369             }
1370         }
1371         if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
1372         {
1373             if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
1374                                                       aCtrlRegion, ControlState::ENABLED, aVal,
1375                                                       aNativeBounds, aNativeContent))
1376             {
1377                 rRadioHeight = aNativeBounds.GetHeight() - 1;
1378                 nRadioWidth = aNativeContent.GetWidth() - 1;
1379             }
1380         }
1381     }
1382     return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight));
1383 }
1384 
ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext,Size & rArrowSize,tools::Long & rArrowSpacing)1385 bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, tools::Long& rArrowSpacing)
1386 {
1387     ImplControlValue aVal;
1388     tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15)));
1389     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
1390     {
1391         tools::Rectangle aNativeContent;
1392         tools::Rectangle aNativeBounds;
1393         if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::SubmenuArrow,
1394                                                   aCtrlRegion, ControlState::ENABLED,
1395                                                   aVal, aNativeBounds, aNativeContent))
1396         {
1397             Size aSize(aNativeContent.GetWidth(), aNativeContent.GetHeight());
1398             rArrowSize = aSize;
1399             rArrowSpacing = aNativeBounds.GetWidth() - aNativeContent.GetWidth();
1400             return true;
1401         }
1402     }
1403     return false;
1404 }
1405 
ImplAddDel(ImplMenuDelData & rDel)1406 void Menu::ImplAddDel( ImplMenuDelData& rDel )
1407 {
1408     SAL_WARN_IF( rDel.mpMenu, "vcl", "Menu::ImplAddDel(): cannot add ImplMenuDelData twice !" );
1409     if( !rDel.mpMenu )
1410     {
1411         rDel.mpMenu = this;
1412         rDel.mpNext = mpFirstDel;
1413         mpFirstDel = &rDel;
1414     }
1415 }
1416 
ImplRemoveDel(ImplMenuDelData & rDel)1417 void Menu::ImplRemoveDel( ImplMenuDelData& rDel )
1418 {
1419     rDel.mpMenu = nullptr;
1420     if ( mpFirstDel == &rDel )
1421     {
1422         mpFirstDel = rDel.mpNext;
1423     }
1424     else
1425     {
1426         ImplMenuDelData* pData = mpFirstDel;
1427         while ( pData && (pData->mpNext != &rDel) )
1428             pData = pData->mpNext;
1429 
1430         SAL_WARN_IF( !pData, "vcl", "Menu::ImplRemoveDel(): ImplMenuDelData not registered !" );
1431         if( pData )
1432             pData->mpNext = rDel.mpNext;
1433     }
1434 }
1435 
ImplCalcSize(vcl::Window * pWin)1436 Size Menu::ImplCalcSize( vcl::Window* pWin )
1437 {
1438     // | Check/Radio/Image| Text| Accel/Popup|
1439 
1440     // for symbols: nFontHeight x nFontHeight
1441     tools::Long nFontHeight = pWin->GetTextHeight();
1442     tools::Long nExtra = nFontHeight/4;
1443 
1444     tools::Long nMinMenuItemHeight = nFontHeight;
1445     tools::Long nCheckHeight = 0, nRadioHeight = 0;
1446     Size aMarkSize = ImplGetNativeCheckAndRadioSize(*pWin->GetOutDev(), nCheckHeight, nRadioHeight);
1447     if( aMarkSize.Height() > nMinMenuItemHeight )
1448         nMinMenuItemHeight = aMarkSize.Height();
1449 
1450     tools::Long aMaxImgWidth = 0;
1451 
1452     const StyleSettings& rSettings = pWin->GetSettings().GetStyleSettings();
1453     if ( rSettings.GetUseImagesInMenus() )
1454     {
1455         if ( 16 > nMinMenuItemHeight )
1456             nMinMenuItemHeight = 16;
1457         for ( size_t i = pItemList->size(); i; )
1458         {
1459             MenuItemData* pData = pItemList->GetDataFromPos( --i );
1460             if ( ImplIsVisible( i )
1461                && (  ( pData->eType == MenuItemType::IMAGE )
1462                   || ( pData->eType == MenuItemType::STRINGIMAGE )
1463                   )
1464                )
1465             {
1466                 Size aImgSz = pData->aImage.GetSizePixel();
1467                 if ( aImgSz.Width() > aMaxImgWidth )
1468                     aMaxImgWidth = aImgSz.Width();
1469                 if ( aImgSz.Height() > nMinMenuItemHeight )
1470                     nMinMenuItemHeight = aImgSz.Height();
1471                 break;
1472             }
1473         }
1474     }
1475 
1476     Size aSz;
1477     tools::Long nMaxWidth = 0;
1478 
1479     for ( size_t n = pItemList->size(); n; )
1480     {
1481         MenuItemData* pData = pItemList->GetDataFromPos( --n );
1482 
1483         pData->aSz.setHeight( 0 );
1484         pData->aSz.setWidth( 0 );
1485 
1486         if ( ImplIsVisible( n ) )
1487         {
1488             tools::Long nWidth = 0;
1489 
1490             // Separator
1491             if (!IsMenuBar()&& (pData->eType == MenuItemType::SEPARATOR))
1492             {
1493                 pData->aSz.setHeight( 4 );
1494             }
1495 
1496             // Image:
1497             if (!IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1498             {
1499                 tools::Long aImgHeight = pData->aImage.GetSizePixel().Height();
1500 
1501                 aImgHeight += 4; // add a border for native marks
1502                 if (aImgHeight > pData->aSz.Height())
1503                     pData->aSz.setHeight(aImgHeight);
1504             }
1505 
1506             // Check Buttons:
1507             if (!IsMenuBar() && pData->HasCheck())
1508             {
1509                 // checks / images take the same place
1510                 if( ( pData->eType != MenuItemType::IMAGE ) && ( pData->eType != MenuItemType::STRINGIMAGE ) )
1511                 {
1512                     nWidth += aMarkSize.Width() + nExtra * 2;
1513                     if (aMarkSize.Height() > pData->aSz.Height())
1514                         pData->aSz.setHeight(aMarkSize.Height());
1515                 }
1516             }
1517 
1518             // Text:
1519             if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) )
1520             {
1521                 const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin->GetOutDev());
1522                 tools::Long nTextWidth = pWin->GetOutDev()->GetCtrlTextWidth(pData->aText, pGlyphs);
1523                 tools::Long nTextHeight = pWin->GetTextHeight() + EXTRAITEMHEIGHT;
1524 
1525                 if (IsMenuBar())
1526                 {
1527                     if ( nTextHeight > pData->aSz.Height() )
1528                         pData->aSz.setHeight( nTextHeight );
1529 
1530                     pData->aSz.setWidth( nTextWidth + 4*nExtra );
1531                     aSz.AdjustWidth(pData->aSz.Width() );
1532                 }
1533                 else
1534                     pData->aSz.setHeight( std::max( std::max( nTextHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
1535 
1536                 nWidth += nTextWidth;
1537             }
1538 
1539             // Accel
1540             if (!IsMenuBar()&& pData->aAccelKey.GetCode() && !officecfg::VCL::VCLSettings::Menu::SuppressAccelerators::get())
1541             {
1542                 OUString aName = pData->aAccelKey.GetName();
1543                 tools::Long nAccWidth = pWin->GetTextWidth( aName );
1544                 nAccWidth += nExtra;
1545                 nWidth += nAccWidth;
1546             }
1547 
1548             // SubMenu?
1549             if (!IsMenuBar() && pData->pSubMenu)
1550             {
1551                 if ( nFontHeight > nWidth )
1552                     nWidth += nFontHeight;
1553 
1554                 pData->aSz.setHeight( std::max( std::max( nFontHeight, pData->aSz.Height() ), nMinMenuItemHeight ) );
1555             }
1556 
1557             if (!IsMenuBar())
1558                 aSz.AdjustHeight(pData->aSz.Height() );
1559 
1560             if ( nWidth > nMaxWidth )
1561                 nMaxWidth = nWidth;
1562 
1563         }
1564     }
1565 
1566     // Additional space for title
1567     nTitleHeight = 0;
1568     if (!IsMenuBar() && aTitleText.getLength() > 0) {
1569         // Set expected font
1570         auto popIt = pWin->GetOutDev()->ScopedPush(PushFlags::FONT);
1571         vcl::Font aFont = pWin->GetFont();
1572         aFont.SetWeight(WEIGHT_BOLD);
1573         pWin->SetFont(aFont);
1574 
1575         // Compute text bounding box
1576         tools::Rectangle aTextBoundRect;
1577         pWin->GetOutDev()->GetTextBoundRect(aTextBoundRect, aTitleText);
1578 
1579         // Vertically, one height of char + extra space for decoration
1580         nTitleHeight =  aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ;
1581         aSz.AdjustHeight(nTitleHeight );
1582 
1583         tools::Long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE;
1584         if ( nWidth > nMaxWidth )
1585             nMaxWidth = nWidth;
1586     }
1587 
1588     if (!IsMenuBar())
1589     {
1590         // popup menus should not be wider than half the screen
1591         // except on rather small screens
1592         // TODO: move GetScreenNumber from SystemWindow to Window ?
1593         // currently we rely on internal privileges
1594         unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->GetUnmirroredGeometry().screen();
1595         tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) );
1596         tools::Long nScreenWidth = aDispRect.GetWidth() >= 800 ? aDispRect.GetWidth() : 800;
1597         if( nMaxWidth > nScreenWidth/2 )
1598             nMaxWidth = nScreenWidth/2;
1599 
1600         sal_uInt16 gfxExtra = static_cast<sal_uInt16>(std::max( nExtra, tools::Long(7) )); // #107710# increase space between checkmarks/images/text
1601         nImgOrChkPos = static_cast<sal_uInt16>(nExtra);
1602         tools::Long nImgOrChkWidth = 0;
1603         if( aMarkSize.Height() > 0 ) // NWF case
1604             nImgOrChkWidth = aMarkSize.Height() + nExtra;
1605         else // non NWF case
1606             nImgOrChkWidth = nFontHeight/2 + gfxExtra;
1607         nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgWidth + gfxExtra );
1608         nTextPos = static_cast<sal_uInt16>(nImgOrChkPos + nImgOrChkWidth);
1609         nTextPos = nTextPos + gfxExtra;
1610 
1611         aSz.setWidth( nTextPos + nMaxWidth + nExtra );
1612         aSz.AdjustWidth(4*nExtra );   // a _little_ more ...
1613 
1614         aSz.AdjustWidth(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderX );
1615         aSz.AdjustHeight(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
1616     }
1617     else
1618     {
1619         nTextPos = static_cast<sal_uInt16>(2*nExtra);
1620         aSz.setHeight( nFontHeight+6 );
1621 
1622         // get menubar height from native methods if supported
1623         if (m_pWindow->IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))
1624         {
1625             ImplControlValue aVal;
1626             tools::Rectangle aNativeBounds;
1627             tools::Rectangle aNativeContent;
1628             Point tmp( 0, 0 );
1629             tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) );
1630             if (m_pWindow->GetNativeControlRegion(ControlType::Menubar,
1631                                                   ControlPart::Entire,
1632                                                   aCtrlRegion,
1633                                                   ControlState::ENABLED,
1634                                                   aVal,
1635                                                   aNativeBounds,
1636                                                   aNativeContent)
1637             )
1638             {
1639                 int nNativeHeight = aNativeBounds.GetHeight();
1640                 if( nNativeHeight > aSz.Height() )
1641                     aSz.setHeight( nNativeHeight );
1642             }
1643         }
1644 
1645         // account for the size of the close button, which actually is a toolbox
1646         // due to NWF this is variable
1647         tools::Long nCloseButtonHeight = static_cast<MenuBarWindow*>(m_pWindow.get())->MinCloseButtonSize().Height();
1648         if (aSz.Height() < nCloseButtonHeight)
1649             aSz.setHeight( nCloseButtonHeight );
1650     }
1651 
1652     return aSz;
1653 }
1654 
ImplPaintCheckBackground(vcl::RenderContext & rRenderContext,vcl::Window const & rWindow,const tools::Rectangle & i_rRect,bool i_bHighlight)1655 static void ImplPaintCheckBackground(vcl::RenderContext & rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight)
1656 {
1657     bool bNativeOk = false;
1658     if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button))
1659     {
1660         ImplControlValue    aControlValue;
1661         aControlValue.setTristateVal(ButtonValue::On);
1662         tools::Rectangle r = i_rRect;
1663         r.AdjustBottom(1);
1664 
1665         bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
1666                                                      r,
1667                                                      ControlState::PRESSED | ControlState::ENABLED,
1668                                                      aControlValue,
1669                                                      OUString());
1670     }
1671 
1672     if (!bNativeOk)
1673     {
1674         const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
1675         Color aColor( i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor() );
1676         RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true, false, nullptr, 2, &aColor);
1677     }
1678 }
1679 
getShortenedString(const OUString & i_rLong,vcl::RenderContext const & rRenderContext,tools::Long i_nMaxWidth)1680 static OUString getShortenedString( const OUString& i_rLong, vcl::RenderContext const & rRenderContext, tools::Long i_nMaxWidth )
1681 {
1682     sal_Int32 nPos = -1;
1683     OUString aNonMnem(removeMnemonicFromString(i_rLong, nPos));
1684     aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis);
1685     // re-insert mnemonic
1686     if (nPos != -1)
1687     {
1688         if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos])
1689             aNonMnem = OUString::Concat(aNonMnem.subView(0, nPos)) + "~" + aNonMnem.subView(nPos);
1690     }
1691     return aNonMnem;
1692 }
1693 
ImplPaintMenuTitle(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect) const1694 void Menu::ImplPaintMenuTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) const
1695 {
1696     // Save previous graphical settings, set new one
1697     rRenderContext.Push(PushFlags::FONT | PushFlags::FILLCOLOR);
1698     Wallpaper aOldBackground = rRenderContext.GetBackground();
1699 
1700     Color aBackgroundColor = rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor();
1701     rRenderContext.SetBackground(Wallpaper(aBackgroundColor));
1702     rRenderContext.SetFillColor(aBackgroundColor);
1703     vcl::Font aFont = rRenderContext.GetFont();
1704     aFont.SetWeight(WEIGHT_BOLD);
1705     rRenderContext.SetFont(aFont);
1706 
1707     // Draw background rectangle
1708     tools::Rectangle aBgRect(rRect);
1709     int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
1710     aBgRect.Move(SPACE_AROUND_TITLE, SPACE_AROUND_TITLE);
1711     aBgRect.setWidth(aBgRect.getOpenWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX);
1712     aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE);
1713     rRenderContext.DrawRect(aBgRect);
1714 
1715     // Draw the text centered
1716     Point aTextTopLeft(aBgRect.TopLeft());
1717     tools::Rectangle aTextBoundRect;
1718     rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText );
1719     aTextTopLeft.AdjustX((aBgRect.getOpenWidth() - aTextBoundRect.GetSize().Width()) / 2 );
1720     aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2
1721                         - aTextBoundRect.Top() );
1722     rRenderContext.DrawText(aTextTopLeft, aTitleText, 0, aTitleText.getLength());
1723 
1724     // Restore
1725     rRenderContext.Pop();
1726     rRenderContext.SetBackground(aOldBackground);
1727 }
1728 
ImplPaint(vcl::RenderContext & rRenderContext,Size const & rSize,sal_uInt16 nBorder,tools::Long nStartY,MenuItemData const * pThisItemOnly,bool bHighlighted,bool bLayout,bool bRollover) const1729 void Menu::ImplPaint(vcl::RenderContext& rRenderContext, Size const & rSize,
1730                      sal_uInt16 nBorder, tools::Long nStartY, MenuItemData const * pThisItemOnly,
1731                      bool bHighlighted, bool bLayout, bool bRollover) const
1732 {
1733     // for symbols: nFontHeight x nFontHeight
1734     tools::Long nFontHeight = rRenderContext.GetTextHeight();
1735     tools::Long nExtra = nFontHeight / 4;
1736 
1737     tools::Long nCheckHeight = 0, nRadioHeight = 0;
1738     ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight);
1739 
1740     DecorationView aDecoView(&rRenderContext);
1741     const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
1742 
1743     Point aTopLeft, aTmpPos;
1744 
1745     int nOuterSpaceX = 0;
1746     if (!IsMenuBar())
1747     {
1748         nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX;
1749         aTopLeft.AdjustX(nOuterSpaceX );
1750         aTopLeft.AdjustY(ImplGetSVData()->maNWFData.mnMenuFormatBorderY );
1751     }
1752 
1753     // for the computations, use size of the underlying window, not of RenderContext
1754     Size aOutSz(rSize);
1755 
1756     size_t nCount = pItemList->size();
1757     if (bLayout)
1758         mpLayoutData->m_aVisibleItemBoundRects.clear();
1759 
1760     // Paint title
1761     if (!pThisItemOnly && !IsMenuBar() && nTitleHeight > 0)
1762         ImplPaintMenuTitle(rRenderContext, tools::Rectangle(aTopLeft, aOutSz));
1763 
1764     bool bHiddenItems = false; // are any items on the GUI hidden
1765 
1766     for (size_t n = 0; n < nCount; n++)
1767     {
1768         MenuItemData* pData = pItemList->GetDataFromPos( n );
1769         if (ImplIsVisible(n) && (!pThisItemOnly || (pData == pThisItemOnly)))
1770         {
1771             if (pThisItemOnly)
1772             {
1773                 if (IsMenuBar())
1774                 {
1775                     if (!ImplGetSVData()->maNWFData.mbRolloverMenubar)
1776                     {
1777                         if (bRollover)
1778                             rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
1779                         else if (bHighlighted)
1780                             rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
1781                     }
1782                     else
1783                     {
1784                         if (bHighlighted)
1785                             rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor());
1786                         else if (bRollover)
1787                             rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor());
1788                     }
1789                     if (!bRollover && !bHighlighted)
1790                         rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
1791                 }
1792                 else if (bHighlighted)
1793                     rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor());
1794             }
1795 
1796             Point aPos(aTopLeft);
1797             aPos.AdjustY(nBorder );
1798             aPos.AdjustY(nStartY );
1799 
1800             if (aPos.Y() >= 0)
1801             {
1802                 tools::Long nTextOffsetY = (pData->aSz.Height() - nFontHeight) / 2;
1803                 if (IsMenuBar())
1804                     nTextOffsetY += (aOutSz.Height()-pData->aSz.Height()) / 2;
1805                 DrawTextFlags   nTextStyle   = DrawTextFlags::NONE;
1806                 DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
1807                 DrawImageFlags  nImageStyle  = DrawImageFlags::NONE;
1808 
1809                 // submenus without items are not disabled when no items are
1810                 // contained. The application itself should check for this!
1811                 // Otherwise it could happen entries are disabled due to
1812                 // asynchronous loading
1813                 if (!pData->bEnabled || !m_pWindow->IsEnabled())
1814                 {
1815                     nTextStyle   |= DrawTextFlags::Disable;
1816                     nSymbolStyle |= DrawSymbolFlags::Disable;
1817                     nImageStyle  |= DrawImageFlags::Disable;
1818                 }
1819 
1820                 // Separator
1821                 if (!bLayout && !IsMenuBar() && (pData->eType == MenuItemType::SEPARATOR))
1822                 {
1823                     bool bNativeOk = false;
1824                     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
1825                     {
1826                         ControlState nState = ControlState::NONE;
1827                         if (pData->bEnabled && m_pWindow->IsEnabled())
1828                             nState |= ControlState::ENABLED;
1829                         if (bHighlighted)
1830                             nState |= ControlState::SELECTED;
1831                         Size aSz(pData->aSz);
1832                         aSz.setWidth( aOutSz.Width() - 2*nOuterSpaceX );
1833                         tools::Rectangle aItemRect(aPos, aSz);
1834                         MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
1835                         bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
1836                                                                      aItemRect, nState, aVal, OUString());
1837                     }
1838                     if (!bNativeOk)
1839                     {
1840                         aTmpPos.setY( aPos.Y() + ((pData->aSz.Height() - 2) / 2) );
1841                         aTmpPos.setX( aPos.X() + 2 + nOuterSpaceX );
1842                         rRenderContext.SetLineColor(rSettings.GetShadowColor());
1843                         rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
1844                         aTmpPos.AdjustY( 1 );
1845                         rRenderContext.SetLineColor(rSettings.GetLightColor());
1846                         rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y()));
1847                         rRenderContext.SetLineColor();
1848                     }
1849                 }
1850 
1851                 tools::Rectangle aOuterCheckRect(Point(aPos.X()+nImgOrChkPos, aPos.Y()),
1852                                           Size(pData->aSz.Height(), pData->aSz.Height()));
1853 
1854                 // CheckMark
1855                 if (!bLayout && !IsMenuBar() && pData->HasCheck())
1856                 {
1857                     // draw selection transparent marker if checked
1858                     // onto that either a checkmark or the item image
1859                     // will be painted
1860                     // however do not do this if native checks will be painted since
1861                     // the selection color too often does not fit the theme's check and/or radio
1862 
1863                     if( (pData->eType != MenuItemType::IMAGE) && (pData->eType != MenuItemType::STRINGIMAGE))
1864                     {
1865                         if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup,
1866                                                                     (pData->nBits & MenuItemBits::RADIOCHECK)
1867                                                                         ? ControlPart::MenuItemCheckMark
1868                                                                         : ControlPart::MenuItemRadioMark))
1869                         {
1870                             ControlPart nPart = ((pData->nBits & MenuItemBits::RADIOCHECK)
1871                                                  ? ControlPart::MenuItemRadioMark
1872                                                  : ControlPart::MenuItemCheckMark);
1873 
1874                             ControlState nState = ControlState::NONE;
1875 
1876                             if (pData->bChecked)
1877                                 nState |= ControlState::PRESSED;
1878 
1879                             if (pData->bEnabled && m_pWindow->IsEnabled())
1880                                 nState |= ControlState::ENABLED;
1881 
1882                             if (bHighlighted)
1883                                 nState |= ControlState::SELECTED;
1884 
1885                             tools::Long nCtrlHeight = (pData->nBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
1886                             aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 );
1887                             aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 );
1888 
1889                             tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight));
1890                             Size aSz(pData->aSz);
1891                             aSz.setWidth( aOutSz.Width() - 2 * nOuterSpaceX );
1892                             tools::Rectangle aItemRect(aPos, aSz);
1893                             MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
1894                             rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect,
1895                                                              nState, aVal, OUString());
1896                         }
1897                         else if (pData->bChecked) // by default do nothing for unchecked items
1898                         {
1899                             ImplPaintCheckBackground(rRenderContext, *m_pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
1900 
1901                             SymbolType eSymbol;
1902                             Size aSymbolSize;
1903                             if (pData->nBits & MenuItemBits::RADIOCHECK)
1904                             {
1905                                 eSymbol = SymbolType::RADIOCHECKMARK;
1906                                 aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2);
1907                             }
1908                             else
1909                             {
1910                                 eSymbol = SymbolType::CHECKMARK;
1911                                 aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2);
1912                             }
1913                             aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width()) / 2 );
1914                             aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height()) / 2 );
1915                             tools::Rectangle aRect(aTmpPos, aSymbolSize);
1916                             aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetTextColor(), nSymbolStyle);
1917                         }
1918                     }
1919                 }
1920 
1921                 // Image:
1922                 if (!bLayout && !IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE)))
1923                 {
1924                     // Don't render an image for a check thing
1925                     if (pData->bChecked)
1926                         ImplPaintCheckBackground(rRenderContext, *m_pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted);
1927 
1928                     Image aImage = pData->aImage;
1929 
1930                     aTmpPos = aOuterCheckRect.TopLeft();
1931                     aTmpPos.AdjustX((aOuterCheckRect.GetWidth() - aImage.GetSizePixel().Width()) / 2 );
1932                     aTmpPos.AdjustY((aOuterCheckRect.GetHeight() - aImage.GetSizePixel().Height()) / 2 );
1933                     rRenderContext.DrawImage(aTmpPos, aImage, nImageStyle);
1934                 }
1935 
1936                 // Text:
1937                 if ((pData->eType == MenuItemType::STRING ) || (pData->eType == MenuItemType::STRINGIMAGE))
1938                 {
1939                     aTmpPos.setX( aPos.X() + nTextPos );
1940                     aTmpPos.setY( aPos.Y() );
1941                     aTmpPos.AdjustY(nTextOffsetY );
1942                     DrawTextFlags nStyle = nTextStyle;
1943 
1944                     const Menu *pMenu = this;
1945                     while (!pMenu->IsMenuBar() && pMenu->pStartedFrom)
1946                         pMenu = pMenu->pStartedFrom;
1947                     if (!pMenu->IsMenuBar() || !static_cast<MenuBarWindow*>(pMenu->m_pWindow.get())->GetMBWHideAccel())
1948                         nStyle |= DrawTextFlags::Mnemonic;
1949 
1950                     if (pData->bIsTemporary)
1951                         nStyle |= DrawTextFlags::Disable;
1952                     std::vector< tools::Rectangle >* pVector = bLayout ? &mpLayoutData->m_aUnicodeBoundRects : nullptr;
1953                     OUString* pDisplayText = bLayout ? &mpLayoutData->m_aDisplayText : nullptr;
1954                     if (bLayout)
1955                     {
1956                         mpLayoutData->m_aLineIndices.push_back(mpLayoutData->m_aDisplayText.getLength());
1957                         mpLayoutData->m_aLineItemIds.push_back(pData->nId);
1958                     }
1959                     // #i47946# with NWF painted menus the background is transparent
1960                     // since DrawCtrlText can depend on the background (e.g. for
1961                     // DrawTextFlags::Disable), temporarily set a background which
1962                     // hopefully matches the NWF background since it is read
1963                     // from the system style settings
1964                     bool bSetTmpBackground = !rRenderContext.IsBackground()
1965                                            && rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire);
1966                     if (bSetTmpBackground)
1967                     {
1968                         Color aBg = IsMenuBar() ? rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor()
1969                                                 : rRenderContext.GetSettings().GetStyleSettings().GetMenuColor();
1970                         rRenderContext.SetBackground(Wallpaper(aBg));
1971                     }
1972                     // how much space is there for the text?
1973                     tools::Long nMaxItemTextWidth = aOutSz.Width() - aTmpPos.X() - nExtra - nOuterSpaceX;
1974                     if (!IsMenuBar() && pData->aAccelKey.GetCode() && !officecfg::VCL::VCLSettings::Menu::SuppressAccelerators::get())
1975                     {
1976                         OUString aAccText = pData->aAccelKey.GetName();
1977                         nMaxItemTextWidth -= rRenderContext.GetTextWidth(aAccText) + 3 * nExtra;
1978                     }
1979                     if (!IsMenuBar() && pData->pSubMenu)
1980                     {
1981                         nMaxItemTextWidth -= nFontHeight - nExtra;
1982                     }
1983 
1984                     OUString aItemText(pData->aText);
1985                     pData->bHiddenOnGUI = false;
1986 
1987                     if (IsMenuBar()) // In case of menubar if we are out of bounds we shouldn't paint the item
1988                     {
1989                         if (nMaxItemTextWidth < rRenderContext.GetTextWidth(aItemText))
1990                         {
1991                             aItemText = "";
1992                             pData->bHiddenOnGUI = true;
1993                             bHiddenItems = true;
1994                         }
1995                     }
1996                     else
1997                     {
1998                         aItemText = getShortenedString(aItemText, rRenderContext, nMaxItemTextWidth);
1999                         pData->bHiddenOnGUI = false;
2000                     }
2001 
2002                     const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(&rRenderContext);
2003                     if (aItemText != pData->aText)
2004                         // Can't use pre-computed glyphs, item text was
2005                         // changed.
2006                         pGlyphs = nullptr;
2007                     rRenderContext.DrawCtrlText(aTmpPos, aItemText, 0, aItemText.getLength(),
2008                                                 nStyle, pVector, pDisplayText, pGlyphs);
2009                     if (bSetTmpBackground)
2010                         rRenderContext.SetBackground();
2011                 }
2012 
2013                 // Accel
2014                 if (!bLayout && !IsMenuBar() && pData->aAccelKey.GetCode() && !officecfg::VCL::VCLSettings::Menu::SuppressAccelerators::get())
2015                 {
2016                     OUString aAccText = pData->aAccelKey.GetName();
2017                     aTmpPos.setX( aOutSz.Width() - rRenderContext.GetTextWidth(aAccText) );
2018                     aTmpPos.AdjustX( -(4 * nExtra) );
2019 
2020                     aTmpPos.AdjustX( -nOuterSpaceX );
2021                     aTmpPos.setY( aPos.Y() );
2022                     aTmpPos.AdjustY(nTextOffsetY );
2023                     rRenderContext.DrawCtrlText(aTmpPos, aAccText, 0, aAccText.getLength(), nTextStyle);
2024                 }
2025 
2026                 // SubMenu?
2027                 if (!bLayout && !IsMenuBar() && pData->pSubMenu)
2028                 {
2029                     bool bNativeOk = false;
2030                     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow))
2031                     {
2032                         ControlState nState = ControlState::NONE;
2033                         Size aTmpSz(0, 0);
2034                         tools::Long aSpacing = 0;
2035 
2036                         if (!ImplGetNativeSubmenuArrowSize(rRenderContext, aTmpSz, aSpacing))
2037                         {
2038                             aTmpSz = Size(nFontHeight, nFontHeight);
2039                             aSpacing = nOuterSpaceX;
2040                         }
2041 
2042                         if (pData->bEnabled && m_pWindow->IsEnabled())
2043                             nState |= ControlState::ENABLED;
2044                         if (bHighlighted)
2045                             nState |= ControlState::SELECTED;
2046 
2047                         aTmpPos.setX( aOutSz.Width() - aTmpSz.Width() - aSpacing - nOuterSpaceX );
2048                         aTmpPos.setY( aPos.Y() + ( pData->aSz.Height() - aTmpSz.Height() ) / 2 );
2049                         aTmpPos.AdjustY(nExtra / 2 );
2050 
2051                         tools::Rectangle aItemRect(aTmpPos, aTmpSz);
2052                         MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect);
2053                         bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::SubmenuArrow,
2054                                                                      aItemRect, nState, aVal, OUString());
2055                     }
2056                     if (!bNativeOk)
2057                     {
2058                         aTmpPos.setX( aOutSz.Width() - nFontHeight + nExtra - nOuterSpaceX );
2059                         aTmpPos.setY( aPos.Y() );
2060                         aTmpPos.AdjustY(nExtra/2 );
2061                         aTmpPos.AdjustY((pData->aSz.Height() / 2) - (nFontHeight / 4) );
2062                         if (pData->nBits & MenuItemBits::POPUPSELECT)
2063                         {
2064                             rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
2065                             Point aTmpPos2(aPos);
2066                             aTmpPos2.setX( aOutSz.Width() - nFontHeight - nFontHeight/4 );
2067                             aDecoView.DrawFrame(tools::Rectangle(aTmpPos2, Size(nFontHeight + nFontHeight / 4,
2068                                                                          pData->aSz.Height())),
2069                                                 DrawFrameStyle::Group);
2070                         }
2071                         aDecoView.DrawSymbol(tools::Rectangle(aTmpPos, Size(nFontHeight / 2, nFontHeight / 2)),
2072                                              SymbolType::SPIN_RIGHT, rRenderContext.GetTextColor(), nSymbolStyle);
2073                     }
2074                 }
2075 
2076                 if (pThisItemOnly && bHighlighted)
2077                 {
2078                     // This restores the normal menu or menu bar text
2079                     // color for when it is no longer highlighted.
2080                     if (IsMenuBar())
2081                         rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor());
2082                     else
2083                         rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
2084                 }
2085             }
2086             if( bLayout )
2087             {
2088                 if (!IsMenuBar())
2089                     mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, Size(aOutSz.Width(), pData->aSz.Height()));
2090                 else
2091                     mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, pData->aSz);
2092             }
2093         }
2094 
2095         if (!IsMenuBar())
2096             aTopLeft.AdjustY(pData->aSz.Height() );
2097         else
2098             aTopLeft.AdjustX(pData->aSz.Width() );
2099     }
2100 
2101     // draw "more" (">>") indicator if some items have been hidden as they go out of visible area
2102     if (bHiddenItems)
2103     {
2104         sal_Int32 nSize = nFontHeight;
2105         tools::Rectangle aRectangle(Point(aOutSz.Width() - nSize, (aOutSz.Height() / 2) - (nSize / 2)), Size(nSize, nSize));
2106         lclDrawMoreIndicator(rRenderContext, aRectangle);
2107     }
2108 }
2109 
ImplGetStartMenu()2110 Menu* Menu::ImplGetStartMenu()
2111 {
2112     Menu* pStart = this;
2113     while ( pStart && pStart->pStartedFrom && ( pStart->pStartedFrom != pStart ) )
2114         pStart = pStart->pStartedFrom;
2115     return pStart;
2116 }
2117 
ImplCallHighlight(sal_uInt16 nItem)2118 void Menu::ImplCallHighlight(sal_uInt16 nItem)
2119 {
2120     ImplMenuDelData aDelData( this );
2121 
2122     nSelectedId = 0;
2123     sSelectedIdent.clear();
2124     MenuItemData* pData = pItemList->GetDataFromPos(nItem);
2125     if (pData)
2126     {
2127         nSelectedId = pData->nId;
2128         sSelectedIdent = pData->sIdent;
2129     }
2130     ImplCallEventListeners( VclEventId::MenuHighlight, GetItemPos( GetCurItemId() ) );
2131 
2132     if( !aDelData.isDeleted() )
2133     {
2134         nSelectedId = 0;
2135         sSelectedIdent.clear();
2136     }
2137 }
2138 
IMPL_LINK_NOARG(Menu,ImplCallSelect,void *,void)2139 IMPL_LINK_NOARG(Menu, ImplCallSelect, void*, void)
2140 {
2141     nEventId = nullptr;
2142     Select();
2143 }
2144 
ImplFindSelectMenu()2145 Menu* Menu::ImplFindSelectMenu()
2146 {
2147     Menu* pSelMenu = nEventId ? this : nullptr;
2148 
2149     for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
2150     {
2151         MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
2152 
2153         if ( pData->pSubMenu )
2154             pSelMenu = pData->pSubMenu->ImplFindSelectMenu();
2155     }
2156 
2157     return pSelMenu;
2158 }
2159 
ImplFindMenu(sal_uInt16 nItemId)2160 Menu* Menu::ImplFindMenu( sal_uInt16 nItemId )
2161 {
2162     Menu* pSelMenu = nullptr;
2163 
2164     for ( size_t n = GetItemList()->size(); n && !pSelMenu; )
2165     {
2166         MenuItemData* pData = GetItemList()->GetDataFromPos( --n );
2167 
2168         if( pData->nId == nItemId )
2169             pSelMenu = this;
2170         else if ( pData->pSubMenu )
2171             pSelMenu = pData->pSubMenu->ImplFindMenu( nItemId );
2172     }
2173 
2174     return pSelMenu;
2175 }
2176 
RemoveDisabledEntries(bool bRemoveEmptyPopups)2177 void Menu::RemoveDisabledEntries( bool bRemoveEmptyPopups )
2178 {
2179     sal_uInt16 n = 0;
2180     while (n < GetItemCount())
2181     {
2182         bool bRemove = false;
2183         MenuItemData* pItem = pItemList->GetDataFromPos( n );
2184         if ( pItem->eType == MenuItemType::SEPARATOR )
2185         {
2186             if ( !n || ( GetItemType( n-1 ) == MenuItemType::SEPARATOR ) )
2187                 bRemove = true;
2188         }
2189         else
2190             bRemove = !pItem->bEnabled;
2191 
2192         if ( pItem->pSubMenu )
2193         {
2194             pItem->pSubMenu->RemoveDisabledEntries();
2195             if ( bRemoveEmptyPopups && !pItem->pSubMenu->GetItemCount() )
2196                 bRemove = true;
2197         }
2198 
2199         if (bRemove)
2200             RemoveItem(n);
2201         else
2202             ++n;
2203     }
2204 
2205     if ( GetItemCount() )
2206     {
2207         sal_uInt16 nLast = GetItemCount() - 1;
2208         MenuItemData* pItem = pItemList->GetDataFromPos( nLast );
2209         if ( pItem->eType == MenuItemType::SEPARATOR )
2210             RemoveItem( nLast );
2211     }
2212     mpLayoutData.reset();
2213 }
2214 
UpdateNativeMenu()2215 void Menu::UpdateNativeMenu()
2216 {
2217     if ( ImplGetSalMenu() )
2218         ImplGetSalMenu()->Update();
2219 }
2220 
MenuBarKeyInput(const KeyEvent &)2221 void Menu::MenuBarKeyInput(const KeyEvent&)
2222 {
2223 }
2224 
ImplKillLayoutData() const2225 void Menu::ImplKillLayoutData() const
2226 {
2227     mpLayoutData.reset();
2228 }
2229 
ImplFillLayoutData() const2230 void Menu::ImplFillLayoutData() const
2231 {
2232     if (!(m_pWindow && m_pWindow->IsReallyVisible()))
2233         return;
2234 
2235     mpLayoutData.reset(new MenuLayoutData);
2236     if (IsMenuBar())
2237     {
2238         ImplPaint(*m_pWindow->GetOutDev(), m_pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME
2239     }
2240     else
2241     {
2242         MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(m_pWindow.get());
2243         ImplPaint(*m_pWindow->GetOutDev(), m_pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(),
2244                   nullptr, false, true); //FIXME
2245     }
2246 }
2247 
GetCharacterBounds(sal_uInt16 nItemID,tools::Long nIndex) const2248 tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, tools::Long nIndex ) const
2249 {
2250     tools::Long nItemIndex = -1;
2251     if( ! mpLayoutData )
2252         ImplFillLayoutData();
2253     if( mpLayoutData )
2254     {
2255         auto it = std::find(mpLayoutData->m_aLineItemIds.begin(), mpLayoutData->m_aLineItemIds.end(), nItemID);
2256         if (it != mpLayoutData->m_aLineItemIds.end())
2257         {
2258             nItemIndex = mpLayoutData->m_aLineIndices[std::distance(mpLayoutData->m_aLineItemIds.begin(), it)];
2259         }
2260     }
2261     return (mpLayoutData && nItemIndex != -1) ? mpLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle();
2262 }
2263 
GetIndexForPoint(const Point & rPoint,sal_uInt16 & rItemID) const2264 tools::Long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const
2265 {
2266     rItemID = 0;
2267     if( ! mpLayoutData )
2268         ImplFillLayoutData();
2269     if( mpLayoutData )
2270     {
2271         tools::Long nIndex = mpLayoutData->GetIndexForPoint( rPoint );
2272         for( size_t i = 0; i < mpLayoutData->m_aLineIndices.size(); i++ )
2273         {
2274             if( mpLayoutData->m_aLineIndices[i] <= nIndex &&
2275                 (i == mpLayoutData->m_aLineIndices.size()-1 || mpLayoutData->m_aLineIndices[i+1] > nIndex) )
2276             {
2277                 rItemID = mpLayoutData->m_aLineItemIds[i];
2278                 // return index relative to item
2279                 return nIndex - mpLayoutData->m_aLineIndices[i];
2280             }
2281         }
2282     }
2283     return -1;
2284 }
2285 
GetBoundingRectangle(sal_uInt16 nPos) const2286 tools::Rectangle Menu::GetBoundingRectangle( sal_uInt16 nPos ) const
2287 {
2288     tools::Rectangle aRet;
2289 
2290     if (!mpLayoutData )
2291         ImplFillLayoutData();
2292     if (mpLayoutData)
2293     {
2294         std::map< sal_uInt16, tools::Rectangle >::const_iterator it = mpLayoutData->m_aVisibleItemBoundRects.find( nPos );
2295         if( it != mpLayoutData->m_aVisibleItemBoundRects.end() )
2296             aRet = it->second;
2297     }
2298     return aRet;
2299 }
2300 
SetAccessibleName(sal_uInt16 nItemId,const OUString & rStr)2301 void Menu::SetAccessibleName( sal_uInt16 nItemId, const OUString& rStr )
2302 {
2303     size_t nPos;
2304     MenuItemData* pData = pItemList->GetData( nItemId, nPos );
2305 
2306     if (pData && !rStr.equals(pData->aAccessibleName))
2307     {
2308         pData->aAccessibleName = rStr;
2309         ImplCallEventListeners(VclEventId::MenuAccessibleNameChanged, nPos);
2310     }
2311 }
2312 
GetAccessibleName(sal_uInt16 nItemId) const2313 OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const
2314 {
2315     MenuItemData* pData = pItemList->GetData( nItemId );
2316 
2317     if ( pData )
2318         return pData->aAccessibleName;
2319 
2320     return OUString();
2321 }
2322 
SetAccessibleDescription(sal_uInt16 nItemId,const OUString & rStr)2323 void Menu::SetAccessibleDescription( sal_uInt16 nItemId, const OUString& rStr )
2324 {
2325     MenuItemData* pData = pItemList->GetData( nItemId );
2326 
2327     if ( pData )
2328         pData->aAccessibleDescription = rStr;
2329 }
2330 
GetAccessibleDescription(sal_uInt16 nItemId) const2331 OUString Menu::GetAccessibleDescription( sal_uInt16 nItemId ) const
2332 {
2333     MenuItemData* pData = pItemList->GetData( nItemId );
2334 
2335     if (pData && !pData->aAccessibleDescription.isEmpty())
2336         return pData->aAccessibleDescription;
2337 
2338     return GetHelpText(nItemId);
2339 }
2340 
GetSystemMenuData(SystemMenuData & rData) const2341 void Menu::GetSystemMenuData(SystemMenuData& rData) const
2342 {
2343     if (ImplGetSalMenu())
2344     {
2345         ImplGetSalMenu()->GetSystemMenuData(rData);
2346     }
2347 }
2348 
IsHighlighted(sal_uInt16 nItemPos) const2349 bool Menu::IsHighlighted( sal_uInt16 nItemPos ) const
2350 {
2351     bool bRet = false;
2352 
2353     if (m_pWindow)
2354     {
2355         if (IsMenuBar())
2356             bRet = ( nItemPos == static_cast< MenuBarWindow * > (m_pWindow.get())->GetHighlightedItem() );
2357         else
2358             bRet = ( nItemPos == static_cast< MenuFloatingWindow * > (m_pWindow.get())->GetHighlightedItem() );
2359     }
2360 
2361     return bRet;
2362 }
2363 
HighlightItem(sal_uInt16 nItemPos)2364 void Menu::HighlightItem( sal_uInt16 nItemPos )
2365 {
2366     if ( !m_pWindow )
2367         return;
2368 
2369     if (IsMenuBar())
2370     {
2371         MenuBarWindow* pMenuWin = static_cast< MenuBarWindow* >( m_pWindow.get() );
2372         pMenuWin->SetAutoPopup( false );
2373         pMenuWin->ChangeHighlightItem( nItemPos, false );
2374     }
2375     else
2376     {
2377         static_cast< MenuFloatingWindow* >( m_pWindow.get() )->ChangeHighlightItem( nItemPos, false );
2378     }
2379 }
2380 
DumpAsPropertyTree(tools::JsonWriter & rJsonWriter) const2381 void Menu::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) const
2382 {
2383     rJsonWriter.put("id", "__MENU__"); // we have single instance of menu at the time per session
2384     rJsonWriter.put("type", "menu");
2385     rJsonWriter.put("count", GetItemCount());
2386     {
2387         auto aEntries = rJsonWriter.startArray("entries");
2388         for (size_t i = 0; i < GetItemCount(); i++)
2389         {
2390             auto aEntry = rJsonWriter.startStruct();
2391             sal_uInt16 nId = GetItemId(i);
2392             rJsonWriter.put("row", GetItemIdent(nId));
2393             {
2394                 auto aColumns = rJsonWriter.startArray("columns");
2395                 auto aColumn = rJsonWriter.startStruct();
2396                 rJsonWriter.put("text", GetItemText(nId));
2397             }
2398         }
2399     }
2400 }
2401 
getMenuBarWindow()2402 MenuBarWindow* MenuBar::getMenuBarWindow()
2403 {
2404     // so far just a dynamic_cast, hopefully to be turned into something saner
2405     // at some stage
2406     MenuBarWindow *pWin = dynamic_cast<MenuBarWindow*>(m_pWindow.get());
2407     //either there is no window (fdo#87663) or it is a MenuBarWindow
2408     assert(!m_pWindow || pWin);
2409     return pWin;
2410 }
2411 
MenuBar()2412 MenuBar::MenuBar()
2413     : mbCloseBtnVisible(false),
2414       mbFloatBtnVisible(false),
2415       mbHideBtnVisible(false),
2416       mbDisplayable(true)
2417 {
2418     mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
2419 }
2420 
MenuBar(const MenuBar & rMenu)2421 MenuBar::MenuBar( const MenuBar& rMenu )
2422     : mbCloseBtnVisible(false),
2423       mbFloatBtnVisible(false),
2424       mbHideBtnVisible(false),
2425       mbDisplayable(true)
2426 {
2427     mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this);
2428     *this = rMenu;
2429 }
2430 
~MenuBar()2431 MenuBar::~MenuBar()
2432 {
2433     disposeOnce();
2434 }
2435 
dispose()2436 void MenuBar::dispose()
2437 {
2438     ImplDestroy(true);
2439     Menu::dispose();
2440 }
2441 
ClosePopup(PopupMenu * pPopupMenu)2442 void MenuBar::ClosePopup(PopupMenu *pPopupMenu)
2443 {
2444     MenuBarWindow* pMenuWin = getMenuBarWindow();
2445     if (!pMenuWin)
2446         return;
2447     pMenuWin->PopupClosed(pPopupMenu);
2448 }
2449 
MenuBarKeyInput(const KeyEvent & rEvent)2450 void MenuBar::MenuBarKeyInput(const KeyEvent& rEvent)
2451 {
2452     m_pWindow->KeyInput(rEvent);
2453 }
2454 
ShowCloseButton(bool bShow)2455 void MenuBar::ShowCloseButton(bool bShow)
2456 {
2457     ShowButtons( bShow, mbFloatBtnVisible, mbHideBtnVisible );
2458 }
2459 
ShowButtons(bool bClose,bool bFloat,bool bHide)2460 void MenuBar::ShowButtons( bool bClose, bool bFloat, bool bHide )
2461 {
2462     if ((bClose != mbCloseBtnVisible) ||
2463         (bFloat != mbFloatBtnVisible) ||
2464         (bHide  != mbHideBtnVisible))
2465     {
2466         mbCloseBtnVisible = bClose;
2467         mbFloatBtnVisible = bFloat;
2468         mbHideBtnVisible = bHide;
2469         MenuBarWindow* pMenuWin = getMenuBarWindow();
2470         if (pMenuWin)
2471             pMenuWin->ShowButtons(bClose, bFloat, bHide);
2472     }
2473 }
2474 
LayoutChanged()2475 void MenuBar::LayoutChanged()
2476 {
2477     MenuBarWindow* pMenuWin = getMenuBarWindow();
2478     if (pMenuWin)
2479         pMenuWin->LayoutChanged();
2480 }
2481 
SetDisplayable(bool bDisplayable)2482 void MenuBar::SetDisplayable( bool bDisplayable )
2483 {
2484     if( bDisplayable != mbDisplayable )
2485     {
2486         if ( ImplGetSalMenu() )
2487             ImplGetSalMenu()->ShowMenuBar( bDisplayable );
2488 
2489         mbDisplayable = bDisplayable;
2490         LayoutChanged();
2491     }
2492 }
2493 
ImplCreate(vcl::Window * pParent,MenuBarWindow * pWindow)2494 VclPtr<MenuBarWindow> MenuBar::ImplCreate(vcl::Window* pParent, MenuBarWindow* pWindow)
2495 {
2496     VclPtr<MenuBarWindow> pMenuBarWindow = pWindow;
2497     if (!pMenuBarWindow)
2498     {
2499         pMenuBarWindow = VclPtr<MenuBarWindow>::Create(pParent);
2500     }
2501 
2502     pStartedFrom = nullptr;
2503     m_pWindow = pMenuBarWindow;
2504     pMenuBarWindow->SetMenu(this);
2505     tools::Long nHeight = ImplCalcSize(pMenuBarWindow).Height();
2506 
2507     // depending on the native implementation or the displayable flag
2508     // the menubar windows is suppressed (ie, height=0)
2509     if (!IsDisplayable() || (ImplGetSalMenu() && ImplGetSalMenu()->VisibleMenuBar()))
2510     {
2511         nHeight = 0;
2512     }
2513 
2514     pMenuBarWindow->SetHeight(nHeight);
2515     return pMenuBarWindow;
2516 }
2517 
ImplDestroy(bool bDelete)2518 void MenuBar::ImplDestroy(bool bDelete)
2519 {
2520     MenuBarWindow* pMenuWin = getMenuBarWindow();
2521     if (pMenuWin && bDelete)
2522     {
2523         pMenuWin->KillActivePopup();
2524         pMenuWin->disposeOnce();
2525     }
2526     m_pWindow = nullptr;
2527     if (mpSalMenu)
2528     {
2529         mpSalMenu->ShowMenuBar(false);
2530     }
2531 }
2532 
ImplHandleKeyEvent(const KeyEvent & rKEvent)2533 bool MenuBar::ImplHandleKeyEvent( const KeyEvent& rKEvent )
2534 {
2535     // No keyboard processing when our menubar is invisible
2536     if (!IsDisplayable())
2537         return false;
2538 
2539     // No keyboard processing when system handles the menu.
2540     SalMenu *pNativeMenu = ImplGetSalMenu();
2541     if (pNativeMenu && pNativeMenu->VisibleMenuBar())
2542     {
2543         // Except when the event is the F6 cycle pane event and we can put our
2544         // focus into it (i.e. the gtk3 menubar case but not the mac/unity case
2545         // where it's not part of the application window)
2546         if (!TaskPaneList::IsCycleKey(rKEvent.GetKeyCode()))
2547             return false;
2548         if (!pNativeMenu->CanGetFocus())
2549             return false;
2550     }
2551 
2552     bool bDone = false;
2553     // check for enabled, if this method is called from another window...
2554     vcl::Window* pWin = GetWindow();
2555     if (pWin && pWin->IsEnabled() && pWin->IsInputEnabled()  && !pWin->IsInModalMode())
2556     {
2557         MenuBarWindow* pMenuWin = getMenuBarWindow();
2558         bDone = pMenuWin && pMenuWin->HandleKeyEvent(rKEvent, false/*bFromMenu*/);
2559     }
2560     return bDone;
2561 }
2562 
ImplHandleCmdEvent(const CommandEvent & rCEvent)2563 bool MenuBar::ImplHandleCmdEvent( const CommandEvent& rCEvent )
2564 {
2565     // No keyboard processing when system handles the menu or our menubar is invisible
2566     if( !IsDisplayable() ||
2567         ( ImplGetSalMenu() && ImplGetSalMenu()->VisibleMenuBar() ) )
2568         return false;
2569 
2570     // check for enabled, if this method is called from another window...
2571     MenuBarWindow* pWin = static_cast<MenuBarWindow*>(GetWindow());
2572     if ( pWin && pWin->IsEnabled() && pWin->IsInputEnabled()  && ! pWin->IsInModalMode() )
2573     {
2574         if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel)
2575         {
2576             const CommandModKeyData* pCData = rCEvent.GetModKeyData ();
2577             if (pWin->m_nHighlightedItem == ITEMPOS_INVALID)
2578             {
2579                 if (pCData && pCData->IsMod2() && pCData->IsDown())
2580                     pWin->SetMBWHideAccel(false);
2581                 pWin->Invalidate(InvalidateFlags::Update);
2582             }
2583             return true;
2584         }
2585     }
2586     return false;
2587 }
2588 
SelectItem(sal_uInt16 nId)2589 void MenuBar::SelectItem(sal_uInt16 nId)
2590 {
2591     if (!m_pWindow)
2592         return;
2593 
2594     m_pWindow->GrabFocus();
2595     nId = GetItemPos( nId );
2596 
2597     MenuBarWindow* pMenuWin = getMenuBarWindow();
2598     if (pMenuWin)
2599     {
2600         // #99705# popup the selected menu
2601         pMenuWin->SetAutoPopup( true );
2602         if (ITEMPOS_INVALID != pMenuWin->GetHighlightedItem())
2603         {
2604             pMenuWin->KillActivePopup();
2605             pMenuWin->ChangeHighlightItem( ITEMPOS_INVALID, false );
2606         }
2607         if (nId != ITEMPOS_INVALID)
2608             pMenuWin->ChangeHighlightItem( nId, false );
2609     }
2610 }
2611 
2612 // handler for native menu selection and command events
HandleMenuActivateEvent(Menu * pMenu)2613 bool Menu::HandleMenuActivateEvent(Menu* pMenu)
2614 {
2615     if( pMenu )
2616     {
2617         ImplMenuDelData aDelData( this );
2618 
2619         pMenu->pStartedFrom = this;
2620         pMenu->bInCallback = true;
2621         pMenu->Activate();
2622 
2623         if( !aDelData.isDeleted() )
2624             pMenu->bInCallback = false;
2625     }
2626     return true;
2627 }
2628 
HandleMenuDeActivateEvent(Menu * pMenu)2629 bool Menu::HandleMenuDeActivateEvent(Menu* pMenu)
2630 {
2631     if( pMenu )
2632     {
2633         ImplMenuDelData aDelData( this );
2634 
2635         pMenu->pStartedFrom = this;
2636         pMenu->bInCallback = true;
2637         pMenu->Deactivate();
2638         if( !aDelData.isDeleted() )
2639             pMenu->bInCallback = false;
2640     }
2641     return true;
2642 }
2643 
HandleMenuHighlightEvent(Menu * pMenu,sal_uInt16 nHighlightEventId)2644 bool MenuBar::HandleMenuHighlightEvent(Menu* pMenu, sal_uInt16 nHighlightEventId)
2645 {
2646     if( !pMenu )
2647         pMenu = ImplFindMenu(nHighlightEventId);
2648     if( pMenu )
2649     {
2650         ImplMenuDelData aDelData( pMenu );
2651 
2652         if( mnHighlightedItemPos != ITEMPOS_INVALID )
2653             pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, mnHighlightedItemPos );
2654 
2655         if( !aDelData.isDeleted() )
2656         {
2657             pMenu->mnHighlightedItemPos = pMenu->GetItemPos( nHighlightEventId );
2658             pMenu->nSelectedId = nHighlightEventId;
2659             pMenu->sSelectedIdent = pMenu->GetItemIdent( nHighlightEventId );
2660             pMenu->pStartedFrom = this;
2661             pMenu->ImplCallHighlight( pMenu->mnHighlightedItemPos );
2662         }
2663         return true;
2664     }
2665     else
2666         return false;
2667 }
2668 
HandleMenuCommandEvent(Menu * pMenu,sal_uInt16 nCommandEventId)2669 bool Menu::HandleMenuCommandEvent(Menu* pMenu, sal_uInt16 nCommandEventId)
2670 {
2671     if( !pMenu )
2672         pMenu = ImplFindMenu(nCommandEventId);
2673     if( pMenu )
2674     {
2675         pMenu->nSelectedId = nCommandEventId;
2676         pMenu->sSelectedIdent = pMenu->GetItemIdent(nCommandEventId);
2677         pMenu->pStartedFrom = this;
2678         pMenu->ImplSelect();
2679         return true;
2680     }
2681     else
2682         return false;
2683 }
2684 
AddMenuBarButton(const Image & i_rImage,const Link<MenuBarButtonCallbackArg &,bool> & i_rLink,const OUString & i_rToolTip)2685 sal_uInt16 MenuBar::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip )
2686 {
2687     MenuBarWindow* pMenuWin = getMenuBarWindow();
2688     return pMenuWin ? pMenuWin->AddMenuBarButton(i_rImage, i_rLink, i_rToolTip) : 0;
2689 }
2690 
SetMenuBarButtonHighlightHdl(sal_uInt16 nId,const Link<MenuBarButtonCallbackArg &,bool> & rLink)2691 void MenuBar::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink )
2692 {
2693     MenuBarWindow* pMenuWin = getMenuBarWindow();
2694     if (!pMenuWin)
2695         return;
2696     pMenuWin->SetMenuBarButtonHighlightHdl(nId, rLink);
2697 }
2698 
RemoveMenuBarButton(sal_uInt16 nId)2699 void MenuBar::RemoveMenuBarButton( sal_uInt16 nId )
2700 {
2701     MenuBarWindow* pMenuWin = getMenuBarWindow();
2702     if (!pMenuWin)
2703         return;
2704     pMenuWin->RemoveMenuBarButton(nId);
2705 }
2706 
GetMenuBarButtonRectPixel(sal_uInt16 nId)2707 tools::Rectangle MenuBar::GetMenuBarButtonRectPixel( sal_uInt16 nId )
2708 {
2709     MenuBarWindow* pMenuWin = getMenuBarWindow();
2710     return pMenuWin ? pMenuWin->GetMenuBarButtonRectPixel(nId) : tools::Rectangle();
2711 }
2712 
HandleMenuButtonEvent(sal_uInt16 i_nButtonId)2713 bool MenuBar::HandleMenuButtonEvent( sal_uInt16 i_nButtonId )
2714 {
2715     MenuBarWindow* pMenuWin = getMenuBarWindow();
2716     return pMenuWin && pMenuWin->HandleMenuButtonEvent(i_nButtonId);
2717 }
2718 
GetMenuBarHeight() const2719 int MenuBar::GetMenuBarHeight() const
2720 {
2721     const SalMenu *pNativeMenu = ImplGetSalMenu();
2722     int nMenubarHeight;
2723     if (pNativeMenu)
2724         nMenubarHeight = pNativeMenu->GetMenuBarHeight();
2725     else
2726     {
2727         vcl::Window* pMenubarWin = GetWindow();
2728         nMenubarHeight = pMenubarWin ? pMenubarWin->GetOutputSizePixel().Height() : 0;
2729     }
2730     return nMenubarHeight;
2731 }
2732 
ImplGetFloatingWindow() const2733 MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const {
2734     return static_cast<MenuFloatingWindow*>(GetWindow());
2735 }
2736 
PopupMenu()2737 PopupMenu::PopupMenu()
2738 {
2739     mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
2740 }
2741 
PopupMenu(const PopupMenu & rMenu)2742 PopupMenu::PopupMenu( const PopupMenu& rMenu )
2743 {
2744     mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this);
2745     *this = rMenu;
2746 }
2747 
~PopupMenu()2748 PopupMenu::~PopupMenu()
2749 {
2750     disposeOnce();
2751 }
2752 
ClosePopup(PopupMenu * pPopupMenu)2753 void PopupMenu::ClosePopup(PopupMenu* pPopupMenu)
2754 {
2755     MenuFloatingWindow* p = ImplGetFloatingWindow();
2756     if (p && pPopupMenu && p->GetActivePopup() == pPopupMenu)
2757         p->KillActivePopup();
2758 }
2759 
2760 namespace vcl
2761 {
IsInPopupMenuExecute()2762     bool IsInPopupMenuExecute()
2763     {
2764         return PopupMenu::GetActivePopupMenu() != nullptr;
2765     }
2766 }
2767 
GetActivePopupMenu()2768 PopupMenu* PopupMenu::GetActivePopupMenu()
2769 {
2770     ImplSVData* pSVData = ImplGetSVData();
2771     return pSVData->maAppData.mpActivePopupMenu;
2772 }
2773 
EndExecute()2774 void PopupMenu::EndExecute()
2775 {
2776     if (GetWindow())
2777         ImplGetFloatingWindow()->EndExecute( 0 );
2778 }
2779 
SelectItem(sal_uInt16 nId)2780 void PopupMenu::SelectItem(sal_uInt16 nId)
2781 {
2782     if (!GetWindow())
2783         return;
2784 
2785     if( nId != ITEMPOS_INVALID )
2786     {
2787         size_t nPos = 0;
2788         MenuItemData* pData = GetItemList()->GetData( nId, nPos );
2789         if (pData && pData->pSubMenu)
2790             ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true );
2791         else
2792             ImplGetFloatingWindow()->EndExecute( nId );
2793     }
2794     else
2795     {
2796         MenuFloatingWindow* pFloat = ImplGetFloatingWindow();
2797         pFloat->GrabFocus();
2798 
2799         for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ )
2800         {
2801             MenuItemData* pData = GetItemList()->GetDataFromPos( nPos );
2802             if( pData->pSubMenu )
2803             {
2804                 pFloat->KillActivePopup();
2805             }
2806         }
2807         pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false );
2808     }
2809 }
2810 
SetSelectedEntry(sal_uInt16 nId)2811 void PopupMenu::SetSelectedEntry( sal_uInt16 nId )
2812 {
2813     nSelectedId = nId;
2814     sSelectedIdent = GetItemIdent(nId);
2815 }
2816 
Execute(vcl::Window * pExecWindow,const Point & rPopupPos)2817 sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos )
2818 {
2819     return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown );
2820 }
2821 
lcl_TranslateFlags(PopupMenuFlags nFlags)2822 static FloatWinPopupFlags lcl_TranslateFlags(PopupMenuFlags nFlags)
2823 {
2824     FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE;
2825     if ( nFlags & PopupMenuFlags::ExecuteDown )
2826         nPopupModeFlags = FloatWinPopupFlags::Down;
2827     else if ( nFlags & PopupMenuFlags::ExecuteUp )
2828         nPopupModeFlags = FloatWinPopupFlags::Up;
2829     else if ( nFlags & PopupMenuFlags::ExecuteRight )
2830         nPopupModeFlags = FloatWinPopupFlags::Right;
2831     else
2832         nPopupModeFlags = FloatWinPopupFlags::Down;
2833 
2834     if (nFlags & PopupMenuFlags::NoMouseUpClose )                      // allow popup menus to stay open on mouse button up
2835         nPopupModeFlags |= FloatWinPopupFlags::NoMouseUpClose;    // useful if the menu was opened on mousebutton down (eg toolbox configuration)
2836 
2837     return nPopupModeFlags;
2838 }
2839 
Execute(vcl::Window * pExecWindow,const tools::Rectangle & rRect,PopupMenuFlags nFlags)2840 sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags )
2841 {
2842     ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 );
2843     return ImplExecute( pExecWindow, rRect, lcl_TranslateFlags(nFlags), nullptr, false );
2844 }
2845 
ImplFlushPendingSelect()2846 void PopupMenu::ImplFlushPendingSelect()
2847 {
2848     // is there still Select?
2849     Menu* pSelect = ImplFindSelectMenu();
2850     if (pSelect)
2851     {
2852         // Select should be called prior to leaving execute in a popup menu!
2853         Application::RemoveUserEvent( pSelect->nEventId );
2854         pSelect->nEventId = nullptr;
2855         pSelect->Select();
2856     }
2857 }
2858 
ImplExecute(const VclPtr<vcl::Window> & pParentWin,const tools::Rectangle & rRect,FloatWinPopupFlags nPopupModeFlags,Menu * pSFrom,bool bPreSelectFirst)2859 sal_uInt16 PopupMenu::ImplExecute(const VclPtr<vcl::Window>& pParentWin, const tools::Rectangle& rRect,
2860                                   FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst)
2861 {
2862     // tdf#126054 hold this until after function completes
2863     VclPtr<PopupMenu> xThis(this);
2864 
2865     bool bRealExecute = false;
2866     const sal_uInt16 nItemCount = GetItemCount();
2867     if (!pSFrom && (vcl::IsInPopupMenuExecute() || !nItemCount))
2868         return 0;
2869 
2870     mpLayoutData.reset();
2871 
2872     ImplSVData* pSVData = ImplGetSVData();
2873 
2874     pStartedFrom = pSFrom;
2875     nSelectedId = 0;
2876     sSelectedIdent.clear();
2877     bCanceled = false;
2878 
2879     VclPtr<vcl::Window> xFocusId;
2880     if ( !pStartedFrom )
2881     {
2882         pSVData->mpWinData->mbNoDeactivate = true;
2883         xFocusId = Window::SaveFocus();
2884         bRealExecute = true;
2885     }
2886     else
2887     {
2888         // assure that only one menu is open at a time
2889         if (pStartedFrom->IsMenuBar() && pSVData->mpWinData->mpFirstFloat)
2890             pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
2891                                                            | FloatWinPopupEndFlags::CloseAll);
2892     }
2893 
2894     tools::Rectangle aRect(rRect);
2895     aRect.SetPos(pParentWin->OutputToScreenPixel(aRect.TopLeft()));
2896 
2897     nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose | FloatWinPopupFlags::GrabFocus;
2898     if (bRealExecute)
2899         nPopupModeFlags |= FloatWinPopupFlags::NewLevel;
2900 
2901     // MenuFlags get clobbered in the Activate function. Restore them after calling.
2902     MenuFlags nMenuFlagsSaved = GetMenuFlags();
2903     bInCallback = true; // set it here, if Activate overridden
2904     Activate();
2905     bInCallback = false;
2906     SetMenuFlags(nMenuFlagsSaved);
2907 
2908     if (pParentWin->isDisposed())
2909         return 0;
2910 
2911     if ( bCanceled || bKilled )
2912         return 0;
2913 
2914     if (!nItemCount)
2915         return 0;
2916 
2917     // The flag MenuFlags::HideDisabledEntries is inherited.
2918     if ( pSFrom )
2919     {
2920         if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries )
2921             nMenuFlags |= MenuFlags::HideDisabledEntries;
2922         else
2923             nMenuFlags &= ~MenuFlags::HideDisabledEntries;
2924     }
2925 
2926     sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount();
2927     if ( !nVisibleEntries )
2928     {
2929         OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
2930 
2931         MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, {});
2932         size_t nPos = 0;
2933         pData = pItemList->GetData( pData->nId, nPos );
2934         assert(pData);
2935         if (pData)
2936         {
2937             pData->bIsTemporary = true;
2938         }
2939         ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos);
2940     }
2941 
2942     VclPtr<MenuFloatingWindow> pWin = VclPtrInstance<MenuFloatingWindow>(this, pParentWin, WB_BORDER | WB_SYSTEMWINDOW);
2943     if (comphelper::LibreOfficeKit::isActive() && get_id() == "editviewspellmenu")
2944     {
2945         VclPtr<vcl::Window> xNotifierParent = pParentWin->GetParentWithLOKNotifier();
2946         assert(xNotifierParent && xNotifierParent->GetLOKNotifier() && "editview menu without LOKNotifier");
2947         pWin->SetLOKNotifier(xNotifierParent->GetLOKNotifier());
2948     }
2949 
2950     if( pSVData->maNWFData.mbFlatMenu )
2951         pWin->SetBorderStyle( WindowBorderStyle::NOBORDER );
2952     else
2953         pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU );
2954 
2955     m_pWindow.disposeAndClear();
2956     m_pWindow = pWin;
2957 
2958     Size aSz = ImplCalcSize( pWin );
2959 
2960     AbsoluteScreenPixelRectangle aDesktopRect(pWin->GetDesktopRectPixel());
2961     if( Application::GetScreenCount() > 1 )
2962     {
2963         vcl::Window* pDeskW = m_pWindow->GetWindow( GetWindowType::RealParent );
2964         if( ! pDeskW )
2965             pDeskW = m_pWindow;
2966         AbsoluteScreenPixelPoint aDesktopTL(pDeskW->OutputToAbsoluteScreenPixel(aRect.TopLeft()));
2967         aDesktopRect = Application::GetScreenPosSizePixel(
2968             Application::GetBestScreen(AbsoluteScreenPixelRectangle(aDesktopTL, aRect.GetSize())));
2969     }
2970 
2971     tools::Long nMaxHeight = aDesktopRect.GetHeight();
2972 
2973     //rhbz#1021915. If a menu won't fit in the desired location the default
2974     //mode is to place it somewhere it will fit.  e.g. above, left, right. For
2975     //some cases, e.g. menubars, it's desirable to limit the options to
2976     //above/below and force the menu to scroll if it won't fit
2977     if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement)
2978     {
2979         vcl::Window* pRef = pWin;
2980         if ( pRef->GetParent() )
2981             pRef = pRef->GetParent();
2982 
2983         AbsoluteScreenPixelRectangle devRect(pRef->OutputToAbsoluteScreenPixel(aRect.TopLeft()),
2984                                              pRef->OutputToAbsoluteScreenPixel(aRect.BottomRight()));
2985 
2986         tools::Long nHeightAbove = devRect.Top() - aDesktopRect.Top();
2987         tools::Long nHeightBelow = aDesktopRect.Bottom() - devRect.Bottom();
2988         nMaxHeight = std::min(nMaxHeight, std::max(nHeightAbove, nHeightBelow));
2989     }
2990 
2991     // In certain cases this might be misdetected with a height of 0, leading to menus not being displayed.
2992     // So assume that the available screen size matches at least the system requirements. With menu origin
2993     // in the middle, nMaxHeight will be at least half of screen height.
2994     SAL_WARN_IF(nMaxHeight < 768 / 2, "vcl",
2995                 "Available height misdetected as " << nMaxHeight
2996                                                    << "px. Setting to 384px instead.");
2997     nMaxHeight = std::max(nMaxHeight, tools::Long(768 / 2));
2998 
2999     if (pStartedFrom && pStartedFrom->IsMenuBar())
3000         nMaxHeight -= pParentWin->GetSizePixel().Height();
3001     sal_Int32 nLeft, nTop, nRight, nBottom;
3002     m_pWindow->GetBorder( nLeft, nTop, nRight, nBottom );
3003     nMaxHeight -= nTop+nBottom;
3004     if ( aSz.Height() > nMaxHeight )
3005     {
3006         pWin->EnableScrollMenu( true );
3007         sal_uInt16 nStart = ImplGetFirstVisible();
3008         sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart );
3009         aSz.setHeight( ImplCalcHeight( nEntries ) );
3010     }
3011 
3012     pWin->SetFocusId( xFocusId );
3013     pWin->SetOutputSizePixel( aSz );
3014 
3015     const bool bNative = Run(pWin, bRealExecute, bPreSelectFirst, nPopupModeFlags, pSFrom, aRect);
3016     FinishRun(pWin, pParentWin, bRealExecute, bNative);
3017     return nSelectedId;
3018 }
3019 
Run(const VclPtr<MenuFloatingWindow> & pWin,const bool bRealExecute,const bool bPreSelectFirst,const FloatWinPopupFlags nPopupModeFlags,Menu * pSFrom,const tools::Rectangle & rRect)3020 bool PopupMenu::Run(const VclPtr<MenuFloatingWindow>& pWin, const bool bRealExecute, const bool bPreSelectFirst,
3021                     const FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, const tools::Rectangle& rRect)
3022 {
3023     SalMenu* pMenu = ImplGetSalMenu();
3024     if (pMenu && bRealExecute && pMenu->ShowNativePopupMenu(pWin, rRect, nPopupModeFlags))
3025         return true;
3026 
3027     pWin->StartPopupMode(rRect, nPopupModeFlags);
3028     if (pSFrom)
3029     {
3030         sal_uInt16 aPos;
3031         if (pSFrom->IsMenuBar())
3032             aPos = static_cast<MenuBarWindow *>(pSFrom->m_pWindow.get())->GetHighlightedItem();
3033         else
3034             aPos = static_cast<MenuFloatingWindow *>(pSFrom->m_pWindow.get())->GetHighlightedItem();
3035 
3036         pWin->SetPosInParent(aPos);  // store position to be sent in SUBMENUDEACTIVATE
3037         pSFrom->ImplCallEventListeners(VclEventId::MenuSubmenuActivate, aPos);
3038     }
3039 
3040     if ( bPreSelectFirst )
3041     {
3042         for (size_t n = 0; n < static_cast<size_t>(GetItemCount()); n++)
3043         {
3044             MenuItemData* pData = pItemList->GetDataFromPos( n );
3045             if (  (  pData->bEnabled
3046                   || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus()
3047                   )
3048                && ( pData->eType != MenuItemType::SEPARATOR )
3049                && ImplIsVisible( n )
3050                && ImplIsSelectable( n )
3051                )
3052             {
3053                 pWin->ChangeHighlightItem(n, false);
3054                 break;
3055             }
3056         }
3057     }
3058 
3059     if (bRealExecute)
3060         pWin->Execute();
3061 
3062     return false;
3063 }
3064 
FinishRun(const VclPtr<MenuFloatingWindow> & pWin,const VclPtr<vcl::Window> & pParentWin,const bool bRealExecute,const bool bIsNativeMenu)3065 void PopupMenu::FinishRun(const VclPtr<MenuFloatingWindow>& pWin, const VclPtr<vcl::Window>& pParentWin, const bool bRealExecute, const bool bIsNativeMenu)
3066 {
3067     if (!bRealExecute || pWin->isDisposed())
3068         return;
3069 
3070     if (!bIsNativeMenu)
3071     {
3072         VclPtr<vcl::Window> xFocusId = pWin->GetFocusId();
3073         assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End");
3074         pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId);
3075 
3076         if (nSelectedId)  // then clean up .. ( otherwise done by TH )
3077         {
3078             PopupMenu* pSub = pWin->GetActivePopup();
3079             while ( pSub )
3080             {
3081                 pSub->ImplGetFloatingWindow()->EndPopupMode();
3082                 pSub = pSub->ImplGetFloatingWindow()->GetActivePopup();
3083             }
3084         }
3085     }
3086     else
3087         pWin->StopExecute();
3088 
3089     pWin->doShutdown();
3090     ImplClosePopupToolBox(pParentWin);
3091     ImplFlushPendingSelect();
3092 }
3093 
ImplCalcVisEntries(tools::Long nMaxHeight,sal_uInt16 nStartEntry,sal_uInt16 * pLastVisible) const3094 sal_uInt16 PopupMenu::ImplCalcVisEntries( tools::Long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const
3095 {
3096     nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight();
3097 
3098     tools::Long nHeight = 0;
3099     size_t nEntries = pItemList->size();
3100     sal_uInt16 nVisEntries = 0;
3101 
3102     if ( pLastVisible )
3103         *pLastVisible = 0;
3104 
3105     for ( size_t n = nStartEntry; n < nEntries; n++ )
3106     {
3107         if ( ImplIsVisible( n ) )
3108         {
3109             MenuItemData* pData = pItemList->GetDataFromPos( n );
3110             nHeight += pData->aSz.Height();
3111             if ( nHeight > nMaxHeight )
3112                 break;
3113 
3114             if ( pLastVisible )
3115                 *pLastVisible = n;
3116             nVisEntries++;
3117         }
3118     }
3119     return nVisEntries;
3120 }
3121 
ImplCalcHeight(sal_uInt16 nEntries) const3122 tools::Long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const
3123 {
3124     tools::Long nHeight = 0;
3125 
3126     sal_uInt16 nFound = 0;
3127     for ( size_t n = 0; ( nFound < nEntries ) && ( n < pItemList->size() ); n++ )
3128     {
3129         if ( ImplIsVisible( static_cast<sal_uInt16>(n) ) )
3130         {
3131             MenuItemData* pData = pItemList->GetDataFromPos( n );
3132             nHeight += pData->aSz.Height();
3133             nFound++;
3134         }
3135     }
3136 
3137     nHeight += 2*ImplGetFloatingWindow()->GetScrollerHeight();
3138 
3139     return nHeight;
3140 }
3141 
CreateMenuInterface()3142 css::uno::Reference<css::awt::XPopupMenu> PopupMenu::CreateMenuInterface()
3143 {
3144     UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
3145     if ( pWrapper )
3146         return pWrapper->CreateMenuInterface(this);
3147     return nullptr;
3148 }
3149 
ImplMenuDelData(const Menu * pMenu)3150 ImplMenuDelData::ImplMenuDelData( const Menu* pMenu )
3151 : mpNext( nullptr )
3152 , mpMenu( nullptr )
3153 {
3154     if( pMenu )
3155         const_cast< Menu* >( pMenu )->ImplAddDel( *this );
3156 }
3157 
~ImplMenuDelData()3158 ImplMenuDelData::~ImplMenuDelData()
3159 {
3160     if( mpMenu )
3161         const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this );
3162 }
3163 
3164 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3165