diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index bed739dc..35d7c8dc 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1155,6 +1155,7 @@ extern "C" { #define GLFW_LOCK_KEY_MODS 0x00033004 #define GLFW_RAW_MOUSE_MOTION 0x00033005 #define GLFW_UNLIMITED_MOUSE_BUTTONS 0x00033006 +#define GLFW_IME 0x00033007 #define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_HIDDEN 0x00034002 @@ -1945,6 +1946,37 @@ typedef void (* GLFWcharfun)(GLFWwindow* window, unsigned int codepoint); */ typedef void (* GLFWcharmodsfun)(GLFWwindow* window, unsigned int codepoint, int mods); +/*! @brief The function signature for preedit callbacks. + * + * This is the function signature for preedit callback functions. + * + * @param[in] window The window that received the event. + * @param[in] length Preedit string length. + * @param[in] string Preedit string. + * @param[in] count Attributed block count. + * @param[in] blocksizes List of attributed block size. + * @param[in] focusedblock Focused block index. + * + * @sa @ref preedit + * @sa glfwSetPreeditCallback + * + * @ingroup input + */ +typedef void (* GLFWpreeditfun)(GLFWwindow*,int,unsigned int*,int,int*,int); + +/*! @brief The function signature for IME status change callbacks. + * + * This is the function signature for IME status change callback functions. + * + * @param[in] window The window that received the event. + * + * @sa @ref preedit + * @sa glfwSetIMEStatusCallback + * + * @ingroup monitor + */ +typedef void (* GLFWimestatusfun)(GLFWwindow*); + /*! @brief The function pointer type for path drop callbacks. * * This is the function pointer type for path drop callbacks. A path drop @@ -4652,8 +4684,8 @@ GLFWAPI void glfwPostEmptyEvent(void); * * This function returns the value of an input option for the specified window. * The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, - * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or - * @ref GLFW_RAW_MOUSE_MOTION. + * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS, + * @ref GLFW_RAW_MOUSE_MOTION or @ref GLFW_IME. * * @param[in] window The window to query. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, @@ -4677,8 +4709,9 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * * This function sets an input mode option for the specified window. The mode * must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, - * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS - * @ref GLFW_RAW_MOUSE_MOTION, or @ref GLFW_UNLIMITED_MOUSE_BUTTONS. + * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS, + * @ref GLFW_RAW_MOUSE_MOTION, @ref GLFW_UNLIMITED_MOUSE_BUTTONS, + * @ref GLFW_IME. * * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor * modes: @@ -4723,10 +4756,13 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * callback, or `GLFW_FALSE` to limit the mouse buttons sent to the callback * to the mouse button token values up to `GLFW_MOUSE_BUTTON_LAST`. * + * If the mode is `GLFW_IME`, the value must be either `GLFW_TRUE` to turn on + * IME, or `GLFW_FALSE` to turn off it. + * * @param[in] window The window whose input mode to set. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, - * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or - * `GLFW_RAW_MOUSE_MOTION`. + * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`, + * `GLFW_RAW_MOUSE_MOTION` or `GLFW_IME`. * @param[in] value The new value of the specified input mode. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref @@ -5156,6 +5192,67 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor); */ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); +/*! @brief Retrieves the position of the text cursor relative to the client area of window. + * + * This function returns position hint to decide the candidate window. + * + * @param[in] window The window to set the text cursor for. + * @param[out] x The text cursor x position (relative position from window coordinates). + * @param[out] y The text cursor y position (relative position from window coordinates). + * @param[out] h The text cursor height. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_char + * + * @since Added in GLFW 3.X. + * + * @ingroup input + */ +GLFWAPI void glfwGetPreeditCursorPos(GLFWwindow* window, int *x, int *y, int *h); + +/*! @brief Notify the text cursor position to window system to decide the candidate window position. + * + * This function teach position hint to decide the candidate window. The candidate window + * is a part of IME(Input Method Editor) and show several candidate strings. + * + * Windows sytems decide proper pisition from text cursor geometry. + * You should call this function in preedit callback. + * + * @param[in] window The window to set the text cursor for. + * @param[in] x The text cursor x position (relative position from window coordinates). + * @param[in] y The text cursor y position (relative position from window coordinates). + * @param[in] h The text cursor height. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_char + * + * @since Added in GLFW 3.X. + * + * @ingroup input + */ +GLFWAPI void glfwSetPreeditCursorPos(GLFWwindow* window, int x, int y, int h); + +/*! @brief Reset IME input status. + * + * This function resets IME's preedit text. + * + * @param[in] window The window. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref preedit + * + * @since Added in GLFW 3.X. + * + * @ingroup input + */ +GLFWAPI void glfwResetPreeditText(GLFWwindow* window); + /*! @brief Sets the key callback. * * This function sets the key callback of the specified window, which is called @@ -5291,6 +5388,58 @@ GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun callback */ GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun callback); +/*! @brief Sets the preedit callback. + * + * This function sets the preedit callback of the specified + * window, which is called when an IME is processing text before commited. + * + * Callback receives relative position of input cursor inside preedit text and + * attributed text blocks. This callback is used for on-the-spot text editing + * with IME. + * + * @param[in] window The window whose callback to set. + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or an + * error occurred. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_char + * + * @since Added in GLFW 3.X + * + * @ingroup input + */ +GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* window, GLFWpreeditfun cbfun); + +/*! @brief Sets the IME status change callback. + * + * This function sets the preedit callback of the specified + * window, which is called when an IME is processing text before commited. + * + * Callback receives relative position of input cursor inside preedit text and + * attributed text blocks. This callback is used for on-the-spot text editing + * with IME. + * + * @param[in] window The window whose callback to set. + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or an + * error occurred. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_char + * + * @since Added in GLFW 3.X + * + * @ingroup input + */ +GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* window, GLFWimestatusfun cbfun); + /*! @brief Sets the mouse button callback. * * This function sets the mouse button callback of the specified window, which diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1057a6f9..a9175cc0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,7 @@ endif() if (GLFW_BUILD_WIN32) list(APPEND glfw_PKG_LIBS "-lgdi32") + list(APPEND glfw_LIBRARIES "imm32") endif() if (GLFW_BUILD_COCOA) diff --git a/src/input.c b/src/input.c index c619eefc..97b64714 100644 --- a/src/input.c +++ b/src/input.c @@ -328,6 +328,20 @@ void _glfwInputChar(_GLFWwindow* window, uint32_t codepoint, int mods, GLFWbool } } +void _glfwInputPreedit(_GLFWwindow* window, int focusedBlock) +{ + if (window->callbacks.preedit) { + window->callbacks.preedit((GLFWwindow*) window, window->ntext, window->preeditText, window->nblocks, window->preeditAttributeBlocks, focusedBlock); + } +} + +void _glfwInputIMEStatus(_GLFWwindow* window) +{ + if (window->callbacks.imestatus) { + window->callbacks.imestatus((GLFWwindow*) window); + } +} + // Notifies shared code of a scroll event // void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset) @@ -580,6 +594,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode) return window->rawMouseMotion; case GLFW_UNLIMITED_MOUSE_BUTTONS: return window->disableMouseButtonLimit; + case GLFW_IME: + return _glfwPlatformGetIMEStatus(window); } _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); @@ -693,6 +709,12 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value) window->disableMouseButtonLimit = value ? GLFW_TRUE : GLFW_FALSE; return; } + + case GLFW_IME: + { + _glfwPlatformSetIMEStatus(window, value ? GLFW_TRUE : GLFW_FALSE); + return; + } } _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); @@ -953,6 +975,30 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle) _glfw.platform.setCursor(window, cursor); } +GLFWAPI void glfwGetPreeditCursorPos(GLFWwindow* handle, int *x, int *y, int *h) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + if (x) + *x = window->preeditCursorPosX; + if (y) + *y = window->preeditCursorPosY; + if (h) + *h = window->preeditCursorHeight; +} + +GLFWAPI void glfwSetPreeditCursorPos(GLFWwindow* handle, int x, int y, int h) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + window->preeditCursorPosX = x; + window->preeditCursorPosY = y; + window->preeditCursorHeight = h; +} + +GLFWAPI void glfwResetPreeditText(GLFWwindow* handle) { + _GLFWwindow* window = (_GLFWwindow*) handle; + _glfwPlatformResetPreeditText(window); +} + GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); @@ -986,6 +1032,22 @@ GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmods return cbfun; } +GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* handle, GLFWpreeditfun cbfun) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP(GLFWpreeditfun, window->callbacks.preedit, cbfun); + return cbfun; +} + +GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* handle, GLFWimestatusfun cbfun) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP(GLFWimestatusfun, window->callbacks.imestatus, cbfun); + return cbfun; +} + GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle, GLFWmousebuttonfun cbfun) { diff --git a/src/internal.h b/src/internal.h index 4f097aa8..cf87ac99 100644 --- a/src/internal.h +++ b/src/internal.h @@ -561,6 +561,15 @@ struct _GLFWwindow double virtualCursorPosX, virtualCursorPosY; GLFWbool rawMouseMotion; + // Preedit texts + unsigned int* preeditText; + int ntext; + int ctext; + int* preeditAttributeBlocks; + int nblocks; + int cblocks; + int preeditCursorPosX, preeditCursorPosY, preeditCursorHeight; + _GLFWcontext context; struct { @@ -580,6 +589,8 @@ struct _GLFWwindow GLFWkeyfun key; GLFWcharfun character; GLFWcharmodsfun charmods; + GLFWpreeditfun preedit; + GLFWimestatusfun imestatus; GLFWdropfun drop; } callbacks; @@ -934,6 +945,8 @@ void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods); void _glfwInputChar(_GLFWwindow* window, uint32_t codepoint, int mods, GLFWbool plain); +void _glfwInputPreedit(_GLFWwindow* window, int focusedBlock); +void _glfwInputIMEStatus(_GLFWwindow* window); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); @@ -954,6 +967,9 @@ void _glfwInputError(int code, const char* format, ...) void _glfwInputError(int code, const char* format, ...); #endif +void _glfwPlatformResetPreeditText(_GLFWwindow* window); +void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active); +int _glfwPlatformGetIMEStatus(_GLFWwindow* window); ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// diff --git a/src/win32_window.c b/src/win32_window.c index d014944b..246318e8 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -35,6 +35,7 @@ #include #include #include +#include // Returns the window style for the specified window // @@ -529,6 +530,15 @@ static void maximizeWindowManually(_GLFWwindow* window) SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED); } +// Set cursor position to decide candidate window +static void _win32ChangeCursorPosition(HIMC hIMC, _GLFWwindow* window) { + int x = window->preeditCursorPosX; + int y = window->preeditCursorPosY; + int h = window->preeditCursorHeight; + CANDIDATEFORM excludeRect = {0, CFS_EXCLUDE, {x, y}, {x, y, x, y+h}}; + ImmSetCandidateWindow(hIMC, &excludeRect); +} + // Window procedure for user-created windows // static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) @@ -800,6 +810,93 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l break; } + case WM_IME_COMPOSITION: + { + if (lParam & GCS_RESULTSTR) { + window->nblocks = 0; + window->ntext = 0; + _glfwInputPreedit(window, 0); + return TRUE; + } + if (lParam & GCS_COMPSTR) { + HIMC hIMC = ImmGetContext(hWnd); + // get preedit data sizes + LONG preeditTextLength = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0); + LONG attrLength = ImmGetCompositionString(hIMC, GCS_COMPATTR, NULL, 0); + LONG clauseLength = ImmGetCompositionString(hIMC, GCS_COMPCLAUSE, NULL, 0); + if (preeditTextLength > 0) { + // get preedit data + int length = preeditTextLength/sizeof(WCHAR); + LPWSTR buffer = (LPWSTR)_glfw_calloc(preeditTextLength, sizeof(WCHAR)); + LPSTR attributes = (LPSTR)_glfw_calloc(attrLength, 1); + DWORD *clauses = (DWORD*)_glfw_calloc(clauseLength, 1); + ImmGetCompositionStringW(hIMC, GCS_COMPSTR, buffer, preeditTextLength); + ImmGetCompositionString(hIMC, GCS_COMPATTR, attributes, attrLength); + ImmGetCompositionString(hIMC, GCS_COMPCLAUSE, clauses, clauseLength); + // store preedit text + int ctext = window->ctext; + while (ctext < length+1) { + ctext = (ctext == 0) ? 1 : ctext*2; + } + if (ctext != window->ctext) { + unsigned int* preeditText = _glfw_realloc(window->preeditText, sizeof(unsigned int)*ctext); + if (preeditText == NULL) { + return 0; + _glfw_free(buffer); + _glfw_free(attributes); + _glfw_free(clauses); + } + window->preeditText = preeditText; + window->ctext = ctext; + } + window->ntext = length; + window->preeditText[length] = 0; + int i; + for (i=0; i < length; i++) { + window->preeditText[i] = buffer[i]; + } + // store blocks + window->nblocks = clauseLength/sizeof(DWORD)-1; + // last element of clauses is a block count, but + // text length is convenient. + clauses[window->nblocks] = length; + int cblocks = window->cblocks; + while (cblocks < window->nblocks) { + cblocks = (cblocks == 0) ? 1 : cblocks*2; + } + if (cblocks != window->cblocks) { + int* blocks = _glfw_realloc(window->preeditAttributeBlocks, sizeof(int)*cblocks); + if (blocks == NULL) { + return 0; + _glfw_free(buffer); + _glfw_free(attributes); + _glfw_free(clauses); + } + window->preeditAttributeBlocks = blocks; + window->cblocks = cblocks; + } + int focusedBlock = 0; + for (i=0; i < window->nblocks; i++) { + window->preeditAttributeBlocks[i] = clauses[i+1]-clauses[i]; + if (attributes[clauses[i]] != ATTR_CONVERTED) { + focusedBlock = i; + } + } + _glfw_free(buffer); + _glfw_free(attributes); + _glfw_free(clauses); + _glfwInputPreedit(window, focusedBlock); + _win32ChangeCursorPosition(hIMC, window); + } + ImmReleaseContext(hWnd, hIMC); + return TRUE; + } + break; + } + case WM_IME_NOTIFY: + if (wParam == IMN_SETOPENSTATUS) + _glfwInputIMEStatus(window); + break; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: @@ -2575,6 +2672,31 @@ VkResult _glfwCreateWindowSurfaceWin32(VkInstance instance, return err; } +void _glfwPlatformResetPreeditText(_GLFWwindow* window) +{ + HWND hWnd = window->win32.handle; + HIMC hIMC = ImmGetContext(hWnd); + ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + ImmReleaseContext(hWnd, hIMC); +} + +void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active) +{ + HWND hWnd = window->win32.handle; + HIMC hIMC = ImmGetContext(hWnd); + ImmSetOpenStatus(hIMC, active ? TRUE : FALSE); + ImmReleaseContext(hWnd, hIMC); +} + +int _glfwPlatformGetIMEStatus(_GLFWwindow* window) +{ + HWND hWnd = window->win32.handle; + HIMC hIMC = ImmGetContext(hWnd); + BOOL result = ImmGetOpenStatus(hIMC); + ImmReleaseContext(hWnd, hIMC); + return result ? GLFW_TRUE : GLFW_FALSE; +} + GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); diff --git a/src/window.c b/src/window.c index e03121a4..2f4ca43d 100644 --- a/src/window.c +++ b/src/window.c @@ -495,6 +495,11 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow* handle) *prev = window->next; } + // Clear memory for preedit text + if (window->preeditText) + _glfw_free(window->preeditText); + if (window->preeditAttributeBlocks) + _glfw_free(window->preeditAttributeBlocks); _glfw_free(window->title); _glfw_free(window); }