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 
10 #include <unx/gtk/gtksalmenu.hxx>
11 
12 #include <unx/gtk/gtkdata.hxx>
13 #include <unx/gtk/glomenu.h>
14 #include <unx/gtk/gloactiongroup.h>
15 #include <vcl/toolkit/floatwin.hxx>
16 #include <vcl/menu.hxx>
17 #include <vcl/pngwrite.hxx>
18 #include <vcl/pdfwriter.hxx> // for escapeStringXML
19 
20 #include <sal/log.hxx>
21 #include <tools/stream.hxx>
22 #include <window.h>
23 #include <strings.hrc>
24 
25 static bool bUnityMode = false;
26 
27 /*
28  * This function generates a unique command name for each menu item
29  */
GetCommandForItem(GtkSalMenu * pParentMenu,sal_uInt16 nItemId)30 static gchar* GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
31 {
32     OString aCommand = "window-" +
33         OString::number(reinterpret_cast<unsigned long>(pParentMenu)) +
34         "-" + OString::number(nItemId);
35     return g_strdup(aCommand.getStr());
36 }
37 
GetCommandForItem(GtkSalMenuItem * pSalMenuItem)38 static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
39 {
40     return GetCommandForItem(pSalMenuItem->mpParentMenu,
41                              pSalMenuItem->mnId);
42 }
43 
PrepUpdate()44 bool GtkSalMenu::PrepUpdate()
45 {
46     return mpMenuModel && mpActionGroup;
47 }
48 
49 /*
50  * Menu updating methods
51  */
52 
RemoveSpareItemsFromNativeMenu(GLOMenu * pMenu,GList ** pOldCommandList,unsigned nSection,unsigned nValidItems)53 static void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nSection, unsigned nValidItems )
54 {
55     sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
56 
57     while ( nSectionItems > static_cast<sal_Int32>(nValidItems) )
58     {
59         gchar* aCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, --nSectionItems );
60 
61         if ( aCommand != nullptr && pOldCommandList != nullptr )
62             *pOldCommandList = g_list_append( *pOldCommandList, g_strdup( aCommand ) );
63 
64         g_free( aCommand );
65 
66         g_lo_menu_remove_from_section( pMenu, nSection, nSectionItems );
67     }
68 }
69 
70 typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;
71 
72 namespace
73 {
decode_command(const gchar * action_name)74     MenuAndId decode_command(const gchar *action_name)
75     {
76         OString sCommand(action_name);
77 
78         sal_Int32 nIndex = 0;
79         OString sWindow = sCommand.getToken(0, '-', nIndex);
80         OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
81         OString sItemId = sCommand.getToken(0, '-', nIndex);
82 
83         GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());
84 
85         assert(sWindow == "window" && pSalSubMenu);
86         (void) sWindow;
87 
88         return MenuAndId(pSalSubMenu, sItemId.toInt32());
89     }
90 }
91 
RemoveDisabledItemsFromNativeMenu(GLOMenu * pMenu,GList ** pOldCommandList,sal_Int32 nSection,GActionGroup * pActionGroup)92 static void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList,
93                                        sal_Int32 nSection, GActionGroup* pActionGroup)
94 {
95     while (nSection >= 0)
96     {
97         sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
98         while (nSectionItems--)
99         {
100             gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems);
101             // remove disabled entries
102             bool bRemove = !g_action_group_get_action_enabled(pActionGroup, pCommand);
103             if (!bRemove)
104             {
105                 //also remove any empty submenus
106                 GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems);
107                 if (pSubMenuModel)
108                 {
109                     gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel));
110                     if (nSubMenuSections == 0)
111                         bRemove = true;
112                     else if (nSubMenuSections == 1)
113                     {
114                         gint nItems = g_lo_menu_get_n_items_from_section(pSubMenuModel, 0);
115                         if (nItems == 0)
116                             bRemove = true;
117                         else if (nItems == 1)
118                         {
119                             //If the only entry is the "No Selection Possible" entry, then we are allowed
120                             //to removed it
121                             gchar* pSubCommand = g_lo_menu_get_command_from_item_in_section(pSubMenuModel, 0, 0);
122                             MenuAndId aMenuAndId(decode_command(pSubCommand));
123                             bRemove = aMenuAndId.second == 0xFFFF;
124                             g_free(pSubCommand);
125                         }
126                     }
127                 }
128             }
129 
130             if (bRemove)
131             {
132                 //but tdf#86850 Always display clipboard functions
133                 bRemove = g_strcmp0(pCommand, ".uno:Cut") &&
134                           g_strcmp0(pCommand, ".uno:Copy") &&
135                           g_strcmp0(pCommand, ".uno:Paste");
136             }
137 
138             if (bRemove)
139             {
140                 if (pCommand != nullptr && pOldCommandList != nullptr)
141                     *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand));
142                 g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems);
143             }
144 
145             g_free(pCommand);
146         }
147         --nSection;
148     }
149 }
150 
RemoveSpareSectionsFromNativeMenu(GLOMenu * pMenu,GList ** pOldCommandList,sal_Int32 nLastSection)151 static void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection )
152 {
153     if ( pMenu == nullptr || pOldCommandList == nullptr )
154         return;
155 
156     sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1;
157 
158     for ( ; n > nLastSection; n--)
159     {
160         RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 );
161         g_lo_menu_remove( pMenu, n );
162     }
163 }
164 
CompareStr(gpointer str1,gpointer str2)165 static gint CompareStr( gpointer str1, gpointer str2 )
166 {
167     return g_strcmp0( static_cast<const gchar*>(str1), static_cast<const gchar*>(str2) );
168 }
169 
RemoveUnusedCommands(GLOActionGroup * pActionGroup,GList * pOldCommandList,GList * pNewCommandList)170 static void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, GList* pNewCommandList )
171 {
172     if ( pActionGroup == nullptr || pOldCommandList == nullptr )
173     {
174         g_list_free_full( pOldCommandList, g_free );
175         g_list_free_full( pNewCommandList, g_free );
176         return;
177     }
178 
179     while ( pNewCommandList != nullptr )
180     {
181         GList* pNewCommand = g_list_first( pNewCommandList );
182         pNewCommandList = g_list_remove_link( pNewCommandList, pNewCommand );
183 
184         gpointer aCommand = g_list_nth_data( pNewCommand, 0 );
185 
186         GList* pOldCommand = g_list_find_custom( pOldCommandList, aCommand, reinterpret_cast<GCompareFunc>(CompareStr) );
187 
188         if ( pOldCommand != nullptr )
189         {
190             pOldCommandList = g_list_remove_link( pOldCommandList, pOldCommand );
191             g_list_free_full( pOldCommand, g_free );
192         }
193 
194         g_list_free_full( pNewCommand, g_free );
195     }
196 
197     while ( pOldCommandList != nullptr )
198     {
199         GList* pCommand = g_list_first( pOldCommandList );
200         pOldCommandList = g_list_remove_link( pOldCommandList, pCommand );
201 
202         gchar* aCommand = static_cast<gchar*>(g_list_nth_data( pCommand, 0 ));
203 
204         g_lo_action_group_remove( pActionGroup, aCommand );
205 
206         g_list_free_full( pCommand, g_free );
207     }
208 }
209 
ImplUpdate(bool bRecurse,bool bRemoveDisabledEntries)210 void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
211 {
212     SolarMutexGuard aGuard;
213 
214     SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate");
215     if( !PrepUpdate() )
216         return;
217 
218     if (mbNeedsUpdate)
219     {
220         mbNeedsUpdate = false;
221         if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
222         {
223             maUpdateMenuBarIdle.Stop();
224             // tdf#124391 Prevent doubled menus in global menu
225             if (!bUnityMode)
226             {
227                 maUpdateMenuBarIdle.Invoke();
228                 return;
229             }
230         }
231     }
232 
233     Menu* pVCLMenu = mpVCLMenu;
234     GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
235     GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
236     SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
237     GList *pOldCommandList = nullptr;
238     GList *pNewCommandList = nullptr;
239 
240     sal_uInt16 nLOMenuSize = g_menu_model_get_n_items( G_MENU_MODEL( pLOMenu ) );
241 
242     if ( nLOMenuSize == 0 )
243         g_lo_menu_new_section( pLOMenu, 0, nullptr );
244 
245     sal_Int32 nSection = 0;
246     sal_Int32 nItemPos = 0;
247     sal_Int32 validItems = 0;
248     sal_Int32 nItem;
249 
250     for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) {
251         if ( !IsItemVisible( nItem ) )
252             continue;
253 
254         GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
255         sal_uInt16 nId = pSalMenuItem->mnId;
256 
257         // PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level
258         // popup menu, but we have our own implementation below, so skip that one.
259         if ( nId == 0xFFFF )
260             continue;
261 
262         if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
263         {
264             // Delete extra items from current section.
265             RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
266 
267             nSection++;
268             nItemPos = 0;
269             validItems = 0;
270 
271             if ( nLOMenuSize <= nSection )
272             {
273                 g_lo_menu_new_section( pLOMenu, nSection, nullptr );
274                 nLOMenuSize++;
275             }
276 
277             continue;
278         }
279 
280         if ( nItemPos >= g_lo_menu_get_n_items_from_section( pLOMenu, nSection ) )
281             g_lo_menu_insert_in_section( pLOMenu, nSection, nItemPos, "EMPTY STRING" );
282 
283         // Get internal menu item values.
284         OUString aText = pVCLMenu->GetItemText( nId );
285         Image aImage = pVCLMenu->GetItemImage( nId );
286         bool bEnabled = pVCLMenu->IsItemEnabled( nId );
287         vcl::KeyCode nAccelKey = pVCLMenu->GetAccelKey( nId );
288         bool bChecked = pVCLMenu->IsItemChecked( nId );
289         MenuItemBits itemBits = pVCLMenu->GetItemBits( nId );
290 
291         // Store current item command in command list.
292         gchar *aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pLOMenu, nSection, nItemPos );
293 
294         if ( aCurrentCommand != nullptr )
295             pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );
296 
297         // Get the new command for the item.
298         gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);
299 
300         // Force updating of native menu labels.
301         NativeSetItemText( nSection, nItemPos, aText );
302         NativeSetItemIcon( nSection, nItemPos, aImage );
303         NativeSetAccelerator( nSection, nItemPos, nAccelKey, nAccelKey.GetName( GetFrame()->GetWindow() ) );
304 
305         if ( g_strcmp0( aNativeCommand, "" ) != 0 && pSalMenuItem->mpSubMenu == nullptr )
306         {
307             NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, bChecked, false );
308             NativeCheckItem( nSection, nItemPos, itemBits, bChecked );
309             NativeSetEnableItem( aNativeCommand, bEnabled );
310 
311             pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
312         }
313 
314         GtkSalMenu* pSubmenu = pSalMenuItem->mpSubMenu;
315 
316         if ( pSubmenu && pSubmenu->GetMenu() )
317         {
318             bool bNonMenuChangedToMenu = NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, false, true );
319             pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
320 
321             GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
322 
323             if ( pSubMenuModel == nullptr )
324             {
325                 g_lo_menu_new_submenu_in_item_in_section( pLOMenu, nSection, nItemPos );
326                 pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
327             }
328 
329             assert(pSubMenuModel);
330 
331             if (bRecurse || bNonMenuChangedToMenu)
332             {
333                 SAL_INFO("vcl.unity", "preparing submenu  " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
334                 pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
335                 pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
336                 pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
337             }
338 
339             g_object_unref( pSubMenuModel );
340         }
341 
342         g_free( aNativeCommand );
343 
344         ++nItemPos;
345         ++validItems;
346     }
347 
348     if (bRemoveDisabledEntries)
349     {
350         // Delete disabled items in last section.
351         RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
352     }
353 
354     // Delete extra items in last section.
355     RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
356 
357     // Delete extra sections.
358     RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );
359 
360     // Delete unused commands.
361     RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );
362 
363     // Resolves: tdf#103166 if the menu is empty, add a disabled
364     // <No Selection Possible> placeholder.
365     sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
366     gint nItemsCount = 0;
367     for (nSection = 0; nSection < nSectionsCount; ++nSection)
368     {
369         nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
370         if (nItemsCount)
371             break;
372     }
373     if (!nItemsCount)
374     {
375         gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
376         OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
377         g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
378                                     OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
379         NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
380         NativeSetEnableItem(aNativeCommand, false);
381         g_free(aNativeCommand);
382     }
383 }
384 
Update()385 void GtkSalMenu::Update()
386 {
387     //find out if top level is a menubar or not, if not, then it's a popup menu
388     //hierarchy and in those we hide (most) disabled entries
389     const GtkSalMenu* pMenu = this;
390     while (pMenu->mpParentSalMenu)
391         pMenu = pMenu->mpParentSalMenu;
392 
393     bool bAlwaysShowDisabledEntries;
394     if (pMenu->mbMenuBar)
395         bAlwaysShowDisabledEntries = true;
396     else
397         bAlwaysShowDisabledEntries = bool(mpVCLMenu->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries);
398 
399     ImplUpdate(false, !bAlwaysShowDisabledEntries);
400 }
401 
402 #if !GTK_CHECK_VERSION(4, 0, 0)
MenuPositionFunc(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)403 static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
404 {
405     Point *pPos = static_cast<Point*>(user_data);
406     *x = pPos->X();
407     if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
408     {
409         GtkRequisition natural_size;
410         gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
411         *x -= natural_size.width;
412     }
413     *y = pPos->Y();
414     *push_in = false;
415 }
416 #endif
417 
ShowNativePopupMenu(FloatingWindow * pWin,const tools::Rectangle & rRect,FloatWinPopupFlags nFlags)418 bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
419                                      FloatWinPopupFlags nFlags)
420 {
421     VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
422     mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());
423 
424     GLOActionGroup* pActionGroup = g_lo_action_group_new();
425     mpActionGroup = G_ACTION_GROUP(pActionGroup);
426     mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
427     // Generate the main menu structure, populates mpMenuModel
428     UpdateFull();
429 
430 #if !GTK_CHECK_VERSION(4, 0, 0)
431     GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
432     gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
433 #else
434     // TODO: gtk_popover_menu_new crashes on submenus with: "signal 'action-added' is invalid for instance  of type 'GtkActionMuxer'"
435     GtkWidget *pWidget = gtk_popover_menu_new_from_model_full(mpMenuModel, GTK_POPOVER_MENU_NESTED);
436     gtk_widget_set_parent(pWidget, mpFrame->getMouseEventWidget());
437     gtk_popover_set_has_arrow(GTK_POPOVER(pWidget), false);
438 #endif
439     gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);
440 
441     //run in a sub main loop because we need to keep vcl PopupMenu alive to use
442     //it during DispatchCommand, returning now to the outer loop causes the
443     //launching PopupMenu to be destroyed, instead run the subloop here
444     //until the gtk menu is destroyed
445     GMainLoop* pLoop = g_main_loop_new(nullptr, true);
446 #if GTK_CHECK_VERSION(4, 0, 0)
447     g_signal_connect_swapped(G_OBJECT(pWidget), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
448 #else
449     g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
450 #endif
451 
452 
453     // tdf#120764 It isn't allowed under wayland to have two visible popups that share
454     // the same top level parent. The problem is that since gtk 3.24 tooltips are also
455     // implemented as popups, which means that we cannot show any popup if there is a
456     // visible tooltip.
457     // hide any current tooltip
458     mpFrame->HideTooltip();
459     // don't allow any more to appear until menu is dismissed
460     mpFrame->BlockTooltip();
461 
462 #if GTK_CHECK_VERSION(4, 0, 0)
463     tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
464     aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
465     GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
466                        static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
467 
468     gtk_popover_set_pointing_to(GTK_POPOVER(pWidget), &rect);
469 
470     if (nFlags & FloatWinPopupFlags::Left)
471         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
472     else if (nFlags & FloatWinPopupFlags::Up)
473         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
474     else if (nFlags & FloatWinPopupFlags::Right)
475         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
476     else
477         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
478 
479     gtk_popover_popup(GTK_POPOVER(pWidget));
480 #else
481 #if GTK_CHECK_VERSION(3,22,0)
482     if (gtk_check_version(3, 22, 0) == nullptr)
483     {
484         tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
485         aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
486         GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
487                            static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
488 
489         GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
490 
491         if (nFlags & FloatWinPopupFlags::Left)
492         {
493             rect_anchor = GDK_GRAVITY_NORTH_WEST;
494             menu_anchor = GDK_GRAVITY_NORTH_EAST;
495         }
496         else if (nFlags & FloatWinPopupFlags::Up)
497         {
498             rect_anchor = GDK_GRAVITY_NORTH_WEST;
499             menu_anchor = GDK_GRAVITY_SOUTH_WEST;
500         }
501         else if (nFlags & FloatWinPopupFlags::Right)
502         {
503             rect_anchor = GDK_GRAVITY_NORTH_EAST;
504         }
505 
506         GdkSurface* gdkWindow = widget_get_surface(mpFrame->getMouseEventWidget());
507         gtk_menu_popup_at_rect(GTK_MENU(pWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
508     }
509     else
510 #endif
511     {
512         guint nButton;
513         guint32 nTime;
514 
515         //typically there is an event, and we can then distinguish if this was
516         //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
517         //doesn't)
518         GdkEvent *pEvent = gtk_get_current_event();
519         if (pEvent)
520         {
521             gdk_event_get_button(pEvent, &nButton);
522             nTime = gdk_event_get_time(pEvent);
523         }
524         else
525         {
526             nButton = 0;
527             nTime = GtkSalFrame::GetLastInputEventTime();
528         }
529 
530         // do the same strange semantics as vcl popup windows to arrive at a frame geometry
531         // in mirrored UI case; best done by actually executing the same code
532         sal_uInt16 nArrangeIndex;
533         Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
534         aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
535 
536         gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
537                        &aPos, nButton, nTime);
538     }
539 #endif
540 
541     if (g_main_loop_is_running(pLoop))
542         main_loop_run(pLoop);
543 
544     g_main_loop_unref(pLoop);
545 
546     mpVCLMenu->Deactivate();
547 
548     gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
549 
550 #if !GTK_CHECK_VERSION(4, 0, 0)
551     gtk_widget_destroy(pWidget);
552 #else
553     g_clear_pointer(&pWidget, gtk_widget_unparent);
554 #endif
555 
556     g_object_unref(mpActionGroup);
557     ClearActionGroupAndMenuModel();
558 
559     // undo tooltip blocking
560     mpFrame->UnblockTooltip();
561 
562     mpFrame = nullptr;
563 
564     return true;
565 }
566 
567 /*
568  * GtkSalMenu
569  */
570 
GtkSalMenu(bool bMenuBar)571 GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
572     mbInActivateCallback( false ),
573     mbMenuBar( bMenuBar ),
574     mbNeedsUpdate( false ),
575     mbReturnFocusToDocument( false ),
576     mbAddedGrab( false ),
577     mpMenuBarContainerWidget( nullptr ),
578     mpMenuAllowShrinkWidget( nullptr ),
579     mpMenuBarWidget( nullptr ),
580     mpMenuBarContainerProvider( nullptr ),
581     mpMenuBarProvider( nullptr ),
582     mpCloseButton( nullptr ),
583     mpVCLMenu( nullptr ),
584     mpParentSalMenu( nullptr ),
585     mpFrame( nullptr ),
586     mpMenuModel( nullptr ),
587     mpActionGroup( nullptr )
588 {
589     //typically this only gets called after the menu has been customized on the
590     //next idle slot, in the normal case of a new menubar SetFrame is called
591     //directly long before this idle would get called.
592     maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
593     maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
594     maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
595 }
596 
IMPL_LINK_NOARG(GtkSalMenu,MenuBarHierarchyChangeHandler,Timer *,void)597 IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
598 {
599     SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
600     if (!mpFrame)
601         return;
602     SetFrame(mpFrame);
603 }
604 
SetNeedsUpdate()605 void GtkSalMenu::SetNeedsUpdate()
606 {
607     GtkSalMenu* pMenu = this;
608     // start that the menu and its parents are in need of an update
609     // on the next activation
610     while (pMenu && !pMenu->mbNeedsUpdate)
611     {
612         pMenu->mbNeedsUpdate = true;
613         pMenu = pMenu->mpParentSalMenu;
614     }
615     // only if a menubar is directly updated do we force in a full
616     // structure update
617     if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
618         maUpdateMenuBarIdle.Start();
619 }
620 
SetMenuModel(GMenuModel * pMenuModel)621 void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
622 {
623     if (mpMenuModel)
624         g_object_unref(mpMenuModel);
625     mpMenuModel = pMenuModel;
626     if (mpMenuModel)
627         g_object_ref(mpMenuModel);
628 }
629 
~GtkSalMenu()630 GtkSalMenu::~GtkSalMenu()
631 {
632     SolarMutexGuard aGuard;
633 
634     // tdf#140225 we expect all items to be removed by Menu::dispose
635     // before this dtor is called
636     assert(maItems.empty());
637 
638     DestroyMenuBarWidget();
639 
640     if (mpMenuModel)
641         g_object_unref(mpMenuModel);
642 
643     if (mpFrame)
644         mpFrame->SetMenu(nullptr);
645 }
646 
VisibleMenuBar()647 bool GtkSalMenu::VisibleMenuBar()
648 {
649     return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
650 }
651 
InsertItem(SalMenuItem * pSalMenuItem,unsigned nPos)652 void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
653 {
654     SolarMutexGuard aGuard;
655     GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );
656 
657     if ( nPos == MENU_APPEND )
658         maItems.push_back( pItem );
659     else
660         maItems.insert( maItems.begin() + nPos, pItem );
661 
662     pItem->mpParentMenu = this;
663 
664     SetNeedsUpdate();
665 }
666 
RemoveItem(unsigned nPos)667 void GtkSalMenu::RemoveItem( unsigned nPos )
668 {
669     SolarMutexGuard aGuard;
670 
671     // tdf#140225 clear associated action when the item is removed
672     if (mpActionGroup)
673     {
674         GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP(mpActionGroup);
675         gchar* pCommand = GetCommandForItem(maItems[nPos]);
676         g_lo_action_group_remove(pActionGroup, pCommand);
677         g_free(pCommand);
678     }
679 
680     maItems.erase( maItems.begin() + nPos );
681     SetNeedsUpdate();
682 }
683 
SetSubMenu(SalMenuItem * pSalMenuItem,SalMenu * pSubMenu,unsigned)684 void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
685 {
686     SolarMutexGuard aGuard;
687     GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
688     GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );
689 
690     if ( pGtkSubMenu == nullptr )
691         return;
692 
693     pGtkSubMenu->mpParentSalMenu = this;
694     pItem->mpSubMenu = pGtkSubMenu;
695 
696     SetNeedsUpdate();
697 }
698 
CloseMenuBar(GtkWidget *,gpointer pMenu)699 static void CloseMenuBar(GtkWidget *, gpointer pMenu)
700 {
701     Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
702 }
703 
AddButton(GtkWidget * pImage)704 GtkWidget* GtkSalMenu::AddButton(GtkWidget *pImage)
705 {
706     GtkWidget* pButton = gtk_button_new();
707 
708 #if !GTK_CHECK_VERSION(4, 0, 0)
709     gtk_button_set_relief(GTK_BUTTON(pButton), GTK_RELIEF_NONE);
710     gtk_button_set_focus_on_click(GTK_BUTTON(pButton), false);
711 #else
712     gtk_button_set_has_frame(GTK_BUTTON(pButton), false);
713     gtk_widget_set_focus_on_click(pButton, false);
714 #endif
715 
716     gtk_widget_set_can_focus(pButton, false);
717 
718     GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pButton));
719 
720     gtk_style_context_add_class(pButtonContext, "flat");
721     gtk_style_context_add_class(pButtonContext, "small-button");
722 
723     gtk_widget_show(pImage);
724 
725     gtk_widget_set_valign(pButton, GTK_ALIGN_CENTER);
726 
727 #if !GTK_CHECK_VERSION(4, 0, 0)
728     gtk_container_add(GTK_CONTAINER(pButton), pImage);
729     gtk_widget_show_all(pButton);
730 #else
731     gtk_button_set_child(GTK_BUTTON(pButton), pImage);
732 #endif
733     return pButton;
734 }
735 
ShowCloseButton(bool bShow)736 void GtkSalMenu::ShowCloseButton(bool bShow)
737 {
738     assert(mbMenuBar);
739     if (!mpMenuBarContainerWidget)
740         return;
741 
742     if (!bShow)
743     {
744         if (mpCloseButton)
745         {
746 #if !GTK_CHECK_VERSION(4, 0, 0)
747             gtk_widget_destroy(mpCloseButton);
748 #else
749             g_clear_pointer(&mpCloseButton, gtk_widget_unparent);
750 #endif
751             mpCloseButton = nullptr;
752         }
753         return;
754     }
755 
756     if (mpCloseButton)
757         return;
758 
759     GIcon* pIcon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
760 #if !GTK_CHECK_VERSION(4, 0, 0)
761     GtkWidget* pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
762 #else
763     GtkWidget* pImage = gtk_image_new_from_gicon(pIcon);
764 #endif
765     g_object_unref(pIcon);
766 
767     mpCloseButton = AddButton(pImage);
768 
769     gtk_widget_set_margin_end(mpCloseButton, 8);
770 
771     OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
772     gtk_widget_set_tooltip_text(mpCloseButton, sToolTip.toUtf8().getStr());
773 
774     MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
775     g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
776 
777     gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, 1, 0, 1, 1);
778 }
779 
780 namespace
781 {
DestroyMemoryStream(gpointer data)782     void DestroyMemoryStream(gpointer data)
783     {
784         SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
785         delete pMemStm;
786     }
787 }
788 
MenuButtonClicked(GtkWidget * pWidget,gpointer pMenu)789 static void MenuButtonClicked(GtkWidget* pWidget, gpointer pMenu)
790 {
791 #if !GTK_CHECK_VERSION(4, 0, 0)
792     const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
793 #else
794     const char* pStr = gtk_buildable_get_buildable_id(GTK_BUILDABLE(pWidget));
795 #endif
796     OString aId(pStr, pStr ? strlen(pStr) : 0);
797     static_cast<MenuBar*>(pMenu)->HandleMenuButtonEvent(aId.toUInt32());
798 }
799 
AddMenuBarButton(const SalMenuButtonItem & rNewItem)800 bool GtkSalMenu::AddMenuBarButton(const SalMenuButtonItem& rNewItem)
801 {
802     if (!mbMenuBar)
803         return false;
804 
805     if (!mpMenuBarContainerWidget)
806         return false;
807 
808     GtkWidget* pImage = nullptr;
809     if (!!rNewItem.maImage)
810     {
811         SvMemoryStream* pMemStm = new SvMemoryStream;
812         vcl::PNGWriter aWriter(rNewItem.maImage.GetBitmapEx());
813         aWriter.Write(*pMemStm);
814 
815         GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
816                                                     pMemStm->TellEnd(),
817                                                     DestroyMemoryStream,
818                                                     pMemStm);
819 
820         GIcon *pIcon = g_bytes_icon_new(pBytes);
821 #if !GTK_CHECK_VERSION(4, 0, 0)
822         pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
823 #else
824         pImage = gtk_image_new_from_gicon(pIcon);
825 #endif
826         g_object_unref(pIcon);
827     }
828 
829     GtkWidget* pButton = AddButton(pImage);
830 
831     maExtraButtons.emplace_back(rNewItem.mnId, pButton);
832 
833 #if !GTK_CHECK_VERSION(4, 0, 0)
834     gtk_buildable_set_name(GTK_BUILDABLE(pButton), OString::number(rNewItem.mnId).getStr());
835 #endif
836 
837     gtk_widget_set_tooltip_text(pButton, rNewItem.maToolTipText.toUtf8().getStr());
838 
839     MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
840     g_signal_connect(pButton, "clicked", G_CALLBACK(MenuButtonClicked), pVclMenuBar);
841 
842     if (mpCloseButton)
843     {
844         gtk_grid_insert_next_to(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, GTK_POS_LEFT);
845         gtk_grid_attach_next_to(GTK_GRID(mpMenuBarContainerWidget), pButton, mpCloseButton,
846                                 GTK_POS_LEFT, 1, 1);
847     }
848     else
849         gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), pButton, 1, 0, 1, 1);
850 
851     return true;
852 }
853 
RemoveMenuBarButton(sal_uInt16 nId)854 void GtkSalMenu::RemoveMenuBarButton( sal_uInt16 nId )
855 {
856     const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
857         return item.first == nId; });
858     if (it != maExtraButtons.end())
859     {
860         gint nAttach(0);
861 #if !GTK_CHECK_VERSION(4, 0, 0)
862         gtk_container_child_get(GTK_CONTAINER(mpMenuBarContainerWidget), it->second, "left-attach", &nAttach, nullptr);
863         gtk_widget_destroy(it->second);
864 #else
865         gtk_grid_query_child(GTK_GRID(mpMenuBarContainerWidget), it->second, &nAttach, nullptr, nullptr, nullptr);
866         g_clear_pointer(&(it->second), gtk_widget_unparent);
867 #endif
868         gtk_grid_remove_column(GTK_GRID(mpMenuBarContainerWidget), nAttach);
869         maExtraButtons.erase(it);
870     }
871 }
872 
GetMenuBarButtonRectPixel(sal_uInt16 nId,SalFrame * pReferenceFrame)873 tools::Rectangle GtkSalMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pReferenceFrame)
874 {
875     if (!pReferenceFrame)
876         return tools::Rectangle();
877 
878     const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
879         return item.first == nId; });
880     if (it == maExtraButtons.end())
881         return tools::Rectangle();
882 
883     GtkWidget* pButton = it->second;
884 
885     GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pReferenceFrame);
886 
887     gtk_coord x, y;
888     if (!gtk_widget_translate_coordinates(pButton, GTK_WIDGET(pFrame->getMouseEventWidget()), 0, 0, &x, &y))
889         return tools::Rectangle();
890 
891     return tools::Rectangle(Point(x, y), Size(gtk_widget_get_allocated_width(pButton),
892                                               gtk_widget_get_allocated_height(pButton)));
893 }
894 
895 //Typically when the menubar is deactivated we want the focus to return
896 //to where it came from. If the menubar was activated because of F6
897 //moving focus into the associated VCL menubar then on pressing ESC
898 //or any other normal reason for deactivation we want focus to return
899 //to the document, definitely not still stuck in the associated
900 //VCL menubar. But if F6 is pressed while the menubar is activated
901 //we want to pass that F6 back to the VCL menubar which will move
902 //focus to the next pane by itself.
ReturnFocus()903 void GtkSalMenu::ReturnFocus()
904 {
905     if (mbAddedGrab)
906     {
907 #if !GTK_CHECK_VERSION(4, 0, 0)
908         gtk_grab_remove(mpMenuBarWidget);
909 #endif
910         mbAddedGrab = false;
911     }
912     if (!mbReturnFocusToDocument)
913         gtk_widget_grab_focus(mpFrame->getMouseEventWidget());
914     else
915         mpFrame->GetWindow()->GrabFocusToDocument();
916     mbReturnFocusToDocument = false;
917 }
918 
919 #if !GTK_CHECK_VERSION(4, 0, 0)
SignalKey(GdkEventKey const * pEvent)920 gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
921 {
922     if (pEvent->keyval == GDK_KEY_F6)
923     {
924         mbReturnFocusToDocument = false;
925         gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
926         //because we return false here, the keypress will continue
927         //to propagate and in the case that vcl focus is in
928         //the vcl menubar then that will also process F6 and move
929         //to the next pane
930     }
931     return false;
932 }
933 #endif
934 
935 //The GtkSalMenu is owned by a Vcl Menu/MenuBar. In the menubar
936 //case the vcl menubar is present and "visible", but with a 0 height
937 //so it not apparent. Normally it acts as though it is not there when
938 //a Native menubar is active. If we return true here, then for keyboard
939 //activation and traversal with F6 through panes then the vcl menubar
940 //acts as though it *is* present and we translate its take focus and F6
941 //traversal key events into the gtk menubar equivalents.
CanGetFocus() const942 bool GtkSalMenu::CanGetFocus() const
943 {
944     return mpMenuBarWidget != nullptr;
945 }
946 
TakeFocus()947 bool GtkSalMenu::TakeFocus()
948 {
949     if (!mpMenuBarWidget)
950         return false;
951 
952 #if !GTK_CHECK_VERSION(4, 0, 0)
953     //Send a keyboard event to the gtk menubar to let it know it has been
954     //activated via the keyboard. Doesn't do anything except cause the gtk
955     //menubar "keyboard_mode" member to get set to true, so typically mnemonics
956     //are shown which will serve as indication that the menubar has focus
957     //(given that we want to show it with no menus popped down)
958     GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
959     gtk_widget_event(mpMenuBarWidget, event);
960     gdk_event_free(event);
961 
962     //this pairing results in a menubar with keyboard focus with no menus
963     //auto-popped down
964     gtk_grab_add(mpMenuBarWidget);
965 
966     mbAddedGrab = true;
967     gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
968     gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
969 #endif
970     mbReturnFocusToDocument = true;
971     return true;
972 }
973 
974 #if !GTK_CHECK_VERSION(4, 0, 0)
MenuBarReturnFocus(GtkMenuShell *,gpointer menu)975 static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
976 {
977     GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
978     GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
979     pMenu->ReturnFocus();
980 }
981 
MenuBarSignalKey(GtkWidget *,GdkEventKey * pEvent,gpointer menu)982 static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
983 {
984     GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
985     return pMenu->SignalKey(pEvent);
986 }
987 #endif
988 
CreateMenuBarWidget()989 void GtkSalMenu::CreateMenuBarWidget()
990 {
991     if (mpMenuBarContainerWidget)
992         return;
993 
994     GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
995     mpMenuBarContainerWidget = gtk_grid_new();
996 
997     gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
998     gtk_grid_insert_row(pGrid, 0);
999     gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);
1000 
1001 #if !GTK_CHECK_VERSION(4, 0, 0)
1002     mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
1003     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
1004 #else
1005     mpMenuAllowShrinkWidget = gtk_scrolled_window_new();
1006     gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), false);
1007 #endif
1008     // tdf#129634 don't allow this scrolled window as a candidate to tab into
1009     gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false);
1010     // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
1011     // but still allow scrolled window to not be sized to the child content.
1012     // So the menubar can be shrunk past its nominal smallest width.
1013     // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
1014     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
1015     gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);
1016 
1017 #if !GTK_CHECK_VERSION(4, 0, 0)
1018     mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
1019 #else
1020     mpMenuBarWidget = gtk_popover_menu_bar_new_from_model(mpMenuModel);
1021 #endif
1022 
1023     gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
1024     gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
1025     gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
1026 #if !GTK_CHECK_VERSION(4, 0, 0)
1027     gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
1028 #else
1029     gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), mpMenuBarWidget);
1030 #endif
1031 
1032 #if !GTK_CHECK_VERSION(4, 0, 0)
1033     g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
1034     g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
1035 #endif
1036 
1037     gtk_widget_show(mpMenuBarWidget);
1038     gtk_widget_show(mpMenuAllowShrinkWidget);
1039     gtk_widget_show(mpMenuBarContainerWidget);
1040 
1041     ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );
1042 
1043     ApplyPersona();
1044 }
1045 
ApplyPersona()1046 void GtkSalMenu::ApplyPersona()
1047 {
1048     if (!mpMenuBarContainerWidget)
1049         return;
1050     assert(mbMenuBar);
1051     // I'm dubious about the persona theming feature, but as it exists, lets try and support
1052     // it, apply the image to the mpMenuBarContainerWidget
1053     const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
1054 
1055     GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
1056     if (mpMenuBarContainerProvider)
1057     {
1058         gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
1059         mpMenuBarContainerProvider = nullptr;
1060     }
1061     GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
1062     if (mpMenuBarProvider)
1063     {
1064         gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
1065         mpMenuBarProvider = nullptr;
1066     }
1067 
1068     if (!rPersonaBitmap.IsEmpty())
1069     {
1070         if (maPersonaBitmap != rPersonaBitmap)
1071         {
1072             vcl::PNGWriter aPNGWriter(rPersonaBitmap);
1073             mxPersonaImage.reset(new utl::TempFile);
1074             mxPersonaImage->EnableKillingFile(true);
1075             SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
1076             aPNGWriter.Write(*pStream);
1077             mxPersonaImage->CloseStream();
1078         }
1079 
1080         mpMenuBarContainerProvider = gtk_css_provider_new();
1081         OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
1082         OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
1083         css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength());
1084         gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
1085                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1086 
1087 
1088         // force the menubar to be transparent when persona is active otherwise for
1089         // me the menubar becomes gray when its in the backdrop
1090         mpMenuBarProvider = gtk_css_provider_new();
1091         static const gchar data[] = "* { "
1092           "background-image: none;"
1093           "background-color: transparent;"
1094           "}";
1095         css_provider_load_from_data(mpMenuBarProvider, data, -1);
1096         gtk_style_context_add_provider(pMenuBarContext,
1097                                        GTK_STYLE_PROVIDER(mpMenuBarProvider),
1098                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1099     }
1100     maPersonaBitmap = rPersonaBitmap;
1101 }
1102 
DestroyMenuBarWidget()1103 void GtkSalMenu::DestroyMenuBarWidget()
1104 {
1105     if (mpMenuBarContainerWidget)
1106     {
1107 #if !GTK_CHECK_VERSION(4, 0, 0)
1108         // tdf#140225 call cancel before destroying it in case there are some
1109         // active menus popped open
1110         gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
1111 
1112         gtk_widget_destroy(mpMenuBarContainerWidget);
1113 #else
1114         g_clear_pointer(&mpMenuBarContainerWidget, gtk_widget_unparent);
1115 #endif
1116         mpMenuBarContainerWidget = nullptr;
1117         mpMenuBarWidget = nullptr;
1118         mpCloseButton = nullptr;
1119     }
1120 }
1121 
SetFrame(const SalFrame * pFrame)1122 void GtkSalMenu::SetFrame(const SalFrame* pFrame)
1123 {
1124     SolarMutexGuard aGuard;
1125     assert(mbMenuBar);
1126     SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
1127     mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));
1128 
1129     // if we had a menu on the GtkSalMenu we have to free it as we generate a
1130     // full menu anyway and we might need to reuse an existing model and
1131     // actiongroup
1132     mpFrame->SetMenu( this );
1133     mpFrame->EnsureAppMenuWatch();
1134 
1135     // Clean menu model and action group if needed.
1136     GtkWidget* pWidget = mpFrame->getWindow();
1137     GdkSurface* gdkWindow = widget_get_surface(pWidget);
1138 
1139     GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
1140     GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
1141     SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);
1142 
1143     if ( pMenuModel )
1144     {
1145         if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
1146             g_lo_menu_remove( pMenuModel, 0 );
1147 
1148         mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
1149     }
1150 
1151     if ( pActionGroup )
1152     {
1153         g_lo_action_group_clear( pActionGroup );
1154         mpActionGroup = G_ACTION_GROUP( pActionGroup );
1155     }
1156 
1157     // Generate the main menu structure.
1158     if ( PrepUpdate() )
1159         UpdateFull();
1160 
1161     g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );
1162 
1163     if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
1164     {
1165         DestroyMenuBarWidget();
1166         CreateMenuBarWidget();
1167     }
1168 }
1169 
GetFrame() const1170 const GtkSalFrame* GtkSalMenu::GetFrame() const
1171 {
1172     SolarMutexGuard aGuard;
1173     const GtkSalMenu* pMenu = this;
1174     while( pMenu && ! pMenu->mpFrame )
1175         pMenu = pMenu->mpParentSalMenu;
1176     return pMenu ? pMenu->mpFrame : nullptr;
1177 }
1178 
NativeCheckItem(unsigned nSection,unsigned nItemPos,MenuItemBits bits,gboolean bCheck)1179 void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
1180 {
1181     SolarMutexGuard aGuard;
1182 
1183     if ( mpActionGroup == nullptr )
1184         return;
1185 
1186     gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1187 
1188     if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
1189     {
1190         GVariant *pCheckValue = nullptr;
1191         GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );
1192 
1193         if ( bits & MenuItemBits::RADIOCHECK )
1194             pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
1195         else
1196         {
1197             // By default, all checked items are checkmark buttons.
1198             if (bCheck || pCurrentState != nullptr)
1199                 pCheckValue = g_variant_new_boolean( bCheck );
1200         }
1201 
1202         if ( pCheckValue != nullptr )
1203         {
1204             if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
1205             {
1206                 g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
1207             }
1208             else
1209             {
1210                 g_variant_unref (pCheckValue);
1211             }
1212         }
1213 
1214         if ( pCurrentState != nullptr )
1215             g_variant_unref( pCurrentState );
1216     }
1217 
1218     if ( aCommand )
1219         g_free( aCommand );
1220 }
1221 
NativeSetEnableItem(gchar const * aCommand,gboolean bEnable)1222 void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
1223 {
1224     SolarMutexGuard aGuard;
1225     GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
1226 
1227     if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
1228         g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
1229 }
1230 
NativeSetItemText(unsigned nSection,unsigned nItemPos,const OUString & rText)1231 void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
1232 {
1233     SolarMutexGuard aGuard;
1234     // Escape all underscores so that they don't get interpreted as hotkeys
1235     OUString aText = rText.replaceAll( "_", "__" );
1236     // Replace the LibreOffice hotkey identifier with an underscore
1237     aText = aText.replace( '~', '_' );
1238     // quick and easy replacement of & to &amp; etc, for e.g. "Zoom & Pa_n" in impress
1239 #if GTK_CHECK_VERSION(4, 0, 0)
1240     {
1241         OUString aTempString;
1242         vcl::escapeStringXML(aText, aTempString);
1243         aText = aTempString;
1244     }
1245 #endif
1246     OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );
1247 
1248     // Update item text only when necessary.
1249     gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1250 
1251     if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
1252         g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );
1253 
1254     if ( aLabel )
1255         g_free( aLabel );
1256 }
1257 
NativeSetItemIcon(unsigned nSection,unsigned nItemPos,const Image & rImage)1258 void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
1259 {
1260 #if GLIB_CHECK_VERSION(2,38,0)
1261     if (!rImage && mbHasNullItemIcon)
1262         return;
1263 
1264     SolarMutexGuard aGuard;
1265 
1266     if (!!rImage)
1267     {
1268         SvMemoryStream* pMemStm = new SvMemoryStream;
1269         vcl::PNGWriter aWriter(rImage.GetBitmapEx());
1270         aWriter.Write(*pMemStm);
1271 
1272         GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
1273                                                     pMemStm->TellEnd(),
1274                                                     DestroyMemoryStream,
1275                                                     pMemStm);
1276 
1277         GIcon *pIcon = g_bytes_icon_new(pBytes);
1278 
1279         g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
1280         g_object_unref(pIcon);
1281         g_bytes_unref(pBytes);
1282         mbHasNullItemIcon = false;
1283     }
1284     else
1285     {
1286         g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
1287         mbHasNullItemIcon = true;
1288     }
1289 #else
1290     (void)nSection;
1291     (void)nItemPos;
1292     (void)rImage;
1293 #endif
1294 }
1295 
NativeSetAccelerator(unsigned nSection,unsigned nItemPos,const vcl::KeyCode & rKeyCode,std::u16string_view rKeyName)1296 void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, std::u16string_view rKeyName )
1297 {
1298     SolarMutexGuard aGuard;
1299 
1300     if ( rKeyName.empty() )
1301         return;
1302 
1303     guint nKeyCode;
1304     GdkModifierType nModifiers;
1305     GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);
1306 
1307     gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );
1308 
1309     gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1310 
1311     if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
1312         g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );
1313 
1314     g_free( aAccelerator );
1315     g_free( aCurrentAccel );
1316 }
1317 
NativeSetItemCommand(unsigned nSection,unsigned nItemPos,sal_uInt16 nId,const gchar * aCommand,MenuItemBits nBits,bool bChecked,bool bIsSubmenu)1318 bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
1319                                        unsigned nItemPos,
1320                                        sal_uInt16 nId,
1321                                        const gchar* aCommand,
1322                                        MenuItemBits nBits,
1323                                        bool bChecked,
1324                                        bool bIsSubmenu )
1325 {
1326     bool bSubMenuAddedOrRemoved = false;
1327 
1328     SolarMutexGuard aGuard;
1329     GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
1330 
1331     GVariant *pTarget = nullptr;
1332 
1333     if (g_action_group_has_action(mpActionGroup, aCommand))
1334         g_lo_action_group_remove(pActionGroup, aCommand);
1335 
1336     if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
1337     {
1338         // Item is a checkmark button.
1339         GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
1340         GVariant* pState = g_variant_new_boolean( bChecked );
1341 
1342         g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
1343     }
1344     else if ( nBits & MenuItemBits::RADIOCHECK )
1345     {
1346         // Item is a radio button.
1347         GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
1348         GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
1349         GVariant* pState = g_variant_new_string( "" );
1350         pTarget = g_variant_new_string( aCommand );
1351 
1352         g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
1353     }
1354     else
1355     {
1356         // Item is not special, so insert a stateless action.
1357         g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
1358     }
1359 
1360     GLOMenu* pMenu = G_LO_MENU( mpMenuModel );
1361 
1362     // Menu item is not updated unless it's necessary.
1363     gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );
1364 
1365     if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
1366     {
1367         bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
1368         bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
1369         if (bSubMenuAddedOrRemoved)
1370         {
1371             //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
1372             //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
1373             //support achieving that
1374             gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
1375             g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
1376             g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
1377             g_free(pLabel);
1378         }
1379 
1380         g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );
1381 
1382         gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );
1383 
1384         if ( bIsSubmenu )
1385             g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
1386         else
1387         {
1388             g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
1389             pTarget = nullptr;
1390         }
1391 
1392         g_free( aItemCommand );
1393     }
1394 
1395     if ( aCurrentCommand )
1396         g_free( aCurrentCommand );
1397 
1398     if (pTarget)
1399         g_variant_unref(pTarget);
1400 
1401     return bSubMenuAddedOrRemoved;
1402 }
1403 
GetTopLevel()1404 GtkSalMenu* GtkSalMenu::GetTopLevel()
1405 {
1406     GtkSalMenu *pMenu = this;
1407     while (pMenu->mpParentSalMenu)
1408         pMenu = pMenu->mpParentSalMenu;
1409     return pMenu;
1410 }
1411 
DispatchCommand(const gchar * pCommand)1412 void GtkSalMenu::DispatchCommand(const gchar *pCommand)
1413 {
1414     SolarMutexGuard aGuard;
1415     MenuAndId aMenuAndId = decode_command(pCommand);
1416     GtkSalMenu* pSalSubMenu = aMenuAndId.first;
1417     GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
1418     if (pTopLevel->mpMenuBarWidget)
1419     {
1420 #if !GTK_CHECK_VERSION(4, 0, 0)
1421         // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
1422         // closing the menu. To handle this properly I imagine we need to set groups for the
1423         // radiobuttons so the others visually untoggle when the active one is toggled and
1424         // we would further need to teach vcl that the state can change more than once.
1425         //
1426         // or we could unconditionally deactivate the menus if regardless of what particular
1427         // type of menu item got activated
1428         gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
1429 #endif
1430     }
1431     pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
1432 }
1433 
ActivateAllSubmenus(Menu * pMenuBar)1434 void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
1435 {
1436     // We can re-enter this method via the new event loop that gets created
1437     // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
1438     // flag to detect that and skip some startup work.
1439     if (!mbInActivateCallback)
1440     {
1441         mbInActivateCallback = true;
1442         pMenuBar->HandleMenuActivateEvent(GetMenu());
1443         mbInActivateCallback = false;
1444         for (GtkSalMenuItem* pSalItem : maItems)
1445         {
1446             if ( pSalItem->mpSubMenu != nullptr )
1447             {
1448                 pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
1449             }
1450         }
1451         Update();
1452         pMenuBar->HandleMenuDeActivateEvent(GetMenu());
1453     }
1454 }
1455 
ClearActionGroupAndMenuModel()1456 void GtkSalMenu::ClearActionGroupAndMenuModel()
1457 {
1458     SetMenuModel(nullptr);
1459     mpActionGroup = nullptr;
1460     for (GtkSalMenuItem* pSalItem : maItems)
1461     {
1462         if ( pSalItem->mpSubMenu != nullptr )
1463         {
1464             pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
1465         }
1466     }
1467 }
1468 
Activate(const gchar * pCommand)1469 void GtkSalMenu::Activate(const gchar* pCommand)
1470 {
1471     MenuAndId aMenuAndId = decode_command(pCommand);
1472     GtkSalMenu* pSalMenu = aMenuAndId.first;
1473     Menu* pVclMenu = pSalMenu->GetMenu();
1474     if (pVclMenu->isDisposed())
1475         return;
1476     GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
1477     Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
1478     GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;
1479 
1480     pSubMenu->mbInActivateCallback = true;
1481     pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
1482     pSubMenu->mbInActivateCallback = false;
1483     pVclSubMenu->UpdateNativeMenu();
1484 }
1485 
Deactivate(const gchar * pCommand)1486 void GtkSalMenu::Deactivate(const gchar* pCommand)
1487 {
1488     MenuAndId aMenuAndId = decode_command(pCommand);
1489     GtkSalMenu* pSalMenu = aMenuAndId.first;
1490     Menu* pVclMenu = pSalMenu->GetMenu();
1491     if (pVclMenu->isDisposed())
1492         return;
1493     GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
1494     Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
1495     pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
1496 }
1497 
EnableUnity(bool bEnable)1498 void GtkSalMenu::EnableUnity(bool bEnable)
1499 {
1500     bUnityMode = bEnable;
1501 
1502     MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
1503     bool bDisplayable(pMenuBar->IsDisplayable());
1504 
1505     if (bEnable)
1506     {
1507         DestroyMenuBarWidget();
1508         UpdateFull();
1509         if (!bDisplayable)
1510             ShowMenuBar(false);
1511     }
1512     else
1513     {
1514         Update();
1515         ShowMenuBar(bDisplayable);
1516     }
1517 
1518     pMenuBar->LayoutChanged();
1519 }
1520 
ShowMenuBar(bool bVisible)1521 void GtkSalMenu::ShowMenuBar( bool bVisible )
1522 {
1523     // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
1524     if (bUnityMode)
1525     {
1526         if (bVisible)
1527             Update();
1528         else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
1529             g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
1530     }
1531     else if (bVisible)
1532         CreateMenuBarWidget();
1533     else
1534         DestroyMenuBarWidget();
1535 }
1536 
IsItemVisible(unsigned nPos)1537 bool GtkSalMenu::IsItemVisible( unsigned nPos )
1538 {
1539     SolarMutexGuard aGuard;
1540     bool bVisible = false;
1541 
1542     if ( nPos < maItems.size() )
1543         bVisible = maItems[ nPos ]->mbVisible;
1544 
1545     return bVisible;
1546 }
1547 
CheckItem(unsigned,bool)1548 void GtkSalMenu::CheckItem( unsigned, bool )
1549 {
1550 }
1551 
EnableItem(unsigned nPos,bool bEnable)1552 void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
1553 {
1554     SolarMutexGuard aGuard;
1555     if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
1556     {
1557         gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
1558         NativeSetEnableItem( pCommand, bEnable );
1559         g_free( pCommand );
1560     }
1561 }
1562 
ShowItem(unsigned nPos,bool bShow)1563 void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
1564 {
1565     SolarMutexGuard aGuard;
1566     if ( nPos < maItems.size() )
1567     {
1568         maItems[ nPos ]->mbVisible = bShow;
1569         if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
1570             Update();
1571     }
1572 }
1573 
SetItemText(unsigned nPos,SalMenuItem * pSalMenuItem,const OUString & rText)1574 void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
1575 {
1576     SolarMutexGuard aGuard;
1577     if ( !bUnityMode || mbInActivateCallback || mbNeedsUpdate || !GetTopLevel()->mbMenuBar || ( nPos >= maItems.size() ) )
1578         return;
1579 
1580     gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );
1581 
1582     gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
1583     for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
1584     {
1585         gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
1586         for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
1587         {
1588             gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );
1589 
1590             if ( !g_strcmp0( pCommandFromModel, pCommand ) )
1591             {
1592                 NativeSetItemText( nSection, nItem, rText );
1593                 g_free( pCommandFromModel );
1594                 g_free( pCommand );
1595                 return;
1596             }
1597 
1598             g_free( pCommandFromModel );
1599         }
1600     }
1601 
1602     g_free( pCommand );
1603 }
1604 
SetItemImage(unsigned,SalMenuItem *,const Image &)1605 void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
1606 {
1607 }
1608 
SetAccelerator(unsigned,SalMenuItem *,const vcl::KeyCode &,const OUString &)1609 void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
1610 {
1611 }
1612 
GetSystemMenuData(SystemMenuData *)1613 void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
1614 {
1615 }
1616 
GetMenuBarHeight() const1617 int GtkSalMenu::GetMenuBarHeight() const
1618 {
1619     return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
1620 }
1621 
1622 /*
1623  * GtkSalMenuItem
1624  */
1625 
GtkSalMenuItem(const SalItemParams * pItemData)1626 GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
1627     mpParentMenu( nullptr ),
1628     mpSubMenu( nullptr ),
1629     mnType( pItemData->eType ),
1630     mnId( pItemData->nId ),
1631     mbVisible( true )
1632 {
1633 }
1634 
~GtkSalMenuItem()1635 GtkSalMenuItem::~GtkSalMenuItem()
1636 {
1637 }
1638 
1639 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1640