Win32: Support preedit candidate feature

You can use this feature when you need to manage the drawing of
the preedit candidates on the application side.
This commit is contained in:
Daijiro Fukuda 2023-04-10 11:05:26 +09:00 committed by Takuro Ashie
parent 79fe47e970
commit 4a2883b4e2
11 changed files with 503 additions and 46 deletions

View File

@ -140,10 +140,17 @@ information on what to include when reporting a bug.
area (#2130)
- Added `glfwResetPreeditText` function to reset preedit of input method
(#2130)
- Added `glfwSetPreeditCandidateCallback` function and
`GLFWpreeditcandidatefun` type for preedit candidates (#2130)
- Added `glfwGetPreeditCandidate` function to get a preeidt candidate text
(#2130)
- Added `GLFW_IME` input mode for `glfwGetInputMode` and `glfwSetInputMode`
(#2130)
- Added `GLFW_X11_ONTHESPOT` init hint for using on-the-spot input method
style on X11 (#2130)
- Added `GLFW_MANAGE_PREEDIT_CANDIDATE` init hint for displaying preedit
candidates on the application side (supported only on Windows currently)
(#2130)
## Contact

View File

@ -414,6 +414,80 @@ glfwResetPreeditText(window);
@endcode
@subsection manage_preedit_candidate Manage preedit candidate
By default, the IME manages the drawing of the preedit candidates, but
sometimes you need to do that on the application side for some reason. In such
a case, you can use
[GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint) init hint.
By setting this to `GLFW_TRUE`, the IME stops managing the drawing of the
candidates and the application needs to manage it by using the following
functions.
@note
@win32 Only the OS currently supports this hint.
You can register the candidate callback as follows.
@code
glfwSetPreeditCandidateCallback(window, candidate_callback);
@endcode
The callback receives the following information.
@code
void candidate_callback(GLFWwindow* window,
int candidates_count,
int selected_index,
int page_start,
int page_size)
{
}
@endcode
`candidates_count` is the number of total candidates. `selected_index` is the
index of the currently selected candidate. Normally all candidates should not
be displayed at once, but divided into pages. You can use `page_start` and
`page_size` to manage the pages. `page_start` is the index of the first
candidate on the current page. `page_size` is the number of the candidates on
the current page.
You can get the text of the candidate on the specific index as follows. Each
character of the returned text is a native endian UTF-32.
@code
int text_count;
unsigned int* text = glfwGetPreeditCandidate(window, index, &text_count);
@endcode
A sample code to get all candidate texts on the current page is as follows.
@code
void candidate_callback(GLFWwindow* window, int candidates_count,
int selected_index, int page_start, int page_size)
{
int i, j;
for (i = 0; i < page_size; ++i)
{
int index = i + page_start;
int text_count;
unsigned int* text = glfwGetPreeditCandidate(window, index, &text_count);
if (index == selected_index)
printf("> ");
for (j = 0; j < text_count; ++j)
{
char encoded[5] = "";
encode_utf8(encoded, text[j]); // Some kind of encoding process
printf("%s", encoded);
}
printf("\n");
}
}
glfwSetPreeditCandidateCallback(window, candidate_callback);
@endcode
## Mouse input {#input_mouse}
Mouse input comes in many forms, including mouse motion, button presses and

View File

@ -118,6 +118,16 @@ The ANGLE platform type is specified via the `EGL_ANGLE_platform_angle`
extension. This extension is not used if this hint is
`GLFW_ANGLE_PLATFORM_TYPE_NONE`, which is the default value.
@anchor GLFW_MANAGE_PREEDIT_CANDIDATE_hint
__GLFW_MANAGE_PREEDIT_CANDIDATE__ specifies whether to manage the preedit
candidates on the application side. Possible values are `GLFW_TRUE` and
`GLFW_FALSE`. The default is `GLFW_FALSE` and there is no need to manage
the candidates on the application side. When you need to do that on the
application side for some reason, you can enable this hint. Please see
@ref ime_support for more information about IME support.
@win32 Only the OS currently supports this hint.
#### macOS specific init hints {#init_hints_osx}
@ -165,10 +175,11 @@ it is recommended not to use this hint in normal cases. Possible values are
#### Supported and default values {#init_hints_values}
Initialization hint | Default value | Supported values
-------------------------------- | ------------------------------- | ----------------
---------------------------------- | ------------------------------- | ----------------
@ref GLFW_PLATFORM | `GLFW_ANY_PLATFORM` | `GLFW_ANY_PLATFORM`, `GLFW_PLATFORM_WIN32`, `GLFW_PLATFORM_COCOA`, `GLFW_PLATFORM_WAYLAND`, `GLFW_PLATFORM_X11` or `GLFW_PLATFORM_NULL`
@ref GLFW_JOYSTICK_HAT_BUTTONS | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
@ref GLFW_ANGLE_PLATFORM_TYPE | `GLFW_ANGLE_PLATFORM_TYPE_NONE` | `GLFW_ANGLE_PLATFORM_TYPE_NONE`, `GLFW_ANGLE_PLATFORM_TYPE_OPENGL`, `GLFW_ANGLE_PLATFORM_TYPE_OPENGLES`, `GLFW_ANGLE_PLATFORM_TYPE_D3D9`, `GLFW_ANGLE_PLATFORM_TYPE_D3D11`, `GLFW_ANGLE_PLATFORM_TYPE_VULKAN` or `GLFW_ANGLE_PLATFORM_TYPE_METAL`
@ref GLFW_MANAGE_PREEDIT_CANDIDATE | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
@ref GLFW_COCOA_CHDIR_RESOURCES | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
@ref GLFW_COCOA_MENUBAR | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
@ref GLFW_WAYLAND_LIBDECOR | `GLFW_WAYLAND_PREFER_LIBDECOR` | `GLFW_WAYLAND_PREFER_LIBDECOR` or `GLFW_WAYLAND_DISABLE_LIBDECOR`

View File

@ -1309,6 +1309,11 @@ extern "C" {
* Platform selection [init hint](@ref GLFW_PLATFORM).
*/
#define GLFW_PLATFORM 0x00050003
/*! @brief Preedit candidate init hint.
*
* Preedit candidate [init hint](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint).
*/
#define GLFW_MANAGE_PREEDIT_CANDIDATE 0x00050004
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
@ -1989,6 +1994,29 @@ typedef void (* GLFWpreeditfun)(GLFWwindow* window,
*/
typedef void (* GLFWimestatusfun)(GLFWwindow* window);
/*! @brief The function pointer type for preedit candidate callbacks.
*
* This is the function pointer type for preedit candidate callback functions.
* Use @ref glfwGetPreeditCandidate to get the candidate text for a specific index.
*
* @param[in] window The window that received the event.
* @param[in] candidates_count Candidates count.
* @param[in] selected_index.Index of selected candidate.
* @param[in] page_start Start index of candidate currently displayed.
* @param[in] page_size Count of candidates currently displayed.
*
* @sa @ref ime_support
* @sa @ref glfwSetPreeditCandidateCallback
* @sa @ref glfwGetPreeditCandidate
*
* @ingroup input
*/
typedef void (* GLFWpreeditcandidatefun)(GLFWwindow* window,
int candidates_count,
int selected_index,
int page_start,
int page_size);
/*! @brief The function pointer type for path drop callbacks.
*
* This is the function pointer type for path drop callbacks. A path drop
@ -5270,6 +5298,34 @@ GLFWAPI void glfwSetPreeditCursorRectangle(GLFWwindow* window, int x, int y, int
*/
GLFWAPI void glfwResetPreeditText(GLFWwindow* window);
/*! @brief Returns the preedit candidate.
*
* This function returns the text and the text-count of the preedit candidate.
*
* By default, the IME manages the preedit candidates, so there is no need to
* use this function. See @ref glfwSetPreeditCandidateCallback and
* [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint) for details.
*
* @param[in] window The window.
* @param[in] index The index of the candidate.
* @param[out] textCount The text-count of the candidate.
* @return The text of the candidate as Unicode code points.
*
* @remark @macos @x11 @wayland Don't support this function.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref ime_support
* @sa @ref glfwSetPreeditCandidateCallback
* @sa [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint)
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI unsigned int* glfwGetPreeditCandidate(GLFWwindow* window, int index, int* textCount);
/*! @brief Sets the key callback.
*
* This function sets the key callback of the specified window, which is called
@ -5478,6 +5534,51 @@ GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* window, GLFWpreeditfun
*/
GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* window, GLFWimestatusfun cbfun);
/*! @brief Sets the preedit candidate change callback.
*
* This function sets the preedit candidate callback of the specified
* window, which is called when the candidates are updated and can be used
* to display them by the application side.
*
* By default, this callback is not called because the IME displays the
* candidates and there is nothing to do on the application side. Only when
* the application side needs to use this to manage the displaying of
* IME candidates, you can set
* [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint) init hint
* and stop the IME from managing it.
*
* @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.
*
* @callback_signature
* @code
* void function_name(GLFWwindow* window,
int candidates_count,
int selected_index,
int page_start,
int page_size)
* @endcode
* For more information about the callback parameters, see the
* [function pointer type](@ref GLFWpreeditcandidatefun).
*
* @remark @macos @x11 @wayland Don't support this function. The callback is
* not called.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref ime_support
* @sa [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint)
*
* @since Added in GLFW 3.X
*
* @ingroup input
*/
GLFWAPI GLFWpreeditcandidatefun glfwSetPreeditCandidateCallback(GLFWwindow* window, GLFWpreeditcandidatefun cbfun);
/*! @brief Sets the mouse button callback.
*
* This function sets the mouse button callback of the specified window, which

View File

@ -52,6 +52,7 @@ static _GLFWinitconfig _glfwInitHints =
.hatButtons = GLFW_TRUE,
.angleType = GLFW_ANGLE_PLATFORM_TYPE_NONE,
.platformID = GLFW_ANY_PLATFORM,
.managePreeditCandidate = GLFW_FALSE,
.vulkanLoader = NULL,
.ns =
{
@ -474,6 +475,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
case GLFW_PLATFORM:
_glfwInitHints.platformID = value;
return;
case GLFW_MANAGE_PREEDIT_CANDIDATE:
_glfwInitHints.managePreeditCandidate = value;
return;
case GLFW_COCOA_CHDIR_RESOURCES:
_glfwInitHints.ns.chdir = value;
return;

View File

@ -355,6 +355,21 @@ void _glfwInputIMEStatus(_GLFWwindow* window)
}
}
// Notifies shared code of a preedit candidate event
//
void _glfwInputPreeditCandidate(_GLFWwindow* window)
{
if (window->callbacks.preeditCandidate)
{
_GLFWpreedit* preedit = &window->preedit;
window->callbacks.preeditCandidate((GLFWwindow*) window,
preedit->candidateCount,
preedit->candidateSelection,
preedit->candidatePageStart,
preedit->candidatePageSize);
}
}
// Notifies shared code of a scroll event
//
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
@ -1029,6 +1044,21 @@ GLFWAPI void glfwResetPreeditText(GLFWwindow* handle)
_glfw.platform.resetPreeditText(window);
}
GLFWAPI unsigned int* glfwGetPreeditCandidate(GLFWwindow* handle, int index, int* textCount)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFWpreedit* preedit = &window->preedit;
if (preedit->candidateCount <= index)
return NULL;
if (textCount)
*textCount = preedit->candidates[index].textCount;
return preedit->candidates[index].text;
}
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@ -1078,6 +1108,15 @@ GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* handle, GLFWimesta
return cbfun;
}
GLFWAPI GLFWpreeditcandidatefun glfwSetPreeditCandidateCallback(GLFWwindow* handle,
GLFWpreeditcandidatefun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP(GLFWpreeditcandidatefun, window->callbacks.preeditCandidate, cbfun);
return cbfun;
}
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
GLFWmousebuttonfun cbfun)
{

View File

@ -70,6 +70,7 @@ typedef struct _GLFWctxconfig _GLFWctxconfig;
typedef struct _GLFWfbconfig _GLFWfbconfig;
typedef struct _GLFWcontext _GLFWcontext;
typedef struct _GLFWpreedit _GLFWpreedit;
typedef struct _GLFWpreeditcandidate _GLFWpreeditcandidate;
typedef struct _GLFWwindow _GLFWwindow;
typedef struct _GLFWplatform _GLFWplatform;
typedef struct _GLFWlibrary _GLFWlibrary;
@ -378,6 +379,7 @@ struct _GLFWinitconfig
GLFWbool hatButtons;
int angleType;
int platformID;
GLFWbool managePreeditCandidate;
PFN_vkGetInstanceProcAddr vulkanLoader;
struct {
GLFWbool menubar;
@ -540,6 +542,24 @@ struct _GLFWpreedit
int focusedBlockIndex;
int caretIndex;
int cursorPosX, cursorPosY, cursorWidth, cursorHeight;
// Used only when apps display candidates by themselves.
// Usually, OS displays them, so apps don't need to do it.
_GLFWpreeditcandidate* candidates;
int candidateCount;
int candidateBufferCount;
int candidateSelection;
int candidatePageStart;
int candidatePageSize;
};
// Preedit candidate structure
//
struct _GLFWpreeditcandidate
{
unsigned int* text;
int textCount;
int textBufferCount;
};
// Window and context structure
@ -601,6 +621,7 @@ struct _GLFWwindow
GLFWcharmodsfun charmods;
GLFWpreeditfun preedit;
GLFWimestatusfun imestatus;
GLFWpreeditcandidatefun preeditCandidate;
GLFWdropfun drop;
} callbacks;
@ -961,6 +982,7 @@ void _glfwInputChar(_GLFWwindow* window,
uint32_t codepoint, int mods, GLFWbool plain);
void _glfwInputPreedit(_GLFWwindow* window);
void _glfwInputIMEStatus(_GLFWwindow* window);
void _glfwInputPreeditCandidate(_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);

View File

@ -170,6 +170,8 @@ static GLFWbool loadLibraries(void)
_glfw.win32.imm32.instance = _glfwPlatformLoadModule("imm32.dll");
if (_glfw.win32.imm32.instance)
{
_glfw.win32.imm32.ImmGetCandidateListW_ = (PFN_ImmGetCandidateListW)
_glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetCandidateListW");
_glfw.win32.imm32.ImmGetCompositionStringW_ = (PFN_ImmGetCompositionStringW)
_glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetCompositionStringW");
_glfw.win32.imm32.ImmGetContext_ = (PFN_ImmGetContext)

View File

@ -318,6 +318,7 @@ typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLO
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
// imm32 function pointer typedefs
typedef DWORD (WINAPI * PFN_ImmGetCandidateListW)(HIMC,DWORD,LPCANDIDATELIST,DWORD);
typedef LONG (WINAPI * PFN_ImmGetCompositionStringW)(HIMC,DWORD,LPVOID,DWORD);
typedef HIMC (WINAPI * PFN_ImmGetContext)(HWND);
typedef BOOL (WINAPI * PFN_ImmGetConversionStatus)(HIMC,LPDWORD,LPDWORD);
@ -327,6 +328,7 @@ typedef BOOL (WINAPI * PFN_ImmNotifyIME)(HIMC,DWORD,DWORD,DWORD);
typedef BOOL (WINAPI * PFN_ImmReleaseContext)(HWND,HIMC);
typedef BOOL (WINAPI * PFN_ImmSetCandidateWindow)(HIMC,LPCANDIDATEFORM);
typedef BOOL (WINAPI * PFN_ImmSetOpenStatus)(HIMC,BOOL);
#define ImmGetCandidateListW _glfw.win32.imm32.ImmGetCandidateListW_
#define ImmGetCompositionStringW _glfw.win32.imm32.ImmGetCompositionStringW_
#define ImmGetContext _glfw.win32.imm32.ImmGetContext_
#define ImmGetConversionStatus _glfw.win32.imm32.ImmGetConversionStatus_
@ -526,6 +528,7 @@ typedef struct _GLFWlibraryWin32
struct {
HINSTANCE instance;
PFN_ImmGetCandidateListW ImmGetCandidateListW_;
PFN_ImmGetCompositionStringW ImmGetCompositionStringW_;
PFN_ImmGetContext ImmGetContext_;
PFN_ImmGetConversionStatus ImmGetConversionStatus_;

View File

@ -35,6 +35,7 @@
#include <assert.h>
#include <windowsx.h>
#include <shellapi.h>
#include <wchar.h>
// Converts utf16 units to Unicode code points (UTF32).
// Returns GLFW_TRUE when the converting completes and the result is assigned to
@ -564,6 +565,117 @@ static void maximizeWindowManually(_GLFWwindow* window)
SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
// Store candidate text from the buffer data
//
static void setCandidate(_GLFWpreeditcandidate* candidate, LPWSTR buffer)
{
size_t bufferCount = wcslen(buffer);
int textBufferCount = candidate->textBufferCount;
uint32_t codepoint;
WCHAR highSurrogate = 0;
int convertedLength = 0;
int i;
while ((size_t) textBufferCount < bufferCount + 1)
textBufferCount = (textBufferCount == 0) ? 1 : textBufferCount * 2;
if (textBufferCount != candidate->textBufferCount)
{
unsigned int* text =
_glfw_realloc(candidate->text,
sizeof(unsigned int) * textBufferCount);
if (text == NULL)
return;
candidate->text = text;
candidate->textBufferCount = textBufferCount;
}
for (i = 0; (size_t) i < bufferCount; ++i)
{
if (convertToUTF32FromUTF16(buffer[i],
&highSurrogate,
&codepoint))
candidate->text[convertedLength++] = codepoint;
}
candidate->textCount = convertedLength;
}
// Get preedit candidates of Imm32 and pass them to candidate-callback
//
static void getImmCandidates(_GLFWwindow* window)
{
_GLFWpreedit* preedit = &window->preedit;
HIMC hIMC = ImmGetContext(window->win32.handle);
DWORD candidateListBytes = ImmGetCandidateListW(hIMC, 0, NULL, 0);
if (candidateListBytes == 0)
{
ImmReleaseContext(window->win32.handle, hIMC);
return;
}
{
int i;
int bufferCount = preedit->candidateBufferCount;
LPCANDIDATELIST candidateList = _glfw_calloc(candidateListBytes, 1);
if (candidateList == NULL)
{
ImmReleaseContext(window->win32.handle, hIMC);
return;
}
ImmGetCandidateListW(hIMC, 0, candidateList, candidateListBytes);
ImmReleaseContext(window->win32.handle, hIMC);
while ((DWORD) bufferCount < candidateList->dwCount + 1)
bufferCount = (bufferCount == 0) ? 1 : bufferCount * 2;
if (bufferCount != preedit->candidateBufferCount)
{
_GLFWpreeditcandidate* candidates =
_glfw_realloc(preedit->candidates,
sizeof(_GLFWpreeditcandidate) * bufferCount);
if (candidates == NULL)
{
_glfw_free(candidateList);
return;
}
// `realloc` does not initialize the increased area with 0.
// This logic should be moved to a more appropriate place to share
// when other platforms support this feature.
for (i = preedit->candidateBufferCount; i < bufferCount; ++i)
{
candidates[i].text = NULL;
candidates[i].textCount = 0;
candidates[i].textBufferCount = 0;
}
preedit->candidates = candidates;
preedit->candidateBufferCount = bufferCount;
}
for (i = 0; (DWORD) i < candidateList->dwCount; ++i)
setCandidate(&preedit->candidates[i],
(LPWSTR)((char*) candidateList + candidateList->dwOffset[i]));
preedit->candidateCount = candidateList->dwCount;
preedit->candidateSelection = candidateList->dwSelection;
preedit->candidatePageStart = candidateList->dwPageStart;
preedit->candidatePageSize = candidateList->dwPageSize;
_glfw_free(candidateList);
}
_glfwInputPreeditCandidate(window);
}
// Clear preedit candidates
static void clearImmCandidate(_GLFWwindow* window)
{
window->preedit.candidateCount = 0;
window->preedit.candidateSelection = 0;
window->preedit.candidatePageStart = 0;
window->preedit.candidatePageSize = 0;
_glfwInputPreeditCandidate(window);
}
// Get preedit texts of Imm32 and pass them to preedit-callback
//
static GLFWbool getImmPreedit(_GLFWwindow* window)
@ -798,6 +910,12 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
// To draw preedit text by an application side
if (lParam & ISC_SHOWUICOMPOSITIONWINDOW)
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
if (_glfw.hints.init.managePreeditCandidate &&
(lParam & ISC_SHOWUICANDIDATEWINDOW))
{
lParam &= ~ISC_SHOWUICANDIDATEWINDOW;
}
break;
}
@ -1044,16 +1162,35 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
case WM_IME_ENDCOMPOSITION:
{
clearImmPreedit(window);
// Usually clearing candidates in IMN_CLOSECANDIDATE is sufficient.
// However, some IME need it here, e.g. Google Japanese Input.
clearImmCandidate(window);
return TRUE;
}
case WM_IME_NOTIFY:
{
if (wParam == IMN_SETOPENSTATUS)
switch (wParam)
{
case IMN_SETOPENSTATUS:
{
_glfwInputIMEStatus(window);
return TRUE;
}
case IMN_OPENCANDIDATE:
case IMN_CHANGECANDIDATE:
{
getImmCandidates(window);
return TRUE;
}
case IMN_CLOSECANDIDATE:
{
clearImmCandidate(window);
return TRUE;
}
}
break;
}

View File

@ -119,11 +119,16 @@ static int currentIMEStatus = GLFW_FALSE;
#define MAX_PREEDIT_LEN 128
static char preeditBuf[MAX_PREEDIT_LEN] = "";
// Assuming that the page-size is 10 at most.
static char candidateBuf[9][MAX_PREEDIT_LEN];
static int candidatePageSize = 0;
void usage(void)
{
printf("Usage: input_text [-h] [-s]\n");
printf("Usage: input_text [-h] [-s] [-c]\n");
printf("Options:\n");
printf(" -s Use on-the-spot sytle on X11. This is ignored on other platforms.\n");
printf(" -c Use manage-preedit-candidate on Win32. This is ignored on other platforms.\n");
printf(" -h Show this help\n");
}
@ -566,11 +571,32 @@ static void set_preedit_labels(GLFWwindow* window, struct nk_context* nk, int he
nk_layout_row_end(nk);
}
static void set_candidate_labels(GLFWwindow* window, struct nk_context* nk)
{
for (int i = 0; i < 5; ++i)
{
nk_layout_row_begin(nk, NK_DYNAMIC, 30, 3);
nk_layout_row_push(nk, 1.f / 3.f);
if (i == 0)
nk_label(nk, "Candidates:", NK_TEXT_LEFT);
else
nk_label(nk, "", NK_TEXT_LEFT);
nk_layout_row_push(nk, 1.f / 3.f);
if (candidatePageSize > i)
nk_label(nk, (const char*) candidateBuf[i], NK_TEXT_LEFT);
nk_layout_row_push(nk, 1.f / 3.f);
if (candidatePageSize > i + 5)
nk_label(nk, (const char*) candidateBuf[i + 5], NK_TEXT_LEFT);
nk_layout_row_end(nk);
}
}
// If it is possible to take the text-cursor position calculated in `nk_do_edit` function in `deps/nuklear.h`,
// we can set preedit-cursor position more easily.
// However, there doesn't seem to be a way to do that, so this does a simplified calculation only for the end
// of the text. (Can not trace the cursor movement)
static void update_cursor_pos(GLFWwindow* window, struct nk_context* nk, struct nk_user_font* f, char* boxBuffer, int boxLen)
static void update_cursor_pos(GLFWwindow* window, struct nk_context* nk, struct nk_user_font* f,
char* boxBuffer, int boxLen, int boxX, int boxY)
{
float lineWidth = 0;
int totalLines = 1;
@ -606,14 +632,10 @@ static void update_cursor_pos(GLFWwindow* window, struct nk_context* nk, struct
}
{
// I don't know how to get these info.
int widgetLayoutX = 10;
int widgetLayoutY = 220;
int lineHeight = f->height + nk->style.edit.row_padding;
int cursorPosX = widgetLayoutX + lineWidth;
int cursorPosY = widgetLayoutY + lineHeight * (totalLines - 1);
int cursorPosX = boxX + lineWidth;
int cursorPosY = boxY + lineHeight * (totalLines - 1);
int cursorHeight = lineHeight;
int cursorWidth;
@ -686,6 +708,30 @@ static void preedit_callback(GLFWwindow* window, int preeditCount,
}
}
static void candidate_callback(GLFWwindow* window, int candidates_count,
int selected_index, int page_start, int page_size)
{
int i, j;
candidatePageSize = page_size;
for (i = 0; i < page_size; ++i)
{
int index = i + page_start;
int textCount;
unsigned int* text = glfwGetPreeditCandidate(window, index, &textCount);
if (index == selected_index)
strcpy(candidateBuf[i], "> ");
else
strcpy(candidateBuf[i], "");
for (j = 0; j < textCount; ++j)
{
char encoded[5] = "";
encode_utf8(encoded, text[j]);
if (strlen(candidateBuf[i]) + strlen(encoded) < MAX_PREEDIT_LEN)
strcat(candidateBuf[i], encoded);
}
}
}
int main(int argc, char** argv)
{
GLFWwindow* window;
@ -694,9 +740,10 @@ int main(int argc, char** argv)
char boxBuffer[MAX_BUFFER_LEN] = "Input text here.";
int boxLen = strlen(boxBuffer);
int isAutoUpdatingCursorPosEnabled = GLFW_TRUE;
int managePreeditCandidate = GLFW_FALSE;
int ch;
while ((ch = getopt(argc, argv, "hs")) != -1)
while ((ch = getopt(argc, argv, "hsc")) != -1)
{
switch (ch)
{
@ -707,6 +754,11 @@ int main(int argc, char** argv)
case 's':
glfwInitHint(GLFW_X11_ONTHESPOT, GLFW_TRUE);
break;
case 'c':
glfwInitHint(GLFW_MANAGE_PREEDIT_CANDIDATE, GLFW_TRUE);
managePreeditCandidate = GLFW_TRUE;
break;
}
}
@ -729,6 +781,7 @@ int main(int argc, char** argv)
glfwSetPreeditCursorRectangle(window, 0, 0, 1, 1);
glfwSetIMEStatusCallback(window, ime_callback);
glfwSetPreeditCallback(window, preedit_callback);
glfwSetPreeditCandidateCallback(window, candidate_callback);
glfwMakeContextCurrent(window);
gladLoadGL(glfwGetProcAddress);
@ -757,6 +810,8 @@ int main(int argc, char** argv)
set_preedit_cursor_edit(window, nk, 30, &isAutoUpdatingCursorPosEnabled);
set_ime_stauts_labels(window, nk, 30);
set_preedit_labels(window, nk, 30);
if (managePreeditCandidate)
set_candidate_labels(window, nk);
nk_layout_row_dynamic(nk, height - 250, 1);
nk_edit_string(nk, NK_EDIT_BOX, boxBuffer, &boxLen, MAX_BUFFER_LEN, nk_filter_default);
@ -768,7 +823,9 @@ int main(int argc, char** argv)
glfwSwapBuffers(window);
if (isAutoUpdatingCursorPosEnabled)
update_cursor_pos(window, nk, &currentFont->handle, boxBuffer, boxLen);
// I don't know how to get the layout info of `nk_edit_string`.
update_cursor_pos(window, nk, &currentFont->handle, boxBuffer, boxLen, 10,
managePreeditCandidate ? 385 : 220);
glfwWaitEvents();
}