From 49883efe2e3ce8647a1cee70ff9ccf39eaa6d802 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 11 Apr 2002 22:21:34 +0000 Subject: [PATCH] copy ime support (almost) into dx8 --- panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx | 48 +++ panda/src/dxgsg8/dxGraphicsStateGuardian8.h | 4 + panda/src/wdxdisplay8/Sources.pp | 1 + panda/src/wdxdisplay8/config_wdxdisplay8.cxx | 6 + panda/src/wdxdisplay8/config_wdxdisplay8.h | 1 + panda/src/wdxdisplay8/wdxGraphicsWindow8.cxx | 331 ++++++++++++------ panda/src/wdxdisplay8/wdxGraphicsWindow8.h | 5 + 7 files changed, 292 insertions(+), 104 deletions(-) diff --git a/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx b/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx index c297252843..dc2d3f45ac 100644 --- a/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx +++ b/panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx @@ -297,6 +297,7 @@ DXGraphicsStateGuardian(GraphicsWindow *win) : GraphicsStateGuardian(win) { ZeroMemory(&scrn,sizeof(DXScreenData)); _bDXisReady = false; + _overlay_windows_supported = false; _pFvfBufBasePtr = NULL; _index_buf=NULL; @@ -743,6 +744,53 @@ dx_init(HCURSOR hMouseCursor) { PRINT_REFCNT(dxgsg,scrn.pD3DDevice); } +//////////////////////////////////////////////////////////////////// +// Function: dxGraphicsStateGuardian::support_overlay_window +// Access: Public +// Description: Specifies whether dialog windows placed on top of the +// dx rendering window should be supported. This +// requires a bit of extra overhead, so it should only +// be activated when necessary; however, if it is not +// activated, a window that pops up over the fullscreen +// DX window (like a dialog box, or particularly like +// the IME composition or candidate windows) may not be +// visible. +// +// This is not necessary when running in windowed mode, +// but it does no harm. +//////////////////////////////////////////////////////////////////// +void DXGraphicsStateGuardian:: +support_overlay_window(bool flag) { + // How is this supposed to be done in DX8? + + /* + if (_overlay_windows_supported && !flag) { + // Disable support for overlay windows. + _overlay_windows_supported = false; + + if (dx_full_screen) { + scrn.pddsPrimary->SetClipper(NULL); + } + + } else if (!_overlay_windows_supported && flag) { + // Enable support for overlay windows. + _overlay_windows_supported = true; + + if (dx_full_screen) { + // Create a Clipper object to blt the whole screen. + LPDIRECTDRAWCLIPPER Clipper; + + if (scrn.pDD->CreateClipper(0, &Clipper, NULL) == DD_OK) { + Clipper->SetHWnd(0, scrn.hWnd); + scrn.pddsPrimary->SetClipper(Clipper); + } + scrn.pDD->FlipToGDISurface(); + Clipper->Release(); + } + } + */ +} + //////////////////////////////////////////////////////////////////// // Function: DXGraphicsStateGuardian::clear // Access: Public, Virtual diff --git a/panda/src/dxgsg8/dxGraphicsStateGuardian8.h b/panda/src/dxgsg8/dxGraphicsStateGuardian8.h index 9db0c55c31..321890a12c 100644 --- a/panda/src/dxgsg8/dxGraphicsStateGuardian8.h +++ b/panda/src/dxgsg8/dxGraphicsStateGuardian8.h @@ -313,6 +313,8 @@ protected: // Color/Alpha Matrix Transition stuff INLINE void transform_color(Colorf &InColor,D3DCOLOR &OutColor); + bool _overlay_windows_supported; + // vars for frames/sec meter DWORD _start_time; DWORD _start_frame_count; @@ -341,6 +343,8 @@ public: void show_frame(); void dx_init(HCURSOR hMouseCursor); + + void support_overlay_window(bool flag); private: static TypeHandle _type_handle; diff --git a/panda/src/wdxdisplay8/Sources.pp b/panda/src/wdxdisplay8/Sources.pp index 86bd581131..29d58f1861 100644 --- a/panda/src/wdxdisplay8/Sources.pp +++ b/panda/src/wdxdisplay8/Sources.pp @@ -2,6 +2,7 @@ #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \ dtoolutil:c dtoolbase:c dtool:m +#define WIN_SYS_LIBS Imm32.lib #define USE_DX yes #begin lib_target diff --git a/panda/src/wdxdisplay8/config_wdxdisplay8.cxx b/panda/src/wdxdisplay8/config_wdxdisplay8.cxx index 41900131d6..f68f997c85 100644 --- a/panda/src/wdxdisplay8/config_wdxdisplay8.cxx +++ b/panda/src/wdxdisplay8/config_wdxdisplay8.cxx @@ -37,6 +37,12 @@ int dx_preferred_deviceID = config_wdxdisplay.GetInt("dx-preferred-device-id", - // if true, use ddraw's GetAvailVidMem to fail if driver says it has too little video mem bool dx_do_vidmemsize_check = config_wdxdisplay.GetBool("do-vidmemsize-check", true); +// For now, set this true to use the IME correctly on Win2000, or +// false on Win98. This is temporary; once we have been able to +// verify that this distinction is actually necessary, we can replace +// this config variable with an actual OS detection. +bool ime_composition_w = config_wdxdisplay.GetBool("ime-composition-w", true); + extern void AtExitFn(void); //////////////////////////////////////////////////////////////////// diff --git a/panda/src/wdxdisplay8/config_wdxdisplay8.h b/panda/src/wdxdisplay8/config_wdxdisplay8.h index b8bad44cfc..8ccb86cd32 100644 --- a/panda/src/wdxdisplay8/config_wdxdisplay8.h +++ b/panda/src/wdxdisplay8/config_wdxdisplay8.h @@ -33,6 +33,7 @@ extern int dx_preferred_deviceID; extern Filename get_icon_filename(); extern Filename get_mono_cursor_filename(); extern Filename get_color_cursor_filename(); +extern bool ime_composition_w; extern EXPCL_PANDADX void init_libwdxdisplay8(); diff --git a/panda/src/wdxdisplay8/wdxGraphicsWindow8.cxx b/panda/src/wdxdisplay8/wdxGraphicsWindow8.cxx index 79fe68efb7..390cc05d06 100644 --- a/panda/src/wdxdisplay8/wdxGraphicsWindow8.cxx +++ b/panda/src/wdxdisplay8/wdxGraphicsWindow8.cxx @@ -239,6 +239,29 @@ void AtExitFn() { DestroyAllWindows(true); } +//////////////////////////////////////////////////////////////////// +// Function: wdxGraphicsWindow::set_window_handle +// Access: Public +// Description: This is called on the object once the actual Window +// has been created. It gives the window handle of the +// created window so the wdxGraphicsWindow object can +// perform any additional initialization based on this. +//////////////////////////////////////////////////////////////////// +void wdxGraphicsWindow:: +set_window_handle(HWND hwnd) { + // Tell the associated dxGSG about the window handle. + _dxgsg->scrn.hWnd = hwnd; + + // Determine the initial open status of the IME. + _ime_open = false; + _ime_active = false; + HIMC hIMC = ImmGetContext(hwnd); + if (hIMC != 0) { + _ime_open = (ImmGetOpenStatus(hIMC) != 0); + ImmReleaseContext(hwnd, hIMC); + } +} + //////////////////////////////////////////////////////////////////// // Function: static_window_proc // Access: @@ -365,65 +388,162 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { set_cursor_visibility(true); break; } - - #if 0 - case WM_SYSCHAR: - case WM_CHAR: // shouldnt receive WM_CHAR unless WM_KEYDOWN stops returning 0 and passes on to DefWindProc - break; - #endif - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: { - - POINT point; - - GetCursorPos(&point); - ScreenToClient(hwnd, &point); - - #ifdef NDEBUG - handle_keypress(lookup_key(wparam), point.x, point.y); - #else - // handle Cntrl-V paste from clipboard - if(!((wparam=='V') && (GetKeyState(VK_CONTROL) < 0))) { - handle_keypress(lookup_key(wparam), point.x, point.y); - } else { - HGLOBAL hglb; - char *lptstr; - - if (!IsClipboardFormatAvailable(CF_TEXT)) - return 0; - - if (!OpenClipboard(NULL)) - return 0; - - hglb = GetClipboardData(CF_TEXT); - if (hglb!=NULL) { - lptstr = (char *) GlobalLock(hglb); - if(lptstr != NULL) { - char *pChar; - for(pChar=lptstr;*pChar!=NULL;pChar++) { - handle_keypress(KeyboardButton::ascii_key((uchar)*pChar), point.x, point.y); - } - GlobalUnlock(hglb); - } - } - CloseClipboard(); + + case WM_IME_NOTIFY: + if (wparam == IMN_SETOPENSTATUS) { + HIMC hIMC = ImmGetContext(hwnd); + nassertr(hIMC != 0, 0); + _ime_open = (ImmGetOpenStatus(hIMC) != 0); + if (!_ime_open) { + _ime_active = false; // Sanity enforcement. + } + ImmReleaseContext(hwnd, hIMC); + } + break; + + case WM_IME_STARTCOMPOSITION: + // In case we're running fullscreen mode, we have to turn on + // explicit DX support for overlay windows now, so we'll be able + // to see the IME window. + _dxgsg->support_overlay_window(true); + _ime_active = true; + break; + + case WM_IME_ENDCOMPOSITION: + // Turn off the support for overlay windows, since we're done + // with the IME window for now and it just slows things down. + _dxgsg->support_overlay_window(false); + _ime_active = false; + break; + + case WM_IME_COMPOSITION: + if (lparam & GCS_RESULTSTR) { + if (!_input_devices.empty()) { + HIMC hIMC = ImmGetContext(hwnd); + nassertr(hIMC != 0, 0); + + static const int max_ime_result = 128; + static char ime_result[max_ime_result]; + + if (_ime_composition_w) { + // Since ImmGetCompositionStringA() doesn't seem to work + // for Win2000 (it always returns question mark + // characters), we have to use ImmGetCompositionStringW() + // on this OS. This is actually the easier of the two + // functions to use. + + DWORD result_size = + ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, + ime_result, max_ime_result); + + // Add this string into the text buffer of the application. + + // ImmGetCompositionStringW() returns a string, but it's + // filled in with wstring data: every two characters defines a + // 16-bit unicode char. The docs aren't clear on the + // endianness of this. I guess it's safe to assume all Win32 + // machines are little-endian. + for (DWORD i = 0; i < result_size; i += 2) { + int result = + ((int)(unsigned char)ime_result[i + 1] << 8) | + (unsigned char)ime_result[i]; + _input_devices[0].keystroke(result); + } + } else { + // On the other hand, ImmGetCompositionStringW() doesn't + // work on Win95 or Win98; for these OS's we must use + // ImmGetCompositionStringA(). + DWORD result_size = + ImmGetCompositionStringA(hIMC, GCS_RESULTSTR, + ime_result, max_ime_result); + + // ImmGetCompositionStringA() returns an encoded ANSI + // string, which we now have to map to wide-character + // Unicode. + static const int max_wide_result = 128; + static wchar_t wide_result[max_wide_result]; + + int wide_size = + MultiByteToWideChar(CP_ACP, 0, + ime_result, result_size, + wide_result, max_wide_result); + if (wide_size == 0) { + PrintErrorMessage(LAST_ERROR); + } + for (int i = 0; i < wide_size; i++) { + _input_devices[0].keystroke(wide_result[i]); + } + } + + ImmReleaseContext(hwnd, hIMC); } - #endif - // want to use defwindproc on Alt syskey so Alt-F4 works, etc - // but do want to bypass defwindproc F10 behavior (it activates the - // main menu, but we have none) - if((msg==WM_SYSKEYDOWN)&&(wparam!=VK_F10)) - break; - else return 0; - } - - case WM_SYSKEYUP: - case WM_KEYUP: { - handle_keyrelease(lookup_key(wparam)); return 0; + } + break; + + case WM_CHAR: + // Ignore WM_CHAR messages if we have the IME open, since + // everything will come in through WM_IME_COMPOSITION. (It's + // supposed to come in through WM_CHAR, too, but there seems to + // be a bug in Win2000 in that it only sends question mark + // characters through here.) + if (!_ime_open && !_input_devices.empty()) { + _input_devices[0].keystroke(wparam); + } + break; + + case WM_SYSKEYDOWN: + // want to use defwindproc on Alt syskey so Alt-F4 works, etc + // but do want to bypass defwindproc F10 behavior (it activates + // the main menu, but we have none) + if (wparam == VK_F10) { + return 0; + } + break; + + case WM_KEYDOWN: { + POINT point; + + GetCursorPos(&point); + ScreenToClient(hwnd, &point); + handle_keypress(lookup_key(wparam), point.x, point.y); + + // Handle Cntrl-V paste from clipboard. Is there a better way + // to detect this hotkey? + if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) && + !_input_devices.empty()) { + HGLOBAL hglb; + char *lptstr; + + if (!IsClipboardFormatAvailable(CF_TEXT)) + return 0; + + if (!OpenClipboard(NULL)) + return 0; + + // Maybe we should support CF_UNICODETEXT if it is available + // too? + hglb = GetClipboardData(CF_TEXT); + if (hglb!=NULL) { + lptstr = (char *) GlobalLock(hglb); + if (lptstr != NULL) { + char *pChar; + for(pChar=lptstr;*pChar!=NULL;pChar++) { + _input_devices[0].keystroke((uchar)*pChar); + } + GlobalUnlock(hglb); + } + } + CloseClipboard(); + } + break; } - + + case WM_SYSKEYUP: + case WM_KEYUP: + handle_keyrelease(lookup_key(wparam)); + break; + case WM_LBUTTONDOWN: button = 0; case WM_MBUTTONDOWN: @@ -938,6 +1058,7 @@ void wdxGraphicsWindow::reactivate_window(void) { // Description: //////////////////////////////////////////////////////////////////// wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe) : GraphicsWindow(pipe) { + _ime_active = false; _pParentWindowGroup=NULL; _pParentWindowGroup=new wdxGraphicsWindowGroup(this); } @@ -949,6 +1070,7 @@ wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe) : GraphicsWindow(pipe) //////////////////////////////////////////////////////////////////// wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe, const GraphicsWindow::Properties& props) : GraphicsWindow(pipe, props) { + _ime_open = false; _pParentWindowGroup=NULL; _pParentWindowGroup=new wdxGraphicsWindowGroup(this); } @@ -1194,7 +1316,7 @@ void wdxGraphicsWindowGroup::CreateWindows(void) { exit(1); } - _windows[devnum]->_dxgsg->scrn.hWnd = hWin; + _windows[devnum]->set_window_handle(hWin); _windows[devnum]->_dxgsg->scrn.pProps = &_windows[devnum]->_props; if(devnum==0) @@ -1229,7 +1351,7 @@ void wdxGraphicsWindowGroup::CreateWindows(void) { window_style, win_rect.left, win_rect.top, win_rect.right-win_rect.left,win_rect.bottom-win_rect.top, NULL, NULL, hProgramInstance, 0); - _windows[0]->_dxgsg->scrn.hWnd = _hParentWindow; + _windows[0]->set_window_handle(_hParentWindow); } if(_hParentWindow==NULL) { @@ -1294,6 +1416,20 @@ void wdxGraphicsWindow::config_window(wdxGraphicsWindowGroup *pParentGroup) { } _dxgsg = DCAST(DXGraphicsStateGuardian, _gsg); _dxgsg->scrn.bIsDX81 = pParentGroup->_bIsDX81; + + // Check the version of the OS we are running. If we are running + // win2000, we must use ImmGetCompositionStringW() to report the + // characters returned by the IME, since WM_CHAR and + // ImmGetCompositionStringA() both just return question marks. + // However, this function doesn't work for Win98; on this OS, we + // have to use ImmGetCompositionStringA() instead, which returns an + // encoded string in shift-jis (which we then have to decode). + + // For now, this is user-configurable, to allow testing of this code + // on both OS's. After we verify that truth of the above claim, we + // should base this decision on GetVersionEx() or maybe + // VerifyVersionInfo(). + _ime_composition_w = ime_composition_w; } void wdxGraphicsWindow::finish_window_setup(void) { @@ -2705,15 +2841,25 @@ TypeHandle wdxGraphicsWindow::get_type(void) const { //////////////////////////////////////////////////////////////////// ButtonHandle wdxGraphicsWindow:: lookup_key(WPARAM wparam) const { + // First, check for a few buttons that we filter out when the IME + // window is open. + if (!_ime_active) { + switch(wparam) { + case VK_BACK: return KeyboardButton::backspace(); + case VK_DELETE: return KeyboardButton::del(); + case VK_ESCAPE: return KeyboardButton::escape(); + case VK_SPACE: return KeyboardButton::space(); + case VK_UP: return KeyboardButton::up(); + case VK_DOWN: return KeyboardButton::down(); + case VK_LEFT: return KeyboardButton::left(); + case VK_RIGHT: return KeyboardButton::right(); + } + } + + // Now check for the rest of the buttons, including the ones that + // we allow through even when the IME window is open. switch(wparam) { - case VK_BACK: return KeyboardButton::backspace(); case VK_TAB: return KeyboardButton::tab(); - case VK_ESCAPE: return KeyboardButton::escape(); - case VK_SPACE: return KeyboardButton::space(); - case VK_UP: return KeyboardButton::up(); - case VK_DOWN: return KeyboardButton::down(); - case VK_LEFT: return KeyboardButton::left(); - case VK_RIGHT: return KeyboardButton::right(); case VK_PRIOR: return KeyboardButton::page_up(); case VK_NEXT: return KeyboardButton::page_down(); case VK_HOME: return KeyboardButton::home(); @@ -2731,7 +2877,6 @@ lookup_key(WPARAM wparam) const { case VK_F11: return KeyboardButton::f11(); case VK_F12: return KeyboardButton::f12(); case VK_INSERT: return KeyboardButton::insert(); - case VK_DELETE: return KeyboardButton::del(); case VK_CAPITAL: return KeyboardButton::caps_lock(); case VK_NUMLOCK: return KeyboardButton::num_lock(); case VK_SCROLL: return KeyboardButton::scroll_lock(); @@ -2755,44 +2900,21 @@ lookup_key(WPARAM wparam) const { default: int key = MapVirtualKey(wparam, 2); - if(isascii(key) && key != 0) { - bool bCapsLockDown=((GetKeyState(VK_CAPITAL) & 0x1)!=0); - bool bShiftUp = (GetKeyState(VK_SHIFT) >= 0); - if(bShiftUp) { - if(bCapsLockDown) - key = toupper(key); - else key = tolower(key); - } else { - switch(key) { - // these keys are unaffected by capslock - case '1': key = '!'; break; - case '2': key = '@'; break; - case '3': key = '#'; break; - case '4': key = '$'; break; - case '5': key = '%'; break; - case '6': key = '^'; break; - case '7': key = '&'; break; - case '8': key = '*'; break; - case '9': key = '('; break; - case '0': key = ')'; break; - case '-': key = '_'; break; - case '=': key = '+'; break; - case ',': key = '<'; break; - case '.': key = '>'; break; - case '/': key = '?'; break; - case ';': key = ':'; break; - case '\'': key = '"'; break; - case '[': key = '{'; break; - case ']': key = '}'; break; - case '\\': key = '|'; break; - case '`': key = '~'; break; - default: - if(bCapsLockDown) - key = tolower(key); - else key = toupper(key); - } - } - return KeyboardButton::ascii_key((uchar)key); + if (isascii(key) && key != 0) { + // We used to try to remap lowercase to uppercase keys + // here based on the state of the shift and/or caps lock + // keys. But that's a mistake, and doesn't allow for + // international or user-defined keyboards; let Windows + // do that mapping. + + // Nowadays, we make a distinction between a "button" + // and a "keystroke". A button corresponds to a + // physical button on the keyboard and has a down and up + // event associated. A keystroke may or may not + // correspond to a physical button, but will be some + // Unicode character and will not have a corresponding + // up event. + return KeyboardButton::ascii_key(tolower(key)); } break; } @@ -3095,6 +3217,7 @@ make_windows(GraphicsPipe *pipe,int num_windows,GraphicsWindow::Properties *WinP wdxGraphicsWindow::wdxGraphicsWindow(GraphicsPipe* pipe, const GraphicsWindow::Properties &props, wdxGraphicsWindowGroup *pParentGroup) : GraphicsWindow(pipe, props) { _pParentWindowGroup=pParentGroup; + _ime_open = false; // just call the GraphicsWindow constructor, have to hold off rest of init } diff --git a/panda/src/wdxdisplay8/wdxGraphicsWindow8.h b/panda/src/wdxdisplay8/wdxGraphicsWindow8.h index 792bfcd377..acfad23a54 100644 --- a/panda/src/wdxdisplay8/wdxGraphicsWindow8.h +++ b/panda/src/wdxdisplay8/wdxGraphicsWindow8.h @@ -68,6 +68,8 @@ public: virtual TypeHandle get_gsg_type() const; static GraphicsWindow* make_wdxGraphicsWindow(const FactoryParams ¶ms); + void set_window_handle(HWND hwnd); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); void process_events(void); @@ -110,6 +112,9 @@ private: typedef enum { NotAdjusting,MovingOrResizing,Resizing } WindowAdjustType; WindowAdjustType _WindowAdjustingType; bool _bSizeIsMaximized; + bool _ime_open; + bool _ime_active; + bool _ime_composition_w; bool _exiting_window; bool _window_inactive; bool _active_minimized_fullscreen;