Add minimum composition text support in IME

This commit is contained in:
Benau 2019-06-26 15:44:55 +08:00
parent 7184119409
commit 1620cbdbd8
6 changed files with 222 additions and 68 deletions

View File

@ -8,6 +8,8 @@
#include "IGUIElement.h"
#include "SColor.h"
#include <string>
namespace irr
{
namespace gui
@ -125,6 +127,9 @@ namespace gui
//! Returns maximum amount of characters, previously set by setMax();
virtual u32 getMax() const = 0;
virtual void setComposingText(const std::u32string& ct) = 0;
virtual void clearComposingText() = 0;
virtual const core::position2di& getICPos() const = 0;
};

View File

@ -124,7 +124,13 @@ namespace gui
//! Reads attributes of the element
virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
virtual void setComposingText(const std::u32string& ct) {}
virtual void clearComposingText() {}
virtual const core::position2di& getICPos() const
{
static core::position2di unused;
return unused;
}
protected:
//! Breaks the single text line.
void breakText();

View File

@ -7,6 +7,10 @@
#ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_
#include "CIrrDeviceWin32.h"
#include "IGUIEditBox.h"
#include "IGUIEnvironment.h"
#include "IGUIFont.h"
#include "IGUISkin.h"
#include "IEventReceiver.h"
#include "irrList.h"
#include "os.h"
@ -952,6 +956,15 @@ irr::CIrrDeviceWin32* getDeviceFromHWnd(HWND hWnd)
return 0;
}
static void updateIMECompositonPosition(irr::core::position2di pos, HWND hwnd, HIMC himc)
{
COMPOSITIONFORM cf;
cf.dwStyle = CFS_POINT;
cf.ptCurrentPos.x = pos.X;
cf.ptCurrentPos.y = pos.Y;
ImmSetCompositionWindow(himc, &cf);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
#ifndef WM_MOUSEWHEEL
@ -1043,7 +1056,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
}
dev = getDeviceFromHWnd(hWnd);
if (dev)
if (dev && !dev->isIMEComposingStarted())
{
dev->postEventFromUser(event);
@ -1078,22 +1091,116 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
case WM_ERASEBKGND:
return 0;
case WM_IME_CHAR:
case WM_SETFOCUS:
case WM_KILLFOCUS:
{
dev = getDeviceFromHWnd(hWnd);
if (dev)
dev->setIMEComposingStarted(false);
return 0;
}
case WM_IME_SETCONTEXT:
{
// application wants to draw composition text by itself.
lParam &= ~(ISC_SHOWUICOMPOSITIONWINDOW);
break;
}
case WM_IME_STARTCOMPOSITION:
case WM_IME_ENDCOMPOSITION:
{
dev = getDeviceFromHWnd(hWnd);
if (!dev)
return 0;
event.EventType = irr::EET_KEY_INPUT_EVENT;
event.KeyInput.PressedDown = true;
event.KeyInput.Char = (char32_t)wParam;
event.KeyInput.Key = irr::IRR_KEY_UNKNOWN;
event.KeyInput.Shift = false;
event.KeyInput.Control = false;
dev->postEventFromUser(event);
irr::gui::IGUIEnvironment* env = dev->getGUIEnvironment();
if (!env)
return 0;
irr::gui::IGUISkin* skin = env->getSkin();
if (!skin)
return 0;
irr::gui::IGUIFont* font = skin->getFont();
if (!font)
return 0;
irr::gui::IGUIEditBox* box = dynamic_cast<irr::gui::IGUIEditBox*>(env->getFocus());
if (!box)
return 0;
box->clearComposingText();
dev->setIMEComposingStarted(message == WM_IME_STARTCOMPOSITION);
HIMC imc = ImmGetContext(hWnd);
if (!imc)
return 0;
if (message == WM_IME_STARTCOMPOSITION)
{
updateIMECompositonPosition(box->getICPos(), hWnd, imc);
// Same height as system font so the composition window is
// positioned correctly vertically
LOGFONT lFont = {0};
lFont.lfHeight = font->getHeightPerLine();
lFont.lfCharSet = OEM_CHARSET;
ImmSetCompositionFont(imc, &lFont);
}
ImmReleaseContext(hWnd, imc);
return 0;
}
case WM_IME_COMPOSITION:
{
dev = getDeviceFromHWnd(hWnd);
if (!dev)
return 0;
irr::gui::IGUIEnvironment* env = dev->getGUIEnvironment();
if (!env)
return 0;
irr::gui::IGUIEditBox* box = dynamic_cast<irr::gui::IGUIEditBox*>(env->getFocus());
if (!box)
return 0;
bool get_comp_str = (lParam & GCS_COMPSTR) != 0;
bool get_result_str = (lParam & GCS_RESULTSTR) != 0;
if (get_comp_str || get_result_str)
{
HIMC imc = ImmGetContext(hWnd);
if (!imc)
return 0;
LONG vec_size = ImmGetCompositionString(imc, get_comp_str ? GCS_COMPSTR : GCS_RESULTSTR, (void*)NULL, 0);
if ((vec_size == IMM_ERROR_NODATA) ||
(vec_size == IMM_ERROR_GENERAL))
{
ImmReleaseContext(hWnd, imc);
return 0;
}
std::vector<wchar_t> ct;
// NULL terminator
ct.resize(vec_size / sizeof(wchar_t) + 1, 0);
ImmGetCompositionString(imc, get_comp_str ? GCS_COMPSTR : GCS_RESULTSTR, ct.data(), vec_size);
std::u32string result = StringUtils::wideToUtf32(ct.data());
if (get_comp_str)
{
box->setComposingText(result);
}
else
{
for (char32_t c : result)
{
event.EventType = irr::EET_KEY_INPUT_EVENT;
event.KeyInput.PressedDown = true;
event.KeyInput.Char = (char32_t)c;
event.KeyInput.Key = irr::IRR_KEY_UNKNOWN;
event.KeyInput.Shift = false;
event.KeyInput.Control = false;
dev->postEventFromUser(event);
}
}
if (get_result_str)
updateIMECompositonPosition(box->getICPos(), hWnd, imc);
ImmReleaseContext(hWnd, imc);
}
return 0;
}
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
@ -1139,7 +1246,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
event.KeyInput.Control = 0;
dev = getDeviceFromHWnd(hWnd);
if (dev)
if (dev && !dev->isIMEComposingStarted())
{
if (conversionResult >= 1)
{
@ -1248,6 +1355,7 @@ CIrrDeviceWin32::CIrrDeviceWin32(const SIrrlichtCreationParameters& params)
setDebugName("CIrrDeviceWin32");
#endif
m_ime_composing_started = false;
// get windows version and create OS operator
core::stringc winversion;
getWindowsVersion(winversion);

View File

@ -388,6 +388,10 @@ namespace irr
HWND getHandle() const { return HWnd; }
bool isIMEComposingStarted() const { return m_ime_composing_started; }
void setIMEComposingStarted(bool started) { m_ime_composing_started = started; }
private:
//! create the driver
@ -400,6 +404,7 @@ namespace irr
void resizeIfNecessary();
bool m_ime_composing_started;
HWND HWnd;
bool ChangedToFullScreen;

View File

@ -223,7 +223,7 @@ CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border,
FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
}
calculateScrollPos();
m_scroll_pos = m_cursor_distance = 0;
}
@ -242,6 +242,8 @@ CGUIEditBox::~CGUIEditBox()
irr_driver->getDevice());
dl->setIMEEnable(false);
}
#elif defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
DestroyCaret();
#endif
if (GUIEngine::ScreenKeyboard::shouldUseScreenKeyboard() &&
irr_driver->getDevice()->hasOnScreenKeyboard())
@ -350,31 +352,33 @@ bool CGUIEditBox::OnEvent(const SEvent& event)
MouseMarking = false;
setTextMarkers(0,0);
}
#ifdef _IRR_COMPILE_WITH_X11_DEVICE_
#if defined(_IRR_COMPILE_WITH_X11_DEVICE_)
if (irr_driver->getDevice()->getType() == irr::EIDT_X11)
{
CIrrDeviceLinux* dl = dynamic_cast<CIrrDeviceLinux*>(
irr_driver->getDevice());
dl->setIMEEnable(false);
}
#elif defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
DestroyCaret();
#endif
m_from_android_edittext = false;
m_composing_start = 0;
m_composing_end = 0;
m_composing_text.clear();
}
else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED)
{
m_mark_begin = m_mark_end = m_cursor_pos = getTextCount();
updateCursorDistance();
#ifdef _IRR_COMPILE_WITH_X11_DEVICE_
if (irr_driver->getDevice()->getType() == irr::EIDT_X11)
{
CIrrDeviceLinux* dl = dynamic_cast<CIrrDeviceLinux*>(
irr_driver->getDevice());
dl->setIMEEnable(true);
dl->setIMELocation(calculateICPos());
}
#endif
calculateScrollPos();
#ifdef ANDROID
if (GUIEngine::ScreenKeyboard::shouldUseScreenKeyboard() &&
irr_driver->getDevice()->hasOnScreenKeyboard() &&
@ -392,6 +396,7 @@ bool CGUIEditBox::OnEvent(const SEvent& event)
{
m_from_android_edittext = false;
}
m_composing_text.clear();
}
break;
case EET_KEY_INPUT_EVENT:
@ -862,17 +867,6 @@ bool CGUIEditBox::processKey(const SEvent& event)
}
//! calculate the position of input composition window
core::position2di CGUIEditBox::calculateICPos()
{
core::position2di pos;
pos.X = CurrentTextRect.UpperLeftCorner.X + m_cursor_distance;
//bug? The text is always drawn in the height of the center. SetTextAlignment() doesn't influence.
pos.Y = AbsoluteRect.getCenter().Y + (Border ? 3 : 0);
return pos;
}
//! draws the element and its children
void CGUIEditBox::draw()
{
@ -880,7 +874,8 @@ void CGUIEditBox::draw()
if (!IsVisible)
return;
updateSurrogatePairText();
if (Environment->hasFocus(this))
updateSurrogatePairText();
GUIEngine::ScreenKeyboard* screen_kbd = GUIEngine::ScreenKeyboard::getCurrent();
bool has_screen_kbd = (screen_kbd && screen_kbd->getEditBox() == this);
@ -933,31 +928,60 @@ void CGUIEditBox::draw()
const s32 realcbgn = m_composing_start < m_composing_end ? m_composing_start : m_composing_end;
const s32 realcend = m_composing_start < m_composing_end ? m_composing_end : m_composing_start;
for (unsigned i = 0; i < m_glyph_layouts.size(); i++)
{
GlyphLayout& glyph = m_glyph_layouts[i];
auto& cluster = glyph.cluster;
for (unsigned c = 0; c < glyph.cluster.size(); c++)
{
if (realmbgn != realmend)
{
if (cluster[c] >= realmbgn && cluster[c] < realmend)
glyph.draw_flags.at(c) = GLD_MARKED;
}
else if (!PasswordBox && realcbgn != realcend)
{
if (cluster[c] >= realcbgn && cluster[c] < realcend)
glyph.draw_flags.at(c) = GLD_COMPOSING;
}
else
glyph.draw_flags.at(c) = GLD_NONE;
}
}
// draw the text layout
font->draw(m_glyph_layouts, CurrentTextRect,
OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
false, true, &localClipRect);
if (!m_composing_text.empty())
{
std::vector<gui::GlyphLayout> ct;
std::u32string total = m_edit_text;
const s32 realcbgn = m_cursor_pos;
const s32 realcend = m_cursor_pos + (s32)m_composing_text.size();
total.insert(m_cursor_pos, m_composing_text);
font_manager->shape(total, ct);
for (unsigned i = 0; i < ct.size(); i++)
{
GlyphLayout& glyph = ct[i];
auto& cluster = glyph.cluster;
for (unsigned c = 0; c < glyph.cluster.size(); c++)
{
if (realcbgn != realcend)
{
if (cluster[c] >= realcbgn && cluster[c] < realcend)
glyph.draw_flags.at(c) = GLD_COMPOSING;
}
else
glyph.draw_flags.at(c) = GLD_NONE;
}
}
font->draw(ct, CurrentTextRect,
OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
false, true, &localClipRect);
}
else
{
for (unsigned i = 0; i < m_glyph_layouts.size(); i++)
{
GlyphLayout& glyph = m_glyph_layouts[i];
auto& cluster = glyph.cluster;
for (unsigned c = 0; c < glyph.cluster.size(); c++)
{
if (realmbgn != realmend)
{
if (cluster[c] >= realmbgn && cluster[c] < realmend)
glyph.draw_flags.at(c) = GLD_MARKED;
}
else if (!PasswordBox && realcbgn != realcend)
{
if (cluster[c] >= realcbgn && cluster[c] < realcend)
glyph.draw_flags.at(c) = GLD_COMPOSING;
}
else
glyph.draw_flags.at(c) = GLD_NONE;
}
}
font->draw(m_glyph_layouts, CurrentTextRect,
OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
false, true, &localClipRect);
}
// Reset draw flags
for (unsigned i = 0; i < m_glyph_layouts.size(); i++)
@ -1338,35 +1362,36 @@ void CGUIEditBox::calculateScrollPos()
m_scroll_pos = 0;
// todo: adjust scrollbar
core::position2di pos = calculateICPos();
// calculate the position of input composition window
#if defined(_IRR_COMPILE_WITH_X11_DEVICE_)
m_ic_pos.X = CurrentTextRect.UpperLeftCorner.X + m_cursor_distance;
// bug? The text is always drawn in the height of the center. SetTextAlignment() doesn't influence.
m_ic_pos.Y = AbsoluteRect.getCenter().Y + (Border ? 3 : 0);
if (irr_driver->getDevice()->getType() == irr::EIDT_X11)
{
CIrrDeviceLinux* dl = dynamic_cast<CIrrDeviceLinux*>(
irr_driver->getDevice());
if (dl)
{
dl->setIMELocation(pos);
dl->setIMELocation(m_ic_pos);
}
}
#elif defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
m_ic_pos.X = CurrentTextRect.UpperLeftCorner.X + m_cursor_distance;
m_ic_pos.Y = CurrentTextRect.UpperLeftCorner.Y;
// We need a native windows api caret for ime language chooser
// positioning, we always hide it though and draw with our own
// opengl caret later
CIrrDeviceWin32* win = NULL;
if (irr_driver->getDevice()->getType() == irr::EIDT_WIN32)
win = dynamic_cast<CIrrDeviceWin32*>(irr_driver->getDevice());
if (!win)
if (!isVisible() || !win)
return;
COMPOSITIONFORM cf;
HIMC hIMC = ImmGetContext(win->getHandle());
if (hIMC)
{
cf.dwStyle = CFS_POINT;
cf.ptCurrentPos.x = pos.X;
cf.ptCurrentPos.y = pos.Y - font->getHeightPerLine();
ImmSetCompositionWindow(hIMC, &cf);
ImmReleaseContext(win->getHandle(), hIMC);
}
CreateCaret(win->getHandle(), NULL, 1, GUIEngine::getFontHeight());
SetCaretPos(m_ic_pos.X, m_ic_pos.Y);
HideCaret(win->getHandle());
#endif
#endif // SERVER_ONLY

View File

@ -133,6 +133,9 @@ namespace GUIEngine
s32 getCursorPosInBox() const { return m_cursor_pos; }
s32 getTextCount() const { return (s32)m_edit_text.size(); }
void setTextBoxType(GUIEngine::TextBoxType t) { m_type = t; }
virtual void setComposingText(const std::u32string& ct) { m_composing_text = ct; }
virtual void clearComposingText() { m_composing_text.clear(); }
virtual const core::position2di& getICPos() const { return m_ic_pos; }
protected:
//! sets the area of the given line
void setTextRect(s32 line);
@ -147,8 +150,6 @@ namespace GUIEngine
void updateCursorDistance();
bool processKey(const SEvent& event);
bool processMouse(const SEvent& event);
//! calculates the input composition position
core::position2di calculateICPos();
s32 getCursorPos(s32 x, s32 y);
void updateGlyphLayouts();
@ -186,9 +187,13 @@ namespace GUIEngine
/* UTF32 string for shaping and editing to avoid wchar_t issue in
* windows */
std::u32string m_edit_text;
/* Used in windows api for native composing text handling. */
std::u32string m_composing_text;
std::vector<GlyphLayout> m_glyph_layouts;
// Pre-edit surrogate chars
std::vector<wchar_t> m_surrogate_chars;
// Position to show composition box in native window (linux / windows)
core::position2di m_ic_pos;
void correctCursor(s32& cursor_pos, bool left);
void updateSurrogatePairText();
};