Resolves: tdf#92067 and tdf#99599, use a foolproof action naming scheme

encode the GtkSalMenu and the item id into the action_name so
each one is unique and directly refers to the menu and item in the
menu so knowing which one is which is direct and simple

Change-Id: I81bb278e73946f864e29aeab884e07e16835dad3
diff --git a/vcl/inc/unx/gtk/gtksalmenu.hxx b/vcl/inc/unx/gtk/gtksalmenu.hxx
index 2e182e6..9bd0a77 100644
--- a/vcl/inc/unx/gtk/gtksalmenu.hxx
+++ b/vcl/inc/unx/gtk/gtksalmenu.hxx
@@ -54,7 +54,6 @@ private:
    GMenuModel*                     mpMenuModel;
    GActionGroup*                   mpActionGroup;

    GtkSalMenu*                 GetMenuForItemCommand( gchar* aCommand, int& rDupsToSkip, gboolean bGetSubmenu );
    void                        ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries);
    void                        ActivateAllSubmenus(Menu* pMenuBar);

@@ -98,17 +97,18 @@ public:
    void                        NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck );
    void                        NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, const OUString& rKeyName );

    void                        DispatchCommand( gint itemId, const gchar* aCommand );
    void                        ActivateAllSubmenus()
    {
        ActivateAllSubmenus(mpVCLMenu);
    }
    void                        Activate( const gchar* aMenuCommand );
    void                        Deactivate( const gchar* aMenuCommand );
    static void                 DispatchCommand(const gchar* pMenuCommand);
    static void                 Activate(const gchar* pMenuCommand);
    static void                 Deactivate(const gchar* pMenuCommand);
    void                        EnableUnity(bool bEnable);
    bool                        PrepUpdate();
    virtual void                Update() override;  // Update this menu only.
    void                        UpdateFull();       // Update full menu hierarchy from this menu.
    GtkSalMenu*                 GetTopLevel();

    void CreateMenuBarWidget();
    void DestroyMenuBarWidget();
diff --git a/vcl/unx/gtk/gloactiongroup.cxx b/vcl/unx/gtk/gloactiongroup.cxx
index 04d3a4c..663f1bb 100644
--- a/vcl/unx/gtk/gloactiongroup.cxx
+++ b/vcl/unx/gtk/gloactiongroup.cxx
@@ -101,7 +101,6 @@ struct GLOActionGroupPrivate
{
    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 *);
@@ -191,19 +190,13 @@ g_lo_action_group_perform_submenu_action (GLOActionGroup *group,
                                          const gchar    *action_name,
                                          GVariant       *state)
{
    gboolean bState = g_variant_get_boolean (state);
    SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);

    GtkSalMenu* pSalMenu = group->priv->topmenu;
    SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " for menu " << pSalMenu);

    if (pSalMenu != nullptr) {
        gboolean bState = g_variant_get_boolean (state);
        SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);

        if (bState)
            pSalMenu->Activate (action_name);
        else
            pSalMenu->Deactivate (action_name);
    }
    if (bState)
        GtkSalMenu::Activate(action_name);
    else
        GtkSalMenu::Deactivate(action_name);
}

static void
@@ -261,20 +254,9 @@ g_lo_action_group_activate (GActionGroup *group,
                            const gchar  *action_name,
                            GVariant     *parameter)
{
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group);
    GtkSalMenu* pSalMenu = lo_group->priv->topmenu;

    if ( parameter != nullptr )
        g_action_group_change_action_state( group, action_name, parameter );

    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 (parameter != nullptr)
        g_action_group_change_action_state(group, action_name, parameter);
    GtkSalMenu::DispatchCommand(action_name);
}

void
@@ -304,7 +286,6 @@ g_lo_action_group_insert_stateful (GLOActionGroup     *group,
    {
        if (old_action != nullptr)
            g_lo_action_group_remove (group, action_name);
//            g_action_group_action_removed (G_ACTION_GROUP (group), action_name);

        GLOAction* action = g_lo_action_new();

@@ -349,17 +330,6 @@ 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 7540f46..c503234 100644
--- a/vcl/unx/gtk/gtksalmenu.cxx
+++ b/vcl/unx/gtk/gtksalmenu.cxx
@@ -28,54 +28,17 @@
#include <window.h>
#include <svids.hrc>

// FIXME Copied from framework/inc/framework/menuconfiguration.hxx to
// avoid circular dependency between modules. It should be in a common
// header (probably in vcl).
const sal_uInt16 START_ITEMID_WINDOWLIST    = 4600;
const sal_uInt16 END_ITEMID_WINDOWLIST      = 4699;

static bool bUnityMode = false;

/*
 * This function generates the proper command name for all actions, including
 * duplicated or special ones.
 * This function generates a unique command name for each menu item
 */
static gchar* GetCommandForItem( GtkSalMenuItem* pSalMenuItem, gchar* aCurrentCommand, GActionGroup* pActionGroup )
static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
{
    gchar* aCommand = nullptr;

    sal_uInt16 nId = pSalMenuItem->mnId;
    Menu* pMenu = pSalMenuItem->mpVCLMenu;

    // If item belongs to window list, generate a command with "window-(id)" format.
    if ( ( nId >= START_ITEMID_WINDOWLIST ) && ( nId <= END_ITEMID_WINDOWLIST ) )
        aCommand = g_strdup_printf( "window-%d", nId );
    else
    {
        if ( !pMenu )
            return nullptr;

        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 );

        // Some items could have duplicated commands. A new one should be generated.
        for ( sal_uInt16 i = 1; ; i++ )
        {
            if ( !g_action_group_has_action( pActionGroup, aCommand )
                    || ( aCurrentCommand && g_strcmp0( aCurrentCommand, aCommand ) == 0 ) )
                break;

            g_free( aCommand );
            aCommand = g_strdup_printf("dup:%d:%s", i, aCommandStr);
        }

        g_free( aCommandStr );
    }

    return aCommand;
    OString aCommand("window-");
    aCommand = aCommand + OString::number(reinterpret_cast<unsigned long>(pSalMenuItem->mpParentMenu));
    aCommand = aCommand + "-" + OString::number(pSalMenuItem->mnId);
    return g_strdup(aCommand.getStr());
}

bool GtkSalMenu::PrepUpdate()
@@ -278,7 +241,7 @@ void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
            pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );

        // Get the new command for the item.
        gchar* aNativeCommand = GetCommandForItem( pSalMenuItem, aCurrentCommand, mpActionGroup );
        gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);

        // Force updating of native menu labels.
        NativeSetItemText( nSection, nItemPos, aText );
@@ -409,8 +372,6 @@ bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const Rectangle& rRec
    aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);

    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
@@ -678,7 +639,6 @@ 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 );
    }

@@ -883,73 +843,42 @@ bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
    return bSubMenuAddedOrRemoved;
}

GtkSalMenu* GtkSalMenu::GetMenuForItemCommand(gchar* aCommand, int& rDupsToSkip, gboolean bGetSubmenu)
GtkSalMenu* GtkSalMenu::GetTopLevel()
{
    SolarMutexGuard aGuard;
    GtkSalMenu* pMenu = nullptr;
    for ( size_t nPos = 0; nPos < maItems.size(); nPos++ )
    {
        GtkSalMenuItem *pSalItem = maItems[ nPos ];

        OUString aItemCommand = mpVCLMenu->GetItemCommand( pSalItem->mnId );
        // Do not join the following two lines, or the OString will be destroyed
        // immediately, and the gchar* pointed to by aItemCommandStr will be
        // freed before it can be used - fdo#69090
        OString aItemCommandOStr = OUStringToOString( aItemCommand, RTL_TEXTENCODING_UTF8 );
        gchar* aItemCommandStr = const_cast<gchar*>(aItemCommandOStr.getStr());

        bool bFound = g_strcmp0( aItemCommandStr, aCommand ) == 0;
        if (bFound && rDupsToSkip)
        {
            --rDupsToSkip;
            bFound = false;
        }
        if (bFound)
        {
            pMenu = bGetSubmenu ? pSalItem->mpSubMenu : this;
            break;
        }
        else
        {
            if ( pSalItem->mpSubMenu != nullptr )
                pMenu = pSalItem->mpSubMenu->GetMenuForItemCommand(aCommand, rDupsToSkip, bGetSubmenu);

            if ( pMenu != nullptr )
               break;
        }
    }

    GtkSalMenu *pMenu = this;
    while (pMenu->mpParentSalMenu)
        pMenu = pMenu->mpParentSalMenu;
    return pMenu;
}

typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;

namespace
{
    const gchar* DetermineDupIndex(const gchar *aCommand, int& rDupsToSkip)
    MenuAndId decode_command(const gchar *action_name)
    {
        if (g_str_has_prefix(aCommand, "dup:"))
        {
            aCommand = aCommand + strlen("dup:");
            gchar *endptr;
            rDupsToSkip = g_ascii_strtoll(aCommand, &endptr, 10);
            aCommand = endptr+1;
        }
        else
            rDupsToSkip = 0;
        OString sCommand(action_name);

        return aCommand;
        sal_Int32 nIndex = 0;
        OString sWindow = sCommand.getToken(0, '-', nIndex);
        OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
        OString sItemId = sCommand.getToken(0, '-', nIndex);

        GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());

        assert(sWindow == "window" && pSalSubMenu);

        return MenuAndId(pSalSubMenu, sItemId.toInt32());
    }
}

void GtkSalMenu::DispatchCommand( gint itemId, const gchar *aCommand )
void GtkSalMenu::DispatchCommand(const gchar *pCommand)
{
    SolarMutexGuard aGuard;

    int nDupsToSkip;
    aCommand = DetermineDupIndex(aCommand, nDupsToSkip);

    GtkSalMenu* pSalSubMenu = GetMenuForItemCommand(const_cast<gchar*>(aCommand), nDupsToSkip, FALSE);
    Menu* pSubMenu = ( pSalSubMenu != nullptr ) ? pSalSubMenu->GetMenu() : nullptr;
    mpVCLMenu->HandleMenuCommandEvent(pSubMenu, itemId);
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalSubMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
    pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
}

void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
@@ -967,29 +896,20 @@ void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
    }
}

void GtkSalMenu::Activate( const gchar* aMenuCommand )
void GtkSalMenu::Activate(const gchar* pCommand)
{
    int nDupsToSkip;
    aMenuCommand = DetermineDupIndex(aMenuCommand, nDupsToSkip);

    GtkSalMenu* pSalSubMenu = GetMenuForItemCommand(const_cast<gchar*>(aMenuCommand), nDupsToSkip, TRUE);

    if ( pSalSubMenu != nullptr ) {
        mpVCLMenu->HandleMenuActivateEvent( pSalSubMenu->mpVCLMenu );
        pSalSubMenu->Update();
    }
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalSubMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
    pTopLevel->GetMenu()->HandleMenuActivateEvent(pSalSubMenu->GetMenu());
}

void GtkSalMenu::Deactivate( const gchar* aMenuCommand )
void GtkSalMenu::Deactivate(const gchar* pCommand)
{
    int nDupsToSkip;
    aMenuCommand = DetermineDupIndex(aMenuCommand, nDupsToSkip);

    GtkSalMenu* pSalSubMenu = GetMenuForItemCommand(const_cast<gchar*>(aMenuCommand), nDupsToSkip, TRUE);

    if ( pSalSubMenu != nullptr ) {
        mpVCLMenu->HandleMenuDeActivateEvent( pSalSubMenu->mpVCLMenu );
    }
    MenuAndId aMenuAndId = decode_command(pCommand);
    GtkSalMenu* pSalSubMenu = aMenuAndId.first;
    GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
    pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pSalSubMenu->GetMenu());
}

void GtkSalMenu::EnableUnity(bool bEnable)