tdf#138778 add has_child_focus which considers a related popup a 'child'

Change-Id: Iab23e399f2650ece702fb1f62d1387acca472b42
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/107499
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx
index 98c0123..d3bdf2f 100644
--- a/include/vcl/weld.hxx
+++ b/include/vcl/weld.hxx
@@ -89,8 +89,12 @@ protected:
public:
    virtual void set_sensitive(bool sensitive) = 0;
    virtual bool get_sensitive() const = 0;

    /* visibility */

    virtual void show() = 0;
    virtual void hide() = 0;

    // This function simply calls show() or hide() but is convenient when the
    // visibility depends on some condition
    virtual void set_visible(bool visible)
@@ -100,17 +104,43 @@ public:
        else
            hide();
    }
    virtual bool get_visible() const = 0; //if this widget visibility is true
    virtual bool is_visible() const = 0; //if this widget visibility and all parents is true

    // return if this widget's visibility is true
    virtual bool get_visible() const = 0;

    // return if this widget's visibility and that of all its parents is true
    virtual bool is_visible() const = 0;

    /* focus */

    // sets if this widget can own the keyboard focus
    virtual void set_can_focus(bool bCanFocus) = 0;

    // causes this widget to have the keyboard focus
    virtual void grab_focus() = 0;

    // returns if this widget has the keyboard focus
    virtual bool has_focus() const = 0;
    virtual bool is_active() const = 0; //if this widget has the focus within the active window

    // if the widget that has focus is a child, which includes toplevel popup
    // children, of this widget. So an Entry with an active popup (or dialog)
    // has has_child_focus of true, but has_focus of false, while its popup is
    // shown
    virtual bool has_child_focus() const = 0;

    // return if this widget has the keyboard focus within the active window
    // TODO: review if this has any practical difference from has_focus()
    virtual bool is_active() const = 0;

    virtual void set_has_default(bool has_default) = 0;
    virtual bool get_has_default() const = 0;

    /* size */
    virtual void set_size_request(int nWidth, int nHeight) = 0;
    virtual Size get_size_request() const = 0;
    virtual Size get_preferred_size() const = 0;

    /* measure */
    virtual float get_approximate_digit_width() const = 0;
    virtual int get_text_height() const = 0;
    virtual Size get_pixel_size(const OUString& rText) const = 0;
@@ -460,6 +490,7 @@ public:
    // resized, calling set_centered_on_parent with false will turn this
    // off again.
    virtual void set_centered_on_parent(bool bTrackGeometryRequests) = 0;
    // returns whether the widget that has focus is within this Window
    virtual bool has_toplevel_focus() const = 0;
    virtual void present() = 0;
    virtual void set_window_state(const OString& rStr) = 0;
diff --git a/svtools/source/control/tabbar.cxx b/svtools/source/control/tabbar.cxx
index d44dd06..1beda5f 100644
--- a/svtools/source/control/tabbar.cxx
+++ b/svtools/source/control/tabbar.cxx
@@ -395,8 +395,9 @@ IMPL_LINK( TabBarEdit, ImplEndEditHdl, void*, pCancel, void )
    ResetPostEvent();
    maLoseFocusIdle.Stop();

    // do it idle in case we quickly regain focus
    if (!m_xEntry->has_focus())
    // We need this query, because the edit gets a losefocus event,
    // when it shows the context menu or the insert symbol dialog
    if (!m_xEntry->has_focus() && m_xEntry->has_child_focus())
        maLoseFocusIdle.Start();
    else
        GetParent()->EndEditMode( pCancel != nullptr );
@@ -406,7 +407,13 @@ IMPL_LINK_NOARG(TabBarEdit, ImplEndTimerHdl, Timer *, void)
{
    if (m_xEntry->has_focus())
        return;
    GetParent()->EndEditMode( true );

    // We need this query, because the edit gets a losefocus event,
    // when it shows the context menu or the insert symbol dialog
    if (m_xEntry->has_child_focus())
        maLoseFocusIdle.Start();
    else
        GetParent()->EndEditMode( true );
}

namespace {
diff --git a/vcl/inc/salvtables.hxx b/vcl/inc/salvtables.hxx
index 2fd7198a7..0a6cbad 100644
--- a/vcl/inc/salvtables.hxx
+++ b/vcl/inc/salvtables.hxx
@@ -213,6 +213,8 @@ public:

    virtual bool is_active() const override;

    virtual bool has_child_focus() const override;

    virtual void set_has_default(bool has_default) override;

    virtual bool get_has_default() const override;
diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx
index ccc3757..1289848 100644
--- a/vcl/source/app/salvtables.cxx
+++ b/vcl/source/app/salvtables.cxx
@@ -271,6 +271,11 @@ bool SalInstanceWidget::has_focus() const { return m_xWidget->HasFocus(); }

bool SalInstanceWidget::is_active() const { return m_xWidget->IsActive(); }

bool SalInstanceWidget::has_child_focus() const
{
    return m_xWidget->HasChildPathFocus(true);
}

void SalInstanceWidget::set_has_default(bool has_default)
{
    m_xWidget->set_property("has-default", OUString::boolean(has_default));
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
index c4df0cc..4cbe5a8 100644
--- a/vcl/unx/gtk3/gtk3gtkinst.cxx
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -2546,6 +2546,39 @@ public:
        return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
    }

    // is the focus in a child of this widget, where a transient popup attached
    // to a widget is considered a child of that widget
    virtual bool has_child_focus() const override
    {
        bool bRet = false;

        GList* pList = gtk_window_list_toplevels();

        for (GList* pEntry = pList; pEntry; pEntry = pEntry->next)
        {
            if (!gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry->data)))
                continue;
            GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pEntry->data));
            if (pFocus && gtk_widget_is_ancestor(pFocus, m_pWidget))
            {
                bRet = true;
                break;
            }
            GtkWidget* pAttachedTo = gtk_window_get_attached_to(GTK_WINDOW(pEntry->data));
            if (!pAttachedTo)
                continue;
            if (pAttachedTo == m_pWidget || gtk_widget_is_ancestor(pAttachedTo, m_pWidget))
            {
                bRet = true;
                break;
            }
        }

        g_list_free(pList);

        return bRet;
    }

    virtual void set_has_default(bool has_default) override
    {
        g_object_set(G_OBJECT(m_pWidget), "has-default", has_default, nullptr);