diff options
Diffstat (limited to 'intern/ghost')
25 files changed, 1474 insertions, 263 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 6f30cecab61..dc55a81b0d8 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -152,7 +152,7 @@ elseif(APPLE AND NOT WITH_X11) intern/GHOST_WindowCocoa.h ) - if (NOT WITH_GL_EGL) + if(NOT WITH_GL_EGL) list(APPEND SRC intern/GHOST_ContextCGL.mm @@ -192,7 +192,7 @@ elseif(WITH_X11) intern/GHOST_WindowX11.h ) - if (NOT WITH_GL_EGL) + if(NOT WITH_GL_EGL) list(APPEND SRC intern/GHOST_ContextGLX.cpp @@ -272,7 +272,7 @@ elseif(WIN32) intern/GHOST_TaskbarWin32.h ) - if (NOT WITH_GL_EGL) + if(NOT WITH_GL_EGL) list(APPEND SRC intern/GHOST_ContextWGL.cpp @@ -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 diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 7b47f0526a2..c0f2651dd8b 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -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 } diff --git a/intern/ghost/GHOST_IWindow.h b/intern/ghost/GHOST_IWindow.h index 71dc193a81b..3f8215dc7c2 100644 --- a/intern/ghost/GHOST_IWindow.h +++ b/intern/ghost/GHOST_IWindow.h @@ -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") diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 7333ba025a5..c4a7490c71c 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -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; diff --git a/intern/ghost/SConscript b/intern/ghost/SConscript index 5a4572c164d..025559e11a4 100644 --- a/intern/ghost/SConscript +++ b/intern/ghost/SConscript @@ -154,6 +154,14 @@ if env['BF_GHOST_DEBUG']: else: sources.remove('intern' + os.sep + 'GHOST_EventPrinter.cpp') +if env['WITH_BF_IME'] and window_system in ('win32-vc', 'win32-mingw', 'win64-vc', 'win64-mingw'): + defs.append('WITH_INPUT_IME') +elif env['WITH_BF_IME']: + print "IME input is only supported on Windows! Please disable WITH_BF_IME!" + Exit() +else: + sources.remove('intern' + os.sep + 'GHOST_ImeWin32.cpp') + if env['WITH_BF_3DMOUSE']: defs.append('WITH_INPUT_NDOF') diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 5a2e638f01a..0da77ac5e20 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -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 */ diff --git a/intern/ghost/intern/GHOST_ContextEGL.cpp b/intern/ghost/intern/GHOST_ContextEGL.cpp index 6a9231835c5..520aa0fffb2 100644 --- a/intern/ghost/intern/GHOST_ContextEGL.cpp +++ b/intern/ghost/intern/GHOST_ContextEGL.cpp @@ -310,7 +310,9 @@ GHOST_TSuccess GHOST_ContextEGL::setSwapInterval(int interval) GHOST_TSuccess GHOST_ContextEGL::getSwapInterval(int &intervalOut) { - intervalOut = m_swap_interval; // XXX jwilkins: make sure there is no way to query this? + // This is a bit of a kludge because there does not seem to + // be a way to query the swap interval with EGL. + intervalOut = m_swap_interval; return GHOST_kSuccess; } diff --git a/intern/ghost/intern/GHOST_ContextGLX.cpp b/intern/ghost/intern/GHOST_ContextGLX.cpp index 726614c48b1..c4e1346456e 100644 --- a/intern/ghost/intern/GHOST_ContextGLX.cpp +++ b/intern/ghost/intern/GHOST_ContextGLX.cpp @@ -54,6 +54,7 @@ GHOST_ContextGLX::GHOST_ContextGLX( GHOST_TUns16 numOfAASamples, Window window, Display *display, + XVisualInfo *visualInfo, int contextProfileMask, int contextMajorVersion, int contextMinorVersion, @@ -61,13 +62,13 @@ GHOST_ContextGLX::GHOST_ContextGLX( int contextResetNotificationStrategy) : GHOST_Context(stereoVisual, numOfAASamples), m_display(display), + m_visualInfo(visualInfo), m_window(window), m_contextProfileMask(contextProfileMask), m_contextMajorVersion(contextMajorVersion), m_contextMinorVersion(contextMinorVersion), m_contextFlags(contextFlags), m_contextResetNotificationStrategy(contextResetNotificationStrategy), - m_visualInfo(NULL), m_context(None) #ifdef WITH_GLEW_MX , @@ -104,8 +105,6 @@ GHOST_ContextGLX::~GHOST_ContextGLX() delete m_glxewContext; #endif } - - XFree(m_visualInfo); } @@ -145,121 +144,6 @@ void GHOST_ContextGLX::initContextGLXEW() GHOST_TSuccess GHOST_ContextGLX::initializeDrawingContext() { - /* Set up the minimum attributes that we require and see if - * X can find us a visual matching those requirements. */ - - std::vector<int> attribs; - attribs.reserve(40); - - int glx_major, glx_minor; /* GLX version: major.minor */ - - if (!glXQueryVersion(m_display, &glx_major, &glx_minor)) { - fprintf(stderr, - "%s:%d: X11 glXQueryVersion() failed, " - "verify working openGL system!\n", - __FILE__, __LINE__); - - /* exit if this is the first window */ - if (s_sharedContext == NULL) { - fprintf(stderr, "initial window could not find the GLX extension, exit!\n"); - exit(EXIT_FAILURE); - } - - return GHOST_kFailure; - } - -#ifdef GHOST_OPENGL_ALPHA - const bool needAlpha = true; -#else - const bool needAlpha = false; -#endif - -#ifdef GHOST_OPENGL_STENCIL - const bool needStencil = true; -#else - const bool needStencil = false; -#endif - - /* Find the display with highest samples, starting at level requested */ - int actualSamples = m_numOfAASamples; - for (;;) { - attribs.clear(); - - if (m_stereoVisual) - attribs.push_back(GLX_STEREO); - - attribs.push_back(GLX_RGBA); - - attribs.push_back(GLX_DOUBLEBUFFER); - - attribs.push_back(GLX_RED_SIZE); - attribs.push_back(1); - - attribs.push_back(GLX_BLUE_SIZE); - attribs.push_back(1); - - attribs.push_back(GLX_GREEN_SIZE); - attribs.push_back(1); - - attribs.push_back(GLX_DEPTH_SIZE); - attribs.push_back(1); - - if (needAlpha) { - attribs.push_back(GLX_ALPHA_SIZE); - attribs.push_back(1); - } - - if (needStencil) { - attribs.push_back(GLX_STENCIL_SIZE); - attribs.push_back(1); - } - - /* GLX >= 1.4 required for multi-sample */ - if (actualSamples > 0 && ((glx_major > 1) || (glx_major == 1 && glx_minor >= 4))) { - attribs.push_back(GLX_SAMPLE_BUFFERS); - attribs.push_back(1); - - attribs.push_back(GLX_SAMPLES); - attribs.push_back(actualSamples); - } - - attribs.push_back(None); - - m_visualInfo = glXChooseVisual(m_display, DefaultScreen(m_display), &attribs[0]); - - /* Any sample level or even zero, which means oversampling disabled, is good - * but we need a valid visual to continue */ - if (m_visualInfo != NULL) { - if (actualSamples < m_numOfAASamples) { - fprintf(stderr, - "Warning! Unable to find a multisample pixel format that supports exactly %d samples. " - "Substituting one that uses %d samples.\n", - m_numOfAASamples, actualSamples); - } - break; - } - - if (actualSamples == 0) { - /* All options exhausted, cannot continue */ - fprintf(stderr, - "%s:%d: X11 glXChooseVisual() failed, " - "verify working openGL system!\n", - __FILE__, __LINE__); - - if (s_sharedContext == None) { - fprintf(stderr, "initial window could not find the GLX extension, exit!\n"); - exit(1); - } - - return GHOST_kFailure; - } - else { - --actualSamples; - } - } - - m_numOfAASamples = actualSamples; - #ifdef WITH_X11_XINPUT /* use our own event handlers to avoid exiting blender, * this would happen for eg: diff --git a/intern/ghost/intern/GHOST_ContextGLX.h b/intern/ghost/intern/GHOST_ContextGLX.h index d32adf3338d..9bc17d476fb 100644 --- a/intern/ghost/intern/GHOST_ContextGLX.h +++ b/intern/ghost/intern/GHOST_ContextGLX.h @@ -65,6 +65,7 @@ public: GHOST_TUns16 numOfAASamples, Window window, Display *display, + XVisualInfo *visualInfo, int contextProfileMask, int contextMajorVersion, int contextMinorVersion, @@ -126,6 +127,7 @@ private: void initContextGLXEW(); Display *m_display; + XVisualInfo *m_visualInfo; Window m_window; const int m_contextProfileMask; @@ -134,8 +136,6 @@ private: const int m_contextFlags; const int m_contextResetNotificationStrategy; - XVisualInfo *m_visualInfo; - GLXContext m_context; #ifdef WITH_GLEW_MX diff --git a/intern/ghost/intern/GHOST_ImeWin32.cpp b/intern/ghost/intern/GHOST_ImeWin32.cpp new file mode 100644 index 00000000000..ef8eb9827ff --- /dev/null +++ b/intern/ghost/intern/GHOST_ImeWin32.cpp @@ -0,0 +1,517 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (c) 2010 The Chromium Authors. All rights reserved. + * All rights reserved. + * + * The Original Code is: some of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + + +/** \file ghost/intern/GHOST_ImeWin32.cpp + * \ingroup GHOST + */ + + +#include "GHOST_C-api.h" +#include "GHOST_ImeWin32.h" +#include "GHOST_WindowWin32.h" +#include "utfconv.h" + + +GHOST_ImeWin32::GHOST_ImeWin32() + : ime_status_(false), + input_language_id_(LANG_USER_DEFAULT), + is_composing_(false), + system_caret_(false), + caret_rect_(-1, -1, 0, 0), + is_first(true), + is_enable(true) +{ +} + + +GHOST_ImeWin32::~GHOST_ImeWin32() +{ +} + + +bool GHOST_ImeWin32::SetInputLanguage() +{ + /** + * Retrieve the current keyboard layout from Windows and determine whether + * or not the current input context has IMEs. + * Also save its input language for language-specific operations required + * while composing a text. + */ + HKL keyboard_layout = ::GetKeyboardLayout(0); + input_language_id_ = reinterpret_cast<LANGID>(keyboard_layout); + ime_status_ = ::ImmIsIME(keyboard_layout); + return ime_status_; +} + + +void GHOST_ImeWin32::CreateImeWindow(HWND window_handle) +{ + /** + * When a user disables TSF (Text Service Framework) and CUAS (Cicero + * Unaware Application Support), Chinese IMEs somehow ignore function calls + * to ::ImmSetCandidateWindow(), i.e. they do not move their candidate + * window to the position given as its parameters, and use the position + * of the current system caret instead, i.e. it uses ::GetCaretPos() to + * retrieve the position of their IME candidate window. + * Therefore, we create a temporary system caret for Chinese IMEs and use + * it during this input context. + * Since some third-party Japanese IME also uses ::GetCaretPos() to determine + * their window position, we also create a caret for Japanese IMEs. + */ + if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE || + PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) { + if (!system_caret_) { + if (::CreateCaret(window_handle, NULL, 1, 1)) { + system_caret_ = true; + } + } + } + /* Restore the positions of the IME windows. */ + UpdateImeWindow(window_handle); +} + + +void GHOST_ImeWin32::SetImeWindowStyle(HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled) +{ + /** + * To prevent the IMM (Input Method Manager) from displaying the IME + * composition window, Update the styles of the IME windows and EXPLICITLY + * call ::DefWindowProc() here. + * NOTE(hbono): We can NEVER let WTL call ::DefWindowProc() when we update + * the styles of IME windows because the 'lparam' variable is a local one + * and all its updates disappear in returning from this function, i.e. WTL + * does not call ::DefWindowProc() with our updated 'lparam' value but call + * the function with its original value and over-writes our window styles. + */ + *handled = TRUE; + lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + ::DefWindowProc(window_handle, message, wparam, lparam); +} + + +void GHOST_ImeWin32::DestroyImeWindow(HWND window_handle) +{ + /* Destroy the system caret if we have created for this IME input context. */ + if (system_caret_) { + ::DestroyCaret(); + system_caret_ = false; + } +} + + +void GHOST_ImeWin32::MoveImeWindow(HWND window_handle, HIMC imm_context) +{ + int x = caret_rect_.m_l; + int y = caret_rect_.m_t; + const int kCaretMargin = 1; + /** + * As written in a comment in GHOST_ImeWin32::CreateImeWindow(), + * Chinese IMEs ignore function calls to ::ImmSetCandidateWindow() + * when a user disables TSF (Text Service Framework) and CUAS (Cicero + * Unaware Application Support). + * On the other hand, when a user enables TSF and CUAS, Chinese IMEs + * ignore the position of the current system caret and uses the + * parameters given to ::ImmSetCandidateWindow() with its 'dwStyle' + * parameter CFS_CANDIDATEPOS. + * Therefore, we do not only call ::ImmSetCandidateWindow() but also + * set the positions of the temporary system caret if it exists. + */ + CANDIDATEFORM candidate_position = { 0, CFS_CANDIDATEPOS, { x, y }, + { 0, 0, 0, 0 } }; + ::ImmSetCandidateWindow(imm_context, &candidate_position); + if (system_caret_) { + switch (PRIMARYLANGID(input_language_id_)) { + case LANG_JAPANESE: + ::SetCaretPos(x, y + caret_rect_.getHeight()); + break; + default: + ::SetCaretPos(x, y); + break; + } + } + if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) { + /** + * Chinese IMEs and Japanese IMEs require the upper-left corner of + * the caret to move the position of their candidate windows. + * On the other hand, Korean IMEs require the lower-left corner of the + * caret to move their candidate windows. + */ + y += kCaretMargin; + } + /** + * Japanese IMEs and Korean IMEs also use the rectangle given to + * ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE + * to move their candidate windows when a user disables TSF and CUAS. + * Therefore, we also set this parameter here. + */ + CANDIDATEFORM exclude_rectangle = { 0, CFS_EXCLUDE, { x, y }, + { x, y, x + caret_rect_.getWidth(), y + caret_rect_.getHeight() } }; + ::ImmSetCandidateWindow(imm_context, &exclude_rectangle); +} + + +void GHOST_ImeWin32::UpdateImeWindow(HWND window_handle) +{ + /* Just move the IME window attached to the given window. */ + if (caret_rect_.m_l >= 0 && caret_rect_.m_t >= 0) { + HIMC imm_context = ::ImmGetContext(window_handle); + if (imm_context) { + MoveImeWindow(window_handle, imm_context); + ::ImmReleaseContext(window_handle, imm_context); + } + } +} + + +void GHOST_ImeWin32::CleanupComposition(HWND window_handle) +{ + /** + * Notify the IMM attached to the given window to complete the ongoing + * composition, (this case happens when the given window is de-activated + * while composing a text and re-activated), and reset the omposition status. + */ + if (is_composing_) { + HIMC imm_context = ::ImmGetContext(window_handle); + if (imm_context) { + ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + ::ImmReleaseContext(window_handle, imm_context); + } + ResetComposition(window_handle); + } +} + + +void GHOST_ImeWin32::CheckFirst(HWND window_handle) +{ + if (is_first) { + this->EndIME(window_handle); + is_first = false; + } +} + + +void GHOST_ImeWin32::ResetComposition(HWND window_handle) +{ + /* Currently, just reset the composition status. */ + is_composing_ = false; +} + + +void GHOST_ImeWin32::CompleteComposition(HWND window_handle, HIMC imm_context) +{ + /** + * We have to confirm there is an ongoing composition before completing it. + * This is for preventing some IMEs from getting confused while completing an + * ongoing composition even if they do not have any ongoing compositions.) + */ + if (is_composing_) { + ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + ResetComposition(window_handle); + } +} + + +void GHOST_ImeWin32::GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *composition) +{ + /** + * This operation is optional and language-dependent because the caret + * style is depended on the language, e.g.: + * * Korean IMEs: the caret is a blinking block, + * (It contains only one hangul character); + * * Chinese IMEs: the caret is a blinking line, + * (i.e. they do not need to retrieve the target selection); + * * Japanese IMEs: the caret is a selection (or underlined) block, + * (which can contain one or more Japanese characters). + */ + int target_start = -1; + int target_end = -1; + switch (PRIMARYLANGID(input_language_id_)) { + case LANG_KOREAN: + if (lparam & CS_NOMOVECARET) { + target_start = 0; + target_end = 1; + } + break; + case LANG_CHINESE: + { + int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, NULL, 0); + if (clause_size) { + static std::vector<unsigned long> clauses; + clause_size = clause_size / sizeof(clauses[0]); + clauses.resize(clause_size); + ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, &clauses[0], + sizeof(clauses[0]) *clause_size); + if (composition->cursor_position == composition->ime_string.size()) { + target_start = clauses[clause_size - 2]; + target_end = clauses[clause_size - 1]; + } + else { + for (int i = 0; i < clause_size - 1; i++) { + if (clauses[i] == composition->cursor_position) { + target_start = clauses[i]; + target_end = clauses[i + 1]; + break; + } + } + } + } + else { + if (composition->cursor_position != -1) { + target_start = composition->cursor_position; + target_end = composition->ime_string.size(); + } + } + break; + } + case LANG_JAPANESE: + + /** + * For Japanese IMEs, the robustest way to retrieve the caret + * is scanning the attribute of the latest composition string and + * retrieving the begining and the end of the target clause, i.e. + * a clause being converted. + */ + if (lparam & GCS_COMPATTR) { + int attribute_size = ::ImmGetCompositionStringW(imm_context, + GCS_COMPATTR, + NULL, 0); + if (attribute_size > 0) { + char *attribute_data = new char[attribute_size]; + if (attribute_data) { + ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, + attribute_data, attribute_size); + for (target_start = 0; target_start < attribute_size; + ++target_start) { + if (IsTargetAttribute(attribute_data[target_start])) + break; + } + for (target_end = target_start; target_end < attribute_size; + ++target_end) { + if (!IsTargetAttribute(attribute_data[target_end])) + break; + } + if (target_start == attribute_size) { + /** + * This composition clause does not contain any target clauses, + * i.e. this clauses is an input clause. + * We treat whole this clause as a target clause. + */ + target_end = target_start; + target_start = 0; + } + if (target_start != -1 && target_start < attribute_size && + attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED) + { + composition->cursor_position = target_start; + } + } + delete[] attribute_data; + } + } + break; + } + composition->target_start = target_start; + composition->target_end = target_end; +} + + +bool GHOST_ImeWin32::GetString(HIMC imm_context, WPARAM lparam, int type, ImeComposition *composition) +{ + bool result = false; + if (lparam & type) { + int string_size = ::ImmGetCompositionStringW(imm_context, type, NULL, 0); + if (string_size > 0) { + int string_length = string_size / sizeof(wchar_t); + wchar_t *string_data = new wchar_t[string_length + 1]; + string_data[string_length] = '\0'; + if (string_data) { + /* Fill the given ImeComposition object. */ + ::ImmGetCompositionStringW(imm_context, type, + string_data, string_size); + composition->string_type = type; + composition->ime_string = string_data; + result = true; + } + delete[] string_data; + } + } + return result; +} + + +bool GHOST_ImeWin32::GetResult(HWND window_handle, LPARAM lparam, ImeComposition *composition) +{ + bool result = false; + HIMC imm_context = ::ImmGetContext(window_handle); + if (imm_context) { + /* Copy the result string to the ImeComposition object. */ + result = GetString(imm_context, lparam, GCS_RESULTSTR, composition); + /** + * Reset all the other parameters because a result string does not + * have composition attributes. + */ + composition->cursor_position = -1; + composition->target_start = -1; + composition->target_end = -1; + ::ImmReleaseContext(window_handle, imm_context); + } + return result; +} + + +bool GHOST_ImeWin32::GetComposition(HWND window_handle, LPARAM lparam, ImeComposition *composition) +{ + bool result = false; + HIMC imm_context = ::ImmGetContext(window_handle); + if (imm_context) { + /* Copy the composition string to the ImeComposition object. */ + result = GetString(imm_context, lparam, GCS_COMPSTR, composition); + + /* Retrieve the cursor position in the IME composition. */ + int cursor_position = ::ImmGetCompositionStringW(imm_context, GCS_CURSORPOS, NULL, 0); + composition->cursor_position = cursor_position; + composition->target_start = -1; + composition->target_end = -1; + + /* Retrieve the target selection and Update the ImeComposition object. */ + GetCaret(imm_context, lparam, composition); + + /* Mark that there is an ongoing composition. */ + is_composing_ = true; + + ::ImmReleaseContext(window_handle, imm_context); + } + return result; +} + + +void GHOST_ImeWin32::EndIME(HWND window_handle) +{ + /** + * A renderer process have moved its input focus to a password input + * when there is an ongoing composition, e.g. a user has clicked a + * mouse button and selected a password input while composing a text. + * For this case, we have to complete the ongoing composition and + * clean up the resources attached to this object BEFORE DISABLING THE IME. + */ + if (!is_enable) return; + is_enable = false; + CleanupComposition(window_handle); + ::ImmAssociateContextEx(window_handle, NULL, 0); + eventImeData.composite_len = 0; +} + + +void GHOST_ImeWin32::BeginIME(HWND window_handle, const GHOST_Rect &caret_rect, bool complete) +{ + if (is_enable && complete) return; + is_enable = true; + /** + * Load the default IME context. + * NOTE(hbono) + * IMM ignores this call if the IME context is loaded. Therefore, we do + * not have to check whether or not the IME context is loaded. + */ + ::ImmAssociateContextEx(window_handle, NULL, IACE_DEFAULT); + /* Complete the ongoing composition and move the IME windows. */ + HIMC imm_context = ::ImmGetContext(window_handle); + if (imm_context) { + if (complete) { + /** + * A renderer process have moved its input focus to another edit + * control when there is an ongoing composition, e.g. a user has + * clicked a mouse button and selected another edit control while + * composing a text. + * For this case, we have to complete the ongoing composition and + * hide the IME windows BEFORE MOVING THEM. + */ + CompleteComposition(window_handle, imm_context); + } + /** + * Save the caret position, and Update the position of the IME window. + * This update is used for moving an IME window when a renderer process + * resize/moves the input caret. + */ + if (caret_rect.m_l >= 0 && caret_rect.m_t >= 0) { + caret_rect_ = caret_rect; + MoveImeWindow(window_handle, imm_context); + } + ::ImmReleaseContext(window_handle, imm_context); + } +} + + +static void convert_utf16_to_utf8_len(std::wstring s, int &len) +{ + if (len >= 0 && len <= s.size()) + len = count_utf_8_from_16(s.substr(0, len).c_str()) - 1; + else + len = -1; +} + + +static size_t updateUtf8Buf(ImeComposition &info) +{ + size_t len = count_utf_8_from_16(info.ime_string.c_str()); + info.utf8_buf.resize(len); + conv_utf_16_to_8(info.ime_string.c_str(), &info.utf8_buf[0], len); + convert_utf16_to_utf8_len(info.ime_string, info.cursor_position); + convert_utf16_to_utf8_len(info.ime_string, info.target_start); + convert_utf16_to_utf8_len(info.ime_string, info.target_end); + return len - 1; +} + + +void GHOST_ImeWin32::UpdateInfo(HWND window_handle) +{ + int res = this->GetResult(window_handle, GCS_RESULTSTR, &resultInfo); + int comp = this->GetComposition(window_handle, GCS_COMPSTR | GCS_COMPATTR, &compInfo); + /* convert wchar to utf8 */ + if (res) { + eventImeData.result_len = (GHOST_TUserDataPtr)updateUtf8Buf(resultInfo); + eventImeData.result = &resultInfo.utf8_buf[0]; + } + else { + eventImeData.result = 0; + eventImeData.result_len = 0; + } + if (comp) { + eventImeData.composite_len = (GHOST_TUserDataPtr)updateUtf8Buf(compInfo); + eventImeData.composite = &compInfo.utf8_buf[0]; + eventImeData.cursor_position = compInfo.cursor_position; + eventImeData.target_start = compInfo.target_start; + eventImeData.target_end = compInfo.target_end; + } + else { + eventImeData.composite = 0; + eventImeData.composite_len = 0; + eventImeData.cursor_position = -1; + eventImeData.target_start = -1; + eventImeData.target_end = -1; + } +} diff --git a/intern/ghost/intern/GHOST_ImeWin32.h b/intern/ghost/intern/GHOST_ImeWin32.h new file mode 100644 index 00000000000..5b2d71875e8 --- /dev/null +++ b/intern/ghost/intern/GHOST_ImeWin32.h @@ -0,0 +1,401 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (c) 2010 The Chromium Authors. All rights reserved. + * All rights reserved. + * + * The Original Code is: some of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** +*/ + +/** \file ghost/intern/GHOST_ImeWin32.h + * \ingroup GHOST + */ + +#ifndef __GHOST_IME_H__ +#define __GHOST_IME_H__ + +#include <windows.h> + +#include <string> + +#include "GHOST_Event.h" +#include "GHOST_Rect.h" +#include <vector> + +class GHOST_EventIME : public GHOST_Event +{ +public: + /** + * Constructor. + * \param msec The time this event was generated. + * \param type The type of key event. + * \param key The key code of the key. + */ + GHOST_EventIME(GHOST_TUns64 msec, + GHOST_TEventType type, + GHOST_IWindow *window, void *customdata) + : GHOST_Event(msec, type, window) + { + this->m_data = customdata; + } + +}; + + +/** + * This header file defines a struct and a class used for encapsulating IMM32 + * APIs, controls IMEs attached to a window, and enables the 'on-the-spot' + * input without deep knowledge about the APIs, i.e. knowledge about the + * language-specific and IME-specific behaviors. + * The following items enumerates the simplest steps for an (window) + * application to control its IMEs with the struct and the class defined + * this file. + * 1. Add an instance of the GHOST_ImeWin32 class to its window class. + * (The GHOST_ImeWin32 class needs a window handle.) + * 2. Add messages handlers listed in the following subsections, follow the + * instructions written in each subsection, and use the GHOST_ImeWin32 class. + * 2.1. WM_IME_SETCONTEXT (0x0281) + * Call the functions listed below: + * - GHOST_ImeWin32::CreateImeWindow(); + * - GHOST_ImeWin32::CleanupComposition(), and; + * - GHOST_ImeWin32::SetImeWindowStyle(). + * An application MUST prevent from calling ::DefWindowProc(). + * 2.2. WM_IME_STARTCOMPOSITION (0x010D) + * Call the functions listed below: + * - GHOST_ImeWin32::CreateImeWindow(), and; + * - GHOST_ImeWin32::ResetComposition(). + * An application MUST prevent from calling ::DefWindowProc(). + * 2.3. WM_IME_COMPOSITION (0x010F) + * Call the functions listed below: + * - GHOST_ImeWin32::UpdateImeWindow(); + * - GHOST_ImeWin32::GetResult(); + * - GHOST_ImeWin32::GetComposition(), and; + * - GHOST_ImeWin32::ResetComposition() (optional). + * An application MUST prevent from calling ::DefWindowProc(). + * 2.4. WM_IME_ENDCOMPOSITION (0x010E) + * Call the functions listed below: + * - GHOST_ImeWin32::ResetComposition(), and; + * - GHOST_ImeWin32::DestroyImeWindow(). + * An application CAN call ::DefWindowProc(). + * 2.5. WM_INPUTLANGCHANGE (0x0051) + * Call the functions listed below: + * - GHOST_ImeWin32::SetInputLanguage(). + * An application CAN call ::DefWindowProc(). + */ + +/* This struct represents the status of an ongoing composition. */ +struct ImeComposition { + /* Represents the 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; + + /** + * Represents the type of the string in the 'ime_string' parameter. + * Its possible values and description are listed below: + * Value Description + * 0 The parameter is not used. + * GCS_RESULTSTR The parameter represents a result string. + * GCS_COMPSTR The parameter represents a composition string. + */ + int string_type; + + /* Represents the string retrieved from IME (Input Method Editor) */ + std::wstring ime_string; + std::vector<char> utf8_buf; + std::vector<unsigned char> format; +}; + +/** + * This class controls the IMM (Input Method Manager) through IMM32 APIs and + * enables it to retrieve the string being controled by the IMM. (I wrote + * a note to describe the reason why I do not use 'IME' but 'IMM' below.) + * NOTE(hbono): + * Fortunately or unfortunately, TSF (Text Service Framework) and + * CUAS (Cicero Unaware Application Support) allows IMM32 APIs for + * retrieving not only the inputs from IMEs (Input Method Editors), used + * only for inputting East-Asian language texts, but also the ones from + * tablets (on Windows XP Tablet PC Edition and Windows Vista), voice + * recognizers (e.g. ViaVoice and Microsoft Office), etc. + * We can disable TSF and CUAS in Windows XP Tablet PC Edition. On the other + * hand, we can NEVER disable either TSF or CUAS in Windows Vista, i.e. + * THIS CLASS IS NOT ONLY USED ON THE INPUT CONTEXTS OF EAST-ASIAN + * LANGUAGES BUT ALSO USED ON THE INPUT CONTEXTS OF ALL LANGUAGES. + */ +class GHOST_ImeWin32 { +public: + GHOST_ImeWin32(); + ~GHOST_ImeWin32(); + + /* Retrieves whether or not there is an ongoing composition. */ + bool is_composing() const {return is_composing_;} + + /** + * Retrieves the input language from Windows and update it. + * Return values + * * true + * The given input language has IMEs. + * * false + * The given input language does not have IMEs. + */ + bool SetInputLanguage(); + + /** + * Create the IME windows, and allocate required resources for them. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + */ + void CreateImeWindow(HWND window_handle); + + /** + * Update the style of the IME windows. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + * * message [in] (UINT) + * * wparam [in] (WPARAM) + * * lparam [in] (LPARAM) + * Represent the windows message of the caller. + * These parameters are used for verifying if this function is called + * in a handler function for WM_IME_SETCONTEXT messages because this + * function uses ::DefWindowProc() to update the style. + * A caller just has to pass the input parameters for the handler + * function without modifications. + * * handled [out] (BOOL*) + * Returns ::DefWindowProc() is really called in this function. + * PLEASE DO NOT CALL ::DefWindowProc() IF THIS VALUE IS TRUE! + * All the window styles set in this function are over-written when + * calling ::DefWindowProc() after returning this function. + */ + void SetImeWindowStyle(HWND window_handle, UINT message, + WPARAM wparam, LPARAM lparam, BOOL* handled); + + /** + * Destroy the IME windows and all the resources attached to them. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + */ + void DestroyImeWindow(HWND window_handle); + + /** + * Update the position of the IME windows. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + */ + void UpdateImeWindow(HWND window_handle); + + /** + * Clean up the all resources attached to the given GHOST_ImeWin32 object, and + * reset its composition status. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + */ + void CleanupComposition(HWND window_handle); + + /** + * Reset the composition status. + * Cancel the ongoing composition if it exists. + * NOTE(hbono): This method does not release the allocated resources. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + */ + void ResetComposition(HWND window_handle); + + /** + * Retrieve a composition result of the ongoing composition if it exists. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + * * lparam [in] (LPARAM) + * Specifies the updated members of the ongoing composition, and must be + * the same parameter of a WM_IME_COMPOSITION message handler. + * This parameter is used for checking if the ongoing composition has + * its result string, + * * composition [out] (ImeComposition) + * Represents the struct contains the composition result. + * Return values + * * true + * The ongoing composition has a composition result. + * * false + * The ongoing composition does not have composition results. + * Remarks + * This function is designed for being called from WM_IME_COMPOSITION + * message handlers. + */ + bool GetResult(HWND window_handle, LPARAM lparam, + ImeComposition* composition); + + /** + * Retrieve the current composition status of the ongoing composition. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + * * lparam [in] (LPARAM) + * Specifies the updated members of the ongoing composition, and must be + * the same parameter of a WM_IME_COMPOSITION message handler. + * This parameter is used for checking if the ongoing composition has + * its result string, + * * composition [out] (ImeComposition) + * Represents the struct contains the composition status. + * Return values + * * true + * The status of the ongoing composition is updated. + * * false + * The status of the ongoing composition is not updated. + * Remarks + * This function is designed for being called from WM_IME_COMPOSITION + * message handlers. + */ + bool GetComposition(HWND window_handle, LPARAM lparam, + ImeComposition* composition); + + /** + * Enable the IME attached to the given window, i.e. allows user-input + * events to be dispatched to the IME. + * In Chrome, this function is used when: + * * a renderer process moves its input focus to another edit control, or; + * * a renrerer process moves the position of the focused edit control. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + * * caret_rect [in] (const gfx::Rect&) + * Represent the rectangle of the input caret. + * This rectangle is used for controlling the positions of IME windows. + * * complete [in] (bool) + * Represents whether or not to complete the ongoing composition. + * + true + * After finishing the ongoing composition and close its IME windows, + * start another composition and display its IME windows to the given + * position. + * + false + * Just move the IME windows of the ongoing composition to the given + * position without finishing it. + */ + void BeginIME(HWND window_handle, + const GHOST_Rect& caret_rect, + bool complete); + + /** + * Disable the IME attached to the given window, i.e. prohibits any user-input + * events from being dispatched to the IME. + * In Chrome, this function is used when: + * * a renreder process sets its input focus to a password input. + * Parameters + * * window_handle [in] (HWND) + * Represents the window handle of the caller. + */ + void EndIME(HWND window_handle); + + /* Updatg resultInfo and compInfo */ + void UpdateInfo(HWND window_handle); + + /* disable ime when start up */ + void CheckFirst(HWND window_handle); + + ImeComposition resultInfo, compInfo; + GHOST_TEventImeData eventImeData; + +protected: + /* Determines whether or not the given attribute represents a target (a.k.a. a selection). */ + bool IsTargetAttribute(char attribute) const { + return (attribute == ATTR_TARGET_CONVERTED || + attribute == ATTR_TARGET_NOTCONVERTED); + } + + /* Retrieve the target area. */ + void GetCaret(HIMC imm_context, LPARAM lparam, + ImeComposition* composition); + + /* Update the position of the IME windows. */ + void MoveImeWindow(HWND window_handle, HIMC imm_context); + + /* Complete the ongoing composition if it exists. */ + void CompleteComposition(HWND window_handle, HIMC imm_context); + + /* Retrieve a string from the IMM. */ + bool GetString(HIMC imm_context, WPARAM lparam, int type, + ImeComposition* composition); + +private: + /** + * Represents whether or not there is an ongoing composition in a browser + * process, i.e. whether or not a browser process is composing a text. + */ + bool is_composing_; + + /** + * This value represents whether or not the current input context has IMEs. + * The following table shows the list of IME status: + * Value Description + * false The current input language does not have IMEs. + * true The current input language has IMEs. + */ + bool ime_status_; + + /** + * The current input Language ID retrieved from Windows, which consists of: + * * Primary Language ID (bit 0 to bit 9), which shows a natunal language + * (English, Korean, Chinese, Japanese, etc.) and; + * * Sub-Language ID (bit 10 to bit 15), which shows a geometrical region + * the language is spoken (For English, United States, United Kingdom, + * Australia, Canada, etc.) + * The following list enumerates some examples for the Language ID: + * * "en-US" (0x0409) + * MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + * * "ko-KR" (0x0412) + * MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN); + * * "zh-TW" (0x0404) + * MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL); + * * "zh-CN" (0x0804) + * MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED); + * * "ja-JP" (0x0411) + * MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN), etc. + * (See <winnt.h> for other available values.) + * This Language ID is used for processing language-specific operations in + * IME functions. + */ + LANGID input_language_id_; + + /** + * Represents whether or not the current input context has created a system + * caret to set the position of its IME candidate window. + * * true: it creates a system caret. + * * false: it does not create a system caret. + */ + bool system_caret_; + + /* The rectangle of the input caret retrieved from a renderer process. */ + GHOST_Rect caret_rect_; + + /* used for disable ime when start up */ + bool is_first, is_enable; +}; + +#endif * __GHOST_IME_H__ diff --git a/intern/ghost/intern/GHOST_NDOFManager.cpp b/intern/ghost/intern/GHOST_NDOFManager.cpp index 9bd6226d5c2..27285e49e9e 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.cpp +++ b/intern/ghost/intern/GHOST_NDOFManager.cpp @@ -146,8 +146,8 @@ static const NDOF_ButtonT SpaceExplorer_HID_map[] = { NDOF_BUTTON_ROTATE }; -/* this is the older SpacePilot (sans Pro) - * thanks to polosson for info about this device */ +// this is the older SpacePilot (sans Pro) +// thanks to polosson for info about this device static const NDOF_ButtonT SpacePilot_HID_map[] = { NDOF_BUTTON_1, NDOF_BUTTON_2, @@ -190,17 +190,17 @@ static const NDOF_ButtonT Generic_HID_map[] = { static const int genericButtonCount = sizeof(Generic_HID_map) / sizeof(NDOF_ButtonT); GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System &sys) - : m_system(sys), - m_deviceType(NDOF_UnknownDevice), /* each platform has its own device detection code */ - m_buttonCount(genericButtonCount), - m_buttonMask(0), - m_hidMap(Generic_HID_map), - m_buttons(0), - m_motionTime(0), - m_prevMotionTime(0), - m_motionState(GHOST_kNotStarted), - m_motionEventPending(false), - m_deadZone(0.0f) + : m_system(sys), + m_deviceType(NDOF_UnknownDevice), // each platform has its own device detection code + m_buttonCount(genericButtonCount), + m_buttonMask(0), + m_hidMap(Generic_HID_map), + m_buttons(0), + m_motionTime(0), + m_prevMotionTime(0), + m_motionState(GHOST_kNotStarted), + m_motionEventPending(false), + m_deadZone(0.0f) { // to avoid the rare situation where one triple is updated and // the other is not, initialize them both here: @@ -228,7 +228,7 @@ bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short produ // that I don't have access to. Thanks! switch (vendor_id) { - case 0x046D: // Logitech (3Dconnexion) + case 0x046D: // Logitech (3Dconnexion was a subsidiary) switch (product_id) { // -- current devices -- case 0xC626: // full-size SpaceNavigator @@ -281,6 +281,29 @@ bool GHOST_NDOFManager::setDevice(unsigned short vendor_id, unsigned short produ printf("ndof: unknown Logitech product %04hx\n", product_id); } break; + case 0x256F: // 3Dconnexion + switch (product_id) { + case 0xC62E: // plugged in + case 0xC62F: // wireless + puts("ndof: using SpaceMouse Wireless"); + m_deviceType = NDOF_SpaceMouseWireless; + m_buttonCount = 2; + m_hidMap = Modern3Dx_HID_map; + break; + case 0xC631: // plugged in + case 0xC632: // wireless + puts("ndof: using SpaceMouse Pro Wireless"); + m_deviceType = NDOF_SpaceMouseProWireless; + m_buttonCount = 27; + // ^^ actually has 15 buttons, but their HID codes range from 0 to 26 + m_buttonMask = 0x07C0F137; + m_hidMap = Modern3Dx_HID_map; + break; + + default: + printf("ndof: unknown 3Dconnexion product %04hx\n", product_id); + } + break; default: printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id); } @@ -390,9 +413,9 @@ void GHOST_NDOFManager::updateButtons(int button_bits, GHOST_TUns64 time) void GHOST_NDOFManager::setDeadZone(float dz) { - if (dz < 0.f) { + if (dz < 0.0f) { // negative values don't make sense, so clamp at zero - dz = 0.f; + dz = 0.0f; } else if (dz > 0.5f) { // warn the rogue user/developer, but allow it @@ -405,14 +428,14 @@ void GHOST_NDOFManager::setDeadZone(float dz) static bool atHomePosition(GHOST_TEventNDOFMotionData *ndof) { -#define HOME(foo) (ndof->foo == 0.f) +#define HOME(foo) (ndof->foo == 0.0f) return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz); #undef HOME } static bool nearHomePosition(GHOST_TEventNDOFMotionData *ndof, float threshold) { - if (threshold == 0.f) { + if (threshold == 0.0f) { return atHomePosition(ndof); } else { @@ -442,7 +465,7 @@ bool GHOST_NDOFManager::sendMotionEvent() // scale axis values here to normalize them to around +/- 1 // they are scaled again for overall sensitivity in the WM based on user prefs - const float scale = 1.f / 350.f; // 3Dconnexion devices send +/- 350 usually + const float scale = 1.0f / 350.0f; // 3Dconnexion devices send +/- 350 usually data->tx = scale * m_translation[0]; data->ty = scale * m_translation[1]; diff --git a/intern/ghost/intern/GHOST_NDOFManager.h b/intern/ghost/intern/GHOST_NDOFManager.h index 0b2080e36a9..48a4881afb1 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.h +++ b/intern/ghost/intern/GHOST_NDOFManager.h @@ -20,7 +20,7 @@ * * ***** END GPL LICENSE BLOCK ***** */ - + #ifndef __GHOST_NDOFMANAGER_H__ #define __GHOST_NDOFMANAGER_H__ @@ -38,6 +38,8 @@ typedef enum { NDOF_SpaceExplorer, NDOF_SpacePilotPro, NDOF_SpaceMousePro, + NDOF_SpaceMouseWireless, + NDOF_SpaceMouseProWireless, // older devices NDOF_SpacePilot, diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 236002e9744..cfa1e8bf566 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -22,6 +22,7 @@ * * Contributors: Maarten Gribnau 05/2001 * Damien Plisson 09/2009 + * Jens Verwiebe 10/2014 * * ***** END GPL LICENSE BLOCK ***** */ @@ -56,6 +57,7 @@ #include "AssertMacros.h" + #pragma mark KeyMap, mouse converters static GHOST_TButtonMask convertButton(int button) @@ -313,8 +315,6 @@ static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction) @end - - #pragma mark initialization/finalization GHOST_SystemCocoa::GHOST_SystemCocoa() @@ -636,7 +636,6 @@ GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const } - #pragma mark Event handlers /** @@ -1573,7 +1572,6 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) } - #pragma mark Clipboard get/set GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index 0767ad5a8f9..4247c4f31ce 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -508,6 +508,7 @@ GHOST_TKey GHOST_SystemWin32::hardKey(GHOST_IWindow *window, RAWINPUT const &raw //! note: this function can be extended to include other exotic cases as they arise. // This function was added in response to bug [#25715] +// This is going to be a long list [T42426] GHOST_TKey GHOST_SystemWin32::processSpecialKey(GHOST_IWindow *window, short vKey, short scanCode) const { GHOST_TKey key = GHOST_kKeyUnknown; @@ -515,6 +516,10 @@ GHOST_TKey GHOST_SystemWin32::processSpecialKey(GHOST_IWindow *window, short vKe case LANG_FRENCH: if (vKey == VK_OEM_8) key = GHOST_kKeyF13; // oem key; used purely for shortcuts . break; + case LANG_ENGLISH: + if (SUBLANGID(m_langId) == SUBLANG_ENGLISH_UK && vKey == VK_OEM_8) // "`¬" + key = GHOST_kKeyAccentGrave; + break; } return key; @@ -787,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, @@ -899,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) { @@ -907,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 //////////////////////////////////////////////////////////////////////// @@ -944,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 //////////////////////////////////////////////////////////////////////// @@ -1060,17 +1130,13 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, /* Get the winow under the mouse and send event to it's queue. */ POINT mouse_pos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - HWND mouse_hwnd = WindowFromPoint(mouse_pos); + HWND mouse_hwnd = ChildWindowFromPoint(HWND_DESKTOP, mouse_pos); GHOST_WindowWin32 *mouse_window = (GHOST_WindowWin32 *)::GetWindowLongPtr(mouse_hwnd, GWLP_USERDATA); if (mouse_window != NULL) { event = processWheelEvent(mouse_window, wParam, lParam); } else { - /* If it happened so window under the mouse is not found (which i'm not - * really sure might happen), then we add event to the focused window - * in order to avoid some possible negative side effects. - * - sergey - - */ + /* Happens when wmouse is not over of any of blender windows. */ event = processWheelEvent(window, wParam, lParam); } diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h index 79fed06c6a5..cb3b8ee3cfc 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.h +++ b/intern/ghost/intern/GHOST_SystemWin32.h @@ -307,6 +307,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. */ diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 75a9223d6a3..d4693e99fa5 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -32,6 +32,11 @@ * \ingroup GHOST */ +#include <X11/Xatom.h> +#include <X11/keysym.h> +#include <X11/XKBlib.h> /* allow detectable autorepeate */ +#include <X11/Xutil.h> + #include "GHOST_SystemX11.h" #include "GHOST_WindowX11.h" #include "GHOST_WindowManager.h" @@ -52,11 +57,6 @@ #include "GHOST_Debug.h" -#include <X11/Xatom.h> -#include <X11/keysym.h> -#include <X11/XKBlib.h> /* allow detectable autorepeate */ -#include <X11/Xutil.h> - #ifdef WITH_XF86KEYSYM #include <X11/XF86keysym.h> #endif @@ -1874,8 +1874,11 @@ GHOST_TSuccess GHOST_SystemX11::pushDragDropEvent(GHOST_TEventType eventType, } #endif -#ifdef WITH_X11_XINPUT -/* +#if defined(USE_X11_ERROR_HANDLERS) || defined(WITH_X11_XINPUT) +/* + * These callbacks can be used for debugging, so we can breakpoint on an X11 error. + + * * Dummy function to get around IO Handler exiting if device invalid * Basically it will not crash blender now if you have a X device that * is configured but not plugged in. @@ -1896,7 +1899,9 @@ int GHOST_X11_ApplicationIOErrorHandler(Display *display) /* No exit! - but keep lint happy */ return 0; } +#endif +#ifdef WITH_X11_XINPUT /* These C functions are copied from Wine 1.1.13's wintab.c */ #define BOOL int #define TRUE 1 diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h index e470e40d43a..ac31c54666b 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -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. @@ -336,7 +352,7 @@ protected: GHOST_TSuccess releaseNativeHandles(); - /** The the of drawing context installed in this window. */ + /** The drawing context installed in this window. */ GHOST_TDrawingContextType m_drawingContextType; /** The window user data */ diff --git a/intern/ghost/intern/GHOST_WindowCocoa.h b/intern/ghost/intern/GHOST_WindowCocoa.h index 1b6e8e63178..ac0973684d3 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.h +++ b/intern/ghost/intern/GHOST_WindowCocoa.h @@ -20,7 +20,7 @@ * * The Original Code is: all of this file. * - * Contributor(s): none yet. + * Contributor(s): Maarten Gribnau, Jason Wilkins * * ***** END GPL LICENSE BLOCK ***** */ @@ -47,18 +47,6 @@ class GHOST_SystemCocoa; -/** - * Window on Mac OSX/Cocoa. - * Carbon windows have a size widget in the lower right corner of the window. - * To force it to be visible, the height of the client rectangle is reduced so - * that applications do not draw in that area. GHOST will manage that area - * which is called the gutter. - * When OpenGL contexts are active, GHOST will use AGL_BUFFER_RECT to prevent - * OpenGL drawing outside the reduced client rectangle. - * XXX jwilkins: This comment seems out of date since we neither use Carbon nor AGL - * \author Maarten Gribnau - * \date May 23, 2001 - */ class GHOST_WindowCocoa : public GHOST_Window { public: /** diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index 8805dc93179..65d371c8ca8 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -23,6 +23,7 @@ * Contributor(s): Maarten Gribnau 05/2001 * Damien Plisson 10/2009 * Jason Wilkins 02/2014 + * Jens Verwiebe 10/2014 * * ***** END GPL LICENSE BLOCK ***** */ @@ -45,8 +46,6 @@ # include <Carbon/Carbon.h> #endif - - #include <sys/sysctl.h> #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 @@ -77,7 +76,6 @@ enum { - (void)windowDidChangeBackingProperties:(NSNotification *)notification; @end - @implementation CocoaWindowDelegate : NSObject - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa { @@ -88,6 +86,8 @@ enum { - (void)windowDidBecomeKey:(NSNotification *)notification { systemCocoa->handleWindowEvent(GHOST_kEventWindowActivate, associatedWindow); + // work around for broken appswitching when combining cmd-tab and missioncontrol + [(NSWindow*)associatedWindow->getOSWindow() orderFrontRegardless]; } - (void)windowDidResignKey:(NSNotification *)notification @@ -171,6 +171,7 @@ enum { - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa; - (GHOST_SystemCocoa*)systemCocoa; @end + @implementation CocoaWindow - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa { @@ -261,8 +262,6 @@ enum { @end - - #pragma mark NSOpenGLView subclass //We need to subclass it in order to give Cocoa the feeling key events are trapped @interface CocoaOpenGLView : NSOpenGLView <NSTextInput> @@ -277,6 +276,7 @@ enum { } - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa; @end + @implementation CocoaOpenGLView - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa @@ -569,8 +569,9 @@ GHOST_WindowCocoa::GHOST_WindowCocoa( rect.size.height = height; m_window = [[CocoaWindow alloc] initWithContentRect:rect - styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask - backing:NSBackingStoreBuffered defer:NO]; + styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask + backing:NSBackingStoreBuffered defer:NO]; + if (m_window == nil) { [pool drain]; return; @@ -623,7 +624,7 @@ GHOST_WindowCocoa::GHOST_WindowCocoa( #endif [m_window registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, - NSStringPboardType, NSTIFFPboardType, nil]]; + NSStringPboardType, NSTIFFPboardType, nil]]; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 if (state != GHOST_kWindowStateFullScreen) { @@ -634,15 +635,18 @@ GHOST_WindowCocoa::GHOST_WindowCocoa( if (state == GHOST_kWindowStateFullScreen) setState(GHOST_kWindowStateFullScreen); - //Starting with 10.9 (darwin 13.x.x), we always use Lion fullscreen, since it - //now has proper multi-monitor support for fullscreen - char darwin_ver[10]; - size_t len = sizeof(darwin_ver); - sysctlbyname("kern.osrelease", &darwin_ver, &len, NULL, 0); - if(darwin_ver[0] == '1' && darwin_ver[1] >= '3') { + // Starting with 10.9 (darwin 13.x.x), we can use Lion fullscreen, + // since it now has better multi-monitor support + // if the screens are spawned, additional screens get useless, + // so we only use lionStyleFullScreen when screens have separate spaces + + if ([NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] && [NSScreen screensHaveSeparateSpaces]) { + // implies we are on >= OSX 10.9 m_lionStyleFullScreen = true; } + [NSApp activateIgnoringOtherApps:YES]; // raise application to front, important for new blender instance animation play case + [pool drain]; } @@ -785,7 +789,7 @@ void GHOST_WindowCocoa::getClientBounds(GHOST_Rect& bounds) const //Max window contents as screen size (excluding title bar...) NSRect contentRect = [CocoaWindow contentRectForFrameRect:screenSize - styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)]; + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)]; rect = [m_window contentRectForFrameRect:[m_window frame]]; @@ -870,12 +874,12 @@ GHOST_TWindowState GHOST_WindowCocoa::getState() const if (masks & NSFullScreenWindowMask) { // Lion style fullscreen - if (!m_immediateDraw) { - state = GHOST_kWindowStateFullScreen; - } - else { - state = GHOST_kWindowStateNormal; - } + if (!m_immediateDraw) { + state = GHOST_kWindowStateFullScreen; + } + else { + state = GHOST_kWindowStateNormal; + } } else #endif @@ -889,12 +893,12 @@ GHOST_TWindowState GHOST_WindowCocoa::getState() const state = GHOST_kWindowStateMaximized; } else { - if (m_immediateDraw) { - state = GHOST_kWindowStateFullScreen; - } - else { - state = GHOST_kWindowStateNormal; - } + if (m_immediateDraw) { + state = GHOST_kWindowStateFullScreen; + } + else { + state = GHOST_kWindowStateNormal; + } } [pool drain]; return state; @@ -1037,10 +1041,10 @@ GHOST_TSuccess GHOST_WindowCocoa::setState(GHOST_TWindowState state) } //Create a fullscreen borderless window CocoaWindow *tmpWindow = [[CocoaWindow alloc] - initWithContentRect:[[m_window screen] frame] - styleMask:NSBorderlessWindowMask - backing:NSBackingStoreBuffered - defer:YES]; + initWithContentRect:[[m_window screen] frame] + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES]; //Copy current window parameters [tmpWindow setTitle:[m_window title]]; [tmpWindow setRepresentedFilename:[m_window representedFilename]]; @@ -1107,10 +1111,10 @@ GHOST_TSuccess GHOST_WindowCocoa::setState(GHOST_TWindowState state) } //Create a fullscreen borderless window CocoaWindow *tmpWindow = [[CocoaWindow alloc] - initWithContentRect:[[m_window screen] frame] - styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) - backing:NSBackingStoreBuffered - defer:YES]; + initWithContentRect:[[m_window screen] frame] + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; //Copy current window parameters [tmpWindow setTitle:[m_window title]]; [tmpWindow setRepresentedFilename:[m_window representedFilename]]; @@ -1118,7 +1122,7 @@ GHOST_TSuccess GHOST_WindowCocoa::setState(GHOST_TWindowState state) [tmpWindow setDelegate:[m_window delegate]]; [tmpWindow setSystemAndWindowCocoa:[m_window systemCocoa] windowCocoa:this]; [tmpWindow registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, - NSStringPboardType, NSTIFFPboardType, nil]]; + NSStringPboardType, NSTIFFPboardType, nil]]; //Forbid to resize the window below the blender defined minimum one [tmpWindow setContentMinSize:NSMakeSize(320, 240)]; @@ -1324,7 +1328,16 @@ GHOST_TSuccess GHOST_WindowCocoa::setProgressBar(float progress) return GHOST_kSuccess; } - +static void postNotification() +{ + NSUserNotification *notification = [[NSUserNotification alloc] init]; + notification.title = @"Blender progress notification"; + notification.informativeText = @"Calculation is finished"; + notification.soundName = NSUserNotificationDefaultSoundName; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + [notification release]; +} + GHOST_TSuccess GHOST_WindowCocoa::endProgressBar() { if (!m_progressBarVisible) return GHOST_kFailure; @@ -1337,14 +1350,22 @@ GHOST_TSuccess GHOST_WindowCocoa::endProgressBar() [[NSImage imageNamed:@"NSApplicationIcon"] drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; [dockIcon unlockFocus]; [NSApp setApplicationIconImage:dockIcon]; + + + // With OSX 10.8 and later, we can use notifications to inform the user when the progress reached 100% + // Atm. just fire this when the progressbar ends, the behavior is controlled in the NotificationCenter + // If Blender is not frontmost window, a message pops up with sound, in any case an entry in notifications + + if ([NSUserNotificationCenter respondsToSelector:@selector(defaultUserNotificationCenter)]) { + postNotification(); + } + [dockIcon release]; [pool drain]; return GHOST_kSuccess; } - - #pragma mark Cursor handling void GHOST_WindowCocoa::loadCursor(bool visible, GHOST_TStandardCursor cursor) const @@ -1532,15 +1553,15 @@ GHOST_TSuccess GHOST_WindowCocoa::setWindowCustomCursorShape(GHOST_TUns8 *bitmap cursorImageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil - pixelsWide:sizex - pixelsHigh:sizey - bitsPerSample:1 - samplesPerPixel:2 - hasAlpha:YES - isPlanar:YES - colorSpaceName:NSDeviceWhiteColorSpace + pixelsWide:sizex + pixelsHigh:sizey + bitsPerSample:1 + samplesPerPixel:2 + hasAlpha:YES + isPlanar:YES + colorSpaceName:NSDeviceWhiteColorSpace bytesPerRow:(sizex/8 + (sizex%8 >0 ?1:0)) - bitsPerPixel:1]; + bitsPerPixel:1]; cursorBitmap = (GHOST_TUns16*)[cursorImageRep bitmapData]; @@ -1568,7 +1589,7 @@ GHOST_TSuccess GHOST_WindowCocoa::setWindowCustomCursorShape(GHOST_TUns8 *bitmap //foreground and background color parameter is not handled for now (10.6) m_customCursor = [[NSCursor alloc] initWithImage:cursorImage - hotSpot:hotSpotPoint]; + hotSpot:hotSpotPoint]; [cursorImageRep release]; [cursorImage release]; diff --git a/intern/ghost/intern/GHOST_WindowNULL.h b/intern/ghost/intern/GHOST_WindowNULL.h index c848d773bd6..0cdb161f40d 100644 --- a/intern/ghost/intern/GHOST_WindowNULL.h +++ b/intern/ghost/intern/GHOST_WindowNULL.h @@ -53,7 +53,7 @@ public: const bool stereoVisual, const GHOST_TUns16 numOfAASamples ) : - GHOST_Window(width, height, state, type, stereoVisual, false, numOfAASamples), + GHOST_Window(width, height, state, stereoVisual, false, numOfAASamples), m_system(system) { setTitle(title); @@ -91,6 +91,12 @@ protected: private: GHOST_SystemNULL *m_system; + + /** + * \param type The type of rendering context create. + * \return Indication of success. + */ + virtual GHOST_Context *newDrawingContext(GHOST_TDrawingContextType type) {return NULL;} }; diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp index 64ea7192616..4e384881f2c 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cpp +++ b/intern/ghost/intern/GHOST_WindowWin32.cpp @@ -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 */ diff --git a/intern/ghost/intern/GHOST_WindowWin32.h b/intern/ghost/intern/GHOST_WindowWin32.h index 7b12d8c583e..c4575d0f9b0 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.h +++ b/intern/ghost/intern/GHOST_WindowWin32.h @@ -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__ diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index 97f8ae73d2d..ed8e9db85a9 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -29,6 +29,10 @@ * \ingroup GHOST */ +/* For standard X11 cursors */ +#include <X11/cursorfont.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> #include "GHOST_WindowX11.h" #include "GHOST_SystemX11.h" @@ -45,11 +49,6 @@ # include "GHOST_ContextGLX.h" #endif - -/* For standard X11 cursors */ -#include <X11/cursorfont.h> -#include <X11/Xatom.h> - #if defined(__sun__) || defined(__sun) || defined(__sparc) || defined(__sparc__) || defined(_AIX) # include <strings.h> #endif @@ -153,7 +152,118 @@ static long BLENDER_ICON_48x48x32[] = { 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, 4671303, }; +static XVisualInfo *x11_visualinfo_from_glx( + Display *display, + bool stereoVisual, GHOST_TUns16 *r_numOfAASamples) +{ + XVisualInfo *visualInfo = NULL; + GHOST_TUns16 numOfAASamples = *r_numOfAASamples; + /* Set up the minimum attributes that we require and see if + * X can find us a visual matching those requirements. */ + + std::vector<int> attribs; + attribs.reserve(40); + + int glx_major, glx_minor; /* GLX version: major.minor */ + if (!glXQueryVersion(display, &glx_major, &glx_minor)) { + fprintf(stderr, + "%s:%d: X11 glXQueryVersion() failed, " + "verify working openGL system!\n", + __FILE__, __LINE__); + + return NULL; + } + +#ifdef GHOST_OPENGL_ALPHA + const bool needAlpha = true; +#else + const bool needAlpha = false; +#endif + +#ifdef GHOST_OPENGL_STENCIL + const bool needStencil = true; +#else + const bool needStencil = false; +#endif + + /* Find the display with highest samples, starting at level requested */ + GHOST_TUns16 actualSamples = numOfAASamples; + for (;;) { + attribs.clear(); + + if (stereoVisual) + attribs.push_back(GLX_STEREO); + + attribs.push_back(GLX_RGBA); + + attribs.push_back(GLX_DOUBLEBUFFER); + + attribs.push_back(GLX_RED_SIZE); + attribs.push_back(1); + + attribs.push_back(GLX_BLUE_SIZE); + attribs.push_back(1); + + attribs.push_back(GLX_GREEN_SIZE); + attribs.push_back(1); + + attribs.push_back(GLX_DEPTH_SIZE); + attribs.push_back(1); + + if (needAlpha) { + attribs.push_back(GLX_ALPHA_SIZE); + attribs.push_back(1); + } + + if (needStencil) { + attribs.push_back(GLX_STENCIL_SIZE); + attribs.push_back(1); + } + + /* GLX >= 1.4 required for multi-sample */ + if (actualSamples > 0 && ((glx_major > 1) || (glx_major == 1 && glx_minor >= 4))) { + attribs.push_back(GLX_SAMPLE_BUFFERS); + attribs.push_back(1); + + attribs.push_back(GLX_SAMPLES); + attribs.push_back(actualSamples); + } + + attribs.push_back(None); + + visualInfo = glXChooseVisual(display, DefaultScreen(display), &attribs[0]); + + /* Any sample level or even zero, which means oversampling disabled, is good + * but we need a valid visual to continue */ + if (visualInfo != NULL) { + if (actualSamples < numOfAASamples) { + fprintf(stderr, + "Warning! Unable to find a multisample pixel format that supports exactly %d samples. " + "Substituting one that uses %d samples.\n", + numOfAASamples, actualSamples); + } + break; + } + + if (actualSamples == 0) { + /* All options exhausted, cannot continue */ + fprintf(stderr, + "%s:%d: X11 glXChooseVisual() failed, " + "verify working openGL system!\n", + __FILE__, __LINE__); + + return NULL; + } + else { + --actualSamples; + } + } + + *r_numOfAASamples = actualSamples; + + return visualInfo; +} GHOST_WindowX11:: GHOST_WindowX11( @@ -172,6 +282,7 @@ GHOST_WindowX11( const GHOST_TUns16 numOfAASamples) : GHOST_Window(width, height, state, stereoVisual, exclusive, numOfAASamples), m_display(display), + m_visualInfo(NULL), m_normal_state(GHOST_kWindowStateNormal), m_system(system), m_valid_setup(false), @@ -180,6 +291,21 @@ GHOST_WindowX11( m_custom_cursor(None), m_visible_cursor(None) { + if (type == GHOST_kDrawingContextTypeOpenGL) { + m_visualInfo = x11_visualinfo_from_glx(m_display, stereoVisual, &m_wantNumOfAASamples); + } + else { + XVisualInfo tmp = {0}; + int n; + m_visualInfo = XGetVisualInfo(m_display, 0, &tmp, &n); + } + + /* exit if this is the first window */ + if (m_visualInfo == NULL) { + fprintf(stderr, "initial window could not find the GLX extension, exit!\n"); + exit(EXIT_FAILURE); + } + int natom; unsigned int xattributes_valuemask = 0; @@ -206,21 +332,26 @@ GHOST_WindowX11( xattributes.override_redirect = True; } + xattributes_valuemask |= CWColormap; + xattributes.colormap = XCreateColormap( + m_display, + RootWindow(m_display, m_visualInfo->screen), + m_visualInfo->visual, + AllocNone + ); + /* create the window! */ if (parentWindow == 0) { - m_window = XCreateWindow(m_display, - RootWindow(m_display, DefaultScreen(m_display)), - left, - top, - width, - height, - 0, /* no border. */ - CopyFromParent, - InputOutput, - CopyFromParent, - xattributes_valuemask, - &xattributes - ); + m_window = XCreateWindow( + m_display, + RootWindow(m_display, m_visualInfo->screen), + left, top, width, height, + 0, /* no border. */ + m_visualInfo->depth, + InputOutput, + m_visualInfo->visual, + xattributes_valuemask, + &xattributes); } else { @@ -237,19 +368,16 @@ GHOST_WindowX11( height = h_return; - m_window = XCreateWindow(m_display, - parentWindow, /* reparent against embedder */ - left, - top, - width, - height, - 0, /* no border. */ - CopyFromParent, - InputOutput, - CopyFromParent, - xattributes_valuemask, - &xattributes - ); + m_window = XCreateWindow( + m_display, + parentWindow, /* reparent against embedder */ + left, top, width, height, + 0, /* no border. */ + m_visualInfo->depth, + InputOutput, + m_visualInfo->visual, + xattributes_valuemask, + &xattributes); XSelectInput(m_display, parentWindow, SubstructureNotifyMask); @@ -602,7 +730,7 @@ screenToClient( Window temp; XTranslateCoordinates(m_display, - RootWindow(m_display, DefaultScreen(m_display)), + RootWindow(m_display, m_visualInfo->screen), m_window, inX, inY, &ax, &ay, @@ -625,7 +753,7 @@ clientToScreen( XTranslateCoordinates( m_display, m_window, - RootWindow(m_display, DefaultScreen(m_display)), + RootWindow(m_display, m_visualInfo->screen), inX, inY, &ax, &ay, &temp); @@ -648,7 +776,7 @@ void GHOST_WindowX11::icccmSetState(int state) xev.xclient.format = 32; xev.xclient.message_type = m_system->m_atom.WM_CHANGE_STATE; xev.xclient.data.l[0] = state; - XSendEvent(m_display, RootWindow(m_display, DefaultScreen(m_display)), + XSendEvent(m_display, RootWindow(m_display, m_visualInfo->screen), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); } @@ -694,7 +822,7 @@ void GHOST_WindowX11::netwmMaximized(bool set) xev.xclient.data.l[2] = m_system->m_atom._NET_WM_STATE_MAXIMIZED_VERT; xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; - XSendEvent(m_display, RootWindow(m_display, DefaultScreen(m_display)), + XSendEvent(m_display, RootWindow(m_display, m_visualInfo->screen), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } @@ -750,7 +878,7 @@ void GHOST_WindowX11::netwmFullScreen(bool set) xev.xclient.data.l[2] = 0; xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; - XSendEvent(m_display, RootWindow(m_display, DefaultScreen(m_display)), + XSendEvent(m_display, RootWindow(m_display, m_visualInfo->screen), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } @@ -969,7 +1097,7 @@ setOrder( xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; - root = RootWindow(m_display, DefaultScreen(m_display)), + root = RootWindow(m_display, m_visualInfo->screen), eventmask = SubstructureRedirectMask | SubstructureNotifyMask; XSendEvent(m_display, root, False, eventmask, &xev); @@ -1068,6 +1196,10 @@ GHOST_WindowX11:: XSetSelectionOwner(m_display, Clipboard_atom, None, CurrentTime); } + if (m_visualInfo) { + XFree(m_visualInfo); + } + #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) if (m_xic) { XDestroyIC(m_xic); @@ -1095,6 +1227,7 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type m_wantNumOfAASamples, m_window, m_display, + m_visualInfo, GLX_CONTEXT_OPENGL_CORE_PROFILE_BIT, 3, 2, GHOST_OPENGL_GLX_CONTEXT_FLAGS, @@ -1105,6 +1238,7 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type m_wantNumOfAASamples, m_window, m_display, + m_visualInfo, GLX_CONTEXT_ES2_PROFILE_BIT_EXT, 2, 0, GHOST_OPENGL_GLX_CONTEXT_FLAGS, @@ -1115,6 +1249,7 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type m_wantNumOfAASamples, m_window, m_display, + m_visualInfo, 0, // profile bit 0, 0, GHOST_OPENGL_GLX_CONTEXT_FLAGS, @@ -1237,7 +1372,7 @@ getEmptyCursor( /* make a blank cursor */ blank = XCreateBitmapFromData( m_display, - RootWindow(m_display, DefaultScreen(m_display)), + RootWindow(m_display, m_visualInfo->screen), data, 1, 1 ); @@ -1356,7 +1491,7 @@ setWindowCustomCursorShape( int fg_color, int bg_color) { - Colormap colormap = DefaultColormap(m_display, DefaultScreen(m_display)); + Colormap colormap = DefaultColormap(m_display, m_visualInfo->screen); Pixmap bitmap_pix, mask_pix; XColor fg, bg; diff --git a/intern/ghost/intern/GHOST_WindowX11.h b/intern/ghost/intern/GHOST_WindowX11.h index 3255751be93..7bb9a858882 100644 --- a/intern/ghost/intern/GHOST_WindowX11.h +++ b/intern/ghost/intern/GHOST_WindowX11.h @@ -35,6 +35,7 @@ #include "GHOST_Window.h" #include <X11/Xlib.h> +#include <X11/Xutil.h> // For tablets #ifdef WITH_X11_XINPUT # include <X11/extensions/XInput.h> @@ -319,6 +320,8 @@ private: Window m_window; Display *m_display; + XVisualInfo *m_visualInfo; + GHOST_TWindowState m_normal_state; /** A pointer to the typed system class. */ |