Resolves: tdf#156751 add "Special Character..." to GtkEntry context menus

translation exists so doesn't require additional translation

Change-Id: Ibc5df15b9b8442307195d79c862c69e0506c4057
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155733
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
diff --git a/solenv/sanitizers/ui/vcl.suppr b/solenv/sanitizers/ui/vcl.suppr
index a07c257..52e3b4d 100644
--- a/solenv/sanitizers/ui/vcl.suppr
+++ b/solenv/sanitizers/ui/vcl.suppr
@@ -7,6 +7,7 @@ vcl/uiconfig/ui/combobox.ui://GtkEntry[@id='entry'] no-labelled-by
vcl/uiconfig/ui/combobox.ui://GtkToggleButton[@id='button'] button-no-label
vcl/uiconfig/ui/combobox.ui://GtkMenuButton[@id='overlaybutton'] button-no-label
vcl/uiconfig/ui/cupspassworddialog.ui://GtkLabel[@id='text'] orphan-label
vcl/uiconfig/ui/editmenu.ui://GtkMenuItem[@id='specialchar'] button-no-label
vcl/uiconfig/ui/menutogglebutton3.ui://GtkToggleButton[@id='togglebutton'] button-no-label
vcl/uiconfig/ui/menutogglebutton3.ui://GtkButton[@id='menubutton'] button-no-label
vcl/uiconfig/ui/menutogglebutton4.ui://GtkToggleButton[@id='togglebutton'] button-no-label
diff --git a/vcl/inc/strings.hrc b/vcl/inc/strings.hrc
index c2e95f2..7f5699a 100644
--- a/vcl/inc/strings.hrc
+++ b/vcl/inc/strings.hrc
@@ -125,6 +125,8 @@

#define STR_UNSAVED_DOCUMENTS                       NC_("STR_UNSAVED_DOCUMENTS", "There are unsaved documents")

#define STR_SPECIAL_CHARACTER_MENU_ENTRY            NC_("editmenu|specialchar", "_Special Character...")

#endif // INCLUDED_VCL_INC_STRINGS_HRC

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/edit.cxx b/vcl/source/control/edit.cxx
index 0db27e8..92c0311 100644
--- a/vcl/source/control/edit.cxx
+++ b/vcl/source/control/edit.cxx
@@ -1978,6 +1978,8 @@ void Edit::Command( const CommandEvent& rCEvt )
        pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
        pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
        pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
        pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
            BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
        pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
        pPopup->EnableItem(
            pPopup->GetItemId(u"undo"),
diff --git a/vcl/source/edit/vclmedit.cxx b/vcl/source/edit/vclmedit.cxx
index 541d6bf..d519735 100644
--- a/vcl/source/edit/vclmedit.cxx
+++ b/vcl/source/edit/vclmedit.cxx
@@ -41,6 +41,8 @@
#include <vcl/weld.hxx>
#include <osl/diagnose.h>
#include <tools/json_writer.hxx>
#include <strings.hrc>
#include <svdata.hxx>

class ImpVclMEdit : public SfxListener
{
@@ -789,6 +791,8 @@ void TextWindow::Command( const CommandEvent& rCEvt )
        pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
        pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
        pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
        pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
            BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
        pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
        pPopup->EnableItem(pPopup->GetItemId(u"undo"), bEnableUndo);
        pPopup->ShowItem(pPopup->GetItemId(u"specialchar"), !vcl::GetGetSpecialCharsFunction());
diff --git a/vcl/uiconfig/ui/editmenu.ui b/vcl/uiconfig/ui/editmenu.ui
index 8c025d4..e0d55fb 100644
--- a/vcl/uiconfig/ui/editmenu.ui
+++ b/vcl/uiconfig/ui/editmenu.ui
@@ -69,7 +69,6 @@
      <object class="GtkMenuItem" id="specialchar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes" context="editmenu|specialchar">_Special Character...</property>
        <property name="use_underline">True</property>
      </object>
    </child>
diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
index 2cfd0d6..141039d 100644
--- a/vcl/unx/gtk3/gtkinst.cxx
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -19024,41 +19024,67 @@ void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectang
}

#if !GTK_CHECK_VERSION(4, 0, 0)

static void InsertSpecialChar(GtkEntry *pEntry)
{
    if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
    {
        weld::Window* pDialogParent = nullptr;

        GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pEntry));
        if (GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr)
            pDialogParent = pFrame->GetFrameWeld();

        std::unique_ptr<GtkInstanceWindow> xFrameWeld;
        if (!pDialogParent && pTopLevel)
        {
            xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel), nullptr, false));
            pDialogParent = xFrameWeld.get();
        }

        OUString aChars = pImplFncGetSpecialChars(pDialogParent, ::get_font(GTK_WIDGET(pEntry)));
        if (!aChars.isEmpty())
        {
            gtk_editable_delete_selection(GTK_EDITABLE(pEntry));
            gint position = gtk_editable_get_position(GTK_EDITABLE(pEntry));
            OString sText(OUStringToOString(aChars, RTL_TEXTENCODING_UTF8));
            gtk_editable_insert_text(GTK_EDITABLE(pEntry), sText.getStr(), sText.getLength(),
                                     &position);
            gtk_editable_set_position(GTK_EDITABLE(pEntry), position);
        }
    }
}

static gboolean signalEntryInsertSpecialCharKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer)
{
    if ((pEvent->keyval == GDK_KEY_S || pEvent->keyval == GDK_KEY_s) &&
        (pEvent->state & GDK_MODIFIER_MASK) == static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK))
    {
        if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
        {
            weld::Window* pDialogParent = nullptr;

            GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pEntry));
            if (GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr)
                pDialogParent = pFrame->GetFrameWeld();

            std::unique_ptr<GtkInstanceWindow> xFrameWeld;
            if (!pDialogParent && pTopLevel)
            {
                xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel), nullptr, false));
                pDialogParent = xFrameWeld.get();
            }

            OUString aChars = pImplFncGetSpecialChars(pDialogParent, ::get_font(GTK_WIDGET(pEntry)));
            if (!aChars.isEmpty())
            {
                gtk_editable_delete_selection(GTK_EDITABLE(pEntry));
                gint position = gtk_editable_get_position(GTK_EDITABLE(pEntry));
                OString sText(OUStringToOString(aChars, RTL_TEXTENCODING_UTF8));
                gtk_editable_insert_text(GTK_EDITABLE(pEntry), sText.getStr(), sText.getLength(),
                                         &position);
                gtk_editable_set_position(GTK_EDITABLE(pEntry), position);
            }
        }
        InsertSpecialChar(pEntry);
        return true;
    }
    return false;
}

static void signalActivateEntryInsertSpecialChar(GtkEntry *pEntry)
{
    InsertSpecialChar(pEntry);
}

static void signalEntryPopulatePopup(GtkEntry* pEntry, GtkWidget* pMenu, gpointer)
{
    if (!GTK_IS_MENU(pMenu))
        return;

    if (!vcl::GetGetSpecialCharsFunction())
        return;

    GtkWidget *item = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)).getStr());
    gtk_widget_show(item);
    g_signal_connect_swapped(item, "activate", G_CALLBACK(signalActivateEntryInsertSpecialChar), pEntry);
    gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), item);
}

#endif

namespace {
@@ -20970,6 +20996,7 @@ private:
    gulong m_nEntryFocusInSignalId;
    gulong m_nEntryFocusOutSignalId;
    gulong m_nEntryKeyPressEventSignalId;
    gulong m_nEntryPopulatePopupMenuSignalId;
    guint m_nAutoCompleteIdleId;
    gint m_nNonCustomLineHeight;
    gint m_nPrePopupCursorPos;
@@ -22071,6 +22098,7 @@ public:
            m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
            m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
            m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this);
            m_nEntryPopulatePopupMenuSignalId = g_signal_connect(m_pEntry, "populate-popup", G_CALLBACK(signalEntryPopulatePopup), nullptr);
            m_nKeyPressEventSignalId = 0;
        }
        else
@@ -22116,6 +22144,7 @@ public:
            m_nEntryFocusInSignalId = 0;
            m_nEntryFocusOutSignalId = 0;
            m_nEntryKeyPressEventSignalId = 0;
            m_nEntryPopulatePopupMenuSignalId = 0;
            m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
        }

@@ -22711,6 +22740,7 @@ public:
            g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
            g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
            g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId);
            g_signal_handler_disconnect(m_pEntry, m_nEntryPopulatePopupMenuSignalId);
        }
        else
            g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
@@ -23835,6 +23865,7 @@ private:
        else if (GTK_IS_ENTRY(pWidget))
        {
            g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalEntryInsertSpecialCharKeyPress), nullptr);
            g_signal_connect(pWidget, "populate-popup", G_CALLBACK(signalEntryPopulatePopup), nullptr);
        }
#endif
        else if (GTK_IS_WINDOW(pWidget))