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