wayland: Make popup menus not show off-screen

Depends on gtk 3.22 for gtk_menu_popup_at_rect.

Had to rework the toolbar context menu to not execute async,
as otherwise it just closes immediately. (Basically it's same
as rhbz#1342823, except that I couldn't find a way to feed
gtk_menu_popup_at_rect with the correct timestamp).

Change-Id: I779d8803c80314d93a342f1294c7280f1adf4c98
Reviewed-on: https://gerrit.libreoffice.org/55772
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Maxim Monastirsky <momonasmon@gmail.com>
diff --git a/include/vcl/toolbox.hxx b/include/vcl/toolbox.hxx
index 7944fd9..58bf32b 100644
--- a/include/vcl/toolbox.hxx
+++ b/include/vcl/toolbox.hxx
@@ -210,7 +210,6 @@ private:
    SAL_DLLPRIVATE bool            ImplHasExternalMenubutton();
    SAL_DLLPRIVATE void            ImplDrawFloatwinBorder(vcl::RenderContext& rRenderContext, ImplToolItem const * pItem );

    DECL_DLLPRIVATE_LINK(    ImplCallExecuteCustomMenu, void*, void );
    DECL_DLLPRIVATE_LINK(    ImplUpdateHdl, Timer*, void );
    DECL_DLLPRIVATE_LINK(    ImplCustomMenuListener, VclMenuEvent&, void );
    DECL_DLLPRIVATE_LINK(    ImplDropdownLongClickHdl, Timer*, void );
diff --git a/vcl/inc/toolbox.h b/vcl/inc/toolbox.h
index 9d928ce..17c5585 100644
--- a/vcl/inc/toolbox.h
+++ b/vcl/inc/toolbox.h
@@ -129,9 +129,7 @@ struct ImplToolBoxPrivateData

    // the optional custom menu
    VclPtr<PopupMenu>   mpMenu;
    tools::Rectangle       maMenuRect;
    ToolBoxMenuType maMenuType;
    ImplSVEvent *   mnEventId;

    // called when menu button is clicked and before the popup menu is executed
    Link<ToolBox *, void> maMenuButtonHdl;
diff --git a/vcl/source/window/toolbox.cxx b/vcl/source/window/toolbox.cxx
index 2df527a..d1007d4 100644
--- a/vcl/source/window/toolbox.cxx
+++ b/vcl/source/window/toolbox.cxx
@@ -1341,10 +1341,6 @@ ToolBox::~ToolBox()

void ToolBox::dispose()
{
    // custom menu event still running?
    if( mpData && mpData->mnEventId )
        Application::RemoveUserEvent( mpData->mnEventId );

    // #103005# make sure our activate/deactivate balance is right
    while( mnActivateCount > 0 )
        Deactivate();
diff --git a/vcl/source/window/toolbox2.cxx b/vcl/source/window/toolbox2.cxx
index cc86b0d..ab4c90e 100644
--- a/vcl/source/window/toolbox2.cxx
+++ b/vcl/source/window/toolbox2.cxx
@@ -48,7 +48,6 @@ ImplToolBoxPrivateData::ImplToolBoxPrivateData() :
{
    meButtonSize = ToolBoxButtonSize::DontCare;
    mpMenu = VclPtr<PopupMenu>::Create();
    mnEventId = nullptr;

    maMenuType = ToolBoxMenuType::NONE;
    maMenubuttonItem.maItemSize = Size( TB_MENUBUTTON_SIZE+TB_MENUBUTTON_OFFSET, TB_MENUBUTTON_SIZE+TB_MENUBUTTON_OFFSET );
@@ -1621,9 +1620,6 @@ namespace
void ToolBox::UpdateCustomMenu()
{
    // fill clipped items into menu
    if( !IsMenuEnabled() )
        return;

    PopupMenu *pMenu = GetMenu();
    pMenu->Clear();

@@ -1680,12 +1676,13 @@ IMPL_LINK( ToolBox, ImplCustomMenuListener, VclMenuEvent&, rEvent, void )
    }
}

IMPL_LINK_NOARG(ToolBox, ImplCallExecuteCustomMenu, void*, void)
void ToolBox::ExecuteCustomMenu( const tools::Rectangle& rRect )
{
    mpData->mnEventId = nullptr;
    if( !IsMenuEnabled() )
    if ( !IsMenuEnabled() || ImplIsInPopupMode() )
        return;

    UpdateCustomMenu();

    if( GetMenuType() & ToolBoxMenuType::Customize )
        // call button handler to allow for menu customization
        mpData->maMenuButtonHdl.Call( this );
@@ -1700,8 +1697,7 @@ IMPL_LINK_NOARG(ToolBox, ImplCallExecuteCustomMenu, void*, void)
    bool bBorderDel = false;

    VclPtr<vcl::Window> pWin = this;
    tools::Rectangle aMenuRect = mpData->maMenuRect;
    mpData->maMenuRect.SetEmpty();
    tools::Rectangle aMenuRect = rRect;
    VclPtr<ImplBorderWindow> pBorderWin;
    if( aMenuRect.IsEmpty() && IsFloatingMode() )
    {
@@ -1733,19 +1729,6 @@ IMPL_LINK_NOARG(ToolBox, ImplCallExecuteCustomMenu, void*, void)

    if( uId )
        GrabFocusToDocument();

}

void ToolBox::ExecuteCustomMenu( const tools::Rectangle& rRect )
{
    if ( IsMenuEnabled() && !ImplIsInPopupMode() )
    {
        UpdateCustomMenu();
        // handle custom menu asynchronously
        // to avoid problems if the toolbox is closed during menu execute
        mpData->maMenuRect = rRect;
        mpData->mnEventId = Application::PostUserEvent( LINK( this, ToolBox, ImplCallExecuteCustomMenu ), nullptr, true );
    }
}

// checks override first, useful during calculation of sizes
diff --git a/vcl/unx/gtk/gtksalmenu.cxx b/vcl/unx/gtk/gtksalmenu.cxx
index 8fd2ea2..84add23 100644
--- a/vcl/unx/gtk/gtksalmenu.cxx
+++ b/vcl/unx/gtk/gtksalmenu.cxx
@@ -410,33 +410,9 @@ bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangl
                                     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 = GtkSalFrame::GetLastInputEventTime();
    }

    VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
    mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());

    // 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
    sal_uInt16 nArrangeIndex;
    Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
    aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);

    GLOActionGroup* pActionGroup = g_lo_action_group_new();
    mpActionGroup = G_ACTION_GROUP(pActionGroup);
    mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
@@ -445,7 +421,6 @@ bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangl

    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
@@ -454,8 +429,66 @@ bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangl
    //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, MenuPositionFunc,
                   &aPos, nButton, nTime);

#if GTK_CHECK_VERSION(3,22,0)
    if (gtk_check_version(3, 22, 0) == nullptr)
    {
        GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;

        if (nFlags & FloatWinPopupFlags::Left)
        {
            rect_anchor = GDK_GRAVITY_NORTH_WEST;
            menu_anchor = GDK_GRAVITY_NORTH_EAST;
        }
        else if (nFlags & FloatWinPopupFlags::Up)
        {
            rect_anchor = GDK_GRAVITY_NORTH_WEST;
            menu_anchor = GDK_GRAVITY_SOUTH_WEST;
        }
        else if (nFlags & FloatWinPopupFlags::Right)
        {
            rect_anchor = GDK_GRAVITY_NORTH_EAST;
        }

        tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
        aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
        GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
                           static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};

        GdkWindow* gdkWindow = widget_get_window(mpFrame->getMouseEventWidget());
        gtk_menu_popup_at_rect(GTK_MENU(pWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
    }
    else
#endif
    {
        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 = 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
        sal_uInt16 nArrangeIndex;
        Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
        aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);

        gtk_menu_popup(GTK_MENU(pWidget), nullptr, nullptr, MenuPositionFunc,
                       &aPos, nButton, nTime);
    }

    if (g_main_loop_is_running(pLoop))
    {
        gdk_threads_leave();