allow closing welded dialogs to be cancelled

from both directions of window manager close button and esc

Change-Id: I3c7bd6f67e3100f5dd3ab04456ad9aa5100c472c
Reviewed-on: https://gerrit.libreoffice.org/72319
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx
index 2195799..5348e20 100644
--- a/vcl/source/window/dialog.cxx
+++ b/vcl/source/window/dialog.cxx
@@ -835,8 +835,26 @@ bool Dialog::Close()
    if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() && !IsInExecute() )
        return false;

    // If there's a cancel button with a custom handler, then always give it a chance to
    // handle Dialog::Close
    PushButton* pCustomCancelButton;
    PushButton* pCancelButton = dynamic_cast<PushButton*>(get_widget_for_response(RET_CANCEL));
    if (!mbInClose && pCancelButton && pCancelButton->GetClickHdl().IsSet())
        pCustomCancelButton = pCancelButton;
    else
        pCustomCancelButton = nullptr;

    mbInClose = true;

    if (pCustomCancelButton)
    {
        pCustomCancelButton->Click();
        if (xWindow->IsDisposed())
            return true;
        mbInClose = false;
        return false;
    }

    if ( !(GetStyle() & WB_CLOSEABLE) )
    {
        bool bRet = true;
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
index dac1bcc..0d60969 100644
--- a/vcl/unx/gtk3/gtk3gtkinst.cxx
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -2995,6 +2995,8 @@ namespace
    }
}

class GtkInstanceButton;

class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
{
private:
@@ -3006,6 +3008,7 @@ private:
    std::function<void(sal_Int32)> m_aFunc;
    gulong m_nCloseSignalId;
    gulong m_nResponseSignalId;
    gulong m_nSignalDeleteId;

    // for calc ref dialog that shrink to range selection widgets and resize back
    GtkWidget* m_pRefEdit;
@@ -3014,10 +3017,12 @@ private:
    int m_nOldEditWidthReq; // Original width request of the input field
    int m_nOldBorderWidth; // border width for expanded dialog

    void signal_close();

    static void signalClose(GtkWidget*, gpointer widget)
    {
        GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
        pThis->response(RET_CANCEL);
        pThis->signal_close();
    }

    static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
@@ -3026,6 +3031,11 @@ private:
        pThis->asyncresponse(ret);
    }

    static gboolean signalAsyncDelete(GtkDialog*, GdkEventAny*, gpointer)
    {
        return true; /* Do not destroy */
    }

    static int GtkToVcl(int ret)
    {
        if (ret == GTK_RESPONSE_OK)
@@ -3043,24 +3053,7 @@ private:
        return ret;
    }

    void asyncresponse(gint ret)
    {
        if (ret == GTK_RESPONSE_HELP)
        {
            help();
            return;
        }
        else if (has_click_handler(ret))
            return;

        hide();
        m_aFunc(GtkToVcl(ret));
        m_aFunc = nullptr;
        // move the self pointer, otherwise it might be de-allocated by time we try to reset it
        std::shared_ptr<GtkInstanceDialog> me = std::move(m_xRunAsyncSelf);
        m_xDialogController.reset();
        me.reset();
    }
    void asyncresponse(gint ret);

public:
    GtkInstanceDialog(GtkDialog* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
@@ -3069,6 +3062,7 @@ public:
        , m_aDialogRun(pDialog)
        , m_nCloseSignalId(g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this))
        , m_nResponseSignalId(0)
        , m_nSignalDeleteId(0)
        , m_pRefEdit(nullptr)
        , m_nOldEditWidth(0)
        , m_nOldEditWidthReq(0)
@@ -3086,6 +3080,7 @@ public:
        show();

        m_nResponseSignalId = g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this);
        m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);

        return true;
    }
@@ -3100,32 +3095,14 @@ public:
        show();

        m_nResponseSignalId = g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this);
        m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);

        return true;
    }

    bool has_click_handler(int nResponse);
    GtkInstanceButton* has_click_handler(int nResponse);

    virtual int run() override
    {
        sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(m_pDialog)));
        int ret;
        while (true)
        {
            ret = m_aDialogRun.run();
            if (ret == GTK_RESPONSE_HELP)
            {
                help();
                continue;
            }
            else if (has_click_handler(ret))
                continue;

            break;
        }
        hide();
        return GtkToVcl(ret);
    }
    virtual int run() override;

    virtual void show() override
    {
@@ -3295,6 +3272,8 @@ public:
        g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
        if (m_nResponseSignalId)
            g_signal_handler_disconnect(m_pDialog, m_nResponseSignalId);
        if (m_nSignalDeleteId)
            g_signal_handler_disconnect(m_pDialog, m_nSignalDeleteId);
    }
};

@@ -4610,6 +4589,60 @@ public:
    }
};

void GtkInstanceDialog::asyncresponse(gint ret)
{
    if (ret == GTK_RESPONSE_HELP)
    {
        help();
        return;
    }

    GtkInstanceButton* pClickHandler = has_click_handler(ret);
    if (pClickHandler)
    {
        // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
        if (ret == GTK_RESPONSE_DELETE_EVENT)
            pClickHandler->clicked();
        return;
    }

    hide();
    m_aFunc(GtkToVcl(ret));
    m_aFunc = nullptr;
    // move the self pointer, otherwise it might be de-allocated by time we try to reset it
    std::shared_ptr<GtkInstanceDialog> me = std::move(m_xRunAsyncSelf);
    m_xDialogController.reset();
    me.reset();
}

int GtkInstanceDialog::run()
{
    sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(m_pDialog)));
    int ret;
    while (true)
    {
        ret = m_aDialogRun.run();
        if (ret == GTK_RESPONSE_HELP)
        {
            help();
            continue;
        }

        GtkInstanceButton* pClickHandler = has_click_handler(ret);
        if (pClickHandler)
        {
            // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
            if (ret == GTK_RESPONSE_DELETE_EVENT)
                pClickHandler->clicked();
            continue;
        }

        break;
    }
    hide();
    return GtkToVcl(ret);
}

weld::Button* GtkInstanceDialog::get_widget_for_response(int nResponse)
{
    GtkButton* pButton = GTK_BUTTON(gtk_dialog_get_widget_for_response(m_pDialog, VclToGtk(nResponse)));
@@ -4631,15 +4664,33 @@ void GtkInstanceDialog::response(int nResponse)
    gtk_dialog_response(m_pDialog, VclToGtk(nResponse));
}

bool GtkInstanceDialog::has_click_handler(int nResponse)

void GtkInstanceDialog::signal_close()
{
    if (GtkWidget* pWidget = gtk_dialog_get_widget_for_response(m_pDialog, VclToGtk(nResponse)))
    GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
    if (pClickHandler)
    {
        g_signal_stop_emission_by_name(m_pDialog, "close");
        // make esc act as if cancel button was pressed
        pClickHandler->clicked();
        return;
    }
    response(RET_CANCEL);
}

GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
{
    GtkInstanceButton* pButton = nullptr;
    // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
    nResponse = VclToGtk(GtkToVcl(nResponse));
    if (GtkWidget* pWidget = gtk_dialog_get_widget_for_response(m_pDialog, nResponse))
    {
        void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
        GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
        return pButton && pButton->has_click_handler();
        pButton = static_cast<GtkInstanceButton*>(pData);
        if (pButton && !pButton->has_click_handler())
            pButton = nullptr;
    }
    return false;
    return pButton;
}

class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton