Add minimum composition text support in IME
This commit is contained in:
parent
7184119409
commit
1620cbdbd8
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user