nss: upgrade to release 3.73
[LibreOffice.git] / vcl / unx / gtk3 / gtk3gtksalmenu.cxx
blob3ed2487ff4f37b4ae012fc7f008152da8dfeaa95
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <unx/gtk/gtksalmenu.hxx>
12 #include <unx/gtk/gtkdata.hxx>
13 #include <unx/gtk/glomenu.h>
14 #include <unx/gtk/gloactiongroup.h>
15 #include <vcl/floatwin.hxx>
16 #include <vcl/menu.hxx>
17 #include <vcl/pngwrite.hxx>
19 #include <sal/log.hxx>
20 #include <tools/stream.hxx>
21 #include <window.h>
22 #include <strings.hrc>
24 static bool bUnityMode = false;
27 * This function generates a unique command name for each menu item
29 static gchar* GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
31 OString aCommand = "window-" +
32 OString::number(reinterpret_cast<unsigned long>(pParentMenu)) +
33 "-" + OString::number(nItemId);
34 return g_strdup(aCommand.getStr());
37 static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
39 return GetCommandForItem(pSalMenuItem->mpParentMenu,
40 pSalMenuItem->mnId);
43 bool GtkSalMenu::PrepUpdate()
45 return mpMenuModel && mpActionGroup;
49 * Menu updating methods
52 static void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nSection, unsigned nValidItems )
54 sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
56 while ( nSectionItems > static_cast<sal_Int32>(nValidItems) )
58 gchar* aCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, --nSectionItems );
60 if ( aCommand != nullptr && pOldCommandList != nullptr )
61 *pOldCommandList = g_list_append( *pOldCommandList, g_strdup( aCommand ) );
63 g_free( aCommand );
65 g_lo_menu_remove_from_section( pMenu, nSection, nSectionItems );
69 typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;
71 namespace
73 MenuAndId decode_command(const gchar *action_name)
75 OString sCommand(action_name);
77 sal_Int32 nIndex = 0;
78 OString sWindow = sCommand.getToken(0, '-', nIndex);
79 OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
80 OString sItemId = sCommand.getToken(0, '-', nIndex);
82 GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());
84 assert(sWindow == "window" && pSalSubMenu);
85 (void) sWindow;
87 return MenuAndId(pSalSubMenu, sItemId.toInt32());
91 static void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList,
92 sal_Int32 nSection, GActionGroup* pActionGroup)
94 while (nSection >= 0)
96 sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
97 while (nSectionItems--)
99 gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems);
100 // remove disabled entries
101 bool bRemove = !g_action_group_get_action_enabled(pActionGroup, pCommand);
102 if (!bRemove)
104 //also remove any empty submenus
105 GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems);
106 if (pSubMenuModel)
108 gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel));
109 if (nSubMenuSections == 0)
110 bRemove = true;
111 else if (nSubMenuSections == 1)
113 gint nItems = g_lo_menu_get_n_items_from_section(pSubMenuModel, 0);
114 if (nItems == 0)
115 bRemove = true;
116 else if (nItems == 1)
118 //If the only entry is the "No Selection Possible" entry, then we are allowed
119 //to removed it
120 gchar* pSubCommand = g_lo_menu_get_command_from_item_in_section(pSubMenuModel, 0, 0);
121 MenuAndId aMenuAndId(decode_command(pSubCommand));
122 bRemove = aMenuAndId.second == 0xFFFF;
123 g_free(pSubCommand);
129 if (bRemove)
131 //but tdf#86850 Always display clipboard functions
132 bRemove = g_strcmp0(pCommand, ".uno:Cut") &&
133 g_strcmp0(pCommand, ".uno:Copy") &&
134 g_strcmp0(pCommand, ".uno:Paste");
137 if (bRemove)
139 if (pCommand != nullptr && pOldCommandList != nullptr)
140 *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand));
141 g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems);
144 g_free(pCommand);
146 --nSection;
150 static void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection )
152 if ( pMenu == nullptr || pOldCommandList == nullptr )
153 return;
155 sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1;
157 for ( ; n > nLastSection; n--)
159 RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 );
160 g_lo_menu_remove( pMenu, n );
164 static gint CompareStr( gpointer str1, gpointer str2 )
166 return g_strcmp0( static_cast<const gchar*>(str1), static_cast<const gchar*>(str2) );
169 static void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, GList* pNewCommandList )
171 if ( pActionGroup == nullptr || pOldCommandList == nullptr )
173 g_list_free_full( pOldCommandList, g_free );
174 g_list_free_full( pNewCommandList, g_free );
175 return;
178 while ( pNewCommandList != nullptr )
180 GList* pNewCommand = g_list_first( pNewCommandList );
181 pNewCommandList = g_list_remove_link( pNewCommandList, pNewCommand );
183 gpointer aCommand = g_list_nth_data( pNewCommand, 0 );
185 GList* pOldCommand = g_list_find_custom( pOldCommandList, aCommand, reinterpret_cast<GCompareFunc>(CompareStr) );
187 if ( pOldCommand != nullptr )
189 pOldCommandList = g_list_remove_link( pOldCommandList, pOldCommand );
190 g_list_free_full( pOldCommand, g_free );
193 g_list_free_full( pNewCommand, g_free );
196 while ( pOldCommandList != nullptr )
198 GList* pCommand = g_list_first( pOldCommandList );
199 pOldCommandList = g_list_remove_link( pOldCommandList, pCommand );
201 gchar* aCommand = static_cast<gchar*>(g_list_nth_data( pCommand, 0 ));
203 g_lo_action_group_remove( pActionGroup, aCommand );
205 g_list_free_full( pCommand, g_free );
209 void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
211 SolarMutexGuard aGuard;
213 SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate");
214 if( !PrepUpdate() )
215 return;
217 if (mbNeedsUpdate)
219 mbNeedsUpdate = false;
220 if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
222 maUpdateMenuBarIdle.Stop();
223 // tdf#124391 Prevent doubled menus in global menu
224 if (!bUnityMode)
226 maUpdateMenuBarIdle.Invoke();
227 return;
232 Menu* pVCLMenu = mpVCLMenu;
233 GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
234 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
235 SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
236 GList *pOldCommandList = nullptr;
237 GList *pNewCommandList = nullptr;
239 sal_uInt16 nLOMenuSize = g_menu_model_get_n_items( G_MENU_MODEL( pLOMenu ) );
241 if ( nLOMenuSize == 0 )
242 g_lo_menu_new_section( pLOMenu, 0, nullptr );
244 sal_Int32 nSection = 0;
245 sal_Int32 nItemPos = 0;
246 sal_Int32 validItems = 0;
247 sal_Int32 nItem;
249 for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) {
250 if ( !IsItemVisible( nItem ) )
251 continue;
253 GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
254 sal_uInt16 nId = pSalMenuItem->mnId;
256 // PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level
257 // popup menu, but we have our own implementation below, so skip that one.
258 if ( nId == 0xFFFF )
259 continue;
261 if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
263 // Delete extra items from current section.
264 RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
266 nSection++;
267 nItemPos = 0;
268 validItems = 0;
270 if ( nLOMenuSize <= nSection )
272 g_lo_menu_new_section( pLOMenu, nSection, nullptr );
273 nLOMenuSize++;
276 continue;
279 if ( nItemPos >= g_lo_menu_get_n_items_from_section( pLOMenu, nSection ) )
280 g_lo_menu_insert_in_section( pLOMenu, nSection, nItemPos, "EMPTY STRING" );
282 // Get internal menu item values.
283 OUString aText = pVCLMenu->GetItemText( nId );
284 Image aImage = pVCLMenu->GetItemImage( nId );
285 bool bEnabled = pVCLMenu->IsItemEnabled( nId );
286 vcl::KeyCode nAccelKey = pVCLMenu->GetAccelKey( nId );
287 bool bChecked = pVCLMenu->IsItemChecked( nId );
288 MenuItemBits itemBits = pVCLMenu->GetItemBits( nId );
290 // Store current item command in command list.
291 gchar *aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pLOMenu, nSection, nItemPos );
293 if ( aCurrentCommand != nullptr )
294 pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );
296 // Get the new command for the item.
297 gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);
299 // Force updating of native menu labels.
300 NativeSetItemText( nSection, nItemPos, aText );
301 NativeSetItemIcon( nSection, nItemPos, aImage );
302 NativeSetAccelerator( nSection, nItemPos, nAccelKey, nAccelKey.GetName( GetFrame()->GetWindow() ) );
304 if ( g_strcmp0( aNativeCommand, "" ) != 0 && pSalMenuItem->mpSubMenu == nullptr )
306 NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, bChecked, false );
307 NativeCheckItem( nSection, nItemPos, itemBits, bChecked );
308 NativeSetEnableItem( aNativeCommand, bEnabled );
310 pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
313 GtkSalMenu* pSubmenu = pSalMenuItem->mpSubMenu;
315 if ( pSubmenu && pSubmenu->GetMenu() )
317 bool bNonMenuChangedToMenu = NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, false, true );
318 pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
320 GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
322 if ( pSubMenuModel == nullptr )
324 g_lo_menu_new_submenu_in_item_in_section( pLOMenu, nSection, nItemPos );
325 pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
328 assert(pSubMenuModel);
330 if (bRecurse || bNonMenuChangedToMenu)
332 SAL_INFO("vcl.unity", "preparing submenu " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
333 pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
334 pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
335 pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
338 g_object_unref( pSubMenuModel );
341 g_free( aNativeCommand );
343 ++nItemPos;
344 ++validItems;
347 if (bRemoveDisabledEntries)
349 // Delete disabled items in last section.
350 RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
353 // Delete extra items in last section.
354 RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
356 // Delete extra sections.
357 RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );
359 // Delete unused commands.
360 RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );
362 // Resolves: tdf#103166 if the menu is empty, add a disabled
363 // <No Selection Possible> placeholder.
364 sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
365 gint nItemsCount = 0;
366 for (nSection = 0; nSection < nSectionsCount; ++nSection)
368 nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
369 if (nItemsCount)
370 break;
372 if (!nItemsCount)
374 gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
375 OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
376 g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
377 OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
378 NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
379 NativeSetEnableItem(aNativeCommand, false);
380 g_free(aNativeCommand);
384 void GtkSalMenu::Update()
386 //find out if top level is a menubar or not, if not, then it's a popup menu
387 //hierarchy and in those we hide (most) disabled entries
388 const GtkSalMenu* pMenu = this;
389 while (pMenu->mpParentSalMenu)
390 pMenu = pMenu->mpParentSalMenu;
392 bool bAlwaysShowDisabledEntries;
393 if (pMenu->mbMenuBar)
394 bAlwaysShowDisabledEntries = true;
395 else
396 bAlwaysShowDisabledEntries = bool(mpVCLMenu->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries);
398 ImplUpdate(false, !bAlwaysShowDisabledEntries);
401 static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
403 Point *pPos = static_cast<Point*>(user_data);
404 *x = pPos->X();
405 if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
407 GtkRequisition natural_size;
408 gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
409 *x -= natural_size.width;
411 *y = pPos->Y();
412 *push_in = false;
415 bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
416 FloatWinPopupFlags nFlags)
418 VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
419 mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());
421 GLOActionGroup* pActionGroup = g_lo_action_group_new();
422 mpActionGroup = G_ACTION_GROUP(pActionGroup);
423 mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
424 // Generate the main menu structure, populates mpMenuModel
425 UpdateFull();
427 GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
428 gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
429 gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);
431 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
432 //it during DispatchCommand, returning now to the outer loop causes the
433 //launching PopupMenu to be destroyed, instead run the subloop here
434 //until the gtk menu is destroyed
435 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
436 g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
439 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
440 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
441 // implemented as popups, which means that we cannot show any popup if there is a
442 // visible tooltip.
443 // hide any current tooltip
444 mpFrame->HideTooltip();
445 // don't allow any more to appear until menu is dismissed
446 mpFrame->BlockTooltip();
448 #if GTK_CHECK_VERSION(3,22,0)
449 if (gtk_check_version(3, 22, 0) == nullptr)
451 GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
453 if (nFlags & FloatWinPopupFlags::Left)
455 rect_anchor = GDK_GRAVITY_NORTH_WEST;
456 menu_anchor = GDK_GRAVITY_NORTH_EAST;
458 else if (nFlags & FloatWinPopupFlags::Up)
460 rect_anchor = GDK_GRAVITY_NORTH_WEST;
461 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
463 else if (nFlags & FloatWinPopupFlags::Right)
465 rect_anchor = GDK_GRAVITY_NORTH_EAST;
468 tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
469 aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
470 GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
471 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
473 GdkWindow* gdkWindow = gtk_widget_get_window(mpFrame->getMouseEventWidget());
474 gtk_menu_popup_at_rect(GTK_MENU(pWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
476 else
477 #endif
479 guint nButton;
480 guint32 nTime;
482 //typically there is an event, and we can then distinguish if this was
483 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
484 //doesn't)
485 GdkEvent *pEvent = gtk_get_current_event();
486 if (pEvent)
488 gdk_event_get_button(pEvent, &nButton);
489 nTime = gdk_event_get_time(pEvent);
491 else
493 nButton = 0;
494 nTime = GtkSalFrame::GetLastInputEventTime();
497 // do the same strange semantics as vcl popup windows to arrive at a frame geometry
498 // in mirrored UI case; best done by actually executing the same code
499 sal_uInt16 nArrangeIndex;
500 Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
501 aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
503 gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
504 &aPos, nButton, nTime);
507 if (g_main_loop_is_running(pLoop))
509 gdk_threads_leave();
510 g_main_loop_run(pLoop);
511 gdk_threads_enter();
513 g_main_loop_unref(pLoop);
515 mpVCLMenu->Deactivate();
517 gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
519 gtk_widget_destroy(pWidget);
521 g_object_unref(mpActionGroup);
522 ClearActionGroupAndMenuModel();
524 // undo tooltip blocking
525 mpFrame->UnblockTooltip();
527 mpFrame = nullptr;
529 return true;
533 * GtkSalMenu
536 GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
537 mbInActivateCallback( false ),
538 mbMenuBar( bMenuBar ),
539 mbNeedsUpdate( false ),
540 mbReturnFocusToDocument( false ),
541 mbAddedGrab( false ),
542 mpMenuBarContainerWidget( nullptr ),
543 mpMenuAllowShrinkWidget( nullptr ),
544 mpMenuBarWidget( nullptr ),
545 mpMenuBarContainerProvider( nullptr ),
546 mpMenuBarProvider( nullptr ),
547 mpCloseButton( nullptr ),
548 mpVCLMenu( nullptr ),
549 mpParentSalMenu( nullptr ),
550 mpFrame( nullptr ),
551 mpMenuModel( nullptr ),
552 mpActionGroup( nullptr )
554 //typically this only gets called after the menu has been customized on the
555 //next idle slot, in the normal case of a new menubar SetFrame is called
556 //directly long before this idle would get called.
557 maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
558 maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
559 maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
562 IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
564 SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
565 if (!mpFrame)
566 return;
567 SetFrame(mpFrame);
570 void GtkSalMenu::SetNeedsUpdate()
572 GtkSalMenu* pMenu = this;
573 // start that the menu and its parents are in need of an update
574 // on the next activation
575 while (pMenu && !pMenu->mbNeedsUpdate)
577 pMenu->mbNeedsUpdate = true;
578 pMenu = pMenu->mpParentSalMenu;
580 // only if a menubar is directly updated do we force in a full
581 // structure update
582 if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
583 maUpdateMenuBarIdle.Start();
586 void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
588 if (mpMenuModel)
589 g_object_unref(mpMenuModel);
590 mpMenuModel = pMenuModel;
591 if (mpMenuModel)
592 g_object_ref(mpMenuModel);
595 GtkSalMenu::~GtkSalMenu()
597 SolarMutexGuard aGuard;
599 // tdf#140225 we expect all items to be removed by Menu::dispose
600 // before this dtor is called
601 assert(maItems.empty());
603 DestroyMenuBarWidget();
605 if (mpMenuModel)
606 g_object_unref(mpMenuModel);
608 if (mpFrame)
609 mpFrame->SetMenu(nullptr);
612 bool GtkSalMenu::VisibleMenuBar()
614 return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
617 void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
619 SolarMutexGuard aGuard;
620 GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );
622 if ( nPos == MENU_APPEND )
623 maItems.push_back( pItem );
624 else
625 maItems.insert( maItems.begin() + nPos, pItem );
627 pItem->mpParentMenu = this;
629 SetNeedsUpdate();
632 void GtkSalMenu::RemoveItem( unsigned nPos )
634 SolarMutexGuard aGuard;
636 // tdf#140225 clear associated action when the item is removed
637 if (mpActionGroup)
639 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP(mpActionGroup);
640 gchar* pCommand = GetCommandForItem(maItems[nPos]);
641 g_lo_action_group_remove(pActionGroup, pCommand);
642 g_free(pCommand);
645 maItems.erase( maItems.begin() + nPos );
646 SetNeedsUpdate();
649 void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
651 SolarMutexGuard aGuard;
652 GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
653 GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );
655 if ( pGtkSubMenu == nullptr )
656 return;
658 pGtkSubMenu->mpParentSalMenu = this;
659 pItem->mpSubMenu = pGtkSubMenu;
661 SetNeedsUpdate();
664 static void CloseMenuBar(GtkWidget *, gpointer pMenu)
666 Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
669 GtkWidget* GtkSalMenu::AddButton(GtkWidget *pImage)
671 GtkWidget* pButton = gtk_button_new();
673 gtk_button_set_relief(GTK_BUTTON(pButton), GTK_RELIEF_NONE);
674 gtk_button_set_focus_on_click(GTK_BUTTON(pButton), false);
675 gtk_widget_set_can_focus(pButton, false);
677 GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pButton));
679 gtk_style_context_add_class(pButtonContext, "flat");
680 gtk_style_context_add_class(pButtonContext, "small-button");
682 gtk_widget_show(pImage);
684 gtk_widget_set_valign(pButton, GTK_ALIGN_CENTER);
686 gtk_container_add(GTK_CONTAINER(pButton), pImage);
687 gtk_widget_show_all(pButton);
688 return pButton;
691 void GtkSalMenu::ShowCloseButton(bool bShow)
693 assert(mbMenuBar);
694 if (!mpMenuBarContainerWidget)
695 return;
697 if (!bShow)
699 if (mpCloseButton)
701 gtk_widget_destroy(mpCloseButton);
702 mpCloseButton = nullptr;
704 return;
707 if (mpCloseButton)
708 return;
710 GIcon* pIcon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
711 GtkWidget* pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
712 g_object_unref(pIcon);
714 mpCloseButton = AddButton(pImage);
716 gtk_widget_set_margin_end(mpCloseButton, 8);
718 OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
719 gtk_widget_set_tooltip_text(mpCloseButton, sToolTip.toUtf8().getStr());
721 MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
722 g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
724 gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, 1, 0, 1, 1);
727 namespace
729 void DestroyMemoryStream(gpointer data)
731 SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
732 delete pMemStm;
736 static void MenuButtonClicked(GtkWidget* pWidget, gpointer pMenu)
738 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
739 OString aId(pStr, pStr ? strlen(pStr) : 0);
740 static_cast<MenuBar*>(pMenu)->HandleMenuButtonEvent(aId.toUInt32());
743 bool GtkSalMenu::AddMenuBarButton(const SalMenuButtonItem& rNewItem)
745 if (!mbMenuBar)
746 return false;
748 if (!mpMenuBarContainerWidget)
749 return false;
751 GtkWidget* pImage = nullptr;
752 if (!!rNewItem.maImage)
754 SvMemoryStream* pMemStm = new SvMemoryStream;
755 vcl::PNGWriter aWriter(rNewItem.maImage.GetBitmapEx());
756 aWriter.Write(*pMemStm);
758 GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
759 pMemStm->TellEnd(),
760 DestroyMemoryStream,
761 pMemStm);
763 GIcon *pIcon = g_bytes_icon_new(pBytes);
764 pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
765 g_object_unref(pIcon);
768 GtkWidget* pButton = AddButton(pImage);
770 maExtraButtons.emplace_back(rNewItem.mnId, pButton);
772 gtk_buildable_set_name(GTK_BUILDABLE(pButton), OString::number(rNewItem.mnId).getStr());
774 gtk_widget_set_tooltip_text(pButton, rNewItem.maToolTipText.toUtf8().getStr());
776 MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
777 g_signal_connect(pButton, "clicked", G_CALLBACK(MenuButtonClicked), pVclMenuBar);
779 if (mpCloseButton)
781 gtk_grid_insert_next_to(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, GTK_POS_LEFT);
782 gtk_grid_attach_next_to(GTK_GRID(mpMenuBarContainerWidget), pButton, mpCloseButton,
783 GTK_POS_LEFT, 1, 1);
785 else
786 gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), pButton, 1, 0, 1, 1);
788 return true;
791 void GtkSalMenu::RemoveMenuBarButton( sal_uInt16 nId )
793 const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
794 return item.first == nId; });
795 if (it != maExtraButtons.end())
797 gint nAttach(0);
798 gtk_container_child_get(GTK_CONTAINER(mpMenuBarContainerWidget), it->second, "left-attach", &nAttach, nullptr);
799 gtk_widget_destroy(it->second);
800 gtk_grid_remove_column(GTK_GRID(mpMenuBarContainerWidget), nAttach);
801 maExtraButtons.erase(it);
805 tools::Rectangle GtkSalMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pReferenceFrame)
807 if (!pReferenceFrame)
808 return tools::Rectangle();
810 const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
811 return item.first == nId; });
812 if (it == maExtraButtons.end())
813 return tools::Rectangle();
815 GtkWidget* pButton = it->second;
817 GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pReferenceFrame);
819 int x, y;
820 if (!gtk_widget_translate_coordinates(pButton, GTK_WIDGET(pFrame->getMouseEventWidget()), 0, 0, &x, &y))
821 return tools::Rectangle();
823 return tools::Rectangle(Point(x, y), Size(gtk_widget_get_allocated_width(pButton),
824 gtk_widget_get_allocated_height(pButton)));
827 //Typically when the menubar is deactivated we want the focus to return
828 //to where it came from. If the menubar was activated because of F6
829 //moving focus into the associated VCL menubar then on pressing ESC
830 //or any other normal reason for deactivation we want focus to return
831 //to the document, definitely not still stuck in the associated
832 //VCL menubar. But if F6 is pressed while the menubar is activated
833 //we want to pass that F6 back to the VCL menubar which will move
834 //focus to the next pane by itself.
835 void GtkSalMenu::ReturnFocus()
837 if (mbAddedGrab)
839 gtk_grab_remove(mpMenuBarWidget);
840 mbAddedGrab = false;
842 if (!mbReturnFocusToDocument)
843 gtk_widget_grab_focus(GTK_WIDGET(mpFrame->getEventBox()));
844 else
845 mpFrame->GetWindow()->GrabFocusToDocument();
846 mbReturnFocusToDocument = false;
849 gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
851 if (pEvent->keyval == GDK_KEY_F6)
853 mbReturnFocusToDocument = false;
854 gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
855 //because we return false here, the keypress will continue
856 //to propagate and in the case that vcl focus is in
857 //the vcl menubar then that will also process F6 and move
858 //to the next pane
860 return false;
863 //The GtkSalMenu is owner by a Vcl Menu/MenuBar. In the menubar
864 //case the vcl menubar is present and "visible", but with a 0 height
865 //so it not apparent. Normally it acts as though it is not there when
866 //a Native menubar is active. If we return true here, then for keyboard
867 //activation and traversal with F6 through panes then the vcl menubar
868 //acts as though it *is* present and we translate its take focus and F6
869 //traversal key events into the gtk menubar equivalents.
870 bool GtkSalMenu::CanGetFocus() const
872 return mpMenuBarWidget != nullptr;
875 bool GtkSalMenu::TakeFocus()
877 if (!mpMenuBarWidget)
878 return false;
880 //Send a keyboard event to the gtk menubar to let it know it has been
881 //activated via the keyboard. Doesn't do anything except cause the gtk
882 //menubar "keyboard_mode" member to get set to true, so typically mnemonics
883 //are shown which will serve as indication that the menubar has focus
884 //(given that we want to show it with no menus popped down)
885 GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
886 gtk_widget_event(mpMenuBarWidget, event);
887 gdk_event_free(event);
889 //this pairing results in a menubar with keyboard focus with no menus
890 //auto-popped down
891 gtk_grab_add(mpMenuBarWidget);
892 mbAddedGrab = true;
893 gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
894 gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
895 mbReturnFocusToDocument = true;
896 return true;
899 static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
901 GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
902 GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
903 pMenu->ReturnFocus();
906 static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
908 GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
909 return pMenu->SignalKey(pEvent);
912 void GtkSalMenu::CreateMenuBarWidget()
914 if (mpMenuBarContainerWidget)
915 return;
917 GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
918 mpMenuBarContainerWidget = gtk_grid_new();
920 gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
921 gtk_grid_insert_row(pGrid, 0);
922 gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);
924 mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
925 // tdf#129634 don't allow this scrolled window as a candidate to tab into
926 gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false);
927 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
928 // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
929 // but still allow scrolled window to not be sized to the child content.
930 // So the menubar can be shrunk past its nominal smallest width.
931 // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
932 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
933 gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);
935 mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
937 gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
938 gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
939 gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
940 gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
942 g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
943 g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
945 gtk_widget_show_all(mpMenuBarContainerWidget);
947 ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );
949 ApplyPersona();
952 void GtkSalMenu::ApplyPersona()
954 if (!mpMenuBarContainerWidget)
955 return;
956 assert(mbMenuBar);
957 // I'm dubious about the persona theming feature, but as it exists, lets try and support
958 // it, apply the image to the mpMenuBarContainerWidget
959 const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
961 GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
962 if (mpMenuBarContainerProvider)
964 gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
965 mpMenuBarContainerProvider = nullptr;
967 GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
968 if (mpMenuBarProvider)
970 gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
971 mpMenuBarProvider = nullptr;
974 if (!rPersonaBitmap.IsEmpty())
976 if (maPersonaBitmap != rPersonaBitmap)
978 vcl::PNGWriter aPNGWriter(rPersonaBitmap);
979 mxPersonaImage.reset(new utl::TempFile);
980 mxPersonaImage->EnableKillingFile(true);
981 SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
982 aPNGWriter.Write(*pStream);
983 mxPersonaImage->CloseStream();
986 mpMenuBarContainerProvider = gtk_css_provider_new();
987 OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
988 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
989 gtk_css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength(), nullptr);
990 gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
991 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
994 // force the menubar to be transparent when persona is active otherwise for
995 // me the menubar becomes gray when its in the backdrop
996 mpMenuBarProvider = gtk_css_provider_new();
997 static const gchar data[] = "* { "
998 "background-image: none;"
999 "background-color: transparent;"
1000 "}";
1001 gtk_css_provider_load_from_data(mpMenuBarProvider, data, -1, nullptr);
1002 gtk_style_context_add_provider(pMenuBarContext,
1003 GTK_STYLE_PROVIDER(mpMenuBarProvider),
1004 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1006 maPersonaBitmap = rPersonaBitmap;
1009 void GtkSalMenu::DestroyMenuBarWidget()
1011 if (mpMenuBarContainerWidget)
1013 // tdf#140225 call cancel before destroying it in case there are some
1014 // active menus popped open
1015 gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
1017 gtk_widget_destroy(mpMenuBarContainerWidget);
1018 mpMenuBarContainerWidget = nullptr;
1019 mpCloseButton = nullptr;
1023 void GtkSalMenu::SetFrame(const SalFrame* pFrame)
1025 SolarMutexGuard aGuard;
1026 assert(mbMenuBar);
1027 SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
1028 mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));
1030 // if we had a menu on the GtkSalMenu we have to free it as we generate a
1031 // full menu anyway and we might need to reuse an existing model and
1032 // actiongroup
1033 mpFrame->SetMenu( this );
1034 mpFrame->EnsureAppMenuWatch();
1036 // Clean menu model and action group if needed.
1037 GtkWidget* pWidget = mpFrame->getWindow();
1038 GdkWindow* gdkWindow = gtk_widget_get_window( pWidget );
1040 GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
1041 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
1042 SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);
1044 if ( pMenuModel )
1046 if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
1047 g_lo_menu_remove( pMenuModel, 0 );
1049 mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
1052 if ( pActionGroup )
1054 g_lo_action_group_clear( pActionGroup );
1055 mpActionGroup = G_ACTION_GROUP( pActionGroup );
1058 // Generate the main menu structure.
1059 if ( PrepUpdate() )
1060 UpdateFull();
1062 g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );
1064 if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
1066 DestroyMenuBarWidget();
1067 CreateMenuBarWidget();
1071 const GtkSalFrame* GtkSalMenu::GetFrame() const
1073 SolarMutexGuard aGuard;
1074 const GtkSalMenu* pMenu = this;
1075 while( pMenu && ! pMenu->mpFrame )
1076 pMenu = pMenu->mpParentSalMenu;
1077 return pMenu ? pMenu->mpFrame : nullptr;
1080 void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
1082 SolarMutexGuard aGuard;
1084 if ( mpActionGroup == nullptr )
1085 return;
1087 gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1089 if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
1091 GVariant *pCheckValue = nullptr;
1092 GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );
1094 if ( bits & MenuItemBits::RADIOCHECK )
1095 pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
1096 else
1098 // By default, all checked items are checkmark buttons.
1099 if (bCheck || pCurrentState != nullptr)
1100 pCheckValue = g_variant_new_boolean( bCheck );
1103 if ( pCheckValue != nullptr )
1105 if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
1107 g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
1109 else
1111 g_variant_unref (pCheckValue);
1115 if ( pCurrentState != nullptr )
1116 g_variant_unref( pCurrentState );
1119 if ( aCommand )
1120 g_free( aCommand );
1123 void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
1125 SolarMutexGuard aGuard;
1126 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
1128 if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
1129 g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
1132 void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
1134 SolarMutexGuard aGuard;
1135 // Escape all underscores so that they don't get interpreted as hotkeys
1136 OUString aText = rText.replaceAll( "_", "__" );
1137 // Replace the LibreOffice hotkey identifier with an underscore
1138 aText = aText.replace( '~', '_' );
1139 OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );
1141 // Update item text only when necessary.
1142 gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1144 if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
1145 g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );
1147 if ( aLabel )
1148 g_free( aLabel );
1151 void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
1153 #if GLIB_CHECK_VERSION(2,38,0)
1154 if (!rImage && mbHasNullItemIcon)
1155 return;
1157 SolarMutexGuard aGuard;
1159 if (!!rImage)
1161 SvMemoryStream* pMemStm = new SvMemoryStream;
1162 vcl::PNGWriter aWriter(rImage.GetBitmapEx());
1163 aWriter.Write(*pMemStm);
1165 GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
1166 pMemStm->TellEnd(),
1167 DestroyMemoryStream,
1168 pMemStm);
1170 GIcon *pIcon = g_bytes_icon_new(pBytes);
1172 g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
1173 g_object_unref(pIcon);
1174 g_bytes_unref(pBytes);
1175 mbHasNullItemIcon = false;
1177 else
1179 g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
1180 mbHasNullItemIcon = true;
1182 #else
1183 (void)nSection;
1184 (void)nItemPos;
1185 (void)rImage;
1186 #endif
1189 void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, std::u16string_view rKeyName )
1191 SolarMutexGuard aGuard;
1193 if ( rKeyName.empty() )
1194 return;
1196 guint nKeyCode;
1197 GdkModifierType nModifiers;
1198 GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);
1200 gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );
1202 gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1204 if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
1205 g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );
1207 g_free( aAccelerator );
1208 g_free( aCurrentAccel );
1211 bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
1212 unsigned nItemPos,
1213 sal_uInt16 nId,
1214 const gchar* aCommand,
1215 MenuItemBits nBits,
1216 bool bChecked,
1217 bool bIsSubmenu )
1219 bool bSubMenuAddedOrRemoved = false;
1221 SolarMutexGuard aGuard;
1222 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
1224 GVariant *pTarget = nullptr;
1226 if (g_action_group_has_action(mpActionGroup, aCommand))
1227 g_lo_action_group_remove(pActionGroup, aCommand);
1229 if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
1231 // Item is a checkmark button.
1232 GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
1233 GVariant* pState = g_variant_new_boolean( bChecked );
1235 g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
1237 else if ( nBits & MenuItemBits::RADIOCHECK )
1239 // Item is a radio button.
1240 GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
1241 GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
1242 GVariant* pState = g_variant_new_string( "" );
1243 pTarget = g_variant_new_string( aCommand );
1245 g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
1247 else
1249 // Item is not special, so insert a stateless action.
1250 g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
1253 GLOMenu* pMenu = G_LO_MENU( mpMenuModel );
1255 // Menu item is not updated unless it's necessary.
1256 gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );
1258 if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
1260 bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
1261 bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
1262 if (bSubMenuAddedOrRemoved)
1264 //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
1265 //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
1266 //support achieving that
1267 gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
1268 g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
1269 g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
1270 g_free(pLabel);
1273 g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );
1275 gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );
1277 if ( bIsSubmenu )
1278 g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
1279 else
1281 g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
1282 pTarget = nullptr;
1285 g_free( aItemCommand );
1288 if ( aCurrentCommand )
1289 g_free( aCurrentCommand );
1291 if (pTarget)
1292 g_variant_unref(pTarget);
1294 return bSubMenuAddedOrRemoved;
1297 GtkSalMenu* GtkSalMenu::GetTopLevel()
1299 GtkSalMenu *pMenu = this;
1300 while (pMenu->mpParentSalMenu)
1301 pMenu = pMenu->mpParentSalMenu;
1302 return pMenu;
1305 void GtkSalMenu::DispatchCommand(const gchar *pCommand)
1307 SolarMutexGuard aGuard;
1308 MenuAndId aMenuAndId = decode_command(pCommand);
1309 GtkSalMenu* pSalSubMenu = aMenuAndId.first;
1310 GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
1311 if (pTopLevel->mpMenuBarWidget)
1313 // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
1314 // closing the menu. To handle this properly I imagine we need to set groups for the
1315 // radiobuttons so the others visually untoggle when the active one is toggled and
1316 // we would further need to teach vcl that the state can change more than once.
1318 // or we could unconditionally deactivate the menus if regardless of what particular
1319 // type of menu item got activated
1320 gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
1322 pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
1325 void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
1327 // We can re-enter this method via the new event loop that gets created
1328 // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
1329 // flag to detect that and skip some startup work.
1330 if (!mbInActivateCallback)
1332 mbInActivateCallback = true;
1333 pMenuBar->HandleMenuActivateEvent(GetMenu());
1334 mbInActivateCallback = false;
1335 for (GtkSalMenuItem* pSalItem : maItems)
1337 if ( pSalItem->mpSubMenu != nullptr )
1339 pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
1342 Update();
1343 pMenuBar->HandleMenuDeActivateEvent(GetMenu());
1347 void GtkSalMenu::ClearActionGroupAndMenuModel()
1349 SetMenuModel(nullptr);
1350 mpActionGroup = nullptr;
1351 for (GtkSalMenuItem* pSalItem : maItems)
1353 if ( pSalItem->mpSubMenu != nullptr )
1355 pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
1360 void GtkSalMenu::Activate(const gchar* pCommand)
1362 MenuAndId aMenuAndId = decode_command(pCommand);
1363 GtkSalMenu* pSalMenu = aMenuAndId.first;
1364 Menu* pVclMenu = pSalMenu->GetMenu();
1365 if (pVclMenu->isDisposed())
1366 return;
1367 GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
1368 Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
1369 GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;
1371 pSubMenu->mbInActivateCallback = true;
1372 pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
1373 pSubMenu->mbInActivateCallback = false;
1374 pVclSubMenu->UpdateNativeMenu();
1377 void GtkSalMenu::Deactivate(const gchar* pCommand)
1379 MenuAndId aMenuAndId = decode_command(pCommand);
1380 GtkSalMenu* pSalMenu = aMenuAndId.first;
1381 Menu* pVclMenu = pSalMenu->GetMenu();
1382 if (pVclMenu->isDisposed())
1383 return;
1384 GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
1385 Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
1386 pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
1389 void GtkSalMenu::EnableUnity(bool bEnable)
1391 bUnityMode = bEnable;
1393 MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
1394 bool bDisplayable(pMenuBar->IsDisplayable());
1396 if (bEnable)
1398 DestroyMenuBarWidget();
1399 UpdateFull();
1400 if (!bDisplayable)
1401 ShowMenuBar(false);
1403 else
1405 Update();
1406 ShowMenuBar(bDisplayable);
1409 pMenuBar->LayoutChanged();
1412 void GtkSalMenu::ShowMenuBar( bool bVisible )
1414 // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
1415 if (bUnityMode)
1417 if (bVisible)
1418 Update();
1419 else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
1420 g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
1422 else if (bVisible)
1423 CreateMenuBarWidget();
1424 else
1425 DestroyMenuBarWidget();
1428 bool GtkSalMenu::IsItemVisible( unsigned nPos )
1430 SolarMutexGuard aGuard;
1431 bool bVisible = false;
1433 if ( nPos < maItems.size() )
1434 bVisible = maItems[ nPos ]->mbVisible;
1436 return bVisible;
1439 void GtkSalMenu::CheckItem( unsigned, bool )
1443 void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
1445 SolarMutexGuard aGuard;
1446 if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
1448 gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
1449 NativeSetEnableItem( pCommand, bEnable );
1450 g_free( pCommand );
1454 void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
1456 SolarMutexGuard aGuard;
1457 if ( nPos < maItems.size() )
1459 maItems[ nPos ]->mbVisible = bShow;
1460 if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
1461 Update();
1465 void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
1467 SolarMutexGuard aGuard;
1468 if ( !bUnityMode || mbInActivateCallback || mbNeedsUpdate || !GetTopLevel()->mbMenuBar || ( nPos >= maItems.size() ) )
1469 return;
1471 gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );
1473 gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
1474 for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
1476 gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
1477 for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
1479 gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );
1481 if ( !g_strcmp0( pCommandFromModel, pCommand ) )
1483 NativeSetItemText( nSection, nItem, rText );
1484 g_free( pCommandFromModel );
1485 g_free( pCommand );
1486 return;
1489 g_free( pCommandFromModel );
1493 g_free( pCommand );
1496 void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
1500 void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
1504 void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
1508 int GtkSalMenu::GetMenuBarHeight() const
1510 return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
1514 * GtkSalMenuItem
1517 GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
1518 mpParentMenu( nullptr ),
1519 mpSubMenu( nullptr ),
1520 mnType( pItemData->eType ),
1521 mnId( pItemData->nId ),
1522 mbVisible( true )
1526 GtkSalMenuItem::~GtkSalMenuItem()
1530 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */