diff options
author | Caolán McNamara <caolanm@redhat.com> | 2020-09-30 14:42:10 +0100 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2020-10-06 12:43:27 +0200 |
commit | 046a43559ba3c5ff53c364a69c99e70357c22e60 (patch) | |
tree | 7517608b721904c1d6884aed4f9e018d80e931b5 /vcl | |
parent | a49a0165bbc7fce216256bc8ee8ca8b0db757c1c (diff) |
tdf#134566 gtk IM support for custom widgets
Change-Id: I5c731161768d09d021db5c353de816e173159096
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/103764
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/103991
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/source/app/customweld.cxx | 6 | ||||
-rw-r--r-- | vcl/source/app/salvtables.cxx | 14 | ||||
-rw-r--r-- | vcl/source/window/layout.cxx | 17 | ||||
-rw-r--r-- | vcl/unx/gtk3/gtk3gtkinst.cxx | 261 |
4 files changed, 293 insertions, 5 deletions
diff --git a/vcl/source/app/customweld.cxx b/vcl/source/app/customweld.cxx index 07dc7935b198..c08a38208312 100644 --- a/vcl/source/app/customweld.cxx +++ b/vcl/source/app/customweld.cxx @@ -37,6 +37,7 @@ CustomWeld::CustomWeld(weld::Builder& rBuilder, const OString& rDrawingId, m_xDrawingArea->connect_style_updated(LINK(this, CustomWeld, DoStyleUpdated)); m_xDrawingArea->connect_command(LINK(this, CustomWeld, DoCommand)); m_xDrawingArea->connect_query_tooltip(LINK(this, CustomWeld, DoRequestHelp)); + m_xDrawingArea->connect_im_context_get_surrounding(LINK(this, CustomWeld, DoGetSurrounding)); m_rWidgetController.SetDrawingArea(m_xDrawingArea.get()); } @@ -94,6 +95,11 @@ IMPL_LINK(CustomWeld, DoRequestHelp, tools::Rectangle&, rHelpArea, OUString) { return m_rWidgetController.RequestHelp(rHelpArea); } + +IMPL_LINK(CustomWeld, DoGetSurrounding, OUString&, rSurrounding, int) +{ + return m_rWidgetController.GetSurroundingText(rSurrounding); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx index 7e80bc2b053d..3108ff5a9440 100644 --- a/vcl/source/app/salvtables.cxx +++ b/vcl/source/app/salvtables.cxx @@ -5501,6 +5501,7 @@ private: DECL_LINK(StyleUpdatedHdl, VclDrawingArea&, void); DECL_LINK(CommandHdl, const CommandEvent&, bool); DECL_LINK(QueryTooltipHdl, tools::Rectangle&, OUString); + DECL_LINK(GetSurroundingHdl, OUString&, int); DECL_LINK(StartDragHdl, VclDrawingArea*, bool); // SalInstanceWidget has a generic listener for all these @@ -5545,6 +5546,7 @@ public: m_xDrawingArea->SetStyleUpdatedHdl(LINK(this, SalInstanceDrawingArea, StyleUpdatedHdl)); m_xDrawingArea->SetCommandHdl(LINK(this, SalInstanceDrawingArea, CommandHdl)); m_xDrawingArea->SetQueryTooltipHdl(LINK(this, SalInstanceDrawingArea, QueryTooltipHdl)); + m_xDrawingArea->SetGetSurroundingHdl(LINK(this, SalInstanceDrawingArea, GetSurroundingHdl)); m_xDrawingArea->SetStartDragHdl(LINK(this, SalInstanceDrawingArea, StartDragHdl)); } @@ -5582,6 +5584,12 @@ public: m_xDrawingArea->SetInputContext(rInputContext); } + virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override + { + tools::Rectangle aCursorRect = m_xDrawingArea->PixelToLogic(rCursorRect); + m_xDrawingArea->SetCursorRect(&aCursorRect, m_xDrawingArea->PixelToLogic(Size(nExtTextInputWidth, 0)).Width()); + } + virtual a11yref get_accessible_parent() override { vcl::Window* pParent = m_xDrawingArea->GetParent(); @@ -5632,6 +5640,7 @@ public: virtual ~SalInstanceDrawingArea() override { + m_xDrawingArea->SetGetSurroundingHdl(Link<OUString&, int>()); m_xDrawingArea->SetQueryTooltipHdl(Link<tools::Rectangle&, OUString>()); m_xDrawingArea->SetCommandHdl(Link<const CommandEvent&, bool>()); m_xDrawingArea->SetStyleUpdatedHdl(Link<VclDrawingArea&, void>()); @@ -5698,6 +5707,11 @@ IMPL_LINK(SalInstanceDrawingArea, CommandHdl, const CommandEvent&, rEvent, bool) return m_aCommandHdl.Call(rEvent); } +IMPL_LINK(SalInstanceDrawingArea, GetSurroundingHdl, OUString&, rSurrounding, int) +{ + return m_aGetSurroundingHdl.Call(rSurrounding); +} + IMPL_LINK(SalInstanceDrawingArea, QueryTooltipHdl, tools::Rectangle&, rHelpArea, OUString) { return m_aQueryTooltipHdl.Call(rHelpArea); diff --git a/vcl/source/window/layout.cxx b/vcl/source/window/layout.cxx index 77009a9b3cc8..080acd5f9901 100644 --- a/vcl/source/window/layout.cxx +++ b/vcl/source/window/layout.cxx @@ -2844,6 +2844,23 @@ void VclDrawingArea::StartDrag(sal_Int8, const Point&) xContainer->StartDrag(this, m_nDragAction); } +OUString VclDrawingArea::GetSurroundingText() const +{ + OUString sSurroundingText; + if (m_aGetSurroundingHdl.Call(sSurroundingText) != -1) + return sSurroundingText; + return Control::GetSurroundingText(); +} + +Selection VclDrawingArea::GetSurroundingTextSelection() const +{ + OUString sSurroundingText; + int nCursor = m_aGetSurroundingHdl.Call(sSurroundingText); + if (nCursor != -1) + return Selection(nCursor, nCursor); + return Control::GetSurroundingTextSelection(); +} + VclHPaned::~VclHPaned() { } diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx index 77af86f2e3f2..792b9381d7d5 100644 --- a/vcl/unx/gtk3/gtk3gtkinst.cxx +++ b/vcl/unx/gtk3/gtk3gtkinst.cxx @@ -12461,7 +12461,10 @@ public: } }; - AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget); +// IMHandler +class IMHandler; + +AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget); class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea { @@ -12470,6 +12473,7 @@ private: a11yref m_xAccessible; AtkObject *m_pAccessible; ScopedVclPtrInstance<VirtualDevice> m_xDevice; + std::unique_ptr<IMHandler> m_xIMHandler; cairo_surface_t* m_pSurface; gulong m_nDrawSignalId; gulong m_nStyleUpdatedSignalId; @@ -12543,7 +12547,7 @@ private: } virtual bool signal_popup_menu(const CommandEvent& rCEvt) override { - return m_aCommandHdl.Call(rCEvt); + return signal_command(rCEvt); } bool signal_scroll(GdkEventScroll* pEvent) { @@ -12602,7 +12606,8 @@ public: { GtkWidget* pParent = gtk_widget_get_parent(m_pWidget); m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible); - g_object_ref(m_pAccessible); + if (m_pAccessible) + g_object_ref(m_pAccessible); } return m_pAccessible; } @@ -12621,9 +12626,18 @@ public: gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(m_pDrawingArea)), pCursor); } - virtual void set_input_context(const InputContext& /*rInputContext*/) override + virtual void set_input_context(const InputContext& rInputContext) override; + + virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override; + + int im_context_get_surrounding(OUString& rSurroundingText) + { + return signal_im_context_get_surrounding(rSurroundingText); + } + + bool im_context_delete_surrounding(const Selection& rRange) { - // TODO follow up for the gtk case + return signal_im_context_delete_surrounding(rRange); } virtual void queue_draw() override @@ -12724,8 +12738,245 @@ public: { return *m_xDevice; } + + bool signal_command(const CommandEvent& rCEvt) + { + return m_aCommandHdl.Call(rCEvt); + } }; +class IMHandler +{ +private: + GtkInstanceDrawingArea* m_pArea; + GtkIMContext* m_pIMContext; + OUString m_sPreeditText; + gulong m_nFocusInSignalId; + gulong m_nFocusOutSignalId; + bool m_bExtTextInput; + +public: + IMHandler(GtkInstanceDrawingArea* pArea) + : m_pArea(pArea) + , m_pIMContext(gtk_im_multicontext_new()) + , m_nFocusInSignalId(g_signal_connect(m_pArea->getWidget(), "focus-in-event", G_CALLBACK(signalFocusIn), this)) + , m_nFocusOutSignalId(g_signal_connect(m_pArea->getWidget(), "focus-out-event", G_CALLBACK(signalFocusOut), this)) + , m_bExtTextInput(false) + { + g_signal_connect(m_pIMContext, "preedit-start", G_CALLBACK(signalIMPreeditStart), this); + g_signal_connect(m_pIMContext, "preedit-end", G_CALLBACK(signalIMPreeditEnd), this); + g_signal_connect(m_pIMContext, "commit", G_CALLBACK(signalIMCommit), this); + g_signal_connect(m_pIMContext, "preedit-changed", G_CALLBACK(signalIMPreeditChanged), this); + g_signal_connect(m_pIMContext, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding), this); + g_signal_connect(m_pIMContext, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding), this); + + GtkWidget* pWidget = m_pArea->getWidget(); + if (!gtk_widget_get_realized(pWidget)) + gtk_widget_realize(pWidget); + GdkWindow* pWin = gtk_widget_get_window(pWidget); + gtk_im_context_set_client_window(m_pIMContext, pWin); + gtk_im_context_focus_in(m_pIMContext); + } + + void signalFocus(bool bIn) + { + if (bIn) + gtk_im_context_focus_in(m_pIMContext); + else + gtk_im_context_focus_out(m_pIMContext); + } + + static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + pThis->signalFocus(true); + return false; + } + + static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + pThis->signalFocus(false); + return false; + } + + ~IMHandler() + { + EndExtTextInput(); + + g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId); + g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId); + + // first give IC a chance to deinitialize + gtk_im_context_set_client_window(m_pIMContext, nullptr); + // destroy old IC + g_object_unref(m_pIMContext); + } + + void updateIMSpotLocation() + { + CommandEvent aCEvt(Point(), CommandEventId::CursorPos); + // we expect set_cursor_location to get triggered by this + m_pArea->signal_command(aCEvt); + } + + void set_cursor_location(const tools::Rectangle& rRect) + { + GdkRectangle aArea{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()), + static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())}; + gtk_im_context_set_cursor_location(m_pIMContext, &aArea); + } + + static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + + // at least editeng expects to have seen a start before accepting a commit + pThis->StartExtTextInput(); + + OUString sText(pText, strlen(pText), RTL_TEXTENCODING_UTF8); + CommandExtTextInputData aData(sText, nullptr, sText.getLength(), 0, false); + CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData); + pThis->m_pArea->signal_command(aCEvt); + + pThis->updateIMSpotLocation(); + + pThis->EndExtTextInput(); + + pThis->m_sPreeditText.clear(); + } + + static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + + sal_Int32 nCursorPos(0); + sal_uInt8 nCursorFlags(0); + std::vector<ExtTextInputAttr> aInputFlags; + OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags); + + // change from nothing to nothing -> do not start preedit e.g. this + // will activate input into a calc cell without user input + if (sText.isEmpty() && pThis->m_sPreeditText.isEmpty()) + return; + + pThis->m_sPreeditText = sText; + + CommandExtTextInputData aData(sText, aInputFlags.data(), nCursorPos, nCursorFlags, false); + CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData); + pThis->m_pArea->signal_command(aCEvt); + + pThis->updateIMSpotLocation(); + } + + static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + + OUString sSurroundingText; + int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText); + + if (nCursorIndex != -1) + { + OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8); + OUString sCursorText(sSurroundingText.copy(0, nCursorIndex)); + gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(), + OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength()); + } + + return true; + } + + static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars, + gpointer im_handler) + { + bool bRet = false; + + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + + OUString sSurroundingText; + sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText); + + if (nCursorIndex != -1) + { + // Note that offset and n_chars are in characters not in bytes + // which differs from the usage other places in GtkIMContext + + if (nOffset > 0) + { + while (nCursorIndex < sSurroundingText.getLength()) + sSurroundingText.iterateCodePoints(&nCursorIndex, 1); + } + else if (nOffset < 0) + { + while (nCursorIndex > 0) + sSurroundingText.iterateCodePoints(&nCursorIndex, -1); + } + + sal_Int32 nCursorEndIndex(nCursorIndex); + sal_Int32 nCount(0); + while (nCount < nChars && nCursorEndIndex < sSurroundingText.getLength()) + ++nCount; + + bRet = pThis->m_pArea->im_context_delete_surrounding(Selection(nCursorIndex, nCursorEndIndex)); + } + + return bRet; + } + + void StartExtTextInput() + { + if (m_bExtTextInput) + return; + CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput); + m_pArea->signal_command(aCEvt); + m_bExtTextInput = true; + } + + static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + pThis->StartExtTextInput(); + pThis->updateIMSpotLocation(); + } + + void EndExtTextInput() + { + if (!m_bExtTextInput) + return; + CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput); + m_pArea->signal_command(aCEvt); + m_bExtTextInput = false; + } + + static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler) + { + IMHandler* pThis = static_cast<IMHandler*>(im_handler); + pThis->updateIMSpotLocation(); + pThis->EndExtTextInput(); + } +}; + +void GtkInstanceDrawingArea::set_input_context(const InputContext& rInputContext) +{ + bool bUseIm(rInputContext.GetOptions() & InputContextFlags::Text); + if (!bUseIm) + { + m_xIMHandler.reset(); + return; + } + // create a new im context + if (!m_xIMHandler) + m_xIMHandler.reset(new IMHandler(this)); +} + +void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int /*nExtTextInputWidth*/) +{ + if (!m_xIMHandler) + return; + m_xIMHandler->set_cursor_location(rCursorRect); +} + } namespace { |