/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* * This function generates a unique command name for each menu item
*/ static OString GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
{
OString aCommand = "window-" +
OString::number(reinterpret_cast<sal_uIntPtr>(pParentMenu)) + "-" + OString::number(nItemId); return aCommand;
}
SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate"); if( !PrepUpdate() ) return;
if (mbNeedsUpdate)
{
mbNeedsUpdate = false; if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
{
maUpdateMenuBarIdle.Stop(); // tdf#124391 Prevent doubled menus in global menu if (!bUnityMode)
{
maUpdateMenuBarIdle.Invoke(); return;
}
}
}
Menu* pVCLMenu = mpVCLMenu;
GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
GList *pOldCommandList = nullptr;
GList *pNewCommandList = nullptr;
for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) { if ( !IsItemVisible( nItem ) ) continue;
GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
sal_uInt16 nId = pSalMenuItem->mnId;
// PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level // popup menu, but we have our own implementation below, so skip that one. if ( nId == 0xFFFF ) continue;
if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
{ // Delete extra items from current section.
RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
if (bRecurse || bNonMenuChangedToMenu)
{
SAL_INFO("vcl.unity", "preparing submenu " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
}
g_object_unref( pSubMenuModel );
}
++nItemPos;
++validItems;
}
if (bRemoveDisabledEntries)
{ // Delete disabled items in last section.
RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
}
// Delete extra items in last section.
RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
// Delete extra sections.
RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );
// Resolves: tdf#103166 if the menu is empty, add a disabled // <No Selection Possible> placeholder.
sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
gint nItemsCount = 0; for (nSection = 0; nSection < nSectionsCount; ++nSection)
{
nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection); if (nItemsCount) break;
} if (!nItemsCount)
{
OString sNativeCommand = GetCommandForItem(this, 0xFFFF);
OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
NativeSetItemCommand(nSection - 1, 0, 0xFFFF, sNativeCommand.getStr(), MenuItemBits::NONE, false, false);
NativeSetEnableItem(sNativeCommand, false);
}
}
void GtkSalMenu::Update()
{ //find out if top level is a menubar or not, if not, then it's a popup menu //hierarchy and in those we hide (most) disabled entries const GtkSalMenu* pMenu = this; while (pMenu->mpParentSalMenu)
pMenu = pMenu->mpParentSalMenu;
staticvoid MenuClosed(GtkPopover* pWidget, GMainLoop* pLoop)
{ // gtk4 4.4.0: click on an entry in a submenu of a menu crashes without this workaround
gtk_widget_grab_focus(gtk_widget_get_parent(GTK_WIDGET(pWidget)));
g_main_loop_quit(pLoop);
}
//run in a sub main loop because we need to keep vcl PopupMenu alive to use //it during DispatchCommand, returning now to the outer loop causes the //launching PopupMenu to be destroyed, instead run the subloop here //until the gtk menu is destroyed
GMainLoop* pLoop = g_main_loop_new(nullptr, true); #if GTK_CHECK_VERSION(4, 0, 0)
g_signal_connect(G_OBJECT(mpMenuWidget), "closed", G_CALLBACK(MenuClosed), pLoop); #else
g_signal_connect(G_OBJECT(mpMenuWidget), "deactivate", G_CALLBACK(MenuClosed), pLoop); #endif
// tdf#120764 It isn't allowed under wayland to have two visible popups that share // the same top level parent. The problem is that since gtk 3.24 tooltips are also // implemented as popups, which means that we cannot show any popup if there is a // visible tooltip. // hide any current tooltip
mpFrame->HideTooltip(); // don't allow any more to appear until menu is dismissed
mpFrame->BlockTooltip();
//typically there is an event, and we can then distinguish if this was //launched from the keyboard (gets auto-mnemoniced) or the mouse (which //doesn't)
GdkEvent *pEvent = gtk_get_current_event(); if (pEvent)
{
gdk_event_get_button(pEvent, &nButton);
nTime = gdk_event_get_time(pEvent);
} else
{
nButton = 0;
nTime = GtkSalFrame::GetLastInputEventTime();
}
// Do the same strange semantics as vcl popup windows to arrive at a frame geometry // in mirrored UI case; best done by actually executing the same code. // (see code in FloatingWindow::StartPopupMode)
sal_uInt16 nArrangeIndex;
Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
AbsoluteScreenPixelPoint aPosAbs = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
maUpdateMenuBarIdle("Native Gtk Menu Update Idle"),
mbInActivateCallback( false ),
mbMenuBar( bMenuBar ),
mbNeedsUpdate( false ),
mbReturnFocusToDocument( false ),
mbAddedGrab( false ),
mpMenuBarContainerWidget( nullptr ),
mpMenuAllowShrinkWidget( nullptr ),
mpMenuBarWidget( nullptr ),
mpMenuWidget( nullptr ),
mpCloseButton( nullptr ),
mpVCLMenu( nullptr ),
mpParentSalMenu( nullptr ),
mpFrame( nullptr ),
mpMenuModel( nullptr ),
mpActionGroup( nullptr )
{ //typically this only gets called after the menu has been customized on the //next idle slot, in the normal case of a new menubar SetFrame is called //directly long before this idle would get called.
maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
}
IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
{
SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!"); if (!mpFrame) return;
SetFrame(mpFrame);
}
void GtkSalMenu::SetNeedsUpdate()
{
GtkSalMenu* pMenu = this; // start that the menu and its parents are in need of an update // on the next activation while (pMenu && !pMenu->mbNeedsUpdate)
{
pMenu->mbNeedsUpdate = true;
pMenu = pMenu->mpParentSalMenu;
} // only if a menubar is directly updated do we force in a full // structure update if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
maUpdateMenuBarIdle.Start();
}
void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
{ if (mpMenuModel)
g_object_unref(mpMenuModel);
mpMenuModel = pMenuModel; if (mpMenuModel)
g_object_ref(mpMenuModel);
}
//Typically when the menubar is deactivated we want the focus to return //to where it came from. If the menubar was activated because of F6 //moving focus into the associated VCL menubar then on pressing ESC //or any other normal reason for deactivation we want focus to return //to the document, definitely not still stuck in the associated //VCL menubar. But if F6 is pressed while the menubar is activated //we want to pass that F6 back to the VCL menubar which will move //focus to the next pane by itself. void GtkSalMenu::ReturnFocus()
{ if (mbAddedGrab)
{ #if !GTK_CHECK_VERSION(4, 0, 0)
gtk_grab_remove(mpMenuBarWidget); #endif
mbAddedGrab = false;
} if (!mbReturnFocusToDocument)
gtk_widget_grab_focus(mpFrame->getMouseEventWidget()); else
mpFrame->GetWindow()->GrabFocusToDocument();
mbReturnFocusToDocument = false;
}
#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
{ if (pEvent->keyval == GDK_KEY_F6)
{
mbReturnFocusToDocument = false;
gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget)); //because we return false here, the keypress will continue //to propagate and in the case that vcl focus is in //the vcl menubar then that will also process F6 and move //to the next pane
} returnfalse;
} #endif
//The GtkSalMenu is owned by a Vcl Menu/MenuBar. In the menubar //case the vcl menubar is present and "visible", but with a 0 height //so it not apparent. Normally it acts as though it is not there when //a Native menubar is active. If we return true here, then for keyboard //activation and traversal with F6 through panes then the vcl menubar //acts as though it *is* present and we translate its take focus and F6 //traversal key events into the gtk menubar equivalents. bool GtkSalMenu::CanGetFocus() const
{ return mpMenuBarWidget != nullptr;
}
bool GtkSalMenu::TakeFocus()
{ if (!mpMenuBarWidget) returnfalse;
#if !GTK_CHECK_VERSION(4, 0, 0) //Send a keyboard event to the gtk menubar to let it know it has been //activated via the keyboard. Doesn't do anything except cause the gtk //menubar "keyboard_mode" member to get set to true, so typically mnemonics //are shown which will serve as indication that the menubar has focus //(given that we want to show it with no menus popped down)
GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
gtk_widget_event(mpMenuBarWidget, event);
gdk_event_free(event);
//this pairing results in a menubar with keyboard focus with no menus //auto-popped down
gtk_grab_add(mpMenuBarWidget);
#if !GTK_CHECK_VERSION(4, 0, 0)
mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE); // tdf#129634 don't allow this scrolled window as a candidate to tab into
gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false); #else
mpMenuAllowShrinkWidget = gtk_scrolled_window_new();
gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), false); #endif // tdf#116290 external policy on scrolledwindow will not show a scrollbar, // but still allow scrolled window to not be sized to the child content. // So the menubar can be shrunk past its nominal smallest width. // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);
void GtkSalMenu::DestroyMenuBarWidget()
{ if (!mpMenuBarContainerWidget) return;
#if !GTK_CHECK_VERSION(4, 0, 0) // tdf#140225 call cancel before destroying it in case there are some // active menus popped open
gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
void GtkSalMenu::SetFrame(const SalFrame* pFrame)
{
SolarMutexGuard aGuard;
assert(mbMenuBar);
SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));
// if we had a menu on the GtkSalMenu we have to free it as we generate a // full menu anyway and we might need to reuse an existing model and // actiongroup
mpFrame->SetMenu( this );
mpFrame->EnsureAppMenuWatch();
// Clean menu model and action group if needed.
GtkWidget* pWidget = mpFrame->getWindow();
GdkSurface* gdkWindow = widget_get_surface(pWidget);
if (!!rImage)
{
SvMemoryStream* pMemStm = new SvMemoryStream; auto aBitmapEx = rImage.GetBitmapEx();
vcl::PngImageWriter aWriter(*pMemStm);
aWriter.write(aBitmapEx);
g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
} else
{ // Item is not special, so insert a stateless action.
g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
}
GLOMenu* pMenu = G_LO_MENU( mpMenuModel );
// Menu item is not updated unless it's necessary.
gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );
if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
{
GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos); bool bOldHasSubmenu = pSubMenuModel != nullptr;
bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu; if (bSubMenuAddedOrRemoved)
{ //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to //support achieving that
gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
g_free(pLabel);
}
// tdf#125803 spacebar will toggle radios and checkbuttons without automatically // closing the menu. To handle this properly I imagine we need to set groups for the // radiobuttons so the others visually untoggle when the active one is toggled and // we would further need to teach vcl that the state can change more than once. // // or we could unconditionally deactivate the menus if regardless of what particular // type of menu item got activated if (pTopLevel->mpMenuBarWidget)
{ #if !GTK_CHECK_VERSION(4, 0, 0)
gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget)); #endif
} if (pTopLevel->mpMenuWidget)
{ #if GTK_CHECK_VERSION(4, 0, 0)
gtk_popover_popdown(GTK_POPOVER(pTopLevel->mpMenuWidget)); #else
gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuWidget)); #endif
}
void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
{ // We can re-enter this method via the new event loop that gets created // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback // flag to detect that and skip some startup work. if (mbInActivateCallback) return;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.