Compare commits

...

3 Commits

Author SHA1 Message Date
Camilla Löwy
ed59b80cc7 Cleanup 2016-12-05 19:14:40 +01:00
Camilla Löwy
0df386d06a Cleanup 2016-12-05 16:44:12 +01:00
Yoshiki Shibukawa
779b56276c Add preedit text callback API
Fixes #41.
Closes #658.
2016-12-05 16:41:53 +01:00
15 changed files with 1024 additions and 14 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 "-limm32")
if (GLFW_USE_HYBRID_HPG)
set(_GLFW_USE_HYBRID_HPG 1)

View File

@ -207,6 +207,112 @@ void charmods_callback(GLFWwindow* window, unsigned int codepoint, int mods)
@endcode
@subsection preedit IME support
All modern operating systems provide a
[IME](https://en.wikipedia.org/wiki/Input_method) (Input Method Editor)
mechanism to input character sets that cannot be mapped to physical keys, such
as [CJK characters](https://en.wikipedia.org/wiki/CJK_characters) (Chinese,
Japanese, Korean). Some operating systems also support speech-to-text input via
the IME mechanism.
GLFW provides IME support functions to help you implement better text input
features. You should add suitable visualization code for pre-edit 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 pre-edit callback to receive pre-edit text before committed.
@code
glfwSetPreeditCallback(window, preedit_callback);
@endcode
The callback function receives the pre-edit text and block information.
@code
static void preedit_callback(GLFWwindow* window, unsigned int* codepoints, int blockCount, int* blocks, int focusedBlock)
{
}
@endcode
The codepoints parameter contains the whole pre-edit text. Each character of the
pre-edit string is a Unicode codepoint like with @ref input_char.
If you want to type the text "寿司" (sushi), usually the callback is called
several times like the following sequence:
@code
-# 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]
@endcode
If pre-edit text includes several semantic blocks, pre-edit callbacks returns
several blocks after a space key pressed:
@code
-# preedit: [string: "わたしはすしをたべます", block: [11], focusedBlock: 0]
-# preedit: [string: "私は寿司を食べます", block: [2, 7], focusedBlock: 1]
@endcode
"blocks" is a list of block length. The above case, it contains the following
blocks and second block is focused.
@code
- 私は
- [寿司を食べます]
@endcode
committed 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 (window coordinates and height). You should call this function in the
above pre-edit text callback function.
@code
int xpos, ypos, height;
glfwSetPreeditCursorPos(window, xpos, ypos, height);
glfwGetPreeditCursorPos(window, &xpos, &ypos, &height);
@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, ime_status_callback);
@endcode
The ime_status_callback has simple signature like this:
@code
static void ime_status_callback(GLFWwindow* window)
{
}
@endcode
You can implement the code that resets or commits pre-edit text when IME status
is changed and pre-edit text is not empty.
When the focus is gone from text box, you can use @ref glfwSetInputMode, @ref
glfwGetInputMode with the `GLFW_IME` mode and the @ref glfwResetPreeditText
function.
@subsection input_key_name Key names
If you wish to refer to keys by name, you can query the keyboard layout

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,36 @@ 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] 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*,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 +3040,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 +3063,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 +3090,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 +3456,69 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor);
*/
GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
/*! @brief Retrieves the position of the caret.
*
* This function returns the position of the caret, in screen coordinates,
* relative to the upper-left corner of the client area of the specified
* window.
*
* @param[in] window The desired window.
* @param[out] xpos Where to store the caret x-coordinate, relative to the
* left edge of the client area, or `NULL`.
* @param[out] ypos Where to store the caret y-coordinate, relative to the to
* top edge of the client area, or `NULL`.
* @param[out] ypos Where to store the caret height, or `NULL`.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI void glfwGetPreeditCaretPos(GLFWwindow* window, int* xpos, int* ypos, int* height);
/*! @brief Sets the caret position used to place the IME candidate window.
*
* This function teach position hint to decide the candidate window. The
* candidate window is a part of IME (Input Method Editor) and shows several
* candidate strings.
*
* Windows sytems decide proper position from text cursor geometry.
* You should call this function in preedit callback.
*
* @param[in] window The window to set the caret position for.
* @param[in] xpos The x-coordinate of the caret within the window client area.
* @param[in] ypos The y-coordinate of the caret within the window client area.
* @param[in] height The height of the caret.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI void glfwSetPreeditCaretPos(GLFWwindow* window, int xpos, int ypos, int height);
/*! @brief Reset IME input status.
*
* This function resets IME's preedit text.
*
* @param[in] window The desired window.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref preedit
*
* @since Added in version 3.3.
*
* @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 +3633,56 @@ 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.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in version 3.3.
*
* @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.
*
* The callback receives the relative position of the input caret 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.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in version 3.3.
*
* @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,43 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[markedText initWithAttributedString:string];
else
[markedText initWithString:string];
NSString* markedTextString = markedText.string;
NSUInteger i, length = [markedTextString length];
free(window->preeditText);
window->preeditText = calloc(length + 1, sizeof(unsigned int));
for (i = 0; i < length; i++)
window->preeditText[i] = [markedTextString characterAtIndex:i];
int focusedBlock = 0;
NSInteger offset = 0;
window->preeditBlockCount = 0;
while (offset < length)
{
NSRange effectiveRange;
NSDictionary *attributes = [markedText attributesAtIndex:offset
effectiveRange:&effectiveRange];
window->preeditBlockCount++;
window->preeditBlocks = realloc(window->preeditBlocks,
window->preeditBlockCount * sizeof(int));
window->preeditBlocks[window->preeditBlockCount - 1] = effectiveRange.length;
offset += effectiveRange.length;
if (!effectiveRange.length)
break;
NSNumber* underline = (NSNumber*) [attributes objectForKey:@"NSUnderline"];
if ([underline intValue] != 1)
focusedBlock = window->preeditBlockCount - 1;
}
_glfwInputPreedit(window, focusedBlock);
}
- (void)unmarkText
@ -742,8 +782,10 @@ 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 +1091,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 +1149,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 +1784,67 @@ 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)
{
// NOTE: macOS 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, 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;
}
return GLFW_FALSE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -79,6 +79,24 @@ 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->preeditText,
window->preeditBlockCount,
window->preeditBlocks,
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 +180,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 +265,14 @@ 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 +497,50 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
_glfwPlatformSetCursor(window, cursor);
}
GLFWAPI void glfwGetPreeditCaretPos(GLFWwindow* handle, int* xpos, int* ypos, int* height)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
if (xpos)
*xpos = 0;
if (ypos)
*ypos = 0;
if (height)
*height = 0;
_GLFW_REQUIRE_INIT();
if (xpos)
*xpos = window->preeditCaretPosX;
if (ypos)
*ypos = window->preeditCaretPosY;
if (height)
*height = window->preeditCaretHeight;
}
GLFWAPI void glfwSetPreeditCaretPos(GLFWwindow* handle, int xpos, int ypos, int height)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
window->preeditCaretPosX = xpos;
window->preeditCaretPosY = ypos;
window->preeditCaretHeight = height;
}
GLFWAPI void glfwResetPreeditText(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
_glfwPlatformResetPreeditText(window);
}
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
@ -499,6 +571,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,17 @@ 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;
// IME preedit data
unsigned int* preeditText;
int* preeditBlocks;
int preeditBlockCount;
int preeditCaretPosX, preeditCaretPosY;
int preeditCaretHeight;
_GLFWcontext context;
struct {
@ -391,6 +399,8 @@ struct _GLFWwindow
GLFWkeyfun key;
GLFWcharfun character;
GLFWcharmodsfun charmods;
GLFWpreeditfun preedit;
GLFWimestatusfun imestatus;
GLFWdropfun drop;
} callbacks;
@ -818,6 +828,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 Sets whether the IME is enabled.
*
* This function sets whether the IME is enabled.
*
* @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 +954,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,17 @@ static void releaseMonitor(_GLFWwindow* window)
_glfwRestoreVideoModeWin32(window->monitor);
}
// Set cursor position to decide candidate window
//
static void updateCaretPosition(_GLFWwindow* window, HIMC imc)
{
const int x = window->preeditCaretPosX;
const int y = window->preeditCaretPosY;
const int h = window->preeditCaretHeight;
CANDIDATEFORM excludeRect = { 0, CFS_EXCLUDE, { x, y }, { x, y, x, y + h } };
ImmSetCandidateWindow(imc, &excludeRect);
}
// Window callback function (handles window messages)
//
static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
@ -536,7 +548,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
}
_glfwInputChar(window, (unsigned int) wParam, getKeyMods(), plain);
return 0;
return TRUE;
}
case WM_KEYDOWN:
@ -571,6 +583,81 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
break;
}
case WM_IME_COMPOSITION:
{
HIMC imc;
LONG textSize, attrSize, clauseSize;
int i, focusedBlock, length;
LPWSTR buffer;
LPSTR attributes;
DWORD* clauses;
if (lParam & GCS_RESULTSTR)
{
window->preeditBlockCount = 0;
_glfwInputPreedit(window, 0);
return TRUE;
}
if (!(lParam & GCS_COMPSTR))
break;
imc = ImmGetContext(hWnd);
textSize = ImmGetCompositionStringW(imc, GCS_COMPSTR, NULL, 0);
attrSize = ImmGetCompositionStringW(imc, GCS_COMPATTR, NULL, 0);
clauseSize = ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, NULL, 0);
if (textSize <= 0)
{
ImmReleaseContext(hWnd, imc);
return TRUE;
}
length = textSize / sizeof(WCHAR);
buffer = calloc(length + 1, sizeof(WCHAR));
attributes = calloc(attrSize, 1);
clauses = calloc(clauseSize, 1);
ImmGetCompositionStringW(imc, GCS_COMPSTR, buffer, textSize);
ImmGetCompositionStringW(imc, GCS_COMPATTR, attributes, attrSize);
ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, clauses, clauseSize);
free(window->preeditText);
window->preeditText = calloc(length + 1, sizeof(unsigned int));
memcpy(window->preeditText, buffer, sizeof(unsigned int) * length);
focusedBlock = 0;
window->preeditBlockCount = clauseSize / sizeof(DWORD) - 1;
free(window->preeditBlocks);
window->preeditBlocks = calloc(window->preeditBlockCount, sizeof(int));
for (i = 0; i < window->preeditBlockCount; i++)
{
window->preeditBlocks[i] = clauses[i + 1] - clauses[i];
if (attributes[clauses[i]] != ATTR_CONVERTED)
focusedBlock = i;
}
free(buffer);
free(attributes);
free(clauses);
_glfwInputPreedit(window, focusedBlock);
updateCaretPosition(window, imc);
ImmReleaseContext(hWnd, imc);
return TRUE;
}
case WM_IME_NOTIFY:
{
if (wParam == IMN_SETOPENSTATUS)
_glfwInputIMEStatus(window);
break;
}
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
@ -1705,6 +1792,28 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return err;
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
HIMC imc = ImmGetContext(window->win32.handle);
ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
ImmReleaseContext(window->win32.handle, imc);
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
HIMC imc = ImmGetContext(window->win32.handle);
ImmSetOpenStatus(imc, active);
ImmReleaseContext(window->win32.handle, imc);
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
HIMC imc = ImmGetContext(window->win32.handle);
BOOL result = ImmGetOpenStatus(imc);
ImmReleaseContext(window->win32.handle, imc);
return result;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -230,6 +230,10 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
if (wndconfig.focused)
_glfwPlatformFocusWindow(window);
}
window->preeditCaretPosX = 0;
window->preeditCaretPosY = height;
window->preeditCaretHeight = 0;
}
return (GLFWwindow*) window;
@ -399,6 +403,9 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
_glfwPlatformDestroyWindow(window);
free(window->preeditText);
free(window->preeditBlocks);
// Unlink window from global linked list
{
_GLFWwindow** prev = &_glfw.windowListHead;

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,218 @@ static char** parseUriList(char* text, int* count)
return paths;
}
// Update caret position to decide candidate window
//
static void updateCaretPosition(_GLFWwindow* window, XIC xic)
{
XVaNestedList attributes;
XPoint spot;
spot.x = window->preeditCursorPosX;
spot.y = window->preeditCursorPosY + window->preeditCursorHeight;
attributes = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
XSetICValues(xic, XNPreeditAttributes, attributes, NULL);
XFree(attributes);
}
// IME start callback (do nothing)
//
static void preeditStartCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
// IME done callback (do nothing)
//
static void preeditDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
// IME draw callback
//
static void preeditDrawCallback(XIC xic, XPointer clientData, XIMPreeditDrawCallbackStruct* callData)
{
int i, j, length, 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)
{
// Composition string is empty
window->preeditBlockCount = 0;
_glfwInputPreedit(window, 0);
return;
}
else
{
text = callData->text;
length = callData->chg_length;
if (text->encoding_is_wchar)
{
// wchar is not supported
return;
}
free(window->preeditText);
window->preeditText = calloc(length + 1, sizeof(unsigned int));
if (!window->preeditBlocks)
window->preeditBlocks = calloc(4, sizeof(int));
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->preeditBlockCount = 1;
window->preeditBlocks[0] = length;
window->preeditBlocks[1] = 0;
_glfwInputPreedit(window, 0);
}
else if (rstart == 0)
{
if (rend == length -1)
{
window->preeditBlockCount = 1;
window->preeditBlocks[0] = length;
window->preeditBlocks[1] = 0;
_glfwInputPreedit(window, 0);
}
else
{
window->preeditBlockCount = 2;
window->preeditBlocks[0] = rend + 1;
window->preeditBlocks[1] = length - rend - 1;
window->preeditBlocks[2] = 0;
_glfwInputPreedit(window, 0);
}
}
else if (rend == length -1)
{
window->preeditBlockCount = 2;
window->preeditBlocks[0] = rstart;
window->preeditBlocks[1] = length - rstart;
window->preeditBlocks[2] = 0;
_glfwInputPreedit(window, 1);
}
else
{
window->preeditBlockCount = 3;
window->preeditBlocks[0] = rstart;
window->preeditBlocks[1] = rend - rstart + 1;
window->preeditBlocks[2] = length - rend - 1;
window->preeditBlocks[3] = 0;
_glfwInputPreedit(window, 1);
}
if ((caretX != window->preeditCaretPosX) ||
(caretY != window->preeditCaretPosY) ||
(caretHeight != window->preeditCaretHeight))
{
updateCaretPosition(window, xic);
}
}
}
// IME Caret callback (do nothing)
//
static void preeditCaretCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
static void statusStartCallback(XIC xic, XPointer clientData, XPointer callData)
{
_GLFWwindow* window = (_GLFWwindow*) clientData;
window->x11.imeFocus = GLFW_TRUE;
}
static void statusDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{
_GLFWwindow* window = (_GLFWwindow*) clientData;
window->x11.imeFocus = GLFW_FALSE;
}
static void statusDrawCallback(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) preeditStartCallback;
window->x11.preeditDoneCallback.client_data = (XPointer)window;
window->x11.preeditDoneCallback.callback = (XIMProc) preeditDoneCallback;
window->x11.preeditDrawCallback.client_data = (XPointer)window;
window->x11.preeditDrawCallback.callback = (XIMProc) preeditDrawCallback;
window->x11.preeditCaretCallback.client_data = (XPointer)window;
window->x11.preeditCaretCallback.callback = (XIMProc) preeditCaretCallback;
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) statusStartCallback;
window->x11.statusDoneCallback.client_data = (XPointer)window;
window->x11.statusDoneCallback.callback = (XIMProc) statusDoneCallback;
window->x11.statusDrawCallback.client_data = (XPointer)window;
window->x11.statusDrawCallback.callback = (XIMProc) statusDrawCallback;
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 +861,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 +2704,45 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
}
}
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
// Restore conversion state after resetting ic later
XIMPreeditState state = XIMPreeditUnKnown;
XVaNestedList attributes;
char* result;
if (*window->preeditText == 0)
return;
attributes = XVaCreateNestedList(0, XNPreeditState, &state, NULL);
XGetICValues(window->x11.ic, XNPreeditAttributes, attributes, NULL);
XFree(attributes);
result = XmbResetIC(window->x11.ic);
attributes = XVaCreateNestedList(0, XNPreeditState, state, NULL);
XSetICValues(window->x11.ic, XNPreeditAttributes, attributes, NULL);
XFree(attributes);
window->preeditBlockCount = 0;
_glfwInputPreedit(window, 0);
XFree(result);
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
if (active)
XSetICFocus(window->x11.ic);
else
XUnsetICFocus(window->x11.ic);
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
return window->x11.imeFocus;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

View File

@ -420,6 +420,57 @@ static void char_mods_callback(GLFWwindow* window, unsigned int codepoint, int m
get_mods_name(mods));
}
static void preedit_callback(GLFWwindow* window,
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 (*string && blockLength)
{
for (i = 0; string[i]; 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);
glfwSetPreeditCaretPos(window, width / 2, height / 2, 20);
}
else
printf("(empty)\n");
}
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 +647,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);