Skip to content

Commit

Permalink
add Windows IME support
Browse files Browse the repository at this point in the history
  • Loading branch information
scheffle committed Dec 16, 2024
1 parent c289b2c commit abaa97b
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 2 deletions.
80 changes: 78 additions & 2 deletions vstgui/lib/ctexteditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "platform/iplatformtextinputclient.h"
#include "platform/platformfactory.h"
#include "platform/platform_macos.h"
#include "platform/platform_win32.h"
#include "controls/cbuttons.h"
#include "controls/ctextedit.h"
#include "animation/timingfunctions.h"
Expand Down Expand Up @@ -206,7 +207,8 @@ struct TextEditorView : public CView,
public TextEditorColorization::IEditorExt,
public IFocusDrawing,
public ViewEventListenerAdapter,
public ICocoaTextInputClient
public ICocoaTextInputClient,
public IIMETextInputClient
{
TextEditorView (ITextEditorController* controller);
void beforeDelete () override;
Expand Down Expand Up @@ -331,6 +333,12 @@ struct TextEditorView : public CView,
std::u32string substringForRange (TextRange range, TextRange& actualRange) override;
size_t characterIndexForPoint (CPoint pos) override;

// IIMETextInputClient
bool ime_queryCharacterPosition (CharPosition& cp) override;
void ime_setMarkedText (const std::u32string& string) override;
void ime_insertText (const std::u32string& string) override;
void ime_unmarkText () override;

private:
template<typename Proc>
bool callSTB (Proc proc) const;
Expand Down Expand Up @@ -662,6 +670,12 @@ void TextEditorView::takeFocus ()
{
cocoaFrame->setTextInputClient (this);
}
#elif WINDOWS
auto pf = getFrame ()->getPlatformFrame ();
if (auto winFrame = dynamic_cast<IWin32PlatformFrame*> (pf))
{
winFrame->setTextInputClient (this);
}
#endif
}

Expand All @@ -677,6 +691,12 @@ void TextEditorView::looseFocus ()
{
cocoaFrame->setTextInputClient (nullptr);
}
#elif WINDOWS
auto pf = getFrame ()->getPlatformFrame ();
if (auto winFrame = dynamic_cast<IWin32PlatformFrame*> (pf))
{
winFrame->setTextInputClient (nullptr);
}
#endif
}

Expand Down Expand Up @@ -3068,7 +3088,7 @@ size_t TextEditorView::characterIndexForPoint (CPoint pos)
pos.y -= md.style->lineSpacing;
if (pos.y >= 0.)
{
auto lineIndex = std::floor (pos.y / md.lineHeight);
auto lineIndex = static_cast<size_t> (std::floor (pos.y / md.lineHeight));
if (lineIndex < md.model.lines.size ())
{
pos.x += md.style->leftMargin;
Expand All @@ -3095,6 +3115,62 @@ size_t TextEditorView::characterIndexForPoint (CPoint pos)
return std::numeric_limits<size_t>::max ();
}

//------------------------------------------------------------------------
// IIMETextInputClient
//------------------------------------------------------------------------
bool TextEditorView::ime_queryCharacterPosition (CharPosition& cp)
{
auto it = findLine (md.model.lines.begin (), md.model.lines.end (), md.editState.cursor);
auto r = calculateLineRect (it);
if (static_cast<int> (it->range.start) != md.editState.cursor)
{
auto t = md.model.text.substr (it->range.start, md.editState.cursor - it->range.start);
auto nonSelectedText = convert (t);
replaceTabs (nonSelectedText, md.style->tabWidth, 0u);
auto str = getPlatformFactory ().createString (nonSelectedText.data ());
r.left += md.style->font->getFontPainter ()->getStringWidth (nullptr, str);
r.right = r.left;
}
r = translateToGlobal (r);
cp.position = r.getTopLeft ();
cp.lineHeight = md.lineHeight * getFrame ()->getScaleFactor ();
cp.documentRect = translateToGlobal (getVisibleViewSize ());
return true;
}

//------------------------------------------------------------------------
void TextEditorView::ime_setMarkedText (const std::u32string& string)
{
if (markedRange.length > 0)
deleteChars (markedRange.start, markedRange.length);
insertChars (md.editState.select_start, string.data (), string.size ());
markedRange.start = md.editState.select_start;
markedRange.length = string.size ();
md.editState.select_end = static_cast<int> (md.editState.select_start + string.size ());
}

//------------------------------------------------------------------------
void TextEditorView::ime_insertText (const std::u32string& string)
{
if (markedRange.length > 0)
deleteChars (markedRange.start, markedRange.length);
markedRange = {};
md.editState.select_start = md.editState.select_end = md.editState.cursor;
callSTB ([&] () {
stb_textedit_paste (this, &md.editState, string.data (),
static_cast<int32_t> (string.size ()));
});
}

//------------------------------------------------------------------------
void TextEditorView::ime_unmarkText ()
{
if (markedRange.length > 0)
deleteChars (markedRange.start, markedRange.length);
markedRange = {};
md.editState.select_start = md.editState.select_end = md.editState.cursor;
}

//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions vstgui/lib/platform/iplatformtextinputclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,24 @@ struct ICocoaTextInputClient
virtual ~ICocoaTextInputClient () noexcept = default;
};

//------------------------------------------------------------------------
struct IIMETextInputClient
{
struct CharPosition
{
uint32_t characterPosition;
CPoint position;
double lineHeight;
CRect documentRect;
};

virtual bool ime_queryCharacterPosition (CharPosition& cp) = 0;
virtual void ime_setMarkedText (const std::u32string& string) = 0;
virtual void ime_insertText (const std::u32string& string) = 0;
virtual void ime_unmarkText () = 0;

virtual ~IIMETextInputClient () noexcept = default;
};

//------------------------------------------------------------------------
} // VSTGUI
3 changes: 3 additions & 0 deletions vstgui/lib/platform/platform_win32.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
//-----------------------------------------------------------------------------
namespace VSTGUI {

struct IIMETextInputClient;

//-----------------------------------------------------------------------------
// extens IPlatformFrame on Microsoft Windows
class IWin32PlatformFrame
{
public:
virtual HWND getHWND () const = 0;
virtual void setTextInputClient (IIMETextInputClient* client) = 0;
};

//-----------------------------------------------------------------------------
Expand Down
101 changes: 101 additions & 0 deletions vstgui/lib/platform/win32/win32frame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <commctrl.h>
#include <cmath>
#include <codecvt>
#include <windowsx.h>
#include "direct2d/d2dbitmap.h"
#include "direct2d/d2dgraphicspath.h"
Expand All @@ -20,6 +21,7 @@
#include "win32dragging.h"
#include "win32directcomposition.h"
#include "win32viewlayer.h"
#include "../iplatformtextinputclient.h"
#include "../common/genericoptionmenu.h"
#include "../common/generictextedit.h"
#include "../../cdropsource.h"
Expand All @@ -37,6 +39,7 @@
// windows libraries VSTGUI depends on
#ifdef _MSC_VER
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "Imm32.lib")
#endif

namespace VSTGUI {
Expand Down Expand Up @@ -738,6 +741,24 @@ static void setupMouseEventFromWParam (MouseEvent& event, WPARAM wParam)
event.modifiers.add (ModifierKey::Super);
}

//------------------------------------------------------------------------
static std::u32string convert (std::wstring_view s)
{
std::string bytes;
bytes.reserve (s.size () * 2);

for (const char16_t c : s)
{
bytes.push_back (static_cast<char> (c / 256));
bytes.push_back (static_cast<char> (c % 256));
}

#pragma warning(disable : 4996) // deprecated
std::wstring_convert<std::codecvt_utf16<char32_t>, char32_t> convert;
return convert.from_bytes (bytes);
#pragma warning(3 : 4996) // deprecated
}

//-----------------------------------------------------------------------------
LONG_PTR WINAPI Win32Frame::proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
Expand Down Expand Up @@ -946,10 +967,90 @@ LONG_PTR WINAPI Win32Frame::proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM
}
break;
}
case WM_IME_STARTCOMPOSITION:
{
if (!textInputClient)
break;
return 0;
}
case WM_IME_ENDCOMPOSITION:
{
if (!textInputClient)
break;
textInputClient->ime_unmarkText ();
return 0;
}
case WM_IME_COMPOSITION:
{
if (!textInputClient)
break;

static constexpr auto getCompositionString = [] (auto context, auto which) {
auto requiredBytes = ImmGetCompositionStringW (context, which, nullptr, 0);
if (requiredBytes > 0)
{
std::wstring str;
str.resize (requiredBytes / sizeof (char16_t));
if (ImmGetCompositionStringW (context, which, str.data (), requiredBytes) ==
requiredBytes)
{
return str;
}
}
return std::wstring ();
};

if (auto immContext = ImmGetContext (hwnd))
{
if (lParam & GCS_RESULTSTR)
{
auto str = getCompositionString (immContext, GCS_RESULTSTR);
auto u32str = convert (str);
textInputClient->ime_insertText (u32str);
}
if (lParam & GCS_COMPSTR)
{
auto str = getCompositionString (immContext, GCS_COMPSTR);
auto u32str = convert (str);
textInputClient->ime_setMarkedText (u32str);
}
ImmReleaseContext (hwnd, immContext);
}
break;
}
case WM_IME_REQUEST:
{
if (!textInputClient)
break;
if (wParam == IMR_QUERYCHARPOSITION)
{
IIMETextInputClient::CharPosition cp {};
if (textInputClient->ime_queryCharacterPosition (cp))
{
auto charPos = reinterpret_cast<IMECHARPOSITION*> (lParam);
charPos->dwSize = sizeof (IMECHARPOSITION);
charPos->cLineHeight = static_cast<UINT> (std::ceil (cp.lineHeight));
charPos->pt = {static_cast<INT> (cp.position.x),
static_cast<INT> (cp.position.y)};
charPos->dwCharPos = cp.characterPosition;
charPos->rcDocument.left = static_cast<LONG> (cp.documentRect.left);
charPos->rcDocument.top = static_cast<LONG> (cp.documentRect.top);
charPos->rcDocument.right = static_cast<LONG> (cp.documentRect.right);
charPos->rcDocument.bottom = static_cast<LONG> (cp.documentRect.bottom);
MapWindowPoints (hwnd, nullptr, &charPos->pt, 1);
MapWindowRect (hwnd, nullptr, &charPos->rcDocument);
return 1;
}
}
break;
}
}
return DefWindowProc (hwnd, message, wParam, lParam);
}

//------------------------------------------------------------------------
void Win32Frame::setTextInputClient (IIMETextInputClient* client) { textInputClient = client; }

//-----------------------------------------------------------------------------
Optional<UTF8String> Win32Frame::convertCurrentKeyEventToText ()
{
Expand Down
3 changes: 3 additions & 0 deletions vstgui/lib/platform/win32/win32frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class Win32Frame final : public IPlatformFrame, public IWin32PlatformFrame
~Win32Frame () noexcept;

HWND getHWND () const override { return windowHandle; }
void setTextInputClient (IIMETextInputClient* client) override;

HWND getPlatformWindow () const { return windowHandle; }
HWND getParentPlatformWindow () const { return parentWindow; }
HWND getOuterWindow () const;
Expand Down Expand Up @@ -85,6 +87,7 @@ class Win32Frame final : public IPlatformFrame, public IWin32PlatformFrame
PlatformGraphicsDeviceContextPtr legacyDrawDevice;
Optional<MSG> currentEvent;
ViewLayers viewLayers;
IIMETextInputClient* textInputClient {nullptr};

bool inPaint;
bool mouseInside;
Expand Down

0 comments on commit abaa97b

Please sign in to comment.