tdf#128921 tdf#130341 tdf#122053 qt5: Native PopupMenus

This implements native PopupMenus for the qt5 VCL plugin,
which not only gives them the native look and feel, but also
makes context menus faster (tdf#128921), accessible (e.g. to the
Orca screen reader, tdf#122053), and makes them work for a case
in Base's relationship dialog where entries in the non-native context
menu were not selectable/clickable (tdf#130341).

For now, this always shows the popup menu at cursor position, which
can be changed by taking the Rectangle passed to
'Qt5Menu::ShowNativePopupMenu' into account if there should be any
need.

Change-Id: Ie52cbc682acacb92716ff51e8bf7f1ab07d34cf0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/88512
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
diff --git a/vcl/inc/qt5/Qt5Menu.hxx b/vcl/inc/qt5/Qt5Menu.hxx
index efcfb8eeb..2e5434f 100644
--- a/vcl/inc/qt5/Qt5Menu.hxx
+++ b/vcl/inc/qt5/Qt5Menu.hxx
@@ -24,6 +24,17 @@ class QMenuBar;
class Qt5MenuItem;
class Qt5Frame;

/*
 * Qt5Menu can represent
 * (1) the top-level menu of a menubar, in which case 'mbMenuBar' is true and
 *     'mpQMenuBar' refers to the corresponding QMenuBar
 * (2) another kind of menu (like a PopupMenu), in which case the corresponding QMenu
 *     object is instantiated and owned by this Qt5Menu (held in 'mpOwnedQMenu').
 * (3) a "submenu" in an existing menu (like (1)), in which case the corresponding
 *     QMenu object is owned by the corresponding Qt5MenuItem.
 *
 * For (2) and (3), member 'mpQMenu' points to the corresponding QMenu object.
 */
class Qt5Menu : public QObject, public SalMenu
{
    Q_OBJECT
@@ -34,6 +45,9 @@ private:
    Qt5Frame* mpFrame;
    bool mbMenuBar;
    QMenuBar* mpQMenuBar;
    // self-created QMenu that this Qt5Menu represents, if applicable (s. comment for class)
    std::unique_ptr<QMenu> mpOwnedQMenu;
    // pointer to QMenu owned by the corresponding Qt5MenuItem or self (-> mpOwnedQMenu)
    QMenu* mpQMenu;
    QPushButton* mpCloseButton;
    QMetaObject::Connection maCloseButtonConnection;
@@ -58,6 +72,8 @@ public:
    virtual void SetFrame(const SalFrame* pFrame) override;
    const Qt5Frame* GetFrame() const;
    virtual void ShowMenuBar(bool bVisible) override;
    virtual bool ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
                                     FloatWinPopupFlags nFlags) override;
    Qt5Menu* GetTopLevel();
    virtual void SetItemBits(unsigned nPos, MenuItemBits nBits) override;
    virtual void CheckItem(unsigned nPos, bool bCheck) override;
diff --git a/vcl/qt5/Qt5Menu.cxx b/vcl/qt5/Qt5Menu.cxx
index b2e752f..9861524 100644
--- a/vcl/qt5/Qt5Menu.cxx
+++ b/vcl/qt5/Qt5Menu.cxx
@@ -24,6 +24,9 @@
#include <strings.hrc>
#include <bitmaps.hlst>

#include <vcl/floatwin.hxx>
#include <window.h>

Qt5Menu::Qt5Menu(bool bMenuBar)
    : mpVCLMenu(nullptr)
    , mpParentSalMenu(nullptr)
@@ -77,8 +80,15 @@ void Qt5Menu::InsertMenuItem(Qt5MenuItem* pSalMenuItem, unsigned nPos)
                    [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
        }
    }
    else if (mpQMenu)
    else
    {
        if (!mpQMenu)
        {
            // no QMenu set, instantiate own one
            mpOwnedQMenu.reset(new QMenu);
            mpQMenu = mpOwnedQMenu.get();
        }

        if (pSalMenuItem->mpSubMenu)
        {
            // submenu
@@ -148,7 +158,9 @@ void Qt5Menu::InsertMenuItem(Qt5MenuItem* pSalMenuItem, unsigned nPos)

                UpdateActionGroupItem(pSalMenuItem);

                pAction->setShortcut(toQString(nAccelKey.GetName(GetFrame()->GetWindow())));
                const Qt5Frame* pFrame = GetFrame();
                if (pFrame)
                    pAction->setShortcut(toQString(nAccelKey.GetName(pFrame->GetWindow())));

                connect(pAction, &QAction::triggered, this,
                        [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
@@ -442,6 +454,11 @@ void Qt5Menu::DoFullMenuUpdate(Menu* pMenuBar)
        Qt5MenuItem* pSalMenuItem = GetItemAtPos(nItem);
        InsertMenuItem(pSalMenuItem, nItem);
        SetItemImage(nItem, pSalMenuItem, pSalMenuItem->maImage);
        const bool bShowDisabled
            = bool(pMenuBar->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries)
              || !bool(pMenuBar->GetMenuFlags() & MenuFlags::HideDisabledEntries);
        const bool bVisible = bShowDisabled || mpVCLMenu->IsItemEnabled(pSalMenuItem->mnId);
        pSalMenuItem->getAction()->setVisible(bVisible);

        if (pSalMenuItem->mpSubMenu != nullptr)
        {
@@ -651,6 +668,19 @@ void Qt5Menu::ShowCloseButton(bool bShow)
        pButton->hide();
}

bool Qt5Menu::ShowNativePopupMenu(FloatingWindow*, const tools::Rectangle&,
                                  FloatWinPopupFlags nFlags)
{
    assert(mpQMenu);
    DoFullMenuUpdate(mpVCLMenu);
    mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));

    const QPoint aPos = QCursor::pos();
    mpQMenu->exec(aPos);

    return true;
}

Qt5MenuItem::Qt5MenuItem(const SalItemParams* pItemData)
    : mpParentMenu(nullptr)
    , mpSubMenu(nullptr)