Input Method Editor (IME) support for text buttons

Original patch by @random (D765) with some minor work done by @campbell
and me.

At this place, I'd like call out a number of people who were involved and
deserve a big "Thank you!":
* At the first place @randon who developed and submitted the patch
* The Blendercn community which helped a lot with testing - espacially
* @yuzukyo, @leon_cheung and @kjym3
* @campbellbarton, @mont29 and @sergey for their help and advises during
* review
* @ton who realized the importance of this early on and asked me for
* reviewing

We are still not finished, as this is only the first part of the
implementaion, but there's more to come!
This commit is contained in:
Severin 2014-12-07 00:58:17 +01:00
parent 06515475b9
commit e81d077c85
36 changed files with 602 additions and 9 deletions

@ -318,6 +318,9 @@ mark_as_advanced(WITH_LIBMV_SCHUR_SPECIALIZATIONS)
option(WITH_FREESTYLE "Enable Freestyle (advanced edges rendering)" ON)
# Misc
if(WIN32)
option(WITH_INPUT_IME "Enable Input Method Editor (IME) for complex Asian character input" ON)
endif()
option(WITH_INPUT_NDOF "Enable NDOF input devices (SpaceNavigator and friends)" ${_init_INPUT_NDOF})
option(WITH_RAYOPTIMIZATION "Enable use of SIMD (SSE) optimizations for the raytracer" ON)
option(WITH_OPENNL "Enable use of Open Numerical Library" ON)
@ -1151,6 +1154,10 @@ elseif(WIN32)
list(APPEND PLATFORM_LINKLIBS ws2_32 vfw32 winmm kernel32 user32 gdi32 comdlg32 advapi32 shfolder shell32 ole32 oleaut32 uuid psapi)
if(WITH_INPUT_IME)
list(APPEND PLATFORM_LINKLIBS imm32)
endif()
add_definitions(
-D_CRT_NONSTDC_NO_DEPRECATE
-D_CRT_SECURE_NO_DEPRECATE
@ -1506,6 +1513,11 @@ elseif(WIN32)
endif()
list(APPEND PLATFORM_LINKLIBS -lshell32 -lshfolder -lgdi32 -lmsvcrt -lwinmm -lmingw32 -lm -lws2_32 -lz -lstdc++ -lole32 -luuid -lwsock32 -lpsapi)
if(WITH_INPUT_IME)
list(APPEND PLATFORM_LINKLIBS -limm32)
endif()
set(PLATFORM_CFLAGS "-pipe -funsigned-char -fno-strict-aliasing")
if(WITH_MINGW64)

@ -266,6 +266,7 @@ if 'blenderlite' in B.targets:
target_env_defs['WITH_BF_BOOLEAN'] = False
target_env_defs['WITH_BF_REMESH'] = False
target_env_defs['WITH_BF_PYTHON'] = False
target_env_defs['WITH_BF_IME'] = False
target_env_defs['WITH_BF_3DMOUSE'] = False
target_env_defs['WITH_BF_LIBMV'] = False
target_env_defs['WITH_BF_FREESTYLE'] = False

@ -170,6 +170,8 @@ BF_BOOST_LIBPATH = '${BF_BOOST}/lib'
WITH_BF_RAYOPTIMIZATION = True
BF_RAYOPTIMIZATION_SSE_FLAGS = ['-msse']
WITH_BF_IME = True
WITH_BF_OPENMP = True
#CUDA
@ -198,6 +200,9 @@ CC_WARN = [ '-Wall' ]
LLIBS = ['-lshell32', '-lshfolder', '-lgdi32', '-lmsvcrt', '-lwinmm', '-lmingw32', '-lm', '-lws2_32', '-lz', '-lstdc++','-lole32','-luuid', '-lwsock32', '-lpsapi']
if WITH_BF_IME:
LLIBS.append('-limm32')
PLATFORM_LINKFLAGS = ['-Xlinker', '--stack=2097152']
## DISABLED, causes linking errors!

@ -151,6 +151,8 @@ BF_OPENCOLLADA_INC = '${BF_OPENCOLLADA}/include/opencollada'
BF_OPENCOLLADA_LIB = 'OpenCOLLADAStreamWriter OpenCOLLADASaxFrameworkLoader OpenCOLLADAFramework OpenCOLLADABaseUtils GeneratedSaxParser MathMLSolver xml pcre buffer ftoa'
BF_OPENCOLLADA_LIBPATH = '${BF_OPENCOLLADA}/lib/opencollada'
WITH_BF_IME = True
WITH_BF_3DMOUSE = True
WITH_BF_OPENMP = True
@ -237,6 +239,9 @@ CXX_WARN = []
LLIBS = ['ws2_32', 'vfw32', 'winmm', 'kernel32', 'user32', 'gdi32', 'comdlg32', 'advapi32', 'shfolder', 'shell32', 'ole32', 'oleaut32', 'uuid', 'psapi']
if WITH_BF_IME:
LLIBS.append('imm32')
PLATFORM_LINKFLAGS = ['/SUBSYSTEM:CONSOLE','/MACHINE:IX86','/STACK:2097152','/INCREMENTAL:NO', '/LARGEADDRESSAWARE', '/NODEFAULTLIB:msvcrt.lib', '/NODEFAULTLIB:msvcmrt.lib', '/NODEFAULTLIB:msvcurt.lib', '/NODEFAULTLIB:msvcrtd.lib']
# # Todo

@ -170,6 +170,8 @@ BF_BOOST_LIBPATH = '${BF_BOOST}/lib'
WITH_BF_RAYOPTIMIZATION = True
BF_RAYOPTIMIZATION_SSE_FLAGS = ['-mmmx', '-msse', '-msse2']
WITH_BF_IME = True
WITH_BF_OPENMP = True
#Freestyle
@ -193,6 +195,9 @@ CC_WARN = [ '-Wall' ]
LLIBS = ['-lshell32', '-lshfolder', '-lgdi32', '-lmsvcrt', '-lwinmm', '-lmingw32', '-lm', '-lws2_32', '-lz', '-lstdc++','-lole32','-luuid', '-lwsock32', '-lpsapi', '-lpthread']
if WITH_BF_IME:
LLIBS.append('-limm32')
PLATFORM_LINKFLAGS = ['-Xlinker', '--stack=2097152']
## DISABLED, causes linking errors!

@ -154,6 +154,8 @@ BF_OPENCOLLADA_INC = '${BF_OPENCOLLADA}/include/opencollada'
BF_OPENCOLLADA_LIB = 'OpenCOLLADAStreamWriter OpenCOLLADASaxFrameworkLoader OpenCOLLADAFramework OpenCOLLADABaseUtils GeneratedSaxParser MathMLSolver xml pcre buffer ftoa'
BF_OPENCOLLADA_LIBPATH = '${BF_OPENCOLLADA}/lib/opencollada'
WITH_BF_IME = True
WITH_BF_3DMOUSE = True
WITH_BF_OPENMP = True
@ -244,6 +246,9 @@ CXX_WARN = []
LLIBS = ['ws2_32', 'vfw32', 'winmm', 'kernel32', 'user32', 'gdi32', 'comdlg32', 'advapi32', 'shfolder', 'shell32', 'ole32', 'oleaut32', 'uuid', 'psapi']
if WITH_BF_IME:
LLIBS.append('imm32')
PLATFORM_LINKFLAGS = ['/SUBSYSTEM:CONSOLE','/MACHINE:X64','/STACK:2097152','/OPT:NOREF','/INCREMENTAL:NO', '/NODEFAULTLIB:msvcrt.lib', '/NODEFAULTLIB:msvcmrt.lib', '/NODEFAULTLIB:msvcurt.lib', '/NODEFAULTLIB:msvcrtd.lib']
BF_CYCLES_CUDA_ENV="C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd"

@ -174,6 +174,7 @@ def validate_arguments(args, bc):
'WITH_BF_CXX_GUARDEDALLOC',
'WITH_BF_JEMALLOC', 'WITH_BF_STATICJEMALLOC', 'BF_JEMALLOC', 'BF_JEMALLOC_INC', 'BF_JEMALLOC_LIBPATH', 'BF_JEMALLOC_LIB', 'BF_JEMALLOC_LIB_STATIC',
'BUILDBOT_BRANCH',
'WITH_BF_IME',
'WITH_BF_3DMOUSE', 'WITH_BF_STATIC3DMOUSE', 'BF_3DMOUSE', 'BF_3DMOUSE_INC', 'BF_3DMOUSE_LIB', 'BF_3DMOUSE_LIBPATH', 'BF_3DMOUSE_LIB_STATIC',
'WITH_BF_CYCLES', 'WITH_BF_CYCLES_CUDA_BINARIES', 'BF_CYCLES_CUDA_NVCC', 'BF_CYCLES_CUDA_NVCC', 'WITH_BF_CYCLES_CUDA_THREADED_COMPILE', 'BF_CYCLES_CUDA_ENV',
'WITH_BF_OIIO', 'WITH_BF_STATICOIIO', 'BF_OIIO', 'BF_OIIO_INC', 'BF_OIIO_LIB', 'BF_OIIO_LIB_STATIC', 'BF_OIIO_LIBPATH',
@ -507,6 +508,8 @@ def read_opts(env, cfg, args):
(BoolVariable('WITH_BF_PLAYER', 'Build blenderplayer if true', False)),
(BoolVariable('WITH_BF_NOBLENDER', 'Do not build blender if true', False)),
(BoolVariable('WITH_BF_IME', 'Enable Input Method Editor (IME) for complex Asian character input', False)),
(BoolVariable('WITH_BF_3DMOUSE', 'Build blender with support of 3D mouses', False)),
(BoolVariable('WITH_BF_STATIC3DMOUSE', 'Staticly link to 3d mouse library', False)),
('BF_3DMOUSE', '3d mouse library base path', ''),

@ -280,6 +280,16 @@ elseif(WIN32)
)
endif()
if(WITH_INPUT_IME)
add_definitions(-DWITH_INPUT_IME)
list(APPEND SRC
intern/GHOST_ImeWin32.cpp
intern/GHOST_ImeWin32.h
)
endif()
if(WITH_INPUT_NDOF)
list(APPEND SRC
intern/GHOST_NDOFManagerWin32.cpp

@ -897,6 +897,30 @@ extern int GHOST_UseNativePixels(void);
*/
extern float GHOST_GetNativePixelSize(GHOST_WindowHandle windowhandle);
/**
* Enable IME attached to the given window, i.e. allows user-input
* events to be dispatched to the IME.
* \param windowhandle Window handle of the caller
* \param x Requested x-coordinate of the rectangle
* \param y Requested y-coordinate of the rectangle
* \param w Requested width of the rectangle
* \param h Requested height of the rectangle
* \param complete Whether or not to complete the ongoing composition
* true: Start a new composition
* false: Move the IME windows to the given position without finishing it.
*/
extern void GHOST_BeginIME(GHOST_WindowHandle windowhandle,
GHOST_TInt32 x,
GHOST_TInt32 y,
GHOST_TInt32 w,
GHOST_TInt32 h,
int complete);
/**
* Disable the IME attached to the given window, i.e. prohibits any user-input
* events from being dispatched to the IME.
* \param windowhandle The window handle of the caller
*/
extern void GHOST_EndIME(GHOST_WindowHandle windowhandle);
#ifdef __cplusplus
}

@ -331,6 +331,29 @@ public:
virtual float getNativePixelSize(void) = 0;
#ifdef WITH_INPUT_IME
/**
* Enable IME attached to the given window, i.e. allows user-input
* events to be dispatched to the IME.
* \param x Requested x-coordinate of the rectangle
* \param y Requested y-coordinate of the rectangle
* \param w Requested width of the rectangle
* \param h Requested height of the rectangle
* \param complete Whether or not to complete the ongoing composition
* true: Start a new composition
* false: Move the IME windows to the given position without finishing it.
*/
virtual void beginIME(
GHOST_TInt32 x, GHOST_TInt32 y,
GHOST_TInt32 w, GHOST_TInt32 h,
int completed) = 0;
/**
* Disable the IME attached to the given window, i.e. prohibits any user-input
* events from being dispatched to the IME.
*/
virtual void endIME() = 0;
#endif /* WITH_INPUT_IME */
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IWindow")

@ -190,6 +190,10 @@ typedef enum {
GHOST_kEventTimer,
GHOST_kEventImeCompositionStart,
GHOST_kEventImeComposition,
GHOST_kEventImeCompositionEnd,
GHOST_kNumEventTypes
} GHOST_TEventType;
@ -436,6 +440,22 @@ typedef struct {
GHOST_TEventDataPtr data;
} GHOST_TEventDragnDropData;
/** similar to wmImeData */
typedef struct {
/** size_t */
GHOST_TUserDataPtr result_len, composite_len;
/** char * utf8 encoding */
GHOST_TUserDataPtr result, composite;
/** Cursor position in the IME composition. */
int cursor_position;
/** Represents the position of the beginning of the selection */
int target_start;
/** Represents the position of the end of the selection */
int target_end;
/** custom temporal data */
GHOST_TUserDataPtr tmp;
} GHOST_TEventImeData;
typedef struct {
int count;
GHOST_TUns8 **strings;

@ -154,6 +154,13 @@ if env['BF_GHOST_DEBUG']:
else:
sources.remove('intern' + os.sep + 'GHOST_EventPrinter.cpp')
if env['WITH_BF_IME']:
if window_system in ('win32-vc', 'win32-mingw', 'win64-vc', 'win64-mingw'):
defs.append('WITH_INPUT_IME')
else:
sources.remove('intern' + os.sep + 'GHOST_ImeWin32.h')
sources.remove('intern' + os.sep + 'GHOST_ImeWin32.cpp')
if env['WITH_BF_3DMOUSE']:
defs.append('WITH_INPUT_NDOF')

@ -915,3 +915,21 @@ float GHOST_GetNativePixelSize(GHOST_WindowHandle windowhandle)
return 1.0f;
}
#ifdef WITH_INPUT_IME
void GHOST_BeginIME(GHOST_WindowHandle windowhandle,
GHOST_TInt32 x, GHOST_TInt32 y,
GHOST_TInt32 w, GHOST_TInt32 h,
int complete)
{
GHOST_IWindow *window = (GHOST_IWindow *) windowhandle;
window->beginIME(x, y, w, h, complete);
}
void GHOST_EndIME(GHOST_WindowHandle windowhandle)
{
GHOST_IWindow *window = (GHOST_IWindow *) windowhandle;
window->endIME();
}
#endif /* WITH_INPUT_IME */

@ -792,6 +792,15 @@ GHOST_Event *GHOST_SystemWin32::processWindowEvent(GHOST_TEventType type, GHOST_
return new GHOST_Event(system->getMilliSeconds(), type, window);
}
#ifdef WITH_INPUT_IME
GHOST_Event *GHOST_SystemWin32::processImeEvent(GHOST_TEventType type, GHOST_IWindow *window, GHOST_TEventImeData *data)
{
GHOST_System *system = (GHOST_System *)getSystem();
return new GHOST_EventIME(system->getMilliSeconds(), type, window, data);
}
#endif
GHOST_TSuccess GHOST_SystemWin32::pushDragDropEvent(
GHOST_TEventType eventType,
GHOST_TDragnDropTypes draggedObjectType,
@ -904,6 +913,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
LRESULT lResult = 0;
GHOST_SystemWin32 *system = ((GHOST_SystemWin32 *)getSystem());
GHOST_EventManager *eventManager = system->getEventManager();
GHOST_ASSERT(system, "GHOST_SystemWin32::s_wndProc(): system not initialized");
if (hwnd) {
@ -912,8 +922,13 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
switch (msg) {
// we need to check if new key layout has AltGr
case WM_INPUTLANGCHANGE:
{
system->handleKeyboardChange();
#ifdef WITH_INPUT_IME
window->getImeInput()->SetInputLanguage();
#endif
break;
}
////////////////////////////////////////////////////////////////////////
// Keyboard events, processed
////////////////////////////////////////////////////////////////////////
@ -949,6 +964,56 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
}
break;
}
#ifdef WITH_INPUT_IME
////////////////////////////////////////////////////////////////////////
// IME events, processed, read more in GHOST_IME.h
////////////////////////////////////////////////////////////////////////
case WM_IME_SETCONTEXT:
{
window->getImeInput()->SetInputLanguage();
window->getImeInput()->CreateImeWindow(window->getHWND());
window->getImeInput()->CleanupComposition(window->getHWND());
window->getImeInput()->CheckFirst(window->getHWND());
break;
}
case WM_IME_STARTCOMPOSITION:
{
eventHandled = true;
/* remove input event before start comp event, avoid redundant input */
eventManager->removeTypeEvents(GHOST_kEventKeyDown, window);
window->getImeInput()->CreateImeWindow(window->getHWND());
window->getImeInput()->ResetComposition(window->getHWND());
event = processImeEvent(
GHOST_kEventImeCompositionStart,
window,
&window->getImeInput()->eventImeData);
break;
}
case WM_IME_COMPOSITION:
{
eventHandled = true;
window->getImeInput()->UpdateImeWindow(window->getHWND());
window->getImeInput()->UpdateInfo(window->getHWND());
event = processImeEvent(
GHOST_kEventImeComposition,
window,
&window->getImeInput()->eventImeData);
break;
}
case WM_IME_ENDCOMPOSITION:
{
eventHandled = true;
/* remove input event after end comp event, avoid redundant input */
eventManager->removeTypeEvents(GHOST_kEventKeyDown, window);
window->getImeInput()->ResetComposition(window->getHWND());
window->getImeInput()->DestroyImeWindow(window->getHWND());
event = processImeEvent(
GHOST_kEventImeCompositionEnd,
window,
&window->getImeInput()->eventImeData);
break;
}
#endif /* WITH_INPUT_IME */
////////////////////////////////////////////////////////////////////////
// Keyboard events, ignored
////////////////////////////////////////////////////////////////////////

@ -306,6 +306,15 @@ protected:
*/
static GHOST_Event *processWindowEvent(GHOST_TEventType type, GHOST_IWindow *window);
/**
* Creates a IME event.
* \param type The type of event to create.
* \param window The window receiving the event (the active window).
* \param data IME data.
* \return The event created.
*/
static GHOST_Event *processImeEvent(GHOST_TEventType type, GHOST_IWindow *window, GHOST_TEventImeData *data);
/**
* Handles minimum window size.
* \param minmax The MINMAXINFO structure.

@ -295,6 +295,22 @@ public:
return 1.0f;
}
#ifdef WITH_INPUT_IME
virtual void beginIME(GHOST_TInt32 x,
GHOST_TInt32 y,
GHOST_TInt32 w,
GHOST_TInt32 h,
int completed)
{
/* do nothing temporarily if not in windows */
}
virtual void endIME()
{
/* do nothing temporarily if not in windows */
}
#endif /* WITH_INPUT_IME */
protected:
/**
* Tries to install a rendering context in this window.

@ -1050,3 +1050,16 @@ GHOST_TSuccess GHOST_WindowWin32::endProgressBar()
return GHOST_kFailure;
}
#ifdef WITH_INPUT_IME
void GHOST_WindowWin32::beginIME(GHOST_TInt32 x, GHOST_TInt32 y, GHOST_TInt32 w, GHOST_TInt32 h, int completed)
{
this->getImeInput()->BeginIME(this->getHWND(), GHOST_Rect(x, y - h , x, y), (bool)completed);
}
void GHOST_WindowWin32::endIME()
{
this->getImeInput()->EndIME(this->getHWND());
}
#endif /* WITH_INPUT_IME */

@ -39,6 +39,9 @@
#include "GHOST_Window.h"
#include "GHOST_TaskbarWin32.h"
#ifdef WITH_INPUT_IME
# include "GHOST_ImeWin32.h"
#endif
#include <wintab.h>
#define PACKETDATA (PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR)
@ -253,6 +256,17 @@ public:
/** if the window currently resizing */
bool m_inLiveResize;
#ifdef WITH_INPUT_IME
GHOST_ImeWin32 *getImeInput() {return &m_imeImput;}
virtual void beginIME(
GHOST_TInt32 x, GHOST_TInt32 y,
GHOST_TInt32 w, GHOST_TInt32 h,
int completed);
virtual void endIME();
#endif /* WITH_INPUT_IME */
private:
/**
@ -339,6 +353,11 @@ private:
/** Hwnd to parent window */
GHOST_TEmbedderWindowID m_parentWindowHwnd;
#ifdef WITH_INPUT_IME
/** Handle input method editors event */
GHOST_ImeWin32 m_imeImput;
#endif
};
#endif // __GHOST_WINDOWWIN32_H__

@ -5677,6 +5677,9 @@ static void direct_link_windowmanager(FileData *fd, wmWindowManager *wm)
win->eventstate = NULL;
win->curswin = NULL;
win->tweak = NULL;
#ifdef WIN32
win->ime_data = NULL;
#endif
BLI_listbase_clear(&win->queue);
BLI_listbase_clear(&win->handlers);

@ -313,6 +313,7 @@ void UI_draw_box_shadow(unsigned char alpha, float minx, float miny, float maxx,
void UI_draw_roundbox_gl_mode(int mode, float minx, float miny, float maxx, float maxy, float rad);
void UI_draw_roundbox_shade_x(int mode, float minx, float miny, float maxx, float maxy, float rad, float shadetop, float shadedown);
void UI_draw_roundbox_shade_y(int mode, float minx, float miny, float maxx, float maxy, float rad, float shadeLeft, float shadeRight);
void UI_draw_text_underline(int pos_x, int pos_y, int len, int height);
/* state for scrolldrawing */
#define UI_SCROLL_PRESSED (1 << 0)

@ -71,6 +71,12 @@ if(WITH_PYTHON)
add_definitions(-DWITH_PYTHON)
endif()
if(WIN32)
if(WITH_INPUT_IME)
add_definitions(-DWITH_INPUT_IME)
endif()
endif()
add_definitions(${GL_DEFINITIONS})
blender_add_lib(bf_editor_interface "${SRC}" "${INC}" "${INC_SYS}")

@ -48,6 +48,10 @@ incs = [
defs = env['BF_GL_DEFINITIONS']
if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'win64-vc', 'win64-mingw'):
if env['WITH_BF_IME']:
defs.append('WITH_INPUT_IME')
if env['WITH_BF_INTERNATIONAL']:
defs.append('WITH_INTERNATIONAL')

@ -209,6 +209,11 @@ void ui_window_to_region(const ARegion *ar, int *x, int *y)
*y -= ar->winrct.ymin;
}
void ui_region_to_window(const ARegion *ar, int *x, int *y)
{
*x += ar->winrct.xmin;
*y += ar->winrct.ymin;
}
/* ******************* block calc ************************* */
void ui_block_translate(uiBlock *block, int x, int y)

@ -395,6 +395,12 @@ void UI_draw_roundbox(float minx, float miny, float maxx, float maxy, float rad)
ui_draw_anti_roundbox(GL_POLYGON, minx, miny, maxx, maxy, rad, roundboxtype & UI_RB_ALPHA);
}
void UI_draw_text_underline(int pos_x, int pos_y, int len, int height)
{
int ofs_y = 4 * U.pixelsize;
glRecti(pos_x, pos_y - ofs_y, pos_x + len, pos_y - ofs_y + (height * U.pixelsize));
}
/* ************** SPECIAL BUTTON DRAWING FUNCTIONS ************* */
void ui_draw_but_IMAGE(ARegion *UNUSED(ar), uiBut *but, uiWidgetColors *UNUSED(wcol), const rcti *rect)

@ -87,6 +87,10 @@
#include "WM_api.h"
#include "WM_types.h"
#ifdef WITH_INPUT_IME
# include "wm_window.h"
#endif
/* place the mouse at the scaled down location when un-grabbing */
#define USE_CONT_MOUSE_CORRECT
/* support dragging toggle buttons */
@ -2425,8 +2429,50 @@ static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const in
return changed;
}
#ifdef WITH_INPUT_IME
/* enable ime, and set up uibut ime data */
static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
{
/* XXX Is this really needed? */
int x, y;
/* enable IME and position to cursor, it's a trick */
x = win->eventstate->x;
/* flip y and move down a bit, prevent the IME panel cover the edit button */
y = win->eventstate->y - 12;
wm_window_IME_begin(win, x, y, 0, 0, true);
}
/* disable ime, and clear uibut ime data */
static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but))
{
wm_window_IME_end(win);
}
void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete)
{
BLI_assert(but->active);
ui_region_to_window(but->active->region, &x, &y);
wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete);
}
/* should be ui_but_ime_data_get */
wmIMEData *ui_but_get_ime_data(uiBut *but)
{
if (but->active && but->active->window) {
return but->active->window->ime_data;
}
else {
return NULL;
}
}
#endif /* WITH_INPUT_IME */
static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
{
wmWindow *win = CTX_wm_window(C);
int len;
if (data->str) {
@ -2482,12 +2528,18 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
but->flag &= ~UI_BUT_REDALERT;
ui_but_update(but);
WM_cursor_modal_set(CTX_wm_window(C), BC_TEXTEDITCURSOR);
WM_cursor_modal_set(win, BC_TEXTEDITCURSOR);
#ifdef WITH_INPUT_IME
ui_textedit_ime_begin(win, but);
#endif
}
static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
{
wmWindow *win = CTX_wm_window(C);
if (but) {
if (ui_but_is_utf8(but)) {
int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
@ -2518,7 +2570,11 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
but->pos = -1;
}
WM_cursor_modal_restore(CTX_wm_window(C));
WM_cursor_modal_restore(win);
#ifdef WITH_INPUT_IME
ui_textedit_ime_end(win, but);
#endif
}
static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
@ -2583,6 +2639,14 @@ static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandle
int retval = WM_UI_HANDLER_CONTINUE;
bool changed = false, inbox = false, update = false;
#ifdef WITH_INPUT_IME
wmWindow *win = CTX_wm_window(C);
wmIMEData *ime_data = win->ime_data;
bool is_ime_composing = ime_data && ime_data->is_ime_composing;
#else
bool is_ime_composing = false;
#endif
switch (event->type) {
case MOUSEMOVE:
case MOUSEPAN:
@ -2603,6 +2667,12 @@ static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandle
case RIGHTMOUSE:
case ESCKEY:
if (event->val == KM_PRESS) {
#ifdef WITH_INPUT_IME
/* skips button handling since it is not wanted */
if (is_ime_composing) {
break;
}
#endif
data->cancel = true;
data->escapecancel = true;
button_activate_state(C, but, BUTTON_STATE_EXIT);
@ -2660,7 +2730,7 @@ static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandle
}
}
if (event->val == KM_PRESS) {
if (event->val == KM_PRESS && !is_ime_composing) {
switch (event->type) {
case VKEY:
case XKEY:
@ -2776,7 +2846,15 @@ static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandle
break;
}
if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)) {
if ((event->ascii || event->utf8_buf[0]) &&
(retval == WM_UI_HANDLER_CONTINUE)
#ifdef WITH_INPUT_IME
&&
!is_ime_composing &&
!WM_event_is_ime_switch(event)
#endif
)
{
char ascii = event->ascii;
const char *utf8_buf = event->utf8_buf;
@ -2811,6 +2889,25 @@ static void ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandle
update = true;
}
#ifdef WITH_INPUT_IME
if (event->type == WM_IME_COMPOSITE_START || event->type == WM_IME_COMPOSITE_EVENT) {
changed = true;
if (event->type == WM_IME_COMPOSITE_START && but->selend > but->selsta) {
ui_textedit_delete_selection(but, data);
}
if (event->type == WM_IME_COMPOSITE_EVENT && ime_data->result_len) {
ui_textedit_type_buf(
but, data,
ime_data->str_result,
ime_data->result_len);
}
}
else if (event->type == WM_IME_COMPOSITE_END) {
changed = true;
}
#endif
if (changed) {
/* only update when typing for TAB key */
if (update && data->interactive) {

@ -433,6 +433,7 @@ extern void ui_block_to_window_rctf(const struct ARegion *ar, uiBlock *block, rc
extern void ui_window_to_block_fl(const struct ARegion *ar, uiBlock *block, float *x, float *y);
extern void ui_window_to_block(const struct ARegion *ar, uiBlock *block, int *x, int *y);
extern void ui_window_to_region(const ARegion *ar, int *x, int *y);
extern void ui_region_to_window(const struct ARegion *ar, int *x, int *y);
extern double ui_but_value_get(uiBut *but);
extern void ui_but_value_set(uiBut *but, double value);
@ -627,6 +628,11 @@ void ui_panel_menu(struct bContext *C, ARegion *ar, Panel *pa);
uiBut *ui_but_find_old(uiBlock *block_old, const uiBut *but_new);
uiBut *ui_but_find_new(uiBlock *block_old, const uiBut *but_new);
#ifdef WITH_INPUT_IME
void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete);
struct wmIMEData *ui_but_get_ime_data(uiBut *but);
#endif
/* interface_widgets.c */
void ui_draw_anti_tria(float x1, float y1, float x2, float y2, float x3, float y3);
void ui_draw_anti_roundbox(int mode, float minx, float miny, float maxx, float maxy, float rad, bool use_alpha);

@ -59,6 +59,10 @@
#include "interface_intern.h"
#ifdef WITH_INPUT_IME
# include "WM_types.h"
#endif
/* icons are 80% of height of button (16 pixels inside 20 height) */
#define ICON_SIZE_FROM_BUTRECT(rect) (0.8f * BLI_rcti_size_y(rect))
@ -1232,6 +1236,50 @@ static void ui_text_clip_right_label(uiFontStyle *fstyle, uiBut *but, const rcti
BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
}
#ifdef WITH_INPUT_IME
static void widget_draw_text_ime_underline(
uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *but, const rcti *rect,
const wmIMEData *ime_data, const char *drawstr)
{
int ofs_x, width;
int rect_x = BLI_rcti_size_x(rect);
int sel_start = ime_data->sel_start, sel_end = ime_data->sel_end;
if (drawstr[0] != 0) {
if (but->pos >= but->ofs) {
ofs_x = BLF_width(fstyle->uifont_id, drawstr + but->ofs, but->pos - but->ofs);
}
else {
ofs_x = 0;
}
width = BLF_width(fstyle->uifont_id, drawstr + but->ofs,
ime_data->composite_len + but->pos - but->ofs);
glColor4ubv((unsigned char *)wcol->text);
UI_draw_text_underline(rect->xmin + ofs_x, rect->ymin + 6 * U.pixelsize, min_ii(width, rect_x - 2) - ofs_x, 1);
/* draw the thick line */
if (sel_start != -1 && sel_end != -1) {
sel_end -= sel_start;
sel_start += but->pos;
if (sel_start >= but->ofs) {
ofs_x = BLF_width(fstyle->uifont_id, drawstr + but->ofs, sel_start - but->ofs);
}
else {
ofs_x = 0;
}
width = BLF_width(fstyle->uifont_id, drawstr + but->ofs,
sel_end + sel_start - but->ofs);
UI_draw_text_underline(rect->xmin + ofs_x, rect->ymin + 6 * U.pixelsize, min_ii(width, rect_x - 2) - ofs_x, 2);
}
}
}
#endif /* WITH_INPUT_IME */
static void widget_draw_text(uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *but, rcti *rect)
{
int drawstr_left_len = UI_MAX_DRAW_STR;
@ -1239,6 +1287,10 @@ static void widget_draw_text(uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *b
const char *drawstr_right = NULL;
bool use_right_only = false;
#ifdef WITH_INPUT_IME
const wmIMEData *ime_data;
#endif
UI_fontstyle_set(fstyle);
if (but->editstr || (but->drawflag & UI_BUT_TEXT_LEFT))
@ -1266,13 +1318,30 @@ static void widget_draw_text(uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *b
/* max length isn't used in this case,
* we rely on string being NULL terminated. */
drawstr_left_len = INT_MAX;
drawstr = but->editstr;
#ifdef WITH_INPUT_IME
/* FIXME, IME is modifying 'const char *drawstr! */
ime_data = ui_but_get_ime_data(but);
if (ime_data && ime_data->composite_len) {
/* insert composite string into cursor pos */
BLI_snprintf((char *)drawstr, UI_MAX_DRAW_STR, "%s%s%s",
but->editstr, ime_data->str_composite,
but->editstr + but->pos);
}
else
#endif
{
drawstr = but->editstr;
}
}
}
/* text button selection and cursor */
/* text button selection, cursor, composite underline */
if (but->editstr && but->pos != -1) {
int but_pos_ofs;
int tx, ty;
/* text button selection */
if ((but->selend - but->selsta) > 0) {
@ -1298,18 +1367,44 @@ static void widget_draw_text(uiFontStyle *fstyle, uiWidgetColors *wcol, uiBut *b
}
/* text cursor */
but_pos_ofs = but->pos;
#ifdef WITH_INPUT_IME
/* if is ime compositing, move the cursor */
if (ime_data && ime_data->composite_len && ime_data->cursor_pos != -1) {
but_pos_ofs += ime_data->cursor_pos;
}
#endif
if (but->pos >= but->ofs) {
int t;
if (drawstr[0] != 0) {
t = BLF_width(fstyle->uifont_id, drawstr + but->ofs, but->pos - but->ofs);
t = BLF_width(fstyle->uifont_id, drawstr + but->ofs, but_pos_ofs - but->ofs);
}
else {
t = 0;
}
glColor3f(0.20, 0.6, 0.9);
glRecti(rect->xmin + t, rect->ymin + 2, rect->xmin + t + 2, rect->ymax - 2);
tx = rect->xmin + t + 2;
ty = rect->ymin + 2;
/* draw cursor */
glRecti(rect->xmin + t, ty, tx, rect->ymax - 2);
}
#ifdef WITH_INPUT_IME
if (ime_data && ime_data->composite_len) {
/* ime cursor following */
if (but->pos >= but->ofs) {
ui_but_ime_reposition(but, tx + 5, ty + 3, false);
}
/* composite underline */
widget_draw_text_ime_underline(fstyle, wcol, but, rect, ime_data, drawstr);
}
#endif
}
if (fstyle->kerning == 1)

@ -165,6 +165,13 @@ enum {
WM_INIT_KEYMAP = (1<<1),
};
/* IME is win32 only! */
#ifndef WIN32
# ifdef __GNUC__
# define ime_data ime_data __attribute__ ((deprecated))
# endif
#endif
/* the savable part, rest of data is local in ghostwinlay */
typedef struct wmWindow {
struct wmWindow *next, *prev;
@ -197,6 +204,10 @@ typedef struct wmWindow {
struct wmGesture *tweak; /* internal for wm_operators.c */
/* Input Method Editor data - complex character input (esp. for asian character input)
* Currently WIN32, runtime-only data */
struct wmIMEData *ime_data;
int drawmethod, drawfail; /* internal for wm_draw.c only */
void *drawdata; /* internal for wm_draw.c only */
@ -208,6 +219,10 @@ typedef struct wmWindow {
ListBase gesture; /* gesture stuff */
} wmWindow;
#ifdef ime_data
# undef ime_data
#endif
/* These two Lines with # tell makesdna this struct can be excluded. */
/* should be something like DNA_EXCLUDE
* but the preprocessor first removes all comments, spaces etc */

@ -130,6 +130,12 @@ if(WITH_BUILDINFO)
add_definitions(-DWITH_BUILDINFO)
endif()
if(WIN32)
if(WITH_INPUT_IME)
add_definitions(-DWITH_INPUT_IME)
endif()
endif()
if(WITH_COMPOSITOR)
add_definitions(-DWITH_COMPOSITOR)
endif()

@ -69,6 +69,10 @@ if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'linuxcross', 'win64-vc', '
if env['BF_BUILDINFO']:
defs.append('WITH_BUILDINFO')
if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'win64-vc', 'win64-mingw'):
if env['WITH_BF_IME']:
defs.append('WITH_INPUT_IME')
if env['WITH_BF_INTERNATIONAL']:
defs.append('WITH_INTERNATIONAL')

@ -462,6 +462,10 @@ void WM_event_ndof_to_quat(const struct wmNDOFMotionData *ndof, float q[4
float WM_event_tablet_data(const struct wmEvent *event, int *pen_flip, float tilt[2]);
bool WM_event_is_tablet(const struct wmEvent *event);
#ifdef WITH_INPUT_IME
bool WM_event_is_ime_switch(const struct wmEvent *event);
#endif
#ifdef __cplusplus
}
#endif

@ -570,6 +570,24 @@ typedef struct wmOperatorType {
} wmOperatorType;
#ifdef WITH_INPUT_IME
/* *********** Input Method Editor (IME) *********** */
/* similar to GHOST_TEventImeData */
typedef struct wmIMEData {
size_t result_len, composite_len;
char *str_result; /* utf8 encoding */
char *str_composite; /* utf8 encoding */
int cursor_pos; /* cursor position in the IME composition. */
int sel_start; /* beginning of the selection */
int sel_end; /* end of the selection */
bool is_ime_composing;
} wmIMEData;
#endif
/* **************** Paint Cursor ******************* */
typedef void (*wmPaintCursorDraw)(struct bContext *C, int, int, void *customdata);

@ -3373,6 +3373,33 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, int U
break;
}
#ifdef WITH_INPUT_IME
case GHOST_kEventImeCompositionStart:
{
event.val = KM_PRESS;
win->ime_data = customdata;
win->ime_data->is_ime_composing = true;
event.type = WM_IME_COMPOSITE_START;
wm_event_add(win, &event);
break;
}
case GHOST_kEventImeComposition:
{
event.val = KM_PRESS;
event.type = WM_IME_COMPOSITE_EVENT;
wm_event_add(win, &event);
break;
}
case GHOST_kEventImeCompositionEnd:
{
event.val = KM_PRESS;
win->ime_data->is_ime_composing = false;
event.type = WM_IME_COMPOSITE_END;
wm_event_add(win, &event);
break;
}
#endif /* WITH_INPUT_IME */
}
#if 0
@ -3479,5 +3506,12 @@ bool WM_event_is_tablet(const struct wmEvent *event)
return (event->tablet_data) ? true : false;
}
#ifdef WITH_INPUT_IME
/* most os using ctrl/oskey + space to switch ime, avoid added space */
bool WM_event_is_ime_switch(const struct wmEvent *event) {
return event->val == KM_PRESS && event->type == SPACEKEY &&
(event->ctrl || event->oskey || event->shift || event->alt);
}
#endif
/** \} */

@ -1524,3 +1524,20 @@ bool WM_window_is_fullscreen(wmWindow *win)
return win->windowstate == GHOST_kWindowStateFullScreen;
}
#ifdef WITH_INPUT_IME
void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete)
{
BLI_assert(win && (win->ime_data == NULL));
GHOST_BeginIME(win->ghostwin, x, win->sizey - y, w, h, complete);
}
void wm_window_IME_end(wmWindow *win)
{
BLI_assert(win && win->ime_data);
GHOST_EndIME(win->ghostwin);
win->ime_data = NULL;
}
#endif /* WITH_INPUT_IME */

@ -86,6 +86,13 @@ enum {
* paint and drawing tools however will want to handle these. */
INBETWEEN_MOUSEMOVE = 0x0011,
/* IME event, GHOST_kEventImeCompositionStart in ghost */
WM_IME_COMPOSITE_START = 0x0014,
/* IME event, GHOST_kEventImeComposition in ghost */
WM_IME_COMPOSITE_EVENT = 0x0015,
/* IME event, GHOST_kEventImeCompositionEnd in ghost */
WM_IME_COMPOSITE_END = 0x0016,
/* *** Start of keyboard codes. *** */
/* standard keyboard.

@ -69,6 +69,11 @@ wmWindow *wm_window_copy (bContext *C, wmWindow *winorig);
void wm_window_testbreak (void);
#ifdef WITH_INPUT_IME
void wm_window_IME_begin (wmWindow *win, int x, int y, int w, int h, bool complete);
void wm_window_IME_end (wmWindow *win);
#endif
/* *************** window operators ************** */
int wm_window_duplicate_exec(bContext *C, struct wmOperator *op);
int wm_window_fullscreen_toggle_exec(bContext *C, struct wmOperator *op);