From 300695674c77936e8a68173503a887eb4ece33a5 Mon Sep 17 00:00:00 2001 From: Caolán McNamara Date: Feb 24 2016 17:17:48 +0000 Subject: native gtk3 popup menus --- diff --git a/0001-tdf-97665-Let-s-hope-that-over-activation-isn-t-real.patch b/0001-tdf-97665-Let-s-hope-that-over-activation-isn-t-real.patch new file mode 100644 index 0000000..ab637f4 --- /dev/null +++ b/0001-tdf-97665-Let-s-hope-that-over-activation-isn-t-real.patch @@ -0,0 +1,328 @@ +From 9e64526a0671b52cdb6b6ad567153402a85d21bf Mon Sep 17 00:00:00 2001 +From: Maxim Monastirsky +Date: Sun, 7 Feb 2016 17:53:40 +0200 +Subject: [PATCH 1/8] tdf#97665 Let's hope that over activation isn't really + needed + +- MenuBarManager::Activate has a check for duplicate activation, + which makes the second activation attempt fail. Removing this + check or deactivating after each activation will likely affect + performance even more, but on the other hand should solve + lp#1296715, which was the main reason of the over activation + in the first place. So let's activate only one menu at a time, + and do full activation only on the initial update. + +- Unfortunately the HUD activation callback doesn't work, so + we still have to keep active status listener for all menu + items. (Which is BTW against the recommendation in + XPopupMenuController::updatePopupMenu IDL doc. Fortunately + the performance problem hardly noticeable on modern hw.) + +Reviewed-on: https://gerrit.libreoffice.org/22369 +Tested-by: Jenkins +Reviewed-by: Maxim Monastirsky +(cherry picked from commit 2abdcfd641883f246fe78f2fbe38499c9382c059) + +Change-Id: I96affa72412f3f38160fdca4b6efd20ca68d059f +--- + framework/source/uielement/menubarmanager.cxx | 11 ++--- + include/vcl/menu.hxx | 9 +--- + vcl/inc/salmenu.hxx | 1 + + vcl/inc/unx/gtk/gtksalmenu.hxx | 4 +- + vcl/source/window/menu.cxx | 46 +++----------------- + vcl/unx/gtk/gloactiongroup.cxx | 2 +- + vcl/unx/gtk/gtkdata.cxx | 18 -------- + vcl/unx/gtk/gtksalmenu.cxx | 62 +++++++-------------------- + 8 files changed, 31 insertions(+), 122 deletions(-) + +diff --git a/framework/source/uielement/menubarmanager.cxx b/framework/source/uielement/menubarmanager.cxx +index 6a368a9..8ce02c6 100644 +--- a/framework/source/uielement/menubarmanager.cxx ++++ b/framework/source/uielement/menubarmanager.cxx +@@ -386,10 +386,6 @@ throw ( RuntimeException, std::exception ) + OUString aFeatureURL = Event.FeatureURL.Complete; + + SolarMutexGuard aSolarGuard; +- if ( m_bHasMenuBar ) +- { +- vcl::MenuInvalidator::Invalidated(); +- } + { + if ( m_bDisposed ) + return; +@@ -488,6 +484,8 @@ throw ( RuntimeException, std::exception ) + pMenuItemHandler->xMenuItemDispatch.clear(); + } + } ++ if ( m_bHasMenuBar && !m_bActive ) ++ m_pVCLMenu->UpdateNativeMenu(); + } + } + +@@ -895,9 +893,8 @@ IMPL_LINK_TYPED( MenuBarManager, Activate, Menu *, pMenu, bool ) + if ( !bPopupMenu ) + { + xMenuItemDispatch->addStatusListener( static_cast< XStatusListener* >( this ), aTargetURL ); +- xMenuItemDispatch->removeStatusListener( static_cast< XStatusListener* >( this ), aTargetURL ); +- if ( m_bHasMenuBar ) +- xMenuItemDispatch->addStatusListener( static_cast< XStatusListener* >( this ), aTargetURL ); ++ if ( !m_bHasMenuBar ) ++ xMenuItemDispatch->removeStatusListener( static_cast< XStatusListener* >( this ), aTargetURL ); + } + } + else if ( !bPopupMenu ) +diff --git a/include/vcl/menu.hxx b/include/vcl/menu.hxx +index ade703b..0d6e16a 100644 +--- a/include/vcl/menu.hxx ++++ b/include/vcl/menu.hxx +@@ -313,6 +313,8 @@ public: + void RemoveDisabledEntries( bool bCheckPopups = true, bool bRemoveEmptyPopups = false ); + bool HasValidEntries( bool bCheckPopups = true ); + ++ void UpdateNativeMenu(); ++ + void SetItemText( sal_uInt16 nItemId, const OUString& rStr ); + OUString GetItemText( sal_uInt16 nItemId ) const; + +@@ -407,13 +409,6 @@ public: + }; + + +-namespace vcl { namespace MenuInvalidator { +- +-VCL_DLLPUBLIC void AddMenuInvalidateListener(const Link&); +-VCL_DLLPUBLIC void Invalidated(); +- +-}} +- + class VCL_DLLPUBLIC MenuBar : public Menu + { + Link maCloseHdl; +diff --git a/vcl/inc/salmenu.hxx b/vcl/inc/salmenu.hxx +index 287e19e..468994a 100644 +--- a/vcl/inc/salmenu.hxx ++++ b/vcl/inc/salmenu.hxx +@@ -80,6 +80,7 @@ public: + virtual bool ShowNativePopupMenu(FloatingWindow * pWin, const Rectangle& rRect, FloatWinPopupFlags nFlags); + virtual bool AddMenuBarButton( const SalMenuButtonItem& ); // return false if not implemented or failure + virtual void RemoveMenuBarButton( sal_uInt16 nId ); ++ virtual void Update() {} + + // TODO: implement show/hide for the Win/Mac VCL native backends + virtual void ShowItem( unsigned nPos, bool bShow ) { EnableItem( nPos, bShow ); } +diff --git a/vcl/inc/unx/gtk/gtksalmenu.hxx b/vcl/inc/unx/gtk/gtksalmenu.hxx +index 5d9c262..8df2c1d 100644 +--- a/vcl/inc/unx/gtk/gtksalmenu.hxx ++++ b/vcl/inc/unx/gtk/gtksalmenu.hxx +@@ -96,11 +96,11 @@ public: + void NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, const OUString& rKeyName ); + + void DispatchCommand( gint itemId, const gchar* aCommand ); +- void Activate(); ++ void Activate( const gchar* aMenuCommand = nullptr ); + void Deactivate( const gchar* aMenuCommand ); + void Display( bool bVisible ); + bool PrepUpdate(); +- void Update(); // Update this menu only. ++ virtual void Update() override; // Update this menu only. + void UpdateFull(); // Update full menu hierarchy from this menu. + }; + +diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx +index 3a6e54c..4aa7d41 100644 +--- a/vcl/source/window/menu.cxx ++++ b/vcl/source/window/menu.cxx +@@ -2314,6 +2314,12 @@ sal_uLong Menu::DeactivateMenuBar(sal_uLong nFocusId) + return nFocusId; + } + ++void Menu::UpdateNativeMenu() ++{ ++ if ( ImplGetSalMenu() ) ++ ImplGetSalMenu()->Update(); ++} ++ + void Menu::MenuBarKeyInput(const KeyEvent&) + { + } +@@ -3251,44 +3257,4 @@ ImplMenuDelData::~ImplMenuDelData() + const_cast< Menu* >( mpMenu )->ImplRemoveDel( *this ); + } + +-namespace vcl { namespace MenuInvalidator { +- +-struct MenuInvalidateListeners : public vcl::DeletionNotifier +-{ +- std::vector> m_aListeners; +-}; +- +-static MenuInvalidateListeners* pMenuInvalidateListeners = nullptr; +- +-void AddMenuInvalidateListener(const Link& rLink) +-{ +- if(!pMenuInvalidateListeners) +- pMenuInvalidateListeners = new MenuInvalidateListeners(); +- // ensure uniqueness +- auto& rListeners = pMenuInvalidateListeners->m_aListeners; +- if (std::find(rListeners.begin(), rListeners.end(), rLink) == rListeners.end()) +- rListeners.push_back( rLink ); +-} +- +-void Invalidated() +-{ +- if(!pMenuInvalidateListeners) +- return; +- +- vcl::DeletionListener aDel( pMenuInvalidateListeners ); +- +- auto& rYieldListeners = pMenuInvalidateListeners->m_aListeners; +- // Copy the list, because this can be destroyed when calling a Link... +- std::vector> aCopy( rYieldListeners ); +- for( Link& rLink : aCopy ) +- { +- if (aDel.isDeleted()) break; +- // check this hasn't been removed in some re-enterancy scenario fdo#47368 +- if( std::find(rYieldListeners.begin(), rYieldListeners.end(), rLink) != rYieldListeners.end() ) +- rLink.Call( nullptr ); +- } +-}; +- +-} } // namespace vcl::MenuInvalidator +- + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/unx/gtk/gloactiongroup.cxx b/vcl/unx/gtk/gloactiongroup.cxx +index 8385388..e710809 100644 +--- a/vcl/unx/gtk/gloactiongroup.cxx ++++ b/vcl/unx/gtk/gloactiongroup.cxx +@@ -201,7 +201,7 @@ g_lo_action_group_perform_submenu_action (GLOActionGroup *group, + SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState); + + if (bState) +- pSalMenu->Activate(); ++ pSalMenu->Activate (action_name); + else + pSalMenu->Deactivate (action_name); + } +diff --git a/vcl/unx/gtk/gtkdata.cxx b/vcl/unx/gtk/gtkdata.cxx +index b5c64e0..9ea6dc9 100644 +--- a/vcl/unx/gtk/gtkdata.cxx ++++ b/vcl/unx/gtk/gtkdata.cxx +@@ -1030,22 +1030,4 @@ void GtkSalDisplay::deregisterFrame( SalFrame* pFrame ) + SalGenericDisplay::deregisterFrame( pFrame ); + } + +-#if GTK_CHECK_VERSION(3,0,0) +-void GtkSalDisplay::RefreshMenusUnity() +-{ +-#ifdef ENABLE_GMENU_INTEGRATION +- for(auto pSalFrame : m_aFrames) { +- auto pGtkSalFrame( static_cast(pSalFrame)); +- GtkSalMenu* pSalMenu = static_cast(pGtkSalFrame->GetMenu()); +- if(pSalMenu) { +- pSalMenu->Activate(); +- pSalMenu->UpdateFull(); +- } +- } +-#else +- (void) this; +-#endif +-} +-#endif +- + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx +index a0cc977..fd29a25 100644 +--- a/vcl/unx/gtk/gtksalmenu.cxx ++++ b/vcl/unx/gtk/gtksalmenu.cxx +@@ -372,54 +372,9 @@ void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsig + pItem->mpSubMenu = pGtkSubMenu; + } + +-static bool bInvalidMenus = false; +-static gboolean RefreshMenusUnity(gpointer) +-{ +- SolarMutexGuard g; +-#if GTK_CHECK_VERSION(3,0,0) +- GetGtkSalData()->GetGtkDisplay()->RefreshMenusUnity(); +-#else +- SalDisplay* pSalDisplay = vcl_sal::getSalDisplay(GetGenericData()); +- std::list< SalFrame* >::const_iterator pSalFrame = pSalDisplay->getFrames().begin(); +- std::list< SalFrame* >::const_iterator pEndSalFrame = pSalDisplay->getFrames().end(); +- for(; pSalFrame != pEndSalFrame; ++pSalFrame) { +- const GtkSalFrame* pGtkSalFrame = static_cast< const GtkSalFrame* >( *pSalFrame ); +- GtkSalFrame* pFrameNonConst = const_cast(pGtkSalFrame); +- GtkSalMenu* pSalMenu = static_cast(pFrameNonConst->GetMenu()); +- if(pSalMenu) { +- pSalMenu->Activate(); +- pSalMenu->UpdateFull(); +- } +- } +-#endif +- bInvalidMenus = false; +- return FALSE; +-} +- +-static void RefreshMenusUnity(void*, LinkParamNone*) +-{ +- if(!bInvalidMenus) { +- g_timeout_add(10, &RefreshMenusUnity, nullptr); +- bInvalidMenus = true; +- } +-} +- +-static Link* getRefreshLinkInstance() +-{ +- static Link* pLink = nullptr; +- if(!pLink) { +- pLink = new Link(nullptr, &RefreshMenusUnity); +- } +- return pLink; +-} +- + void GtkSalMenu::SetFrame( const SalFrame* pFrame ) + { + SolarMutexGuard aGuard; +- { +- vcl::MenuInvalidator::AddMenuInvalidateListener(*getRefreshLinkInstance()); +- } +- + assert(mbMenuBar); + SAL_INFO("vcl.unity", "GtkSalMenu set to frame"); + mpFrame = static_cast< const GtkSalFrame* >( pFrame ); +@@ -674,6 +629,7 @@ void GtkSalMenu::DispatchCommand( gint itemId, const gchar *aCommand ) + void GtkSalMenu::ActivateAllSubmenus(MenuBar* pMenuBar) + { + pMenuBar->HandleMenuActivateEvent(mpVCLMenu); ++ pMenuBar->HandleMenuDeActivateEvent(mpVCLMenu); + for ( size_t nPos = 0; nPos < maItems.size(); nPos++ ) + { + GtkSalMenuItem *pSalItem = maItems[ nPos ]; +@@ -685,11 +641,23 @@ void GtkSalMenu::ActivateAllSubmenus(MenuBar* pMenuBar) + } + } + +-void GtkSalMenu::Activate() ++void GtkSalMenu::Activate( const gchar* aMenuCommand ) + { + if ( !mbMenuBar ) + return; +- ActivateAllSubmenus(static_cast(mpVCLMenu)); ++ ++ if ( !aMenuCommand ) { ++ ActivateAllSubmenus( static_cast< MenuBar* >( mpVCLMenu ) ); ++ return; ++ } ++ ++ GtkSalMenu* pSalSubMenu = GetMenuForItemCommand( const_cast(aMenuCommand), TRUE ); ++ ++ if ( pSalSubMenu != nullptr ) { ++ MenuBar* pMenuBar = static_cast< MenuBar* >( mpVCLMenu ); ++ pMenuBar->HandleMenuActivateEvent( pSalSubMenu->mpVCLMenu ); ++ pSalSubMenu->Update(); ++ } + } + + void GtkSalMenu::Deactivate( const gchar* aMenuCommand ) +-- +2.7.1 + diff --git a/0002-gtk3-some-changes-towards-enabling-native-gtk3-popup.patch b/0002-gtk3-some-changes-towards-enabling-native-gtk3-popup.patch new file mode 100644 index 0000000..86b314e --- /dev/null +++ b/0002-gtk3-some-changes-towards-enabling-native-gtk3-popup.patch @@ -0,0 +1,172 @@ +From 684f2191c0010aaa5c802a306bef209660df0968 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Mon, 22 Feb 2016 20:57:52 +0000 +Subject: [PATCH 2/8] gtk3: some changes towards enabling native gtk3 popup + menus + +these menubar things can be menu things and can then do +away with the casting, no logic changes intended + +Change-Id: Ibb1b5354d5e1483327f172d6890e134f1e4b9ee4 +(cherry picked from commit c13a0b1f9e76584a4ffaea0ba754c8f9a01793d8) +--- + include/vcl/menu.hxx | 8 ++++---- + vcl/inc/unx/gtk/gtksalmenu.hxx | 4 ++-- + vcl/source/window/menu.cxx | 15 +++++++-------- + vcl/unx/gtk/gtksalmenu.cxx | 13 +++++-------- + 4 files changed, 18 insertions(+), 22 deletions(-) + +diff --git a/include/vcl/menu.hxx b/include/vcl/menu.hxx +index 0d6e16a..1dbd85e 100644 +--- a/include/vcl/menu.hxx ++++ b/include/vcl/menu.hxx +@@ -406,8 +406,11 @@ public: + + void HighlightItem( sal_uInt16 nItemPos ); + void DeHighlight() { HighlightItem( 0xFFFF ); } // MENUITEMPOS_INVALID +-}; + ++ bool HandleMenuCommandEvent(Menu *pMenu, sal_uInt16 nEventId) const; ++ bool HandleMenuActivateEvent(Menu *pMenu) const; ++ bool HandleMenuDeActivateEvent(Menu *pMenu) const; ++}; + + class VCL_DLLPUBLIC MenuBar : public Menu + { +@@ -458,10 +461,7 @@ public: + void ShowButtons( bool bClose, bool bFloat, bool bHide ); + + virtual void SelectItem(sal_uInt16 nId) override; +- bool HandleMenuActivateEvent(Menu *pMenu) const; +- bool HandleMenuDeActivateEvent(Menu *pMenu) const; + bool HandleMenuHighlightEvent(Menu *pMenu, sal_uInt16 nEventId) const; +- bool HandleMenuCommandEvent(Menu *pMenu, sal_uInt16 nEventId) const; + bool HandleMenuButtonEvent(Menu *pMenu, sal_uInt16 nEventId); + + void SetCloseButtonClickHdl( const Link& rLink ) { maCloseHdl = rLink; } +diff --git a/vcl/inc/unx/gtk/gtksalmenu.hxx b/vcl/inc/unx/gtk/gtksalmenu.hxx +index 8df2c1d..1d58b7a 100644 +--- a/vcl/inc/unx/gtk/gtksalmenu.hxx ++++ b/vcl/inc/unx/gtk/gtksalmenu.hxx +@@ -42,7 +42,7 @@ class GtkSalMenu : public SalMenu + private: + std::vector< GtkSalMenuItem* > maItems; + +- bool mbMenuBar; ++ bool mbMenuBar; + Menu* mpVCLMenu; + GtkSalMenu* mpParentSalMenu; + const GtkSalFrame* mpFrame; +@@ -53,7 +53,7 @@ private: + + GtkSalMenu* GetMenuForItemCommand( gchar* aCommand, gboolean bGetSubmenu ); + void ImplUpdate( gboolean bRecurse ); +- void ActivateAllSubmenus(MenuBar* pMenuBar); ++ void ActivateAllSubmenus(Menu* pMenuBar); + + public: + GtkSalMenu( bool bMenuBar ); +diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx +index 4aa7d41..999085e 100644 +--- a/vcl/source/window/menu.cxx ++++ b/vcl/source/window/menu.cxx +@@ -2679,14 +2679,13 @@ void MenuBar::SelectItem(sal_uInt16 nId) + } + + // handler for native menu selection and command events +- +-bool MenuBar::HandleMenuActivateEvent( Menu *pMenu ) const ++bool Menu::HandleMenuActivateEvent( Menu *pMenu ) const + { + if( pMenu ) + { + ImplMenuDelData aDelData( this ); + +- pMenu->pStartedFrom = const_cast(this); ++ pMenu->pStartedFrom = const_cast(this); + pMenu->bInCallback = true; + pMenu->Activate(); + +@@ -2696,13 +2695,13 @@ bool MenuBar::HandleMenuActivateEvent( Menu *pMenu ) const + return true; + } + +-bool MenuBar::HandleMenuDeActivateEvent( Menu *pMenu ) const ++bool Menu::HandleMenuDeActivateEvent( Menu *pMenu ) const + { + if( pMenu ) + { + ImplMenuDelData aDelData( this ); + +- pMenu->pStartedFrom = const_cast(this); ++ pMenu->pStartedFrom = const_cast(this); + pMenu->bInCallback = true; + pMenu->Deactivate(); + if( !aDelData.isDeleted() ) +@@ -2735,14 +2734,14 @@ bool MenuBar::HandleMenuHighlightEvent( Menu *pMenu, sal_uInt16 nHighlightEventI + return false; + } + +-bool MenuBar::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const ++bool Menu::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const + { + if( !pMenu ) +- pMenu = const_cast(this)->ImplFindMenu(nCommandEventId); ++ pMenu = const_cast(this)->ImplFindMenu(nCommandEventId); + if( pMenu ) + { + pMenu->nSelectedId = nCommandEventId; +- pMenu->pStartedFrom = const_cast(this); ++ pMenu->pStartedFrom = const_cast(this); + pMenu->ImplSelect(); + return true; + } +diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx +index fd29a25..6887ade 100644 +--- a/vcl/unx/gtk/gtksalmenu.cxx ++++ b/vcl/unx/gtk/gtksalmenu.cxx +@@ -622,11 +622,10 @@ void GtkSalMenu::DispatchCommand( gint itemId, const gchar *aCommand ) + GtkSalMenu* pSalSubMenu = GetMenuForItemCommand( const_cast(aCommand), FALSE ); + Menu* pSubMenu = ( pSalSubMenu != nullptr ) ? pSalSubMenu->GetMenu() : nullptr; + +- MenuBar* pMenuBar = static_cast< MenuBar* >( mpVCLMenu ); +- pMenuBar->HandleMenuCommandEvent( pSubMenu, itemId ); ++ mpVCLMenu->HandleMenuCommandEvent( pSubMenu, itemId ); + } + +-void GtkSalMenu::ActivateAllSubmenus(MenuBar* pMenuBar) ++void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar) + { + pMenuBar->HandleMenuActivateEvent(mpVCLMenu); + pMenuBar->HandleMenuDeActivateEvent(mpVCLMenu); +@@ -647,15 +646,14 @@ void GtkSalMenu::Activate( const gchar* aMenuCommand ) + return; + + if ( !aMenuCommand ) { +- ActivateAllSubmenus( static_cast< MenuBar* >( mpVCLMenu ) ); ++ ActivateAllSubmenus(mpVCLMenu); + return; + } + + GtkSalMenu* pSalSubMenu = GetMenuForItemCommand( const_cast(aMenuCommand), TRUE ); + + if ( pSalSubMenu != nullptr ) { +- MenuBar* pMenuBar = static_cast< MenuBar* >( mpVCLMenu ); +- pMenuBar->HandleMenuActivateEvent( pSalSubMenu->mpVCLMenu ); ++ mpVCLMenu->HandleMenuActivateEvent( pSalSubMenu->mpVCLMenu ); + pSalSubMenu->Update(); + } + } +@@ -668,8 +666,7 @@ void GtkSalMenu::Deactivate( const gchar* aMenuCommand ) + GtkSalMenu* pSalSubMenu = GetMenuForItemCommand( const_cast(aMenuCommand), TRUE ); + + if ( pSalSubMenu != nullptr ) { +- MenuBar* pMenuBar = static_cast< MenuBar* >( mpVCLMenu ); +- pMenuBar->HandleMenuDeActivateEvent( pSalSubMenu->mpVCLMenu ); ++ mpVCLMenu->HandleMenuDeActivateEvent( pSalSubMenu->mpVCLMenu ); + } + } + +-- +2.7.1 + diff --git a/0003-gtk3-vcl-popups-flush-any-unexecuted-Select-events-o.patch b/0003-gtk3-vcl-popups-flush-any-unexecuted-Select-events-o.patch new file mode 100644 index 0000000..62bda65 --- /dev/null +++ b/0003-gtk3-vcl-popups-flush-any-unexecuted-Select-events-o.patch @@ -0,0 +1,81 @@ +From 67d1d0061c1cf5bb8aba0de75776435150c8559e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Tue, 23 Feb 2016 14:42:29 +0000 +Subject: [PATCH 3/8] gtk3: vcl popups flush any unexecuted Select events on + popdown + +so if the gtksalmenu integration wants to drive popups by setting a selection +on the vcl popup, then the same flush is needed after ShowNativePopupMenu + +(cherry picked from commit 3cb62eacae001df546c2a8f39ae4d37c33791d0b) + +Change-Id: I59be60de5742d1e382cabefcbf0d8cdd5fc30b00 +--- + include/vcl/menu.hxx | 1 + + vcl/source/window/menu.cxx | 24 +++++++++++++++--------- + 2 files changed, 16 insertions(+), 9 deletions(-) + +diff --git a/include/vcl/menu.hxx b/include/vcl/menu.hxx +index 1dbd85e..f10141b 100644 +--- a/include/vcl/menu.hxx ++++ b/include/vcl/menu.hxx +@@ -515,6 +515,7 @@ private: + + protected: + SAL_DLLPRIVATE sal_uInt16 ImplExecute( vcl::Window* pWindow, const Rectangle& rRect, FloatWinPopupFlags nPopupFlags, Menu* pStaredFrom, bool bPreSelectFirst ); ++ SAL_DLLPRIVATE void ImplFlushPendingSelect(); + SAL_DLLPRIVATE long ImplCalcHeight( sal_uInt16 nEntries ) const; + SAL_DLLPRIVATE sal_uInt16 ImplCalcVisEntries( long nMaxHeight, sal_uInt16 nStartEntry = 0, sal_uInt16* pLastVisible = nullptr ) const; + +diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx +index 999085e..b753fac 100644 +--- a/vcl/source/window/menu.cxx ++++ b/vcl/source/window/menu.cxx +@@ -2933,6 +2933,19 @@ sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Rectangle& rRect, + return ImplExecute( pExecWindow, rRect, nPopupModeFlags, nullptr, false ); + } + ++void PopupMenu::ImplFlushPendingSelect() ++{ ++ // is there still Select? ++ Menu* pSelect = ImplFindSelectMenu(); ++ if (pSelect) ++ { ++ // Select should be called prior to leaving execute in a popup menu! ++ Application::RemoveUserEvent( pSelect->nEventId ); ++ pSelect->nEventId = nullptr; ++ pSelect->Select(); ++ } ++} ++ + sal_uInt16 PopupMenu::ImplExecute( vcl::Window* pW, const Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst ) + { + if ( !pSFrom && ( PopupMenu::IsInExecute() || !GetItemCount() ) ) +@@ -3097,6 +3110,7 @@ sal_uInt16 PopupMenu::ImplExecute( vcl::Window* pW, const Rectangle& rRect, Floa + SalMenu* pMenu = ImplGetSalMenu(); + if( pMenu && bRealExecute && pMenu->ShowNativePopupMenu( pWin, aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus ) ) + { ++ ImplFlushPendingSelect(); + pWin->StopExecute(); + pWin->doShutdown(); + pWindow->doLazyDelete(); +@@ -3180,15 +3194,7 @@ sal_uInt16 PopupMenu::ImplExecute( vcl::Window* pW, const Rectangle& rRect, Floa + pWindow->doLazyDelete(); + pWindow = nullptr; + +- // is there still Select? +- Menu* pSelect = ImplFindSelectMenu(); +- if ( pSelect ) +- { +- // Select should be called prior to leaving execute in a popup menu! +- Application::RemoveUserEvent( pSelect->nEventId ); +- pSelect->nEventId = nullptr; +- pSelect->Select(); +- } ++ ImplFlushPendingSelect(); + } + + return bRealExecute ? nSelectedId : 0; +-- +2.7.1 + diff --git a/0004-gtk3-replace-old-action-if-same-command-is-added.patch b/0004-gtk3-replace-old-action-if-same-command-is-added.patch new file mode 100644 index 0000000..d0537d7 --- /dev/null +++ b/0004-gtk3-replace-old-action-if-same-command-is-added.patch @@ -0,0 +1,77 @@ +From 1d40ccd2f55173f954e596ce59bc4307eceae453 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Wed, 24 Feb 2016 10:40:10 +0000 +Subject: [PATCH 4/8] gtk3: replace old action if same command is added + +i.e. originally we preferred the old action, now +prefer the new action because e.g. wrap items in +writer only contain their "checkable" state on +their update + +Change-Id: I6a6ce94126253396cc273834a7e8a4fb0a56921d +(cherry picked from commit 36bddcbaa2d1673c1331c788eae9534aca2c5ec3) +--- + vcl/unx/gtk/gtksalmenu.cxx | 45 +++++++++++++++++++++++---------------------- + 1 file changed, 23 insertions(+), 22 deletions(-) + +diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx +index 6887ade..a9a0932 100644 +--- a/vcl/unx/gtk/gtksalmenu.cxx ++++ b/vcl/unx/gtk/gtksalmenu.cxx +@@ -530,30 +530,31 @@ void GtkSalMenu::NativeSetItemCommand( unsigned nSection, + + GVariant *pTarget = nullptr; + +- if ( g_action_group_has_action( mpActionGroup, aCommand ) == FALSE ) { +- if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu ) +- { +- // Item is a checkmark button. +- GVariantType* pStateType = g_variant_type_new( reinterpret_cast(G_VARIANT_TYPE_BOOLEAN) ); +- GVariant* pState = g_variant_new_boolean( bChecked ); ++ if (g_action_group_has_action(mpActionGroup, aCommand)) ++ g_lo_action_group_remove(pActionGroup, aCommand); + +- g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState ); +- } +- else if ( nBits & MenuItemBits::RADIOCHECK ) +- { +- // Item is a radio button. +- GVariantType* pParameterType = g_variant_type_new( reinterpret_cast(G_VARIANT_TYPE_STRING) ); +- GVariantType* pStateType = g_variant_type_new( reinterpret_cast(G_VARIANT_TYPE_STRING) ); +- GVariant* pState = g_variant_new_string( "" ); +- pTarget = g_variant_new_string( aCommand ); ++ if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu ) ++ { ++ // Item is a checkmark button. ++ GVariantType* pStateType = g_variant_type_new( reinterpret_cast(G_VARIANT_TYPE_BOOLEAN) ); ++ GVariant* pState = g_variant_new_boolean( bChecked ); + +- 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 ); +- } ++ g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState ); ++ } ++ else if ( nBits & MenuItemBits::RADIOCHECK ) ++ { ++ // Item is a radio button. ++ GVariantType* pParameterType = g_variant_type_new( reinterpret_cast(G_VARIANT_TYPE_STRING) ); ++ GVariantType* pStateType = g_variant_type_new( reinterpret_cast(G_VARIANT_TYPE_STRING) ); ++ GVariant* pState = g_variant_new_string( "" ); ++ pTarget = g_variant_new_string( aCommand ); ++ ++ 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 ); +-- +2.7.1 + diff --git a/0005-gtk3-handle-items-without-commands.patch b/0005-gtk3-handle-items-without-commands.patch new file mode 100644 index 0000000..d7b22f5 --- /dev/null +++ b/0005-gtk3-handle-items-without-commands.patch @@ -0,0 +1,32 @@ +From 33d05b5ca87d1ddb76fef373a92833d07c00cb15 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Wed, 24 Feb 2016 12:19:49 +0000 +Subject: [PATCH 5/8] gtk3: handle items without commands + +e.g. the draw/impress context menus. Handle these like +MenuManager::Activate does + +Change-Id: I02a0e377a2d3a57ac7ac9239aaa75dbb856489d2 +(cherry picked from commit b8ee342576b707dbffe877f5c225b640ee65276d) +--- + vcl/unx/gtk/gtksalmenu.cxx | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx +index a9a0932..346e42d 100644 +--- a/vcl/unx/gtk/gtksalmenu.cxx ++++ b/vcl/unx/gtk/gtksalmenu.cxx +@@ -52,7 +52,9 @@ static gchar* GetCommandForItem( GtkSalMenuItem* pSalMenuItem, gchar* aCurrentCo + if ( !pMenu ) + return nullptr; + +- OUString aMenuCommand = pMenu->GetItemCommand( nId ); ++ OUString aMenuCommand = pMenu->GetItemCommand(nId); ++ if (aMenuCommand.isEmpty()) ++ aMenuCommand = "slot:" + OUString::number(nId); + gchar* aCommandStr = g_strdup( OUStringToOString( aMenuCommand, RTL_TEXTENCODING_UTF8 ).getStr() ); + aCommand = g_strdup( aCommandStr ); + +-- +2.7.1 + diff --git a/0006-mark-checkable-toolbox-menu-entries-as-checkable.patch b/0006-mark-checkable-toolbox-menu-entries-as-checkable.patch new file mode 100644 index 0000000..bb422f3 --- /dev/null +++ b/0006-mark-checkable-toolbox-menu-entries-as-checkable.patch @@ -0,0 +1,64 @@ +From 7dc5977edb7e34aed68aeb68846dee227f1a32de Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Wed, 24 Feb 2016 13:02:40 +0000 +Subject: [PATCH 6/8] mark checkable toolbox menu entries as checkable + +e.g. the toplevel toolbars put excess entries in +menus. If the entry is not marked as checkable then +a native gtk menu entry will appear to be stateless +when it actually does have a toggle state + +Change-Id: I7168b44d59fd64dfe264ed8ca26355252d697251 +(cherry picked from commit 13917e0755bb864f22d0cf75a43854acbdb1eaec) +--- + vcl/source/window/toolbox2.cxx | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/vcl/source/window/toolbox2.cxx b/vcl/source/window/toolbox2.cxx +index 0c916f6..663b7d6 100644 +--- a/vcl/source/window/toolbox2.cxx ++++ b/vcl/source/window/toolbox2.cxx +@@ -1791,6 +1791,20 @@ bool ToolBox::ImplHasClippedItems() + return false; + } + ++namespace ++{ ++ MenuItemBits ConvertBitsFromToolBoxToMenu(ToolBoxItemBits nToolItemBits) ++ { ++ MenuItemBits nMenuItemBits = MenuItemBits::NONE; ++ if ((nToolItemBits & ToolBoxItemBits::CHECKABLE) || ++ (nToolItemBits & ToolBoxItemBits::DROPDOWN)) ++ { ++ nMenuItemBits |= MenuItemBits::CHECKABLE; ++ } ++ return nMenuItemBits; ++ } ++} ++ + void ToolBox::UpdateCustomMenu() + { + // fill clipped items into menu +@@ -1826,7 +1840,8 @@ void ToolBox::UpdateCustomMenu() + if( it->IsClipped() ) + { + sal_uInt16 id = it->mnId + TOOLBOX_MENUITEM_START; +- pMenu->InsertItem( id, it->maText, it->maImageOriginal, MenuItemBits::NONE, OString()); ++ MenuItemBits nMenuItemBits = ConvertBitsFromToolBoxToMenu(it->mnBits); ++ pMenu->InsertItem( id, it->maText, it->maImageOriginal, nMenuItemBits, OString()); + pMenu->SetItemCommand( id, it->maCommandStr ); + pMenu->EnableItem( id, it->mbEnabled ); + pMenu->CheckItem ( id, it->meState == TRISTATE_TRUE ); +@@ -1843,7 +1858,8 @@ void ToolBox::UpdateCustomMenu() + if( it->IsItemHidden() ) + { + sal_uInt16 id = it->mnId + TOOLBOX_MENUITEM_START; +- pMenu->InsertItem( id, it->maText, it->maImageOriginal, MenuItemBits::NONE, OString() ); ++ MenuItemBits nMenuItemBits = ConvertBitsFromToolBoxToMenu(it->mnBits); ++ pMenu->InsertItem( id, it->maText, it->maImageOriginal, nMenuItemBits, OString() ); + pMenu->SetItemCommand( id, it->maCommandStr ); + pMenu->EnableItem( id, it->mbEnabled ); + pMenu->CheckItem( id, it->meState == TRISTATE_TRUE ); +-- +2.7.1 + diff --git a/0007-set-gtk-layout-direction-to-match-ours.patch b/0007-set-gtk-layout-direction-to-match-ours.patch new file mode 100644 index 0000000..c74771b --- /dev/null +++ b/0007-set-gtk-layout-direction-to-match-ours.patch @@ -0,0 +1,26 @@ +From 205dd9979f77c709cf36b92da1836ce5374879ab Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Wed, 24 Feb 2016 15:24:53 +0000 +Subject: [PATCH 7/8] set gtk layout direction to match ours + +Change-Id: I27610f28f42368355bef1b3461fc3ccea1b07218 +(cherry picked from commit b50071c817657866f8b22873be26d34970005a2d) +--- + vcl/unx/gtk/gtkdata.cxx | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/vcl/unx/gtk/gtkdata.cxx b/vcl/unx/gtk/gtkdata.cxx +index 9ea6dc9..f15cce2 100644 +--- a/vcl/unx/gtk/gtkdata.cxx ++++ b/vcl/unx/gtk/gtkdata.cxx +@@ -110,6 +110,7 @@ GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) : + #endif + #endif + ++ gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + } + + GtkSalDisplay::~GtkSalDisplay() +-- +2.7.1 + diff --git a/0008-gtk3-implement-native-context-menus.patch b/0008-gtk3-implement-native-context-menus.patch new file mode 100644 index 0000000..50a6c7f --- /dev/null +++ b/0008-gtk3-implement-native-context-menus.patch @@ -0,0 +1,550 @@ +From b265bcddde36bea2f5b31ce5df407301cbfe82b5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= +Date: Tue, 23 Feb 2016 15:57:11 +0000 +Subject: [PATCH 8/8] gtk3: implement native context menus + +This reuses lots of the unity machinery which is similar +to the mac concept of a single toplevel menubar. + +So to drive popup menus, part of this is a rework that does away with the idea +that the "menubar" is the controller of the hierarchy, and instead the top +element becomes the controller + +Change-Id: I4336391718844bc73cfc47c1043f99f0e3b812d8 +(cherry picked from commit a0c700b1493c7b51540d1e77b44d1edd9bf920f0) +--- + vcl/inc/unx/gtk/gloactiongroup.h | 3 + + vcl/inc/unx/gtk/gtksalmenu.hxx | 9 +- + vcl/unx/gtk/gloactiongroup.cxx | 43 ++++---- + vcl/unx/gtk/gtksalmenu.cxx | 228 +++++++++++++++++++++++++++++---------- + 4 files changed, 203 insertions(+), 80 deletions(-) + +diff --git a/vcl/inc/unx/gtk/gloactiongroup.h b/vcl/inc/unx/gtk/gloactiongroup.h +index 080b679..ec6bd39 100644 +--- a/vcl/inc/unx/gtk/gloactiongroup.h ++++ b/vcl/inc/unx/gtk/gloactiongroup.h +@@ -46,6 +46,9 @@ GType g_lo_action_group_get_type (void) G_GNUC_CONST; + + GLOActionGroup * g_lo_action_group_new (gpointer frame); + ++void g_lo_action_group_set_top_menu (GLOActionGroup *group, ++ gpointer top_menu); ++ + void g_lo_action_group_insert (GLOActionGroup *group, + const gchar *action_name, + gint item_id, +diff --git a/vcl/inc/unx/gtk/gtksalmenu.hxx b/vcl/inc/unx/gtk/gtksalmenu.hxx +index 1d58b7a..d95d25c 100644 +--- a/vcl/inc/unx/gtk/gtksalmenu.hxx ++++ b/vcl/inc/unx/gtk/gtksalmenu.hxx +@@ -43,16 +43,17 @@ private: + std::vector< GtkSalMenuItem* > maItems; + + bool mbMenuBar; ++ bool mbMenuVisibility; + Menu* mpVCLMenu; + GtkSalMenu* mpParentSalMenu; +- const GtkSalFrame* mpFrame; ++ GtkSalFrame* mpFrame; + + // GMenuModel and GActionGroup attributes + GMenuModel* mpMenuModel; + GActionGroup* mpActionGroup; + + GtkSalMenu* GetMenuForItemCommand( gchar* aCommand, gboolean bGetSubmenu ); +- void ImplUpdate( gboolean bRecurse ); ++ void ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries); + void ActivateAllSubmenus(Menu* pMenuBar); + + public: +@@ -77,7 +78,7 @@ public: + + void SetMenu( Menu* pMenu ) { mpVCLMenu = pMenu; } + Menu* GetMenu() { return mpVCLMenu; } +- void SetMenuModel( GMenuModel* pMenuModel ) { mpMenuModel = pMenuModel; } ++ void SetMenuModel(GMenuModel* pMenuModel); + unsigned GetItemCount() { return maItems.size(); } + GtkSalMenuItem* GetItemAtPos( unsigned nPos ) { return maItems[ nPos ]; } + void SetActionGroup( GActionGroup* pActionGroup ) { mpActionGroup = pActionGroup; } +@@ -102,6 +103,8 @@ public: + bool PrepUpdate(); + virtual void Update() override; // Update this menu only. + void UpdateFull(); // Update full menu hierarchy from this menu. ++ ++ virtual bool ShowNativePopupMenu(FloatingWindow * pWin, const Rectangle& rRect, FloatWinPopupFlags nFlags) override; + }; + + class GtkSalMenuItem : public SalMenuItem +diff --git a/vcl/unx/gtk/gloactiongroup.cxx b/vcl/unx/gtk/gloactiongroup.cxx +index e710809..110e0dc 100644 +--- a/vcl/unx/gtk/gloactiongroup.cxx ++++ b/vcl/unx/gtk/gloactiongroup.cxx +@@ -100,8 +100,9 @@ g_lo_action_class_init (GLOActionClass *klass) + + struct _GLOActionGroupPrivate + { +- GHashTable *table; /* string -> GLOAction */ +- GtkSalFrame *frame; /* Frame to which GActionGroup is associated. */ ++ GHashTable *table; /* string -> GLOAction */ ++ GtkSalFrame *frame; /* Frame to which GActionGroup is associated. */ ++ GtkSalMenu *topmenu; /* TopLevel Menu to which GActionGroup is associated. */ + }; + + static void g_lo_action_group_iface_init (GActionGroupInterface *); +@@ -187,13 +188,7 @@ g_lo_action_group_perform_submenu_action (GLOActionGroup *group, + GVariant *state) + { + +- GtkSalFrame* pFrame = group->priv->frame; +- SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " for frame " << pFrame); +- +- if (pFrame == nullptr) +- return; +- +- GtkSalMenu* pSalMenu = static_cast (pFrame->GetMenu()); ++ GtkSalMenu* pSalMenu = group->priv->topmenu; + SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " for menu " << pSalMenu); + + if (pSalMenu != nullptr) { +@@ -263,23 +258,18 @@ g_lo_action_group_activate (GActionGroup *group, + GVariant *parameter) + { + GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group); +- GtkSalFrame *pFrame = lo_group->priv->frame; +- SAL_INFO("vcl.unity", "g_lo_action_group_activate on group " << group << " for frame " << pFrame << " with parameter " << parameter); ++ GtkSalMenu* pSalMenu = lo_group->priv->topmenu; + + if ( parameter != nullptr ) + g_action_group_change_action_state( group, action_name, parameter ); + +- if ( pFrame != nullptr ) +- { +- GtkSalMenu* pSalMenu = static_cast< GtkSalMenu* >( pFrame->GetMenu() ); +- SAL_INFO("vcl.unity", "g_lo_action_group_activate for menu " << pSalMenu); ++ SAL_INFO("vcl.unity", "g_lo_action_group_activate for menu " << pSalMenu); + +- if ( pSalMenu != nullptr ) +- { +- GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name)); +- SAL_INFO("vcl.unity", "g_lo_action_group_activate dispatching action " << action << " named " << action_name << " on menu " << pSalMenu); +- pSalMenu->DispatchCommand( action->item_id, action_name ); +- } ++ if ( pSalMenu != nullptr ) ++ { ++ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name)); ++ SAL_INFO("vcl.unity", "g_lo_action_group_activate dispatching action " << action << " named " << action_name << " on menu " << pSalMenu); ++ pSalMenu->DispatchCommand( action->item_id, action_name ); + } + } + +@@ -355,6 +345,17 @@ g_lo_action_group_init (GLOActionGroup *group) + group->priv->table = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + group->priv->frame = nullptr; ++ group->priv->topmenu = nullptr; ++} ++ ++void ++g_lo_action_group_set_top_menu (GLOActionGroup *group, ++ gpointer top_menu) ++{ ++ group->priv = G_TYPE_INSTANCE_GET_PRIVATE (group, ++ G_TYPE_LO_ACTION_GROUP, ++ GLOActionGroupPrivate); ++ group->priv->topmenu = static_cast(top_menu); + } + + static void +diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx +index 346e42d..7bc9232 100644 +--- a/vcl/unx/gtk/gtksalmenu.cxx ++++ b/vcl/unx/gtk/gtksalmenu.cxx +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -24,6 +25,7 @@ + #endif + + #include ++#include + + // FIXME Copied from framework/inc/framework/menuconfiguration.hxx to + // avoid circular dependency between modules. It should be in a common +@@ -31,8 +33,6 @@ + const sal_uInt16 START_ITEMID_WINDOWLIST = 4600; + const sal_uInt16 END_ITEMID_WINDOWLIST = 4699; + +-static bool bMenuVisibility = false; +- + /* + * This function generates the proper command name for all actions, including + * duplicated or special ones. +@@ -77,20 +77,17 @@ static gchar* GetCommandForItem( GtkSalMenuItem* pSalMenuItem, gchar* aCurrentCo + + bool GtkSalMenu::PrepUpdate() + { +- const GtkSalFrame* pFrame = GetFrame(); +- if (pFrame) +- { +- GtkSalFrame* pNonConstFrame = const_cast(pFrame); +- GtkSalMenu* pSalMenu = this; +- +- if ( !pNonConstFrame->GetMenu() ) +- pNonConstFrame->SetMenu( pSalMenu ); ++ bool bMenuVisibility; + +- if ( bMenuVisibility && mpMenuModel && mpActionGroup ) +- return true; +- } ++ //get top level visibility ++ const GtkSalMenu* pMenu = this; ++ do ++ { ++ bMenuVisibility = pMenu->mbMenuVisibility; ++ pMenu = pMenu->mpParentSalMenu; ++ } while (pMenu); + +- return false; ++ return bMenuVisibility && mpMenuModel && mpActionGroup; + } + + /* +@@ -114,14 +111,58 @@ void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, un + } + } + +-void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nLastSection ) ++void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList, ++ sal_Int32 nSection, GActionGroup* pActionGroup) ++{ ++ while (nSection >= 0) ++ { ++ sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection ); ++ while (nSectionItems--) ++ { ++ gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems); ++ // remove disabled entries ++ bool bRemove = g_action_group_get_action_enabled(pActionGroup, pCommand) == false; ++ if (!bRemove) ++ { ++ //also remove any empty submenus ++ GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems); ++ if (pSubMenuModel) ++ { ++ gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel)); ++ bRemove = (nSubMenuSections == 0 || ++ (nSubMenuSections == 1 && g_lo_menu_get_n_items_from_section(pSubMenuModel, 0) == 0)); ++ } ++ } ++ ++ if (bRemove) ++ { ++ //but tdf#86850 Always display clipboard functions ++ bRemove = g_strcmp0(pCommand, ".uno:Cut") && ++ g_strcmp0(pCommand, ".uno:Copy") && ++ g_strcmp0(pCommand, ".uno:Paste"); ++ } ++ ++ if (bRemove) ++ { ++ if (pCommand != nullptr && pOldCommandList != nullptr) ++ *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand)); ++ g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems); ++ } ++ ++ g_free(pCommand); ++ } ++ --nSection; ++ } ++} ++ ++void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection ) + { + if ( pMenu == nullptr || pOldCommandList == nullptr ) + return; + + sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1; + +- for ( ; n > (sal_Int32) nLastSection; n-- ) ++ for ( ; n > nLastSection; n--) + { + RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 ); + g_lo_menu_remove( pMenu, n ); +@@ -173,7 +214,7 @@ void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, + } + } + +-void GtkSalMenu::ImplUpdate( gboolean bRecurse ) ++void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries) + { + SolarMutexGuard aGuard; + +@@ -277,7 +318,7 @@ void GtkSalMenu::ImplUpdate( gboolean bRecurse ) + 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( bRecurse ); ++ pSubmenu->ImplUpdate(bRecurse, bRemoveDisabledEntries); + } + } + +@@ -287,6 +328,12 @@ void GtkSalMenu::ImplUpdate( gboolean bRecurse ) + ++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 ); + +@@ -299,12 +346,89 @@ void GtkSalMenu::ImplUpdate( gboolean bRecurse ) + + void GtkSalMenu::Update() + { +- ImplUpdate( FALSE ); ++ //find out if top level is a menubar or not, if not, then its a popup menu ++ //hierarchy and in those we hide (most) disabled entries ++ const GtkSalMenu* pMenu = this; ++ while (pMenu->mpParentSalMenu) ++ pMenu = pMenu->mpParentSalMenu; ++ ImplUpdate(false, !pMenu->mbMenuBar); + } + + void GtkSalMenu::UpdateFull() + { +- ImplUpdate( TRUE ); ++ //find out if top level is a menubar or not, if not, then its a popup menu ++ //hierarchy and in those we hide (most) disabled entries ++ const GtkSalMenu* pMenu = this; ++ while (pMenu->mpParentSalMenu) ++ pMenu = pMenu->mpParentSalMenu; ++ ImplUpdate(true, !pMenu->mbMenuBar); ++} ++ ++bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const Rectangle& /*rRect*/, ++ FloatWinPopupFlags /*nFlags*/) ++{ ++#if GTK_CHECK_VERSION(3,0,0) ++ guint nButton; ++ guint32 nTime; ++ ++ //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 = gtk_get_current_event_time(); ++ } ++ ++ Display(true); ++ ++ mpFrame = static_cast(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame()); ++ ++ GLOActionGroup* pActionGroup = g_lo_action_group_new(static_cast(mpFrame)); ++ g_lo_action_group_set_top_menu(pActionGroup, static_cast(this)); ++ ++ mpActionGroup = G_ACTION_GROUP(pActionGroup); ++ mpMenuModel = G_MENU_MODEL(g_lo_menu_new()); ++ // Generate the main menu structure, populates mpMenuModel ++ UpdateFull(); ++ ++ GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel); ++ gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr); ++ ++ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup); ++ ++ //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); ++ g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop); ++ gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, nullptr, nullptr, nButton, nTime); ++ if (g_main_loop_is_running(pLoop)) ++ { ++ gdk_threads_leave(); ++ g_main_loop_run(pLoop); ++ gdk_threads_enter(); ++ } ++ g_main_loop_unref(pLoop); ++ ++ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr); ++ ++ gtk_widget_destroy(pWidget); ++ ++ g_object_unref(mpActionGroup); ++ ++ return true; ++#else ++ (void)pWin; ++ return false; ++#endif + } + + /* +@@ -313,6 +437,7 @@ void GtkSalMenu::UpdateFull() + + GtkSalMenu::GtkSalMenu( bool bMenuBar ) : + mbMenuBar( bMenuBar ), ++ mbMenuVisibility( false ), + mpVCLMenu( nullptr ), + mpParentSalMenu( nullptr ), + mpFrame( nullptr ), +@@ -321,25 +446,28 @@ GtkSalMenu::GtkSalMenu( bool bMenuBar ) : + { + } + ++void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel) ++{ ++ if (mpMenuModel) ++ g_object_unref(mpMenuModel); ++ mpMenuModel = pMenuModel; ++ if (mpMenuModel) ++ g_object_ref(mpMenuModel); ++} ++ + GtkSalMenu::~GtkSalMenu() + { + SolarMutexGuard aGuard; + +- if ( mbMenuBar ) +- { +- if ( mpMenuModel ) +- { +-// g_lo_menu_remove( G_LO_MENU( mpMenuModel ), 0 ); +- g_object_unref( mpMenuModel ); +- } +- } ++ if (mpMenuModel) ++ g_object_unref(mpMenuModel); + + maItems.clear(); + } + + bool GtkSalMenu::VisibleMenuBar() + { +- return bMenuVisibility; ++ return mbMenuBar && mbMenuVisibility; + } + + void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) +@@ -374,22 +502,21 @@ void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsig + pItem->mpSubMenu = pGtkSubMenu; + } + +-void GtkSalMenu::SetFrame( const SalFrame* pFrame ) ++void GtkSalMenu::SetFrame(const SalFrame* pFrame) + { + SolarMutexGuard aGuard; + assert(mbMenuBar); + SAL_INFO("vcl.unity", "GtkSalMenu set to frame"); +- mpFrame = static_cast< const GtkSalFrame* >( pFrame ); +- GtkSalFrame* pFrameNonConst = const_cast(mpFrame); ++ mpFrame = const_cast(static_cast(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 +- pFrameNonConst->SetMenu( this ); +- pFrameNonConst->EnsureAppMenuWatch(); ++ mpFrame->SetMenu( this ); ++ mpFrame->EnsureAppMenuWatch(); + + // Clean menu model and action group if needed. +- GtkWidget* pWidget = pFrameNonConst->getWindow(); ++ GtkWidget* pWidget = mpFrame->getWindow(); + GdkWindow* gdkWindow = gtk_widget_get_window( pWidget ); + + GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) ); +@@ -407,11 +534,12 @@ void GtkSalMenu::SetFrame( const SalFrame* pFrame ) + if ( pActionGroup ) + { + g_lo_action_group_clear( pActionGroup ); ++ g_lo_action_group_set_top_menu(pActionGroup, static_cast(this)); + mpActionGroup = G_ACTION_GROUP( pActionGroup ); + } + + // Generate the main menu structure. +- if (bMenuVisibility) ++ if (mbMenuVisibility) + UpdateFull(); + + g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel ); +@@ -618,14 +746,9 @@ GtkSalMenu* GtkSalMenu::GetMenuForItemCommand( gchar* aCommand, gboolean bGetSub + void GtkSalMenu::DispatchCommand( gint itemId, const gchar *aCommand ) + { + SolarMutexGuard aGuard; +- // Only the menubar is allowed to dispatch commands. +- if ( !mbMenuBar ) +- return; +- + GtkSalMenu* pSalSubMenu = GetMenuForItemCommand( const_cast(aCommand), FALSE ); + Menu* pSubMenu = ( pSalSubMenu != nullptr ) ? pSalSubMenu->GetMenu() : nullptr; +- +- mpVCLMenu->HandleMenuCommandEvent( pSubMenu, itemId ); ++ mpVCLMenu->HandleMenuCommandEvent(pSubMenu, itemId); + } + + void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar) +@@ -645,9 +768,6 @@ void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar) + + void GtkSalMenu::Activate( const gchar* aMenuCommand ) + { +- if ( !mbMenuBar ) +- return; +- + if ( !aMenuCommand ) { + ActivateAllSubmenus(mpVCLMenu); + return; +@@ -663,9 +783,6 @@ void GtkSalMenu::Activate( const gchar* aMenuCommand ) + + void GtkSalMenu::Deactivate( const gchar* aMenuCommand ) + { +- if ( !mbMenuBar ) +- return; +- + GtkSalMenu* pSalSubMenu = GetMenuForItemCommand( const_cast(aMenuCommand), TRUE ); + + if ( pSalSubMenu != nullptr ) { +@@ -675,15 +792,14 @@ void GtkSalMenu::Deactivate( const gchar* aMenuCommand ) + + void GtkSalMenu::Display( bool bVisible ) + { +- if ( !mbMenuBar || mpVCLMenu == nullptr ) +- return; ++ mbMenuVisibility = bVisible; + +- bMenuVisibility = bVisible; +- +- bool bVCLMenuVisible = !bVisible; +- +- MenuBar* pMenuBar = static_cast< MenuBar* >( mpVCLMenu ); +- pMenuBar->SetDisplayable( bVCLMenuVisible ); ++ if (mbMenuBar) ++ { ++ bool bVCLMenuVisible = !bVisible; ++ MenuBar* pMenuBar = static_cast(mpVCLMenu); ++ pMenuBar->SetDisplayable(bVCLMenuVisible); ++ } + } + + bool GtkSalMenu::IsItemVisible( unsigned nPos ) +-- +2.7.1 + diff --git a/libreoffice.spec b/libreoffice.spec index d874635..0d01be3 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -255,6 +255,14 @@ Patch20: 0003-rename-X11WindowProvider-to-a-NativeWindowHandle-pro.patch Patch21: 0004-implement-wayland-handle-passing-for-gstreamer.patch Patch22: 0005-gtk3-wayland-play-video-via-gtksink-gstreamer-elemen.patch Patch23: 0001-gtk3-get-app-menu-working-again-under-gtk3.patch +Patch24: 0001-tdf-97665-Let-s-hope-that-over-activation-isn-t-real.patch +Patch25: 0002-gtk3-some-changes-towards-enabling-native-gtk3-popup.patch +Patch26: 0003-gtk3-vcl-popups-flush-any-unexecuted-Select-events-o.patch +Patch27: 0004-gtk3-replace-old-action-if-same-command-is-added.patch +Patch28: 0005-gtk3-handle-items-without-commands.patch +Patch29: 0006-mark-checkable-toolbox-menu-entries-as-checkable.patch +Patch30: 0007-set-gtk-layout-direction-to-match-ours.patch +Patch21: 0008-gtk3-implement-native-context-menus.patch %if 0%{?rhel} # not upstreamed