diff --git a/lib/irrlicht/include/IGUIEditBox.h b/lib/irrlicht/include/IGUIEditBox.h index e7b637e08..8e7616d9d 100644 --- a/lib/irrlicht/include/IGUIEditBox.h +++ b/lib/irrlicht/include/IGUIEditBox.h @@ -8,6 +8,8 @@ #include "IGUIElement.h" #include "SColor.h" +#include + 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; }; diff --git a/lib/irrlicht/source/Irrlicht/CGUIEditBox.h b/lib/irrlicht/source/Irrlicht/CGUIEditBox.h index 8b73aa61d..7546df2e6 100644 --- a/lib/irrlicht/source/Irrlicht/CGUIEditBox.h +++ b/lib/irrlicht/source/Irrlicht/CGUIEditBox.h @@ -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(); diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.cpp b/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.cpp index 141f161fa..544df3c5b 100755 --- a/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.cpp +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.cpp @@ -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(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(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 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); diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.h b/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.h index 8e67f22ca..2daa6bddc 100644 --- a/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.h +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceWin32.h @@ -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; diff --git a/src/guiengine/widgets/CGUIEditBox.cpp b/src/guiengine/widgets/CGUIEditBox.cpp index 38f0d969c..0ef5b6775 100644 --- a/src/guiengine/widgets/CGUIEditBox.cpp +++ b/src/guiengine/widgets/CGUIEditBox.cpp @@ -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( 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( 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 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( 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(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 diff --git a/src/guiengine/widgets/CGUIEditBox.hpp b/src/guiengine/widgets/CGUIEditBox.hpp index f23dc7456..5411d40ae 100644 --- a/src/guiengine/widgets/CGUIEditBox.hpp +++ b/src/guiengine/widgets/CGUIEditBox.hpp @@ -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 m_glyph_layouts; // Pre-edit surrogate chars std::vector 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(); };