You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libreoffice/0008-gtk3-implement-native-...

551 lines
20 KiB

From b265bcddde36bea2f5b31ce5df407301cbfe82b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= <caolanm@redhat.com>
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<GtkSalMenu*> (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<GtkSalMenu*>(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 <unx/gtk/gtkdata.hxx>
#include <unx/gtk/glomenu.h>
#include <unx/gtk/gloactiongroup.h>
+#include <vcl/floatwin.hxx>
#include <vcl/menu.hxx>
#include <unx/gtk/gtkinst.hxx>
@@ -24,6 +25,7 @@
#endif
#include <sal/log.hxx>
+#include <window.h>
// 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<GtkSalFrame*>(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<GtkSalFrame*>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
+
+ GLOActionGroup* pActionGroup = g_lo_action_group_new(static_cast<gpointer>(mpFrame));
+ g_lo_action_group_set_top_menu(pActionGroup, static_cast<gpointer>(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<GtkSalFrame*>(mpFrame);
+ 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
- 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<gpointer>(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<gchar*>(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<gchar*>(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<MenuBar*>(mpVCLMenu);
+ pMenuBar->SetDisplayable(bVCLMenuVisible);
+ }
}
bool GtkSalMenu::IsItemVisible( unsigned nPos )
--
2.7.1