Add preedit text callback API

Fixes #41.
Closes #658.
This commit is contained in:
Yoshiki Shibukawa 2015-12-09 12:05:39 +09:00 committed by Camilla Löwy
parent f383f7721c
commit 779b56276c
15 changed files with 959 additions and 16 deletions

1
.gitignore vendored
View File

@ -81,4 +81,3 @@ tests/timeout
tests/title
tests/vulkan
tests/windows

View File

@ -226,6 +226,7 @@ endif()
if (_GLFW_WIN32)
list(APPEND glfw_PKG_LIBS "-lgdi32")
list(APPEND glfw_LIBRARIES "imm32")
if (GLFW_USE_HYBRID_HPG)
set(_GLFW_USE_HYBRID_HPG 1)

View File

@ -206,6 +206,96 @@ void charmods_callback(GLFWwindow* window, unsigned int codepoint, int mods)
}
@endcode
@subsection preedit IME Support
All desktop operating systems support IME (Input Method Editor) to input characters
that are not mapped with physical keys. IME have been popular among Eeastern Asian people.
And some operating systems start supporting voice input via IME mechanism.
GLFW provides IME support functions to help
you implement better text input features. You should add suitable visualization code for
preedit text.
IME works in front of actual character input events (@ref input_char).
If your application uses text input and you want to support IME,
you should register preedit callback to receive preedit text before committed.
@code
glfwSetPreeditCallback(window, preedit_callback);
@endcode
The callback function receives chunk of text and focused block information.
@code
static void preedit_callback(GLFWwindow* window, int strLength, unsigned int* string, int blockLength, int* blocks, int focusedBlock) {
}
@endcode
strLength and string parameter reprsent whole preedit text. Each character of the preedit string is a codepoint like @ref input_char.
If you want to type the text "寿司(sushi)", Usually the callback is called several times like the following sequence:
-# key event: s
-# preedit: [string: "", block: [1], focusedBlock: 0]
-# key event: u
-# preedit: [string: "す", block: [1], focusedBlock: 0]
-# key event: s
-# preedit: [string: "すs", block: [2], focusedBlock: 0]
-# key event: h
-# preedit: [string: "すsh", block: [2], focusedBlock: 0]
-# key event: i
-# preedit: [string: "すし", block: [2], focusedBlock: 0]
-# key event: ' '
-# preedit: [string: "寿司", block: [2], focusedBlock: 0]
-# char: '寿'
-# char: '司'
-# preedit: [string: "", block: [], focusedBlock: 0]
If preedit text includes several semantic blocks, preedit callbacks returns several blocks after a space key pressed:
-# preedit: [string: "わたしはすしをたべます", block: [11], focusedBlock: 0]
-# preedit: [string: "私は寿司を食べます", block: [2, 7], focusedBlock: 1]
"blocks" is a list of block length. The above case, it contains the following blocks and second block is focused.
- 私は
- [寿司を食べます]
commited text(passed via regular @ref input_char event), unfocused block, focused block should have different text style.
GLFW provides helper function to teach suitable position of the candidate window to window system.
Window system decides the best position from text cursor geometry (x, y coords and height). You should call this function
in the above preedit text callback function.
@code
glfwSetPreeditCursorPos(window, x, y, h);
glfwGetPreeditCursorPos(window, &x, &y, &h);
@endcode
Sometimes IME task is interrupted by user or application. There are several functions to support these situation.
You can receive notification about IME status change(on/off) by using the following function:
@code
glfwSetIMEStatusCallback(window, imestatus_callback);
@endcode
imestatus_callback has simple sigunature like this:
@code
static void imestatus_callback(GLFWwindow* window) {
}
@endcode
You can implement the code that resets or commits preedit text when IME status is changed and preedit text is not empty.
When the focus is gone from text box, you can use the following functions to reset IME status:
@code
void glfwResetPreeditText(GLFWwindow* window);
void glfwSetIMEStatus(GLFWwindow* window, int active)
int glfwGetIMEStatus(GLFWwindow* window)
@endcode
@subsection input_key_name Key names

View File

@ -686,6 +686,7 @@ extern "C" {
#define GLFW_CURSOR 0x00033001
#define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_IME 0x00033004
#define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002
@ -1100,6 +1101,37 @@ typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int);
*/
typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int);
/*! @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 signature for file drop callbacks.
*
* This is the function signature for file drop callbacks.
@ -3009,12 +3041,12 @@ GLFWAPI void glfwPostEmptyEvent(void);
/*! @brief Returns the value of an input option for the specified window.
*
* This function returns the value of an input option for the specified window.
* The mode must be one of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or
* `GLFW_STICKY_MOUSE_BUTTONS`.
* The mode must be one of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_IME`.
*
* @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or
* `GLFW_STICKY_MOUSE_BUTTONS`.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_IME`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
@ -3032,8 +3064,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
/*! @brief Sets an input option for the specified window.
*
* This function sets an input mode option for the specified window. The mode
* must be one of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or
* `GLFW_STICKY_MOUSE_BUTTONS`.
* must be one of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_IME`.
*
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes:
@ -3059,9 +3091,12 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* you are only interested in whether mouse buttons have been pressed but not
* when or in which order.
*
* 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` or
* `GLFW_STICKY_MOUSE_BUTTONS`.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_IME`
* @param[in] value The new value of the specified input mode.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
@ -3422,6 +3457,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
@ -3536,6 +3632,58 @@ GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun);
*/
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun);
/*! @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

View File

@ -316,8 +316,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
_glfwInputWindowFocus(window, GLFW_FALSE);
}
@end
- (void)imeStatusChangeNotified:(NSNotification *)notification {
_glfwInputIMEStatus(window);
}
@end
//------------------------------------------------------------------------
// Delegate for application related notifications
@ -714,6 +717,56 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[markedText initWithAttributedString:string];
else
[markedText initWithString:string];
NSString* markedTextString = markedText.string;
NSUInteger i, length = [markedTextString length];
int ctext = window->ctext;
while (ctext < length+1) {
ctext = (ctext == 0) ? 1 : ctext*2;
}
if (ctext != window->ctext) {
unsigned int* preeditText = realloc(window->preeditText, sizeof(unsigned int)*ctext);
if (preeditText == NULL) {
return;
}
window->preeditText = preeditText;
window->ctext = ctext;
}
window->ntext = length;
window->preeditText[length] = 0;
for (i = 0; i < length; i++)
{
const unichar codepoint = [markedTextString characterAtIndex:i];
window->preeditText[i] = codepoint;
}
int focusedBlock = 0;
NSInteger offset = 0;
window->nblocks = 0;
while (offset < length) {
NSRange effectiveRange;
NSDictionary *attributes = [markedText attributesAtIndex:offset effectiveRange:&effectiveRange];
if (window->nblocks == window->cblocks) {
int cblocks = window->cblocks * 2;
int* blocks = realloc(window->preeditAttributeBlocks, sizeof(int)*cblocks);
if (blocks == NULL) {
return;
}
window->preeditAttributeBlocks = blocks;
window->cblocks = cblocks;
}
window->preeditAttributeBlocks[window->nblocks] = effectiveRange.length;
offset += effectiveRange.length;
if (effectiveRange.length == 0) {
break;
}
NSNumber* underline = (NSNumber*) [attributes objectForKey:@"NSUnderline"];
if ([underline intValue] != 1) {
focusedBlock = window->nblocks;
}
window->nblocks++;
}
_glfwInputPreedit(window, focusedBlock);
}
- (void)unmarkText
@ -742,8 +795,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
{
int xpos, ypos;
_glfwPlatformGetWindowPos(window, &xpos, &ypos);
const NSRect contentRect = [window->ns.view frame];
return NSMakeRect(xpos, transformY(ypos + contentRect.size.height), 0.0, 0.0);
return NSMakeRect(xpos + window->preeditCursorPosX, transformY(ypos + window->preeditCursorPosY), 0.0, window->preeditCursorHeight);
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
@ -1049,6 +1101,11 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
[window->ns.object setAcceptsMouseMovedEvents:YES];
[window->ns.object setRestorable:NO];
[[NSNotificationCenter defaultCenter]
addObserver: window->ns.delegate
selector:@selector(imeStatusChangeNotified:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object: nil];
return GLFW_TRUE;
}
@ -1102,6 +1159,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (_glfw.ns.disabledCursorWindow == window)
_glfw.ns.disabledCursorWindow = NULL;
[[NSNotificationCenter defaultCenter] removeObserver: window->ns.delegate];
[window->ns.object orderOut:nil];
if (window->monitor)
@ -1735,6 +1794,49 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
#endif
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
NSTextInputContext *context = [NSTextInputContext currentInputContext];
[context discardMarkedText];
[window->ns.view unmarkText];
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
// Mac OS has several input sources.
// this code assumes input methods not in ascii capable inputs using IME.
NSArray* asciiInputSources = CFBridgingRelease(TISCreateASCIICapableInputSourceList());
TISInputSourceRef asciiSource = (__bridge TISInputSourceRef)([asciiInputSources firstObject]);
if (active) {
NSArray* allInputSources = CFBridgingRelease(TISCreateInputSourceList(NULL, false));
NSString* asciiSourceID = (__bridge NSString *)(TISGetInputSourceProperty(asciiSource, kTISPropertyInputSourceID));
int i;
int count = [allInputSources count];
for (i = 0; i < count; i++) {
TISInputSourceRef source = (__bridge TISInputSourceRef)([allInputSources objectAtIndex: i]);
NSString* sourceID = (__bridge NSString *)(TISGetInputSourceProperty(source, kTISPropertyInputSourceID));
if ([asciiSourceID compare: sourceID] != NSOrderedSame) {
TISSelectInputSource(source);
break;
}
}
} else if (asciiSource) {
TISSelectInputSource(asciiSource);
}
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
TISInputSourceRef currentSource = TISCopyCurrentKeyboardInputSource();
NSString* currentSourceID = (__bridge NSString *)(TISGetInputSourceProperty(currentSource, kTISPropertyInputSourceID));
NSArray* asciiInputSources = CFBridgingRelease(TISCreateASCIICapableInputSourceList());
TISInputSourceRef asciiSource = (__bridge TISInputSourceRef)([asciiInputSources firstObject]);
if (asciiSource) {
NSString* asciiSourceID = (__bridge NSString *)(TISGetInputSourceProperty(asciiSource, kTISPropertyInputSourceID));
return ([asciiSourceID compare: currentSourceID] == NSOrderedSame) ? GLFW_FALSE : GLFW_TRUE;
}
return GLFW_FALSE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -79,6 +79,20 @@ void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWb
}
}
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);
}
}
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
{
if (window->callbacks.scroll)
@ -162,6 +176,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
return window->stickyKeys;
case GLFW_STICKY_MOUSE_BUTTONS:
return window->stickyMouseButtons;
case GLFW_IME:
return _glfwPlatformGetIMEStatus(window);
default:
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode %i", mode);
return 0;
@ -245,6 +261,13 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
window->stickyMouseButtons = value ? GLFW_TRUE : GLFW_FALSE;
return;
}
case GLFW_IME:
_glfwPlatformSetIMEStatus(window, value ? GLFW_TRUE : GLFW_FALSE);
break;
default:
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode %i", mode);
break;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode %i", mode);
@ -469,6 +492,30 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
_glfwPlatformSetCursor(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)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
@ -499,6 +546,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_POINTERS(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_POINTERS(window->callbacks.imestatus, cbfun);
return cbfun;
}
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
GLFWmousebuttonfun cbfun)
{

View File

@ -370,9 +370,19 @@ struct _GLFWwindow
int cursorMode;
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
char keys[GLFW_KEY_LAST + 1];
// Virtual cursor position when cursor is disabled
double virtualCursorPosX, virtualCursorPosY;
// Preedit texts
unsigned int* preeditText;
int ntext;
int ctext;
int* preeditAttributeBlocks;
int nblocks;
int cblocks;
int preeditCursorPosX, preeditCursorPosY, preeditCursorHeight;
_GLFWcontext context;
struct {
@ -391,6 +401,8 @@ struct _GLFWwindow
GLFWkeyfun key;
GLFWcharfun character;
GLFWcharmodsfun charmods;
GLFWpreeditfun preedit;
GLFWimestatusfun imestatus;
GLFWdropfun drop;
} callbacks;
@ -818,6 +830,46 @@ int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhy
*/
VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface);
/*! @copydoc glfwResetPreeditText
* @ingroup platform
*/
void _glfwPlatformResetPreeditText(_GLFWwindow* window);
/*! @brief Set IME status.
*
* This function set IME status.
*
* @param[in] window The window.
* @param[in] active Turns on IME if `GFLW_TRUE` is given. Otherwise (`GLFW_FALSE`) turns off.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref preedit
*
* @since Added in GLFW 3.X.
*
* @ingroup platform
*/
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active);
/*! @brief Get IME status.
*
* This function get IME status.
*
* @param[in] window The window.
* @return When IME is active, this function returns `GFLW_TRUE`. Otherwise `GLFW_FALSE`.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref preedit
*
* @since Added in GLFW 3.X.
*
* @ingroup platform
*/
int _glfwPlatformGetIMEStatus(_GLFWwindow* window);
//========================================================================
// Event API functions
@ -904,6 +956,19 @@ void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int m
*/
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain);
/*! @brief Notifies shared code of a IME preedit text update event.
* @param[in] window The window that received the event.
* @param[in] focusedBlock Focused preedit text block index.
* @ingroup event
*/
void _glfwInputPreedit(_GLFWwindow* window, int focusedBlock);
/*! @brief Notifies shared code of a IME status change.
* @param[in] window The window that received the event.
* @ingroup event
*/
void _glfwInputIMEStatus(_GLFWwindow* window);
/*! @brief Notifies shared code of a scroll event.
* @param[in] window The window that received the event.
* @param[in] xoffset The scroll offset along the x-axis.

View File

@ -890,6 +890,25 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return err;
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Mir: Unsupported function %s", __PRETTY_FUNCTION__);
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Mir: Unsupported function %s", __PRETTY_FUNCTION__);
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Mir: Unsupported function %s", __PRETTY_FUNCTION__);
return GLFW_FALSE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -301,3 +301,16 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return VK_ERROR_INITIALIZATION_FAILED;
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
return GLFW_FALSE;
}

View File

@ -33,6 +33,7 @@
#include <string.h>
#include <windowsx.h>
#include <shellapi.h>
#include <imm.h>
#define _GLFW_KEY_INVALID -2
@ -422,6 +423,15 @@ static void releaseMonitor(_GLFWwindow* window)
_glfwRestoreVideoModeWin32(window->monitor);
}
// 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 callback function (handles window messages)
//
static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
@ -536,7 +546,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
}
_glfwInputChar(window, (unsigned int) wParam, getKeyMods(), plain);
return 0;
return TRUE;
}
case WM_KEYDOWN:
@ -570,7 +580,93 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
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)malloc(sizeof(WCHAR)+preeditTextLength);
LPSTR attributes = (LPSTR)malloc(attrLength);
DWORD *clauses = (DWORD*)malloc(clauseLength);
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 = realloc(window->preeditText, sizeof(unsigned int)*ctext);
if (preeditText == NULL) {
return 0;
free(buffer);
free(attributes);
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 = realloc(window->preeditAttributeBlocks, sizeof(int)*cblocks);
if (blocks == NULL) {
return 0;
free(buffer);
free(attributes);
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;
}
}
free(buffer);
free(attributes);
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:
@ -1705,6 +1801,30 @@ VkResult _glfwPlatformCreateWindowSurface(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;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -230,6 +230,10 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
if (wndconfig.focused)
_glfwPlatformFocusWindow(window);
}
window->preeditCursorPosX = 0;
window->preeditCursorPosY = height;
window->preeditCursorHeight = 0;
}
return (GLFWwindow*) window;
@ -408,7 +412,11 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
*prev = window->next;
}
// Clear memory for preedit text
if (window->preeditText)
free(window->preeditText);
if (window->preeditAttributeBlocks)
free(window->preeditAttributeBlocks);
free(window);
}

View File

@ -1027,6 +1027,28 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return err;
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: IME not implemented yet");
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: IME not implemented yet");
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: IME not implemented yet");
return GLFW_FALSE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -128,6 +128,16 @@ typedef struct _GLFWwindowX11
unsigned int lastKeyCode;
Time lastKeyTime;
// Preedit callbacks
XIMCallback preeditStartCallback;
XIMCallback preeditDoneCallback;
XIMCallback preeditDrawCallback;
XIMCallback preeditCaretCallback;
XIMCallback statusStartCallback;
XIMCallback statusDoneCallback;
XIMCallback statusDrawCallback;
int imeFocus;
} _GLFWwindowX11;
// X11-specific global data

View File

@ -48,6 +48,9 @@
#define Button6 6
#define Button7 7
#if defined(X_HAVE_UTF8_STRING)
static unsigned int decodeUTF8(const char** s);
#endif
// Wait for data to arrive using select
// This avoids blocking other threads via the per-display Xlib lock that also
@ -437,6 +440,196 @@ static char** parseUriList(char* text, int* count)
return paths;
}
// Update cursor position to decide candidate window
static void _ximChangeCursorPosition(XIC xic, _GLFWwindow* window)
{
XVaNestedList preedit_attr;
XPoint spot;
spot.x = window->preeditCursorPosX;
spot.y = window->preeditCursorPosY + window->preeditCursorHeight;
preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
XSetICValues(xic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
}
// IME Start callback (do nothing)
static void _ximPreeditStartCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
// IME Done callback (do nothing)
static void _ximPreeditDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
// IME Draw callback
static void _ximPreeditDrawCallback(XIC xic, XPointer clientData, XIMPreeditDrawCallbackStruct *callData)
{
int i, j, length, ctext, rstart, rend;
XIMText* text;
const char* src;
unsigned int codePoint;
unsigned int* preeditText;
XIMFeedback f;
_GLFWwindow* window = (_GLFWwindow*)clientData;
// keep cursor position to reduce API call
int cursorX = window->preeditCursorPosX;
int cursorY = window->preeditCursorPosY;
int cursorHeight = window->preeditCursorHeight;
if (!callData->text) {
// preedit text is empty
window->ntext = 0;
window->nblocks = 0;
_glfwInputPreedit(window, 0);
return;
} else {
text = callData->text;
length = callData->chg_length;
if (text->encoding_is_wchar) {
// wchar is not supported
return;
}
ctext = window->ctext;
while (ctext < length+1) {
ctext = (ctext == 0) ? 1 : ctext * 2;
}
if (ctext != window->ctext) {
preeditText = realloc(window->preeditText, sizeof(unsigned int)*ctext);
if (preeditText == NULL) {
return;
}
window->preeditText = preeditText;
window->ctext = ctext;
}
window->ntext = length;
window->preeditText[length] = 0;
if (window->cblocks == 0) {
window->preeditAttributeBlocks = malloc(sizeof(int)*4);
window->cblocks = 4;
}
src = text->string.multi_byte;
rend = 0;
rstart = length;
for (i = 0, j = 0; i < text->length; i++) {
#if defined(X_HAVE_UTF8_STRING)
codePoint = decodeUTF8(&src);
#else
codePoint = *src;
src++;
#endif
if (i < callData->chg_first || callData->chg_first+length < i) {
continue;
}
window->preeditText[j++] = codePoint;
f = text->feedback[i];
if ((f & XIMReverse) || (f & XIMHighlight)) {
rend = i;
if (i < rstart) {
rstart = i;
}
}
}
if (rstart == length) {
window->nblocks = 1;
window->preeditAttributeBlocks[0] = length;
window->preeditAttributeBlocks[1] = 0;
_glfwInputPreedit(window, 0);
} else if (rstart == 0) {
if (rend == length -1) {
window->nblocks = 1;
window->preeditAttributeBlocks[0] = length;
window->preeditAttributeBlocks[1] = 0;
_glfwInputPreedit(window, 0);
} else {
window->nblocks = 2;
window->preeditAttributeBlocks[0] = rend + 1;
window->preeditAttributeBlocks[1] = length - rend - 1;
window->preeditAttributeBlocks[2] = 0;
_glfwInputPreedit(window, 0);
}
} else if (rend == length -1) {
window->nblocks = 2;
window->preeditAttributeBlocks[0] = rstart;
window->preeditAttributeBlocks[1] = length - rstart;
window->preeditAttributeBlocks[2] = 0;
_glfwInputPreedit(window, 1);
} else {
window->nblocks = 3;
window->preeditAttributeBlocks[0] = rstart;
window->preeditAttributeBlocks[1] = rend - rstart + 1;
window->preeditAttributeBlocks[2] = length - rend - 1;
window->preeditAttributeBlocks[3] = 0;
_glfwInputPreedit(window, 1);
}
if ((cursorX != window->preeditCursorPosX)
|| (cursorY != window->preeditCursorPosY)
|| (cursorHeight != window->preeditCursorHeight)) {
_ximChangeCursorPosition(xic, window);
}
}
}
// IME Caret callback (do nothing)
static void _ximPreeditCaretCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
static void _ximStatusStartCallback(XIC xic, XPointer clientData, XPointer callData)
{
_GLFWwindow* window = (_GLFWwindow*)clientData;
window->x11.imeFocus = GLFW_TRUE;
}
static void _ximStatusDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{
_GLFWwindow* window = (_GLFWwindow*)clientData;
window->x11.imeFocus = GLFW_FALSE;
}
static void _ximStatusDrawCallback(XIC xic, XPointer clientData, XIMStatusDrawCallbackStruct* callData)
{
_GLFWwindow* window = (_GLFWwindow*)clientData;
_glfwInputIMEStatus(window);
}
// Create XIM Preedit callback
static XVaNestedList _createXIMPreeditCallbacks(_GLFWwindow* window)
{
window->x11.preeditStartCallback.client_data = (XPointer)window;
window->x11.preeditStartCallback.callback = (XIMProc)_ximPreeditStartCallback;
window->x11.preeditDoneCallback.client_data = (XPointer)window;
window->x11.preeditDoneCallback.callback = (XIMProc)_ximPreeditDoneCallback;
window->x11.preeditDrawCallback.client_data = (XPointer)window;
window->x11.preeditDrawCallback.callback = (XIMProc)_ximPreeditDrawCallback;
window->x11.preeditCaretCallback.client_data = (XPointer)window;
window->x11.preeditCaretCallback.callback = (XIMProc)_ximPreeditCaretCallback;
return XVaCreateNestedList (0,
XNPreeditStartCallback, &window->x11.preeditStartCallback.client_data,
XNPreeditDoneCallback, &window->x11.preeditDoneCallback.client_data,
XNPreeditDrawCallback, &window->x11.preeditDrawCallback.client_data,
XNPreeditCaretCallback, &window->x11.preeditCaretCallback.client_data,
NULL);
}
// Create XIM status callback
static XVaNestedList _createXIMStatusCallbacks(_GLFWwindow* window)
{
window->x11.statusStartCallback.client_data = (XPointer)window;
window->x11.statusStartCallback.callback = (XIMProc)_ximStatusStartCallback;
window->x11.statusDoneCallback.client_data = (XPointer)window;
window->x11.statusDoneCallback.callback = (XIMProc)_ximStatusDoneCallback;
window->x11.statusDrawCallback.client_data = (XPointer)window;
window->x11.statusDrawCallback.callback = (XIMProc)_ximStatusDrawCallback;
return XVaCreateNestedList (0,
XNStatusStartCallback, &window->x11.statusStartCallback.client_data,
XNStatusDoneCallback, &window->x11.statusDoneCallback.client_data,
XNStatusDrawCallback, &window->x11.statusDrawCallback.client_data,
NULL);
}
// Centers the cursor over the window client area
//
static void centerCursor(_GLFWwindow* window)
@ -646,14 +839,23 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
if (_glfw.x11.im)
{
XVaNestedList preeditList = _createXIMPreeditCallbacks(window);
XVaNestedList statusList = _createXIMStatusCallbacks(window);
window->x11.ic = XCreateIC(_glfw.x11.im,
XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XIMPreeditCallbacks | XIMStatusCallbacks,
XNClientWindow,
window->x11.handle,
XNFocusWindow,
window->x11.handle,
XNPreeditAttributes,
preeditList,
XNStatusAttributes,
statusList,
NULL);
XFree(preeditList);
XFree(statusList);
window->x11.imeFocus = GLFW_FALSE;
}
_glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos);
@ -2480,6 +2682,46 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
}
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window) {
XIC ic = window->x11.ic;
/* restore conversion state after resetting ic later */
XIMPreeditState preedit_state = XIMPreeditUnKnown;
XVaNestedList preedit_attr;
char* result;
if (window->ntext == 0)
return;
preedit_attr = XVaCreateNestedList(0, XNPreeditState, &preedit_state, NULL);
XGetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
result = XmbResetIC(ic);
preedit_attr = XVaCreateNestedList(0, XNPreeditState, preedit_state, NULL);
XSetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
window->ntext = 0;
window->nblocks = 0;
_glfwInputPreedit(window, 0);
XFree (result);
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active) {
XIC ic = window->x11.ic;
if (active) {
XSetICFocus(ic);
} else {
XUnsetICFocus(ic);
}
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window) {
return window->x11.imeFocus;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -420,6 +420,45 @@ static void char_mods_callback(GLFWwindow* window, unsigned int codepoint, int m
get_mods_name(mods));
}
static void preedit_callback(GLFWwindow* window, int strLength, unsigned int* string, int blockLength, int* blocks, int focusedBlock) {
Slot* slot = glfwGetWindowUserPointer(window);
int i, blockIndex = -1, blockCount = 0;
int width, height;
printf("%08x to %i at %0.3f: Preedit text ",
counter++, slot->number, glfwGetTime());
if (strLength == 0 || blockLength == 0) {
printf("(empty)\n");
} else {
for (i = 0; i < strLength; i++) {
if (blockCount == 0) {
if (blockIndex == focusedBlock) {
printf("]");
}
blockIndex++;
blockCount = blocks[blockIndex];
printf("\n block %d: ", blockIndex);
if (blockIndex == focusedBlock) {
printf("[");
}
}
printf("%s", get_character_string(string[i]));
blockCount--;
}
if (blockIndex == focusedBlock) {
printf("]");
}
printf("\n");
glfwGetWindowSize(window, &width, &height);
glfwSetPreeditCursorPos(window, width/2, height/2, 20);
}
}
static void ime_callback(GLFWwindow* window) {
Slot* slot = glfwGetWindowUserPointer(window);
printf("%08x to %i at %0.3f: IME switched\n",
counter++, slot->number, glfwGetTime());
}
static void drop_callback(GLFWwindow* window, int count, const char** paths)
{
int i;
@ -596,6 +635,8 @@ int main(int argc, char** argv)
glfwSetKeyCallback(slots[i].window, key_callback);
glfwSetCharCallback(slots[i].window, char_callback);
glfwSetCharModsCallback(slots[i].window, char_mods_callback);
glfwSetPreeditCallback(slots[i].window, preedit_callback);
glfwSetIMEStatusCallback(slots[i].window, ime_callback);
glfwSetDropCallback(slots[i].window, drop_callback);
glfwMakeContextCurrent(slots[i].window);