Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / unx / gtk3 / gtk3gtksalmenu.cxx
blob301322d8c78d316e7f93303d2ce775ed5f5a9ccd
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 g_object_unref( 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);
339 g_free( aNativeCommand );
341 ++nItemPos;
342 ++validItems;
345 if (bRemoveDisabledEntries)
347 // Delete disabled items in last section.
348 RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
351 // Delete extra items in last section.
352 RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
354 // Delete extra sections.
355 RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );
357 // Delete unused commands.
358 RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );
360 // Resolves: tdf#103166 if the menu is empty, add a disabled
361 // <No Selection Possible> placeholder.
362 sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
363 gint nItemsCount = 0;
364 for (nSection = 0; nSection < nSectionsCount; ++nSection)
366 nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
367 if (nItemsCount)
368 break;
370 if (!nItemsCount)
372 gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
373 OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
374 g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
375 OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
376 NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
377 NativeSetEnableItem(aNativeCommand, false);
378 g_free(aNativeCommand);
382 void GtkSalMenu::Update()
384 //find out if top level is a menubar or not, if not, then it's a popup menu
385 //hierarchy and in those we hide (most) disabled entries
386 const GtkSalMenu* pMenu = this;
387 while (pMenu->mpParentSalMenu)
388 pMenu = pMenu->mpParentSalMenu;
389 ImplUpdate(false, !pMenu->mbMenuBar);
392 static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
394 Point *pPos = static_cast<Point*>(user_data);
395 *x = pPos->X();
396 if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
398 GtkRequisition natural_size;
399 gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
400 *x -= natural_size.width;
402 *y = pPos->Y();
403 *push_in = false;
406 bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
407 FloatWinPopupFlags nFlags)
409 VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
410 mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());
412 GLOActionGroup* pActionGroup = g_lo_action_group_new();
413 mpActionGroup = G_ACTION_GROUP(pActionGroup);
414 mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
415 // Generate the main menu structure, populates mpMenuModel
416 UpdateFull();
418 GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
419 gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
420 gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);
422 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
423 //it during DispatchCommand, returning now to the outer loop causes the
424 //launching PopupMenu to be destroyed, instead run the subloop here
425 //until the gtk menu is destroyed
426 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
427 g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
429 #if GTK_CHECK_VERSION(3,22,0)
430 if (gtk_check_version(3, 22, 0) == nullptr)
432 GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
434 if (nFlags & FloatWinPopupFlags::Left)
436 rect_anchor = GDK_GRAVITY_NORTH_WEST;
437 menu_anchor = GDK_GRAVITY_NORTH_EAST;
439 else if (nFlags & FloatWinPopupFlags::Up)
441 rect_anchor = GDK_GRAVITY_NORTH_WEST;
442 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
444 else if (nFlags & FloatWinPopupFlags::Right)
446 rect_anchor = GDK_GRAVITY_NORTH_EAST;
449 tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
450 aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
451 GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
452 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
454 GdkWindow* gdkWindow = widget_get_window(mpFrame->getMouseEventWidget());
455 gtk_menu_popup_at_rect(GTK_MENU(pWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
457 else
458 #endif
460 guint nButton;
461 guint32 nTime;
463 //typically there is an event, and we can then distinguish if this was
464 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
465 //doesn't)
466 GdkEvent *pEvent = gtk_get_current_event();
467 if (pEvent)
469 gdk_event_get_button(pEvent, &nButton);
470 nTime = gdk_event_get_time(pEvent);
472 else
474 nButton = 0;
475 nTime = GtkSalFrame::GetLastInputEventTime();
478 // do the same strange semantics as vcl popup windows to arrive at a frame geometry
479 // in mirrored UI case; best done by actually executing the same code
480 sal_uInt16 nArrangeIndex;
481 Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
482 aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
484 gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
485 &aPos, nButton, nTime);
488 if (g_main_loop_is_running(pLoop))
490 gdk_threads_leave();
491 g_main_loop_run(pLoop);
492 gdk_threads_enter();
494 g_main_loop_unref(pLoop);
496 mpVCLMenu->Deactivate();
498 gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
500 gtk_widget_destroy(pWidget);
502 g_object_unref(mpActionGroup);
503 ClearActionGroupAndMenuModel();
505 mpFrame = nullptr;
507 return true;
511 * GtkSalMenu
514 GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
515 mbInActivateCallback( false ),
516 mbMenuBar( bMenuBar ),
517 mbNeedsUpdate( false ),
518 mbReturnFocusToDocument( false ),
519 mbAddedGrab( false ),
520 mpMenuBarContainerWidget( nullptr ),
521 mpMenuAllowShrinkWidget( nullptr ),
522 mpMenuBarWidget( nullptr ),
523 mpMenuBarContainerProvider( nullptr ),
524 mpMenuBarProvider( nullptr ),
525 mpCloseButton( nullptr ),
526 mpVCLMenu( nullptr ),
527 mpParentSalMenu( nullptr ),
528 mpFrame( nullptr ),
529 mpMenuModel( nullptr ),
530 mpActionGroup( nullptr )
532 //typically this only gets called after the menu has been customized on the
533 //next idle slot, in the normal case of a new menubar SetFrame is called
534 //directly long before this idle would get called.
535 maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
536 maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
537 maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
540 IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
542 SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
543 if (!mpFrame)
544 return;
545 SetFrame(mpFrame);
548 void GtkSalMenu::SetNeedsUpdate()
550 GtkSalMenu* pMenu = this;
551 // start that the menu and its parents are in need of an update
552 // on the next activation
553 while (pMenu && !pMenu->mbNeedsUpdate)
555 pMenu->mbNeedsUpdate = true;
556 pMenu = pMenu->mpParentSalMenu;
558 // only if a menubar is directly updated do we force in a full
559 // structure update
560 if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
561 maUpdateMenuBarIdle.Start();
564 void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
566 if (mpMenuModel)
567 g_object_unref(mpMenuModel);
568 mpMenuModel = pMenuModel;
569 if (mpMenuModel)
570 g_object_ref(mpMenuModel);
573 GtkSalMenu::~GtkSalMenu()
575 SolarMutexGuard aGuard;
577 DestroyMenuBarWidget();
579 if (mpMenuModel)
580 g_object_unref(mpMenuModel);
582 maItems.clear();
584 if (mpFrame)
585 mpFrame->SetMenu(nullptr);
588 bool GtkSalMenu::VisibleMenuBar()
590 return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
593 void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
595 SolarMutexGuard aGuard;
596 GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );
598 if ( nPos == MENU_APPEND )
599 maItems.push_back( pItem );
600 else
601 maItems.insert( maItems.begin() + nPos, pItem );
603 pItem->mpParentMenu = this;
605 SetNeedsUpdate();
608 void GtkSalMenu::RemoveItem( unsigned nPos )
610 SolarMutexGuard aGuard;
611 maItems.erase( maItems.begin() + nPos );
612 SetNeedsUpdate();
615 void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
617 SolarMutexGuard aGuard;
618 GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
619 GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );
621 if ( pGtkSubMenu == nullptr )
622 return;
624 pGtkSubMenu->mpParentSalMenu = this;
625 pItem->mpSubMenu = pGtkSubMenu;
627 SetNeedsUpdate();
630 static void CloseMenuBar(GtkWidget *, gpointer pMenu)
632 Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
635 void GtkSalMenu::ShowCloseButton(bool bShow)
637 assert(mbMenuBar);
638 if (!mpMenuBarContainerWidget)
639 return;
641 if (!bShow)
643 if (mpCloseButton)
645 gtk_widget_destroy(mpCloseButton);
646 mpCloseButton = nullptr;
648 return;
651 MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
652 mpCloseButton = gtk_button_new();
653 g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
655 gtk_button_set_relief(GTK_BUTTON(mpCloseButton), GTK_RELIEF_NONE);
656 gtk_button_set_focus_on_click(GTK_BUTTON(mpCloseButton), false);
657 gtk_widget_set_can_focus(mpCloseButton, false);
659 GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(mpCloseButton));
661 GtkCssProvider *pProvider = gtk_css_provider_new();
662 static const gchar data[] = "* { "
663 "padding: 0;"
664 "margin-left: 8px;"
665 "margin-right: 8px;"
666 "min-width: 18px;"
667 "min-height: 18px;"
668 "}";
669 const gchar olddata[] = "* { "
670 "padding: 0;"
671 "margin-left: 8px;"
672 "margin-right: 8px;"
673 "}";
674 gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
675 gtk_style_context_add_provider(pButtonContext,
676 GTK_STYLE_PROVIDER(pProvider),
677 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
679 gtk_style_context_add_class(pButtonContext, "flat");
680 gtk_style_context_add_class(pButtonContext, "small-button");
682 GIcon* icon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
683 GtkWidget* image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
684 gtk_widget_show(image);
685 g_object_unref(icon);
687 OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
688 gtk_widget_set_tooltip_text(mpCloseButton,
689 OUStringToOString(sToolTip, RTL_TEXTENCODING_UTF8).getStr());
691 gtk_widget_set_valign(mpCloseButton, GTK_ALIGN_CENTER);
693 gtk_container_add(GTK_CONTAINER(mpCloseButton), image);
694 gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), GTK_WIDGET(mpCloseButton), 1, 0, 1, 1);
695 gtk_widget_show_all(mpCloseButton);
698 //Typically when the menubar is deactivated we want the focus to return
699 //to where it came from. If the menubar was activated because of F6
700 //moving focus into the associated VCL menubar then on pressing ESC
701 //or any other normal reason for deactivation we want focus to return
702 //to the document, definitely not still stuck in the associated
703 //VCL menubar. But if F6 is pressed while the menubar is activated
704 //we want to pass that F6 back to the VCL menubar which will move
705 //focus to the next pane by itself.
706 void GtkSalMenu::ReturnFocus()
708 if (mbAddedGrab)
710 gtk_grab_remove(mpMenuBarWidget);
711 mbAddedGrab = false;
713 if (!mbReturnFocusToDocument)
714 gtk_widget_grab_focus(GTK_WIDGET(mpFrame->getEventBox()));
715 else
716 mpFrame->GetWindow()->GrabFocusToDocument();
717 mbReturnFocusToDocument = false;
720 gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
722 if (pEvent->keyval == GDK_KEY_F6)
724 mbReturnFocusToDocument = false;
725 gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
726 //because we return false here, the keypress will continue
727 //to propagate and in the case that vcl focus is in
728 //the vcl menubar then that will also process F6 and move
729 //to the next pane
731 return false;
734 //The GtkSalMenu is owner by a Vcl Menu/MenuBar. In the menubar
735 //case the vcl menubar is present and "visible", but with a 0 height
736 //so it not apparent. Normally it acts as though it is not there when
737 //a Native menubar is active. If we return true here, then for keyboard
738 //activation and traversal with F6 through panes then the vcl menubar
739 //acts as though it *is* present and we translate its take focus and F6
740 //traversal key events into the gtk menubar equivalents.
741 bool GtkSalMenu::CanGetFocus() const
743 return mpMenuBarWidget != nullptr;
746 bool GtkSalMenu::TakeFocus()
748 if (!mpMenuBarWidget)
749 return false;
751 //Send a keyboard event to the gtk menubar to let it know it has been
752 //activated via the keyboard. Doesn't do anything except cause the gtk
753 //menubar "keyboard_mode" member to get set to true, so typically mnemonics
754 //are shown which will serve as indication that the menubar has focus
755 //(given that we want to show it with no menus popped down)
756 GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
757 gtk_widget_event(mpMenuBarWidget, event);
758 gdk_event_free(event);
760 //this pairing results in a menubar with keyboard focus with no menus
761 //auto-popped down
762 gtk_grab_add(mpMenuBarWidget);
763 mbAddedGrab = true;
764 gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
765 gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
766 mbReturnFocusToDocument = true;
767 return true;
770 static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
772 GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
773 GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
774 pMenu->ReturnFocus();
777 static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
779 GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
780 return pMenu->SignalKey(pEvent);
783 void GtkSalMenu::CreateMenuBarWidget()
785 if (mpMenuBarContainerWidget)
786 return;
788 GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
789 mpMenuBarContainerWidget = gtk_grid_new();
791 gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
792 gtk_grid_insert_row(pGrid, 0);
793 gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);
795 mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
796 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
797 // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
798 // but still allow scrolled window to not be sized to the child content.
799 // So the menubar can be shrunk past its nominal smallest width.
800 // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
801 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
802 gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);
804 mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
806 gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
807 gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
808 gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
809 gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
811 g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
812 g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
814 gtk_widget_show_all(mpMenuBarContainerWidget);
816 ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );
818 ApplyPersona();
821 void GtkSalMenu::ApplyPersona()
823 if (!mpMenuBarContainerWidget)
824 return;
825 assert(mbMenuBar);
826 // I'm dubious about the persona theming feature, but as it exists, lets try and support
827 // it, apply the image to the mpMenuBarContainerWidget
828 const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
830 GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
831 if (mpMenuBarContainerProvider)
833 gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
834 mpMenuBarContainerProvider = nullptr;
836 GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
837 if (mpMenuBarProvider)
839 gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
840 mpMenuBarProvider = nullptr;
843 if (!rPersonaBitmap.IsEmpty())
845 if (maPersonaBitmap != rPersonaBitmap)
847 vcl::PNGWriter aPNGWriter(rPersonaBitmap);
848 mxPersonaImage.reset(new utl::TempFile);
849 mxPersonaImage->EnableKillingFile(true);
850 SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
851 aPNGWriter.Write(*pStream);
852 mxPersonaImage->CloseStream();
855 mpMenuBarContainerProvider = gtk_css_provider_new();
856 OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
857 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
858 gtk_css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength(), nullptr);
859 gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
860 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
863 // force the menubar to be transparent when persona is active otherwise for
864 // me the menubar becomes gray when its in the backdrop
865 mpMenuBarProvider = gtk_css_provider_new();
866 static const gchar data[] = "* { "
867 "background-image: none;"
868 "background-color: transparent;"
869 "}";
870 gtk_css_provider_load_from_data(mpMenuBarProvider, data, -1, nullptr);
871 gtk_style_context_add_provider(pMenuBarContext,
872 GTK_STYLE_PROVIDER(mpMenuBarProvider),
873 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
875 maPersonaBitmap = rPersonaBitmap;
878 void GtkSalMenu::DestroyMenuBarWidget()
880 if (mpMenuBarContainerWidget)
882 gtk_widget_destroy(mpMenuBarContainerWidget);
883 mpMenuBarContainerWidget = nullptr;
884 mpCloseButton = nullptr;
888 void GtkSalMenu::SetFrame(const SalFrame* pFrame)
890 SolarMutexGuard aGuard;
891 assert(mbMenuBar);
892 SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
893 mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));
895 // if we had a menu on the GtkSalMenu we have to free it as we generate a
896 // full menu anyway and we might need to reuse an existing model and
897 // actiongroup
898 mpFrame->SetMenu( this );
899 mpFrame->EnsureAppMenuWatch();
901 // Clean menu model and action group if needed.
902 GtkWidget* pWidget = mpFrame->getWindow();
903 GdkWindow* gdkWindow = gtk_widget_get_window( pWidget );
905 GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
906 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
907 SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);
909 if ( pMenuModel )
911 if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
912 g_lo_menu_remove( pMenuModel, 0 );
914 mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
917 if ( pActionGroup )
919 g_lo_action_group_clear( pActionGroup );
920 mpActionGroup = G_ACTION_GROUP( pActionGroup );
923 // Generate the main menu structure.
924 if ( PrepUpdate() )
925 UpdateFull();
927 g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );
929 if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
931 DestroyMenuBarWidget();
932 CreateMenuBarWidget();
936 const GtkSalFrame* GtkSalMenu::GetFrame() const
938 SolarMutexGuard aGuard;
939 const GtkSalMenu* pMenu = this;
940 while( pMenu && ! pMenu->mpFrame )
941 pMenu = pMenu->mpParentSalMenu;
942 return pMenu ? pMenu->mpFrame : nullptr;
945 void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
947 SolarMutexGuard aGuard;
949 if ( mpActionGroup == nullptr )
950 return;
952 gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
954 if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
956 GVariant *pCheckValue = nullptr;
957 GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );
959 if ( bits & MenuItemBits::RADIOCHECK )
960 pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
961 else
963 // By default, all checked items are checkmark buttons.
964 if (bCheck || pCurrentState != nullptr)
965 pCheckValue = g_variant_new_boolean( bCheck );
968 if ( pCheckValue != nullptr )
970 if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
972 g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
974 else
976 g_variant_unref (pCheckValue);
980 if ( pCurrentState != nullptr )
981 g_variant_unref( pCurrentState );
984 if ( aCommand )
985 g_free( aCommand );
988 void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
990 SolarMutexGuard aGuard;
991 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
993 if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
994 g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
997 void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
999 SolarMutexGuard aGuard;
1000 // Escape all underscores so that they don't get interpreted as hotkeys
1001 OUString aText = rText.replaceAll( "_", "__" );
1002 // Replace the LibreOffice hotkey identifier with an underscore
1003 aText = aText.replace( '~', '_' );
1004 OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );
1006 // Update item text only when necessary.
1007 gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1009 if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
1010 g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );
1012 if ( aLabel )
1013 g_free( aLabel );
1016 namespace
1018 void DestroyMemoryStream(gpointer data)
1020 SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
1021 delete pMemStm;
1025 void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
1027 #if GLIB_CHECK_VERSION(2,38,0)
1028 if (!!rImage && mbHasNullItemIcon)
1029 return;
1031 SolarMutexGuard aGuard;
1033 if (!!rImage)
1035 SvMemoryStream* pMemStm = new SvMemoryStream;
1036 vcl::PNGWriter aWriter(rImage.GetBitmapEx());
1037 aWriter.Write(*pMemStm);
1039 GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
1040 pMemStm->TellEnd(),
1041 DestroyMemoryStream,
1042 pMemStm);
1044 GIcon *pIcon = g_bytes_icon_new(pBytes);
1046 g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
1047 g_object_unref(pIcon);
1048 g_bytes_unref(pBytes);
1049 mbHasNullItemIcon = false;
1051 else
1053 g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
1054 mbHasNullItemIcon = true;
1056 #else
1057 (void)nSection;
1058 (void)nItemPos;
1059 (void)rImage;
1060 #endif
1063 void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, const OUString& rKeyName )
1065 SolarMutexGuard aGuard;
1067 if ( rKeyName.isEmpty() )
1068 return;
1070 guint nKeyCode;
1071 GdkModifierType nModifiers;
1072 GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);
1074 gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );
1076 gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
1078 if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
1079 g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );
1081 g_free( aAccelerator );
1082 g_free( aCurrentAccel );
1085 bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
1086 unsigned nItemPos,
1087 sal_uInt16 nId,
1088 const gchar* aCommand,
1089 MenuItemBits nBits,
1090 bool bChecked,
1091 bool bIsSubmenu )
1093 bool bSubMenuAddedOrRemoved = false;
1095 SolarMutexGuard aGuard;
1096 GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
1098 GVariant *pTarget = nullptr;
1100 if (g_action_group_has_action(mpActionGroup, aCommand))
1101 g_lo_action_group_remove(pActionGroup, aCommand);
1103 if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
1105 // Item is a checkmark button.
1106 GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
1107 GVariant* pState = g_variant_new_boolean( bChecked );
1109 g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
1111 else if ( nBits & MenuItemBits::RADIOCHECK )
1113 // Item is a radio button.
1114 GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
1115 GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
1116 GVariant* pState = g_variant_new_string( "" );
1117 pTarget = g_variant_new_string( aCommand );
1119 g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
1121 else
1123 // Item is not special, so insert a stateless action.
1124 g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
1127 GLOMenu* pMenu = G_LO_MENU( mpMenuModel );
1129 // Menu item is not updated unless it's necessary.
1130 gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );
1132 if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
1134 bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
1135 bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
1136 if (bSubMenuAddedOrRemoved)
1138 //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
1139 //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
1140 //support achieving that
1141 gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
1142 g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
1143 g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
1144 g_free(pLabel);
1147 g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );
1149 gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );
1151 if ( bIsSubmenu )
1152 g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
1153 else
1155 g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
1156 pTarget = nullptr;
1159 g_free( aItemCommand );
1162 if ( aCurrentCommand )
1163 g_free( aCurrentCommand );
1165 if (pTarget)
1166 g_variant_unref(pTarget);
1168 return bSubMenuAddedOrRemoved;
1171 GtkSalMenu* GtkSalMenu::GetTopLevel()
1173 GtkSalMenu *pMenu = this;
1174 while (pMenu->mpParentSalMenu)
1175 pMenu = pMenu->mpParentSalMenu;
1176 return pMenu;
1179 void GtkSalMenu::DispatchCommand(const gchar *pCommand)
1181 SolarMutexGuard aGuard;
1182 MenuAndId aMenuAndId = decode_command(pCommand);
1183 GtkSalMenu* pSalSubMenu = aMenuAndId.first;
1184 GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
1185 if (pTopLevel->mpMenuBarWidget)
1187 // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
1188 // closing the menu. To handle this properly I imagine we need to set groups for the
1189 // radiobuttons so the others visually untoggle when the active one is toggled and
1190 // we would further need to teach vcl that the state can change more than once.
1192 // or we could unconditionally deactivate the menus if regardless of what particular
1193 // type of menu item got activated
1194 gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
1196 pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
1199 void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
1201 for (GtkSalMenuItem* pSalItem : maItems)
1203 if ( pSalItem->mpSubMenu != nullptr )
1205 // We can re-enter this method via the new event loop that gets created
1206 // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
1207 // flag to detect that and skip some startup work.
1208 if (!pSalItem->mpSubMenu->mbInActivateCallback)
1210 pSalItem->mpSubMenu->mbInActivateCallback = true;
1211 pMenuBar->HandleMenuActivateEvent(pSalItem->mpSubMenu->GetMenu());
1212 pSalItem->mpSubMenu->mbInActivateCallback = false;
1213 pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
1214 pSalItem->mpSubMenu->Update();
1215 pMenuBar->HandleMenuDeActivateEvent(pSalItem->mpSubMenu->GetMenu());
1221 void GtkSalMenu::ClearActionGroupAndMenuModel()
1223 SetMenuModel(nullptr);
1224 mpActionGroup = nullptr;
1225 for (GtkSalMenuItem* pSalItem : maItems)
1227 if ( pSalItem->mpSubMenu != nullptr )
1229 pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
1234 void GtkSalMenu::Activate(const gchar* pCommand)
1236 MenuAndId aMenuAndId = decode_command(pCommand);
1237 GtkSalMenu* pSalMenu = aMenuAndId.first;
1238 GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
1239 Menu* pVclMenu = pSalMenu->GetMenu();
1240 Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
1241 GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;
1243 pSubMenu->mbInActivateCallback = true;
1244 pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
1245 pSubMenu->mbInActivateCallback = false;
1246 pVclSubMenu->UpdateNativeMenu();
1249 void GtkSalMenu::Deactivate(const gchar* pCommand)
1251 MenuAndId aMenuAndId = decode_command(pCommand);
1252 GtkSalMenu* pSalMenu = aMenuAndId.first;
1253 GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
1254 Menu* pVclMenu = pSalMenu->GetMenu();
1255 Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
1256 pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
1259 void GtkSalMenu::EnableUnity(bool bEnable)
1261 bUnityMode = bEnable;
1263 MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
1264 bool bDisplayable(pMenuBar->IsDisplayable());
1266 if (bEnable)
1268 DestroyMenuBarWidget();
1269 UpdateFull();
1270 if (!bDisplayable)
1271 ShowMenuBar(false);
1273 else
1275 Update();
1276 ShowMenuBar(bDisplayable);
1279 pMenuBar->LayoutChanged();
1282 void GtkSalMenu::ShowMenuBar( bool bVisible )
1284 // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
1285 if (bUnityMode)
1287 if (bVisible)
1288 Update();
1289 else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
1290 g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
1292 else if (bVisible)
1293 CreateMenuBarWidget();
1294 else
1295 DestroyMenuBarWidget();
1298 bool GtkSalMenu::IsItemVisible( unsigned nPos )
1300 SolarMutexGuard aGuard;
1301 bool bVisible = false;
1303 if ( nPos < maItems.size() )
1304 bVisible = maItems[ nPos ]->mbVisible;
1306 return bVisible;
1309 void GtkSalMenu::CheckItem( unsigned, bool )
1313 void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
1315 SolarMutexGuard aGuard;
1316 if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
1318 gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
1319 NativeSetEnableItem( pCommand, bEnable );
1320 g_free( pCommand );
1324 void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
1326 SolarMutexGuard aGuard;
1327 if ( nPos < maItems.size() )
1329 maItems[ nPos ]->mbVisible = bShow;
1330 if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
1331 Update();
1335 void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
1337 SolarMutexGuard aGuard;
1338 if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
1340 gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );
1342 gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
1343 for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
1345 gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
1346 for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
1348 gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );
1350 if ( !g_strcmp0( pCommandFromModel, pCommand ) )
1352 NativeSetItemText( nSection, nItem, rText );
1353 g_free( pCommandFromModel );
1354 g_free( pCommand );
1355 return;
1358 g_free( pCommandFromModel );
1362 g_free( pCommand );
1366 void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
1370 void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
1374 void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
1378 int GtkSalMenu::GetMenuBarHeight() const
1380 return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
1384 * GtkSalMenuItem
1387 GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
1388 mpParentMenu( nullptr ),
1389 mpSubMenu( nullptr ),
1390 mnType( pItemData->eType ),
1391 mnId( pItemData->nId ),
1392 mbVisible( true )
1396 GtkSalMenuItem::~GtkSalMenuItem()
1400 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */