gtk3: generate missing mnemonics

Change-Id: Ib0e94b8484dabb7e859c53aeb0e4adf75727fcd6
Reviewed-on: https://gerrit.libreoffice.org/52839
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/include/vcl/mnemonic.hxx b/include/vcl/mnemonic.hxx
index bf2591a..f7db3a3 100644
--- a/include/vcl/mnemonic.hxx
+++ b/include/vcl/mnemonic.hxx
@@ -51,15 +51,16 @@

class VCL_DLLPUBLIC MnemonicGenerator
{
    sal_Unicode m_cMnemonic;
    // 0 == Mnemonic; >0 == count of characters
    sal_uInt8               maMnemonics[MAX_MNEMONICS];
    css::uno::Reference< css::i18n::XCharacterClassification > mxCharClass;

    SAL_DLLPRIVATE static sal_uInt16 ImplGetMnemonicIndex( sal_Unicode c );
    SAL_DLLPRIVATE static sal_Unicode ImplFindMnemonic( const OUString& rKey );
    SAL_DLLPRIVATE sal_Unicode ImplFindMnemonic( const OUString& rKey );

public:
                        MnemonicGenerator();
                        MnemonicGenerator(sal_Unicode cMnemonic = MNEMONIC_CHAR);

    void                RegisterMnemonic( const OUString& rKey );
    OUString            CreateMnemonic( const OUString& rKey );
diff --git a/vcl/source/window/mnemonic.cxx b/vcl/source/window/mnemonic.cxx
index fe26cd1..43db6de 100644
--- a/vcl/source/window/mnemonic.cxx
+++ b/vcl/source/window/mnemonic.cxx
@@ -28,7 +28,8 @@

using namespace ::com::sun::star;

MnemonicGenerator::MnemonicGenerator()
MnemonicGenerator::MnemonicGenerator(sal_Unicode cMnemonic)
    : m_cMnemonic(cMnemonic)
{
    memset( maMnemonics, 1, sizeof( maMnemonics ) );
}
@@ -59,10 +60,10 @@ sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
sal_Unicode MnemonicGenerator::ImplFindMnemonic( const OUString& rKey )
{
    sal_Int32 nIndex = 0;
    while ( (nIndex = rKey.indexOf( MNEMONIC_CHAR, nIndex )) != -1 )
    while ( (nIndex = rKey.indexOf( m_cMnemonic, nIndex )) != -1 )
    {
        sal_Unicode cMnemonic = rKey[ nIndex+1 ];
        if ( cMnemonic != MNEMONIC_CHAR )
        if ( cMnemonic != m_cMnemonic )
            return cMnemonic;
        nIndex += 2;
    }
@@ -187,7 +188,7 @@ OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
                if ( maMnemonics[nMnemonicIndex] )
                {
                    maMnemonics[nMnemonicIndex] = 0;
                    rKey = rKey.replaceAt( nIndex, 0, OUString(MNEMONIC_CHAR) );
                    rKey = rKey.replaceAt( nIndex, 0, OUString(m_cMnemonic) );
                    bChanged = true;
                    break;
                }
@@ -239,7 +240,7 @@ OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
            if ( nBestCount != 0xFFFF )
            {
                maMnemonics[nBestMnemonicIndex] = 0;
                rKey = rKey.replaceAt( nBestIndex, 0, OUString(MNEMONIC_CHAR) );
                rKey = rKey.replaceAt( nBestIndex, 0, OUString(m_cMnemonic) );
                bChanged = true;
            }
        }
@@ -260,7 +261,7 @@ OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
                {
                    maMnemonics[nMnemonicIndex] = 0;
                    OUString aStr = OUStringBuffer().
                        append('(').append(MNEMONIC_CHAR).append(sal_Unicode(rtl::toAsciiUpperCase(c))).
                        append('(').append(m_cMnemonic).append(sal_Unicode(rtl::toAsciiUpperCase(c))).
                        append(')').makeStringAndClear();
                    nIndex = rKey.getLength();
                    if( nIndex >= 2 )
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
index 957b58f..9425d93 100644
--- a/vcl/unx/gtk3/gtk3gtkinst.cxx
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -31,6 +31,7 @@
#include <rtl/bootstrap.hxx>
#include <tools/fract.hxx>
#include <tools/stream.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/pngwrite.hxx>
#include <vcl/weld.hxx>

@@ -2343,6 +2344,31 @@ public:
    }
};

namespace
{
    OUString get_label(GtkLabel* pLabel)
    {
        const gchar* pStr = gtk_label_get_label(pLabel);
        return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
    }

    void set_label(GtkLabel* pLabel, const OUString& rText)
    {
        gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
    }

    OUString get_label(GtkButton* pButton)
    {
        const gchar* pStr = gtk_button_get_label(pButton);
        return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
    }

    void set_label(GtkButton* pButton, const OUString& rText)
    {
        gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
    }
}

class GtkInstanceButton : public GtkInstanceContainer, public virtual weld::Button
{
private:
@@ -2367,13 +2393,12 @@ public:

    virtual void set_label(const OUString& rText) override
    {
        gtk_button_set_label(m_pButton, MapToGtkAccelerator(rText).getStr());
        ::set_label(m_pButton, rText);
    }

    virtual OUString get_label() const override
    {
        const gchar* pStr = gtk_button_get_label(m_pButton);
        return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
        return ::get_label(m_pButton);
    }

    virtual void clicked() override
@@ -3101,13 +3126,12 @@ public:

    virtual void set_label(const OUString& rText) override
    {
        gtk_label_set_label(m_pLabel, MapToGtkAccelerator(rText).getStr());
        ::set_label(m_pLabel, rText);
    }

    virtual OUString get_label() const override
    {
        const char* pLabel = gtk_label_get_label(m_pLabel);
        return OUString(pLabel, strlen(pLabel), RTL_TEXTENCODING_UTF8);
        return ::get_label(m_pLabel);
    }
};

@@ -3868,62 +3892,6 @@ namespace

        return false;
    }

    void postprocess(gpointer data, gpointer user_data)
    {
        GObject* pObject = static_cast<GObject*>(data);
        if (!GTK_IS_WIDGET(pObject))
            return;
        OString* pHelpRoot = static_cast<OString*>(user_data);
        //fixup icons
        //wanted: better way to do this, e.g. make gtk use gio for
        //loading from a filename and provide gio protocol handler
        //for our image in a zip urls
        //
        //unpack the images and keep them as dirs and just
        //add the paths to the gtk icon theme dir
        if (GTK_IS_IMAGE(pObject))
        {
            GtkImage* pImage = GTK_IMAGE(pObject);
            const gchar* icon_name;
            gtk_image_get_icon_name(pImage, &icon_name, nullptr);
            GtkIconSize size;
            g_object_get(pImage, "icon-size", &size, nullptr);
            if (icon_name)
            {
                OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);

                SvMemoryStream aMemStm;
                BitmapEx aBitmap(aIconName);
                vcl::PNGWriter aWriter(aBitmap);
                aWriter.Write(aMemStm);

                GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new();
                gdk_pixbuf_loader_write(pixbuf_loader, static_cast<const guchar*>(aMemStm.GetData()),
                                        aMemStm.Seek(STREAM_SEEK_TO_END), nullptr);
                gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
                GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);

                gtk_image_set_from_pixbuf(pImage, pixbuf);
                g_object_unref(pixbuf_loader);
            }
        }
        //set helpids
        GtkWidget* pWidget = GTK_WIDGET(pObject);
        const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
        size_t nLen = pStr ? strlen(pStr) : 0;
        if (!nLen)
            return;
        OString sHelpId = *pHelpRoot + OString(pStr, nLen);
        set_help_id(pWidget, sHelpId);
        //hook up for extended help
        const ImplSVData* pSVData = ImplGetSVData();
        if (pSVData->maHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget))
        {
            gtk_widget_set_has_tooltip(pWidget, true);
            g_signal_connect(pObject, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
        }
    }
}

namespace
@@ -3961,9 +3929,84 @@ class GtkInstanceBuilder : public weld::Builder
{
private:
    OUString m_sHelpRoot;
    OString m_aUtf8HelpRoot;
    GtkBuilder* m_pBuilder;
    GSList* m_pObjectList;
    GtkWidget* m_pParentWidget;
    std::vector<GtkButton*> m_aMnemonicButtons;
    std::vector<GtkLabel*> m_aMnemonicLabels;

    void postprocess_widget(GtkWidget* pWidget)
    {
        //fixup icons
        //wanted: better way to do this, e.g. make gtk use gio for
        //loading from a filename and provide gio protocol handler
        //for our image in a zip urls
        //
        //unpack the images and keep them as dirs and just
        //add the paths to the gtk icon theme dir
        if (GTK_IS_IMAGE(pWidget))
        {
            GtkImage* pImage = GTK_IMAGE(pWidget);
            const gchar* icon_name;
            gtk_image_get_icon_name(pImage, &icon_name, nullptr);
            GtkIconSize size;
            g_object_get(pImage, "icon-size", &size, nullptr);
            if (icon_name)
            {
                OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);

                SvMemoryStream aMemStm;
                BitmapEx aBitmap(aIconName);
                vcl::PNGWriter aWriter(aBitmap);
                aWriter.Write(aMemStm);

                GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new();
                gdk_pixbuf_loader_write(pixbuf_loader, static_cast<const guchar*>(aMemStm.GetData()),
                                        aMemStm.Seek(STREAM_SEEK_TO_END), nullptr);
                gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
                GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);

                gtk_image_set_from_pixbuf(pImage, pixbuf);
                g_object_unref(pixbuf_loader);
            }
        }
        //set helpids
        const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
        size_t nLen = pStr ? strlen(pStr) : 0;
        if (!nLen)
            return;
        OString sHelpId = m_aUtf8HelpRoot + OString(pStr, nLen);
        set_help_id(pWidget, sHelpId);
        //hook up for extended help
        const ImplSVData* pSVData = ImplGetSVData();
        if (pSVData->maHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget))
        {
            gtk_widget_set_has_tooltip(pWidget, true);
            g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
        }

        //missing mnemonics
        if (GTK_IS_BUTTON(pWidget))
        {
            if (gtk_button_get_use_underline(GTK_BUTTON(pWidget)))
                m_aMnemonicButtons.push_back(GTK_BUTTON(pWidget));
        }
        else if (GTK_IS_LABEL(pWidget))
        {
            if (gtk_label_get_use_underline(GTK_LABEL(pWidget)))
                m_aMnemonicLabels.push_back(GTK_LABEL(pWidget));
        }
    }

    static void postprocess(gpointer data, gpointer user_data)
    {
        GObject* pObject = static_cast<GObject*>(data);
        if (!GTK_IS_WIDGET(pObject))
            return;
        GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
        pThis->postprocess_widget(GTK_WIDGET(pObject));
    }
public:
    GtkInstanceBuilder(GtkWidget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
        : weld::Builder(rUIFile)
@@ -3981,10 +4024,41 @@ public:
        if (nIdx != -1)
            m_sHelpRoot = m_sHelpRoot.copy(0, nIdx);
        m_sHelpRoot = m_sHelpRoot + OUString('/');
        m_aUtf8HelpRoot = OUStringToOString(m_sHelpRoot, RTL_TEXTENCODING_UTF8);

        m_pObjectList = gtk_builder_get_objects(m_pBuilder);
        OString aUtf8HelpRoot(OUStringToOString(m_sHelpRoot, RTL_TEXTENCODING_UTF8));
        g_slist_foreach(m_pObjectList, postprocess, &aUtf8HelpRoot);
        g_slist_foreach(m_pObjectList, postprocess, this);

        GenerateMissingMnemonics();
    }

    void GenerateMissingMnemonics()
    {
        MnemonicGenerator aMnemonicGenerator('_');
        for (const auto a : m_aMnemonicButtons)
            aMnemonicGenerator.RegisterMnemonic(get_label(a));
        for (const auto a : m_aMnemonicLabels)
            aMnemonicGenerator.RegisterMnemonic(get_label(a));

        for (const auto a : m_aMnemonicButtons)
        {
            OUString aLabel(get_label(a));
            OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
            if (aLabel == aNewLabel)
                continue;
            set_label(a, aNewLabel);
        }
        for (const auto a : m_aMnemonicLabels)
        {
            OUString aLabel(get_label(a));
            OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
            if (aLabel == aNewLabel)
                continue;
            set_label(a, aNewLabel);
        }

        m_aMnemonicLabels.clear();
        m_aMnemonicButtons.clear();
    }

    virtual ~GtkInstanceBuilder() override