diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index d10dc6746a..53bfdfcae3 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -1,1312 +1,1320 @@ -// Filename: winGraphicsWindow.cxx -// Created by: drose (20Dec02) -// -//////////////////////////////////////////////////////////////////// -// -// PANDA 3D SOFTWARE -// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved -// -// All use of this software is subject to the terms of the Panda 3d -// Software license. You should have received a copy of this license -// along with this source code; you will also find a current copy of -// the license at http://www.panda3d.org/license.txt . -// -// To contact the maintainers of this program write to -// panda3d@yahoogroups.com . -// -//////////////////////////////////////////////////////////////////// - -#include "winGraphicsWindow.h" -#include "config_windisplay.h" -#include "winGraphicsPipe.h" - -#include "graphicsPipe.h" -#include "keyboardButton.h" -#include "mouseButton.h" -#include "clockObject.h" - -#include - -TypeHandle WinGraphicsWindow::_type_handle; - -bool WinGraphicsWindow::_got_dynamic_fns = false; -WinGraphicsWindow::PFN_TRACKMOUSEEVENT WinGraphicsWindow::_pfnTrackMouseEvent = NULL; - -bool WinGraphicsWindow::_loaded_custom_cursor; -HCURSOR WinGraphicsWindow::_mouse_cursor; -const char * const WinGraphicsWindow::_window_class_name = "WinGraphicsWindow"; -bool WinGraphicsWindow::_window_class_registered = false; - -WinGraphicsWindow::WindowHandles WinGraphicsWindow::_window_handles; -WinGraphicsWindow *WinGraphicsWindow::_creating_window = NULL; - -WinGraphicsWindow *WinGraphicsWindow::_cursor_window = NULL; -bool WinGraphicsWindow::_cursor_hidden = false; - -// These are used to save the previous state of the fancy Win2000 -// effects that interfere with rendering when the mouse wanders into a -// window's client area. -bool WinGraphicsWindow::_got_saved_params = false; -int WinGraphicsWindow::_saved_mouse_trails; -bool WinGraphicsWindow::_saved_cursor_shadow; -bool WinGraphicsWindow::_saved_mouse_vanish; - -static const char * const errorbox_title = "Panda3D Error"; - - -// because we dont have access to ModifierButtons, as a hack just -// synchronize state of these keys on get/lose keybd focus -#define NUM_MODIFIER_KEYS 16 -static unsigned int hardcoded_modifier_buttons[NUM_MODIFIER_KEYS] = { - VK_SHIFT, VK_MENU, VK_CONTROL, VK_SPACE, VK_TAB, - VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_PRIOR, VK_NEXT, VK_HOME, VK_END, - VK_INSERT, VK_DELETE, VK_ESCAPE -}; - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -WinGraphicsWindow:: -WinGraphicsWindow(GraphicsPipe *pipe) : - GraphicsWindow(pipe) -{ - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard("keyboard/mouse"); - _input_devices.push_back(device); - _mwindow = (HWND)0; - _ime_open = false; - _ime_active = false; - _ime_composition_w = false; - _tracking_mouse_leaving = false; - _maximized = false; - - if (!_got_dynamic_fns) { - // these fns arent defined on win95, so get dynamic ptrs to them - // to avoid ugly DLL loader failures on w95 - HINSTANCE hUser32 = (HINSTANCE)LoadLibrary("user32.dll"); - if (hUser32) { - _pfnTrackMouseEvent = - (PFN_TRACKMOUSEEVENT)GetProcAddress(hUser32, "TrackMouseEvent"); - FreeLibrary(hUser32); - } - - _got_dynamic_fns = true; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::Destructor -// Access: Public, Virtual -// Description: -//////////////////////////////////////////////////////////////////// -WinGraphicsWindow:: -~WinGraphicsWindow() { -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::begin_flip -// Access: Public, Virtual -// Description: This function will be called within the draw thread -// after end_frame() has been called on all windows, to -// initiate the exchange of the front and back buffers. -// -// This should instruct the window to prepare for the -// flip at the next video sync, but it should not wait. -// -// We have the two separate functions, begin_flip() and -// end_flip(), to make it easier to flip all of the -// windows at the same time. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -begin_flip() { -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::process_events -// Access: Public, Virtual -// Description: Do whatever processing is necessary to ensure that -// the window responds to user events. Also, honor any -// requests recently made via request_properties() -// -// This function is called only within the window -// thread. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -process_events() { - GraphicsWindow::process_events(); - - // We can't treat the message loop specially just because the window - // is minimized, because we might be reading messages queued up for - // some other window, which is not minimized. - /* - if (!_window_active) { - // Get 1 msg at a time until no more are left and we block and sleep, - // or message changes _return_control_to_app or !_window_active status - - while(!_window_active && (!_return_control_to_app)) { - process_1_event(); - } - _return_control_to_app = false; - - } else - */ - - MSG msg; - - // Handle all the messages on the queue in a row. Some of these - // might be for another window, but they will get dispatched - // appropriately. - while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - process_1_event(); - } -} -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::set_properties_now -// Access: Public, Virtual -// Description: Applies the requested set of properties to the -// window, if possible, for instance to request a change -// in size or minimization status. -// -// The window properties are applied immediately, rather -// than waiting until the next frame. This implies that -// this method may *only* be called from within the -// window thread. -// -// The properties that have been applied are cleared -// from the structure by this function; so on return, -// whatever remains in the properties structure are -// those that were unchanged for some reason (probably -// because the underlying interface does not support -// changing that property on an open window). -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -set_properties_now(WindowProperties &properties) { - GraphicsWindow::set_properties_now(properties); - if (!properties.is_any_specified()) { - // The base class has already handled this case. - return; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::close_window -// Access: Protected, Virtual -// Description: Closes the window right now. Called from the window -// thread. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -close_window() { - set_cursor_out_of_window(); - DestroyWindow(_mwindow); - - // Remove the window handle from our global map. - _window_handles.erase(_mwindow); - _mwindow = (HWND)0; - - if (is_fullscreen()) { - // revert to default display mode. - ChangeDisplaySettings(NULL, 0x0); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::open_window -// Access: Protected, Virtual -// Description: Opens the window right now. Called from the window -// thread. Returns true if the window is successfully -// opened, or false if there was a problem. -//////////////////////////////////////////////////////////////////// -bool WinGraphicsWindow:: -open_window() { - // Store the current window pointer in _creating_window, so we can - // call CreateWindow() and know which window it is sending events to - // even before it gives us a handle. Warning: this is not thread - // safe! - _creating_window = this; - bool opened; - if (is_fullscreen()) { - opened = open_fullscreen_window(); - } else { - opened = open_regular_window(); - } - _creating_window = (WinGraphicsWindow *)NULL; - - if (!opened) { - return false; - } - - // Now that we have a window handle, store it in our global map, so - // future messages for this window can be routed properly. - _window_handles.insert(WindowHandles::value_type(_mwindow, this)); - - // move window to top of zorder. - SetWindowPos(_mwindow, HWND_TOP, 0,0,0,0, - SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE); - - // need to do twice to override any minimized flags in StartProcessInfo - ShowWindow(_mwindow, SW_SHOWNORMAL); - ShowWindow(_mwindow, SW_SHOWNORMAL); - - if (!SetForegroundWindow(_mwindow)) { - windisplay_cat.warning() - << "SetForegroundWindow() failed!\n"; - } - - // Determine the initial open status of the IME. - _ime_open = false; - _ime_active = false; - HIMC hIMC = ImmGetContext(_mwindow); - if (hIMC != 0) { - _ime_open = (ImmGetOpenStatus(hIMC) != 0); - ImmReleaseContext(_mwindow, hIMC); - } - - // 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; - - // need to re-evaluate above in light of this, it seems that - // ImmGetCompositionStringW should work on both: - // The Input Method Editor and Unicode Windows 98/Me, Windows - // NT/2000/XP: Windows supports a Unicode interface for the - // IME, in addition to the ANSI interface originally supported. - // Windows 98/Me supports all the Unicode functions except - // ImmIsUIMessage. Also, all the messages in Windows 98/Me are - // ANSI based. Since Windows 98/Me does not support Unicode - // messages, applications can use ImmGetCompositionString to - // receive Unicode characters from a Unicode based IME on - // Windows 98/Me. There are two issues involved with Unicode - // handling and the IME. One is that the Unicode versions of - // IME routines return the size of a buffer in bytes rather - // than 16-bit Unicode characters,and the other is the IME - // normally returns Unicode characters (rather than DBCS) in - // the WM_CHAR and WM_IME_CHAR messages. Use RegisterClassW - // to cause the WM_CHAR and WM_IME_CHAR messages to return - // Unicode characters in the wParam parameter rather than DBCS - // characters. This is only available under Windows NT; it is - // stubbed out in Windows 95/98/Me. - - return true; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::fullscreen_minimized -// Access: Protected, Virtual -// Description: This is a hook for derived classes to do something -// special, if necessary, when a fullscreen window has -// been minimized. The given WindowProperties struct -// will be applied to this window's properties after -// this function returns. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -fullscreen_minimized(WindowProperties &) { -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::fullscreen_restored -// Access: Protected, Virtual -// Description: This is a hook for derived classes to do something -// special, if necessary, when a fullscreen window has -// been restored after being minimized. The given -// WindowProperties struct will be applied to this -// window's properties after this function returns. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -fullscreen_restored(WindowProperties &) { -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::open_window -// Access: Protected, Virtual -// Description: Called from the window thread in response to a request -// from within the code (via request_properties()) to -// change the size and/or position of the window. -// Returns true if the window is successfully changed, -// or false if there was a problem. -//////////////////////////////////////////////////////////////////// -bool WinGraphicsWindow:: -do_reshape_request(int x_origin, int y_origin, int x_size, int y_size) { - if (!is_fullscreen()) { - // Compute the appropriate size and placement for the window, - // including decorations. - RECT view_rect; - SetRect(&view_rect, x_origin, y_origin, - x_origin + x_size, y_origin + y_size); - WINDOWINFO wi; - GetWindowInfo(_mwindow, &wi); - AdjustWindowRectEx(&view_rect, wi.dwStyle, false, wi.dwExStyle); - - SetWindowPos(_mwindow, NULL, view_rect.left, view_rect.top, - view_rect.right - view_rect.left, - view_rect.bottom - view_rect.top, - SWP_NOZORDER | SWP_NOMOVE | SWP_NOSENDCHANGING); - - // This isn't quite right, because handle_reshape() calls - // system_changed_properties(), generating the event indicating - // the window has changed size externally--even though it changed - // due to an internal request. - handle_reshape(); - return true; - } - - // Resizing a fullscreen window is a little trickier. - return do_fullscreen_resize(x_size, y_size); -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::handle_reshape -// Access: Protected, Virtual -// Description: Called in the window thread when the window size or -// location is changed, this updates the properties -// structure accordingly. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -handle_reshape() { - RECT view_rect; - GetClientRect(_mwindow, &view_rect); - ClientToScreen(_mwindow, (POINT*)&view_rect.left); // translates top,left pnt - ClientToScreen(_mwindow, (POINT*)&view_rect.right); // translates right,bottom pnt - - WindowProperties properties; - properties.set_size((view_rect.right - view_rect.left), - (view_rect.bottom - view_rect.top)); - - // _props origin should reflect upper left of view rectangle - properties.set_origin(view_rect.left, view_rect.top); - - if (windisplay_cat.is_spam()) { - windisplay_cat.spam() - << "reshape to origin: (" << properties.get_x_origin() << "," - << properties.get_y_origin() << "), size: (" << properties.get_x_size() - << "," << properties.get_y_size() << ")\n"; - } - - system_changed_properties(properties); -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::do_fullscreen_resize -// Access: Protected, Virtual -// Description: Called in the window thread to resize a fullscreen -// window. -//////////////////////////////////////////////////////////////////// -bool WinGraphicsWindow:: -do_fullscreen_resize(int x_size, int y_size) { - return false; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::get_client_rect_screen -// Access: Protected -// Description: Fills view_rect with the coordinates of the client -// area of the indicated window, converted to screen -// coordinates. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -get_client_rect_screen(HWND hwnd, RECT *view_rect) { - GetClientRect(hwnd, view_rect); - - POINT ul, lr; - ul.x = view_rect->left; - ul.y = view_rect->top; - lr.x = view_rect->right; - lr.y = view_rect->bottom; - - ClientToScreen(hwnd, &ul); - ClientToScreen(hwnd, &lr); - - view_rect->left = ul.x; - view_rect->top = ul.y; - view_rect->right = lr.x; - view_rect->bottom = lr.y; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::open_fullscreen_window -// Access: Private -// Description: Creates a fullscreen-style window. -//////////////////////////////////////////////////////////////////// -bool WinGraphicsWindow:: -open_fullscreen_window() { - // from MSDN: - // An OpenGL window has its own pixel format. Because of this, only - // device contexts retrieved for the client area of an OpenGL - // window are allowed to draw into the window. As a result, an - // OpenGL window should be created with the WS_CLIPCHILDREN and - // WS_CLIPSIBLINGS styles. Additionally, the window class attribute - // should not include the CS_PARENTDC style. - DWORD window_style = - WS_POPUP | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; - - if (!_properties.has_size()) { - // Just pick a stupid default size if one isn't specified. - _properties.set_size(640, 480); - } - - DWORD dwWidth = _properties.get_x_size(); - DWORD dwHeight = _properties.get_y_size(); - - HWND hDesktopWindow = GetDesktopWindow(); - HDC scrnDC = GetDC(hDesktopWindow); - DWORD cur_bitdepth = GetDeviceCaps(scrnDC, BITSPIXEL); - // DWORD drvr_ver = GetDeviceCaps(scrnDC, DRIVERVERSION); - // DWORD cur_scrnwidth = GetDeviceCaps(scrnDC, HORZRES); - // DWORD cur_scrnheight = GetDeviceCaps(scrnDC, VERTRES); - ReleaseDC(hDesktopWindow, scrnDC); - - DWORD dwFullScreenBitDepth = cur_bitdepth; - - if (!find_acceptable_display_mode(dwWidth, dwHeight, dwFullScreenBitDepth, - _fullscreen_display_mode)) { - windisplay_cat.error() - << "Videocard has no supported display resolutions at specified res (" - << dwWidth << " x " << dwHeight << " x " << dwFullScreenBitDepth <<")\n"; - return false; - } - - string title; - if (_properties.has_title()) { - title = _properties.get_title(); - } - - // I'd prefer to CreateWindow after DisplayChange in case it messes - // up GL somehow, but I need the window's black background to cover - // up the desktop during the mode change - register_window_class(); - HINSTANCE hinstance = GetModuleHandle(NULL); - _mwindow = CreateWindow(_window_class_name, title.c_str(), window_style, - 0, 0, dwWidth, dwHeight, - hDesktopWindow, NULL, hinstance, 0); - if (!_mwindow) { - windisplay_cat.error() - << "CreateWindow() failed!" << endl; - show_error_message(); - return false; - } - - int chg_result = ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN); - if (chg_result != DISP_CHANGE_SUCCESSFUL) { - windisplay_cat.error() - << "ChangeDisplaySettings failed (error code: " - << chg_result << ") for specified res (" << dwWidth - << " x " << dwHeight << " x " << dwFullScreenBitDepth - << "), " << _fullscreen_display_mode.dmDisplayFrequency << "Hz\n"; - return false; - } - - _properties.set_origin(0, 0); - _properties.set_size(dwWidth, dwHeight); - return true; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::open_regular_window -// Access: Private -// Description: Creates a non-fullscreen window, on the desktop. -//////////////////////////////////////////////////////////////////// -bool WinGraphicsWindow:: -open_regular_window() { - // from MSDN: - // An OpenGL window has its own pixel format. Because of this, only - // device contexts retrieved for the client area of an OpenGL - // window are allowed to draw into the window. As a result, an - // OpenGL window should be created with the WS_CLIPCHILDREN and - // WS_CLIPSIBLINGS styles. Additionally, the window class attribute - // should not include the CS_PARENTDC style. - DWORD window_style = - WS_POPUP | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; - - if (!_properties.has_undecorated() || !_properties.get_undecorated()) { - window_style |= WS_OVERLAPPEDWINDOW; - } - - if (!_properties.has_origin()) { - _properties.set_origin(0, 0); - } - if (!_properties.has_size()) { - _properties.set_size(100, 100); - } - - RECT win_rect; - SetRect(&win_rect, - _properties.get_x_origin(), - _properties.get_y_origin(), - _properties.get_x_origin() + _properties.get_x_size(), - _properties.get_y_origin() + _properties.get_y_size()); - - // compute window size based on desired client area size - if (!AdjustWindowRect(&win_rect, window_style, FALSE)) { - windisplay_cat.error() - << "AdjustWindowRect failed!" << endl; - return false; - } - - // make sure origin is on screen; slide far bounds over if necessary - if (win_rect.left < 0) { - win_rect.right += abs(win_rect.left); - win_rect.left = 0; - } - if (win_rect.top < 0) { - win_rect.bottom += abs(win_rect.top); - win_rect.top = 0; - } - - string title; - if (_properties.has_title()) { - title = _properties.get_title(); - } - - register_window_class(); - HINSTANCE hinstance = GetModuleHandle(NULL); - _mwindow = CreateWindow(_window_class_name, title.c_str(), window_style, - win_rect.left, win_rect.top, - win_rect.right - win_rect.left, - win_rect.bottom - win_rect.top, - NULL, NULL, hinstance, 0); - - if (!_mwindow) { - windisplay_cat.error() - << "CreateWindow() failed!" << endl; - show_error_message(); - return false; - } - - return true; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::track_mouse_leaving -// Access: Private -// Description: Intended to be called whenever mouse motion is -// detected within the window, this indicates that the -// mouse is within the window and tells Windows that we -// want to be told when the mouse leaves the window. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -track_mouse_leaving(HWND hwnd) { - // Note: could use _TrackMouseEvent in comctrl32.dll (part of IE - // 3.0+) which emulates TrackMouseEvent on w95, but that requires - // another 500K of memory to hold that DLL, which is lame just to - // support w95, which probably has other issues anyway - if (_pfnTrackMouseEvent != NULL) { - TRACKMOUSEEVENT tme = { - sizeof(TRACKMOUSEEVENT), - TME_LEAVE, - hwnd, - 0 - }; - - // tell win32 to post WM_MOUSELEAVE msgs - BOOL bSucceeded = _pfnTrackMouseEvent(&tme); - - if ((!bSucceeded) && windisplay_cat.is_debug()) { - windisplay_cat.debug() - << "TrackMouseEvent failed!, LastError=" << GetLastError() << endl; - } - - _tracking_mouse_leaving = true; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::window_proc -// Access: Private -// Description: This is the nonstatic window_proc function. It is -// called to handle window events for this particular -// window. -//////////////////////////////////////////////////////////////////// -LONG WinGraphicsWindow:: -window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - /* - cerr << ClockObject::get_global_clock()->get_real_time() - << " window_proc(" << (void *)this << ", " << hwnd << ", " - << msg << ", " << wparam << ", " << lparam << ")\n"; - */ - WindowProperties properties; - int button = -1; - - switch (msg) { - case WM_MOUSEMOVE: - if (!_tracking_mouse_leaving) { - // need to re-call TrackMouseEvent every time mouse re-enters window - track_mouse_leaving(hwnd); - } - set_cursor_in_window(); - handle_mouse_motion(translate_mouse(LOWORD(lparam)), - translate_mouse(HIWORD(lparam))); - break; - - case WM_MOUSELEAVE: - _tracking_mouse_leaving = false; - handle_mouse_exit(); - set_cursor_out_of_window(); - break; - - case WM_CREATE: - track_mouse_leaving(hwnd); - - // Assume the mouse cursor is within the window initially. It - // remains to be seen whether this is assumption does any harm. - set_cursor_in_window(); - break; - - case WM_CLOSE: - properties.set_open(false); - system_changed_properties(properties); - - // TODO: make sure we release the GSG properly. - break; - - case WM_ACTIVATE: - properties.set_minimized((wparam & 0xffff0000) != 0); - if ((wparam & 0xffff) != WA_INACTIVE) { - properties.set_foreground(true); - if (is_fullscreen()) { - // When a fullscreen window goes active, it automatically gets - // un-minimized. - ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN); - GdiFlush(); - SetWindowPos(_mwindow, HWND_TOP, 0,0,0,0, - SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER); - fullscreen_restored(properties); - } - } else { - properties.set_foreground(false); - if (is_fullscreen()) { - // When a fullscreen window goes inactive, it automatically - // gets minimized. - properties.set_minimized(true); - - // It seems order is important here. We must minimize the - // window before restoring the display settings, or risk - // losing the graphics context. - ShowWindow(_mwindow, SW_MINIMIZE); - GdiFlush(); - ChangeDisplaySettings(NULL, 0x0); - fullscreen_minimized(properties); - } - } - system_changed_properties(properties); - break; - - case WM_SIZE: - // for maximized, unmaximize, need to call resize code - // artificially since no WM_EXITSIZEMOVE is generated. - if (wparam == SIZE_MAXIMIZED) { - _maximized = true; - handle_reshape(); - - } else if (wparam == SIZE_RESTORED && _maximized) { - // SIZE_RESTORED might mean we restored to its original size - // before the maximize, but it might also be called while the - // user is resizing the window by hand. Checking the _maximized - // flag that we set above allows us to differentiate the two - // cases. - _maximized = false; - handle_reshape(); - } - break; - - case WM_EXITSIZEMOVE: - handle_reshape(); - break; - - case WM_LBUTTONDOWN: - button = 0; - // fall through - case WM_MBUTTONDOWN: - if (button < 0) { - button = 1; - } - // fall through - case WM_RBUTTONDOWN: - if (button < 0) { - button = 2; - } - SetCapture(hwnd); - handle_keypress(MouseButton::button(button), - translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - break; - - case WM_LBUTTONUP: - button = 0; - // fall through - case WM_MBUTTONUP: - if (button < 0) { - button = 1; - } - // fall through - case WM_RBUTTONUP: - if (button < 0) { - button = 2; - } - ReleaseCapture(); - handle_keyrelease(MouseButton::button(button)); - break; - - 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: - _ime_active = true; - break; - - case WM_IME_ENDCOMPOSITION: - _ime_active = false; - break; - - case WM_IME_COMPOSITION: - if (lparam & GCS_RESULTSTR) { - 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) { - show_error_message(); - } - for (int i = 0; i < wide_size; i++) { - _input_devices[0].keystroke(wide_result[i]); - } - } - - ImmReleaseContext(hwnd, hIMC); - 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[0].keystroke(wparam); - } - break; - - case WM_SYSKEYDOWN: - { - // Alt and F10 are sent as WM_SYSKEYDOWN instead of WM_KEYDOWN - // want to use defwindproc on Alt syskey so std windows cmd - // Alt-F4 works, etc - POINT point; - GetCursorPos(&point); - ScreenToClient(hwnd, &point); - handle_keypress(lookup_key(wparam), point.x, point.y); - if (wparam == VK_F10) { - // bypass default windproc F10 behavior (it activates the main - // menu, but we have none) - return 0; - } - } - break; - - case WM_SYSCOMMAND: - if (wparam == SC_KEYMENU) { - // if Alt is released (alone w/o other keys), defwindproc will - // send this command, which will 'activate' the title bar menu - // (we have none) and give focus to it. we dont want this to - // happen, so kill this msg - 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) && OpenClipboard(NULL)) { - // 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_SETFOCUS: - { - POINT point; - GetCursorPos(&point); - ScreenToClient(hwnd, &point); - - // this is a hack to make sure common modifier keys have proper - // state since at focus loss, app may never receive key-up event - // corresponding to a key-down. it would be better to know the - // exact set of ModifierButtons the user is using, since we may - // miss some here - int i; - for (i=0; i < NUM_MODIFIER_KEYS; i++) { - if (GetKeyState(hardcoded_modifier_buttons[i]) < 0) { - handle_keypress(lookup_key(hardcoded_modifier_buttons[i]), - point.x, point.y); - } - } - } - return 0; - - case WM_KILLFOCUS: - { - int i; - for (i=0; i < NUM_MODIFIER_KEYS; i++) { - if (GetKeyState(hardcoded_modifier_buttons[i]) < 0) { - handle_keyrelease(lookup_key(hardcoded_modifier_buttons[i])); - } - } - } - return 0; - } - - return DefWindowProc(hwnd, msg, wparam, lparam); -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::static_window_proc -// Access: Private, Static -// Description: This is attached to the window class for all -// WinGraphicsWindow windows; it is called to handle -// window events. -//////////////////////////////////////////////////////////////////// -LONG WINAPI WinGraphicsWindow:: -static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { - // Look up the window in our global map. - WindowHandles::const_iterator wi; - wi = _window_handles.find(hwnd); - if (wi != _window_handles.end()) { - // We found the window. - return (*wi).second->window_proc(hwnd, msg, wparam, lparam); - } - - // The window wasn't in the map; we must be creating it right now. - if (_creating_window != (WinGraphicsWindow *)NULL) { - return _creating_window->window_proc(hwnd, msg, wparam, lparam); - } - - // Oops, we weren't creating a window! Don't know how to handle the - // message, so just pass it on to Windows to deal with it. - return DefWindowProc(hwnd, msg, wparam, lparam); -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::process_1_event -// Access: Private, Static -// Description: Handles one event from the message queue. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -process_1_event() { - MSG msg; - - if (!GetMessage(&msg, NULL, 0, 0)) { - // WM_QUIT received. We need a cleaner way to deal with this. - // DestroyAllWindows(false); - exit(msg.wParam); // this will invoke AtExitFn - } - - // Translate virtual key messages - TranslateMessage(&msg); - // Call window_proc - DispatchMessage(&msg); -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::update_cursor_window -// Access: Private, Static -// Description: Changes _cursor_window from its current value to the -// indicated value. This also changes the cursor -// properties appropriately. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -update_cursor_window(WinGraphicsWindow *to_window) { - bool hide_cursor = false; - if (to_window == (WinGraphicsWindow *)NULL) { - // We are leaving a graphics window; we should restore the Win2000 - // effects. - if (_got_saved_params) { - SystemParametersInfo(SPI_SETMOUSETRAILS, NULL, - (PVOID)_saved_mouse_trails, NULL); - SystemParametersInfo(SPI_SETCURSORSHADOW, NULL, - (PVOID)_saved_cursor_shadow, NULL); - SystemParametersInfo(SPI_SETMOUSEVANISH, NULL, - (PVOID)_saved_mouse_vanish, NULL); - _got_saved_params = false; - } - - } else { - const WindowProperties &to_props = to_window->get_properties(); - hide_cursor = to_props.get_cursor_hidden(); - - // We are entering a graphics window; we should save and disable - // the Win2000 effects. These don't work at all well over a 3-D - // window. - - // These parameters are only defined for Win2000/XP, but they - // should just cause a silent error on earlier OS's, which is OK. - if (!_got_saved_params) { - SystemParametersInfo(SPI_GETMOUSETRAILS, NULL, - &_saved_mouse_trails, NULL); - SystemParametersInfo(SPI_GETCURSORSHADOW, NULL, - &_saved_cursor_shadow, NULL); - SystemParametersInfo(SPI_GETMOUSEVANISH, NULL, - &_saved_mouse_vanish, NULL); - _got_saved_params = true; - - SystemParametersInfo(SPI_SETMOUSETRAILS, NULL, (PVOID)0, NULL); - SystemParametersInfo(SPI_SETCURSORSHADOW, NULL, (PVOID)false, NULL); - SystemParametersInfo(SPI_SETMOUSEVANISH, NULL, (PVOID)false, NULL); - } - } - - if (hide_cursor) { - // We should hide the cursor in the new window. - if (!_cursor_hidden) { - ShowCursor(false); - _cursor_hidden = true; - } - } else { - // We should reveal the cursor in the new window. - if (_cursor_hidden) { - ShowCursor(true); - _cursor_hidden = false; - } - } - - _cursor_window = to_window; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::register_window_class -// Access: Private, Static -// Description: Registers a Window class for all WinGraphicsWindows. -// This only needs to be done once per session. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -register_window_class() { - if (_window_class_registered) { - return; - } - - WNDCLASS wc; - - HINSTANCE instance = GetModuleHandle(NULL); - - // Clear before filling in window structure! - ZeroMemory(&wc, sizeof(WNDCLASS)); - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wc.lpfnWndProc = (WNDPROC)static_window_proc; - wc.hInstance = instance; - - // Might be nice to move these properties into the WindowProperties - // structure, so they don't have to be global for all windows. - string windows_icon_filename = get_icon_filename().to_os_specific(); - string windows_mono_cursor_filename = get_mono_cursor_filename().to_os_specific(); - - if (!windows_icon_filename.empty()) { - // Note: LoadImage seems to cause win2k internal heap corruption - // (outputdbgstr warnings) if icon is more than 8bpp - - // loads a .ico fmt file - wc.hIcon = (HICON)LoadImage(NULL, windows_icon_filename.c_str(), - IMAGE_ICON, 0, 0, LR_LOADFROMFILE); - - if (wc.hIcon == NULL) { - windisplay_cat.warning() - << "windows icon filename '" << windows_icon_filename - << "' not found!!\n"; - } - } else { - wc.hIcon = NULL; // use default app icon - } - - _loaded_custom_cursor = false; - if (!windows_mono_cursor_filename.empty()) { - // Note: LoadImage seems to cause win2k internal heap corruption - // (outputdbgstr warnings) if icon is more than 8bpp (because it - // was 'mapping' 16bpp colors to the device?) - - DWORD load_flags = LR_LOADFROMFILE; - - /* - if (_props._fullscreen) { - // I think cursors should use LR_CREATEDIBSECTION since they - // should not be mapped to the device palette (in the case of - // 256-color cursors) since they are not going to be used on the - // desktop - load_flags |= LR_CREATEDIBSECTION; - - // Of course, we can't make this determination here because one - // window class is used for all windows, fullscreen as well as - // desktop windows. - } - */ - - // loads a .cur fmt file - _mouse_cursor = (HCURSOR) LoadImage(NULL, windows_mono_cursor_filename.c_str(), IMAGE_CURSOR, 0, 0, load_flags); - - if (_mouse_cursor == NULL) { - windisplay_cat.warning() - << "windows cursor filename '" << windows_mono_cursor_filename - << "' not found!!\n"; - } else { - _loaded_custom_cursor = true; - } - } - - if (!_loaded_custom_cursor) { - _mouse_cursor = LoadCursor(NULL, IDC_ARROW); - } - - // even if cursor isnt visible, we need to load it so its visible - // in client-area window border - wc.hCursor = _mouse_cursor; - wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wc.lpszMenuName = NULL; - wc.lpszClassName = _window_class_name; - - if (!RegisterClass(&wc)) { - windisplay_cat.error() - << "could not register window class!" << endl; - return; - } - _window_class_registered = true; -} - -// dont pick any video modes < MIN_REFRESH_RATE Hz -#define MIN_REFRESH_RATE 60 -// EnumDisplaySettings may indicate 0 or 1 for refresh rate, which means use driver default rate (assume its >min_refresh_rate) -#define ACCEPTABLE_REFRESH_RATE(RATE) ((RATE >= MIN_REFRESH_RATE) || (RATE==0) || (RATE==1)) - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::find_acceptable_display_mode -// Access: Private, Static -// Description: Looks for a fullscreen mode that meets the specified -// size and bitdepth requirements. Returns true if a -// suitable mode is found, false otherwise. -//////////////////////////////////////////////////////////////////// -bool WinGraphicsWindow:: -find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, DWORD bpp, - DEVMODE &dm) { - int modenum = 0; - - while (1) { - ZeroMemory(&dm, sizeof(dm)); - dm.dmSize = sizeof(dm); - - if (!EnumDisplaySettings(NULL, modenum, &dm)) { - break; - } - - if ((dm.dmPelsWidth == dwWidth) && (dm.dmPelsHeight == dwHeight) && - (dm.dmBitsPerPel == bpp) && - ACCEPTABLE_REFRESH_RATE(dm.dmDisplayFrequency)) { - return true; - } - modenum++; - } - - return false; -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::show_error_message -// Access: Private, Static -// Description: Pops up a dialog box with the indicated Windows error -// message ID (or the last error message generated) for -// meaningful display to the user. -//////////////////////////////////////////////////////////////////// -void WinGraphicsWindow:: -show_error_message(DWORD message_id) { - LPTSTR message_buffer; - - if (message_id == 0) { - message_id = GetLastError(); - } - - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, message_id, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language - (LPTSTR)&message_buffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER - 1024, NULL); - MessageBox(GetDesktopWindow(), message_buffer, _T(errorbox_title), MB_OK); - windisplay_cat.fatal() << "System error msg: " << message_buffer << endl; - LocalFree(message_buffer); -} - -//////////////////////////////////////////////////////////////////// -// Function: WinGraphicsWindow::lookup_key -// Access: Private -// Description: Translates the keycode reported by Windows to an -// appropriate Panda ButtonHandle. -//////////////////////////////////////////////////////////////////// -ButtonHandle WinGraphicsWindow:: -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_TAB: return KeyboardButton::tab(); - case VK_PRIOR: return KeyboardButton::page_up(); - case VK_NEXT: return KeyboardButton::page_down(); - case VK_HOME: return KeyboardButton::home(); - case VK_END: return KeyboardButton::end(); - case VK_F1: return KeyboardButton::f1(); - case VK_F2: return KeyboardButton::f2(); - case VK_F3: return KeyboardButton::f3(); - case VK_F4: return KeyboardButton::f4(); - case VK_F5: return KeyboardButton::f5(); - case VK_F6: return KeyboardButton::f6(); - case VK_F7: return KeyboardButton::f7(); - case VK_F8: return KeyboardButton::f8(); - case VK_F9: return KeyboardButton::f9(); - case VK_F10: return KeyboardButton::f10(); - case VK_F11: return KeyboardButton::f11(); - case VK_F12: return KeyboardButton::f12(); - case VK_INSERT: return KeyboardButton::insert(); - case VK_CAPITAL: return KeyboardButton::caps_lock(); - case VK_NUMLOCK: return KeyboardButton::num_lock(); - case VK_SCROLL: return KeyboardButton::scroll_lock(); - case VK_SNAPSHOT: return KeyboardButton::print_screen(); - - case VK_SHIFT: - case VK_LSHIFT: - case VK_RSHIFT: - return KeyboardButton::shift(); - - case VK_CONTROL: - case VK_LCONTROL: - case VK_RCONTROL: - return KeyboardButton::control(); - - case VK_MENU: - case VK_LMENU: - case VK_RMENU: - return KeyboardButton::alt(); - - default: - int key = MapVirtualKey(wparam, 2); - 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; - } - return ButtonHandle::none(); -} +// Filename: winGraphicsWindow.cxx +// Created by: drose (20Dec02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "winGraphicsWindow.h" +#include "config_windisplay.h" +#include "winGraphicsPipe.h" + +#include "graphicsPipe.h" +#include "keyboardButton.h" +#include "mouseButton.h" +#include "clockObject.h" + +#include + +TypeHandle WinGraphicsWindow::_type_handle; + +bool WinGraphicsWindow::_got_dynamic_fns = false; +WinGraphicsWindow::PFN_TRACKMOUSEEVENT WinGraphicsWindow::_pfnTrackMouseEvent = NULL; + +bool WinGraphicsWindow::_loaded_custom_cursor; +HCURSOR WinGraphicsWindow::_mouse_cursor; +const char * const WinGraphicsWindow::_window_class_name = "WinGraphicsWindow"; +bool WinGraphicsWindow::_window_class_registered = false; + +WinGraphicsWindow::WindowHandles WinGraphicsWindow::_window_handles; +WinGraphicsWindow *WinGraphicsWindow::_creating_window = NULL; + +WinGraphicsWindow *WinGraphicsWindow::_cursor_window = NULL; +bool WinGraphicsWindow::_cursor_hidden = false; + +// These are used to save the previous state of the fancy Win2000 +// effects that interfere with rendering when the mouse wanders into a +// window's client area. +bool WinGraphicsWindow::_got_saved_params = false; +int WinGraphicsWindow::_saved_mouse_trails; +bool WinGraphicsWindow::_saved_cursor_shadow; +bool WinGraphicsWindow::_saved_mouse_vanish; + +static const char * const errorbox_title = "Panda3D Error"; + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +WinGraphicsWindow:: +WinGraphicsWindow(GraphicsPipe *pipe) : + GraphicsWindow(pipe) +{ + GraphicsWindowInputDevice device = + GraphicsWindowInputDevice::pointer_and_keyboard("keyboard/mouse"); + _input_devices.push_back(device); + _mwindow = (HWND)0; + _ime_open = false; + _ime_active = false; + _ime_composition_w = false; + _tracking_mouse_leaving = false; + _maximized = false; + memset(_keyboard_state, 0, sizeof(BYTE) * num_virtual_keys); + + if (!_got_dynamic_fns) { + // these fns arent defined on win95, so get dynamic ptrs to them + // to avoid ugly DLL loader failures on w95 + HINSTANCE hUser32 = (HINSTANCE)LoadLibrary("user32.dll"); + if (hUser32) { + _pfnTrackMouseEvent = + (PFN_TRACKMOUSEEVENT)GetProcAddress(hUser32, "TrackMouseEvent"); + FreeLibrary(hUser32); + } + + _got_dynamic_fns = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +WinGraphicsWindow:: +~WinGraphicsWindow() { +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::begin_flip +// Access: Public, Virtual +// Description: This function will be called within the draw thread +// after end_frame() has been called on all windows, to +// initiate the exchange of the front and back buffers. +// +// This should instruct the window to prepare for the +// flip at the next video sync, but it should not wait. +// +// We have the two separate functions, begin_flip() and +// end_flip(), to make it easier to flip all of the +// windows at the same time. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +begin_flip() { +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::process_events +// Access: Public, Virtual +// Description: Do whatever processing is necessary to ensure that +// the window responds to user events. Also, honor any +// requests recently made via request_properties() +// +// This function is called only within the window +// thread. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +process_events() { + GraphicsWindow::process_events(); + + // We can't treat the message loop specially just because the window + // is minimized, because we might be reading messages queued up for + // some other window, which is not minimized. + /* + if (!_window_active) { + // Get 1 msg at a time until no more are left and we block and sleep, + // or message changes _return_control_to_app or !_window_active status + + while(!_window_active && (!_return_control_to_app)) { + process_1_event(); + } + _return_control_to_app = false; + + } else + */ + + MSG msg; + + // Handle all the messages on the queue in a row. Some of these + // might be for another window, but they will get dispatched + // appropriately. + while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { + process_1_event(); + } +} +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::set_properties_now +// Access: Public, Virtual +// Description: Applies the requested set of properties to the +// window, if possible, for instance to request a change +// in size or minimization status. +// +// The window properties are applied immediately, rather +// than waiting until the next frame. This implies that +// this method may *only* be called from within the +// window thread. +// +// The properties that have been applied are cleared +// from the structure by this function; so on return, +// whatever remains in the properties structure are +// those that were unchanged for some reason (probably +// because the underlying interface does not support +// changing that property on an open window). +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +set_properties_now(WindowProperties &properties) { + GraphicsWindow::set_properties_now(properties); + if (!properties.is_any_specified()) { + // The base class has already handled this case. + return; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::close_window +// Access: Protected, Virtual +// Description: Closes the window right now. Called from the window +// thread. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +close_window() { + set_cursor_out_of_window(); + DestroyWindow(_mwindow); + + // Remove the window handle from our global map. + _window_handles.erase(_mwindow); + _mwindow = (HWND)0; + + if (is_fullscreen()) { + // revert to default display mode. + ChangeDisplaySettings(NULL, 0x0); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::open_window +// Access: Protected, Virtual +// Description: Opens the window right now. Called from the window +// thread. Returns true if the window is successfully +// opened, or false if there was a problem. +//////////////////////////////////////////////////////////////////// +bool WinGraphicsWindow:: +open_window() { + // Store the current window pointer in _creating_window, so we can + // call CreateWindow() and know which window it is sending events to + // even before it gives us a handle. Warning: this is not thread + // safe! + _creating_window = this; + bool opened; + if (is_fullscreen()) { + opened = open_fullscreen_window(); + } else { + opened = open_regular_window(); + } + _creating_window = (WinGraphicsWindow *)NULL; + + if (!opened) { + return false; + } + + // Now that we have a window handle, store it in our global map, so + // future messages for this window can be routed properly. + _window_handles.insert(WindowHandles::value_type(_mwindow, this)); + + // move window to top of zorder. + SetWindowPos(_mwindow, HWND_TOP, 0,0,0,0, + SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE); + + // need to do twice to override any minimized flags in StartProcessInfo + ShowWindow(_mwindow, SW_SHOWNORMAL); + ShowWindow(_mwindow, SW_SHOWNORMAL); + + if (!SetForegroundWindow(_mwindow)) { + windisplay_cat.warning() + << "SetForegroundWindow() failed!\n"; + } + + // Determine the initial open status of the IME. + _ime_open = false; + _ime_active = false; + HIMC hIMC = ImmGetContext(_mwindow); + if (hIMC != 0) { + _ime_open = (ImmGetOpenStatus(hIMC) != 0); + ImmReleaseContext(_mwindow, hIMC); + } + + // 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; + + // need to re-evaluate above in light of this, it seems that + // ImmGetCompositionStringW should work on both: + // The Input Method Editor and Unicode Windows 98/Me, Windows + // NT/2000/XP: Windows supports a Unicode interface for the + // IME, in addition to the ANSI interface originally supported. + // Windows 98/Me supports all the Unicode functions except + // ImmIsUIMessage. Also, all the messages in Windows 98/Me are + // ANSI based. Since Windows 98/Me does not support Unicode + // messages, applications can use ImmGetCompositionString to + // receive Unicode characters from a Unicode based IME on + // Windows 98/Me. There are two issues involved with Unicode + // handling and the IME. One is that the Unicode versions of + // IME routines return the size of a buffer in bytes rather + // than 16-bit Unicode characters,and the other is the IME + // normally returns Unicode characters (rather than DBCS) in + // the WM_CHAR and WM_IME_CHAR messages. Use RegisterClassW + // to cause the WM_CHAR and WM_IME_CHAR messages to return + // Unicode characters in the wParam parameter rather than DBCS + // characters. This is only available under Windows NT; it is + // stubbed out in Windows 95/98/Me. + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::fullscreen_minimized +// Access: Protected, Virtual +// Description: This is a hook for derived classes to do something +// special, if necessary, when a fullscreen window has +// been minimized. The given WindowProperties struct +// will be applied to this window's properties after +// this function returns. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +fullscreen_minimized(WindowProperties &) { +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::fullscreen_restored +// Access: Protected, Virtual +// Description: This is a hook for derived classes to do something +// special, if necessary, when a fullscreen window has +// been restored after being minimized. The given +// WindowProperties struct will be applied to this +// window's properties after this function returns. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +fullscreen_restored(WindowProperties &) { +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::open_window +// Access: Protected, Virtual +// Description: Called from the window thread in response to a request +// from within the code (via request_properties()) to +// change the size and/or position of the window. +// Returns true if the window is successfully changed, +// or false if there was a problem. +//////////////////////////////////////////////////////////////////// +bool WinGraphicsWindow:: +do_reshape_request(int x_origin, int y_origin, int x_size, int y_size) { + if (!is_fullscreen()) { + // Compute the appropriate size and placement for the window, + // including decorations. + RECT view_rect; + SetRect(&view_rect, x_origin, y_origin, + x_origin + x_size, y_origin + y_size); + WINDOWINFO wi; + GetWindowInfo(_mwindow, &wi); + AdjustWindowRectEx(&view_rect, wi.dwStyle, false, wi.dwExStyle); + + SetWindowPos(_mwindow, NULL, view_rect.left, view_rect.top, + view_rect.right - view_rect.left, + view_rect.bottom - view_rect.top, + SWP_NOZORDER | SWP_NOMOVE | SWP_NOSENDCHANGING); + + // This isn't quite right, because handle_reshape() calls + // system_changed_properties(), generating the event indicating + // the window has changed size externally--even though it changed + // due to an internal request. + handle_reshape(); + return true; + } + + // Resizing a fullscreen window is a little trickier. + return do_fullscreen_resize(x_size, y_size); +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::handle_reshape +// Access: Protected, Virtual +// Description: Called in the window thread when the window size or +// location is changed, this updates the properties +// structure accordingly. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +handle_reshape() { + RECT view_rect; + GetClientRect(_mwindow, &view_rect); + ClientToScreen(_mwindow, (POINT*)&view_rect.left); // translates top,left pnt + ClientToScreen(_mwindow, (POINT*)&view_rect.right); // translates right,bottom pnt + + WindowProperties properties; + properties.set_size((view_rect.right - view_rect.left), + (view_rect.bottom - view_rect.top)); + + // _props origin should reflect upper left of view rectangle + properties.set_origin(view_rect.left, view_rect.top); + + if (windisplay_cat.is_spam()) { + windisplay_cat.spam() + << "reshape to origin: (" << properties.get_x_origin() << "," + << properties.get_y_origin() << "), size: (" << properties.get_x_size() + << "," << properties.get_y_size() << ")\n"; + } + + system_changed_properties(properties); +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::do_fullscreen_resize +// Access: Protected, Virtual +// Description: Called in the window thread to resize a fullscreen +// window. +//////////////////////////////////////////////////////////////////// +bool WinGraphicsWindow:: +do_fullscreen_resize(int x_size, int y_size) { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::get_client_rect_screen +// Access: Protected +// Description: Fills view_rect with the coordinates of the client +// area of the indicated window, converted to screen +// coordinates. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +get_client_rect_screen(HWND hwnd, RECT *view_rect) { + GetClientRect(hwnd, view_rect); + + POINT ul, lr; + ul.x = view_rect->left; + ul.y = view_rect->top; + lr.x = view_rect->right; + lr.y = view_rect->bottom; + + ClientToScreen(hwnd, &ul); + ClientToScreen(hwnd, &lr); + + view_rect->left = ul.x; + view_rect->top = ul.y; + view_rect->right = lr.x; + view_rect->bottom = lr.y; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::open_fullscreen_window +// Access: Private +// Description: Creates a fullscreen-style window. +//////////////////////////////////////////////////////////////////// +bool WinGraphicsWindow:: +open_fullscreen_window() { + // from MSDN: + // An OpenGL window has its own pixel format. Because of this, only + // device contexts retrieved for the client area of an OpenGL + // window are allowed to draw into the window. As a result, an + // OpenGL window should be created with the WS_CLIPCHILDREN and + // WS_CLIPSIBLINGS styles. Additionally, the window class attribute + // should not include the CS_PARENTDC style. + DWORD window_style = + WS_POPUP | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + + if (!_properties.has_size()) { + // Just pick a stupid default size if one isn't specified. + _properties.set_size(640, 480); + } + + DWORD dwWidth = _properties.get_x_size(); + DWORD dwHeight = _properties.get_y_size(); + + HWND hDesktopWindow = GetDesktopWindow(); + HDC scrnDC = GetDC(hDesktopWindow); + DWORD cur_bitdepth = GetDeviceCaps(scrnDC, BITSPIXEL); + // DWORD drvr_ver = GetDeviceCaps(scrnDC, DRIVERVERSION); + // DWORD cur_scrnwidth = GetDeviceCaps(scrnDC, HORZRES); + // DWORD cur_scrnheight = GetDeviceCaps(scrnDC, VERTRES); + ReleaseDC(hDesktopWindow, scrnDC); + + DWORD dwFullScreenBitDepth = cur_bitdepth; + + if (!find_acceptable_display_mode(dwWidth, dwHeight, dwFullScreenBitDepth, + _fullscreen_display_mode)) { + windisplay_cat.error() + << "Videocard has no supported display resolutions at specified res (" + << dwWidth << " x " << dwHeight << " x " << dwFullScreenBitDepth <<")\n"; + return false; + } + + string title; + if (_properties.has_title()) { + title = _properties.get_title(); + } + + // I'd prefer to CreateWindow after DisplayChange in case it messes + // up GL somehow, but I need the window's black background to cover + // up the desktop during the mode change + register_window_class(); + HINSTANCE hinstance = GetModuleHandle(NULL); + _mwindow = CreateWindow(_window_class_name, title.c_str(), window_style, + 0, 0, dwWidth, dwHeight, + hDesktopWindow, NULL, hinstance, 0); + if (!_mwindow) { + windisplay_cat.error() + << "CreateWindow() failed!" << endl; + show_error_message(); + return false; + } + + int chg_result = ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN); + if (chg_result != DISP_CHANGE_SUCCESSFUL) { + windisplay_cat.error() + << "ChangeDisplaySettings failed (error code: " + << chg_result << ") for specified res (" << dwWidth + << " x " << dwHeight << " x " << dwFullScreenBitDepth + << "), " << _fullscreen_display_mode.dmDisplayFrequency << "Hz\n"; + return false; + } + + _properties.set_origin(0, 0); + _properties.set_size(dwWidth, dwHeight); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::open_regular_window +// Access: Private +// Description: Creates a non-fullscreen window, on the desktop. +//////////////////////////////////////////////////////////////////// +bool WinGraphicsWindow:: +open_regular_window() { + // from MSDN: + // An OpenGL window has its own pixel format. Because of this, only + // device contexts retrieved for the client area of an OpenGL + // window are allowed to draw into the window. As a result, an + // OpenGL window should be created with the WS_CLIPCHILDREN and + // WS_CLIPSIBLINGS styles. Additionally, the window class attribute + // should not include the CS_PARENTDC style. + DWORD window_style = + WS_POPUP | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + + if (!_properties.has_undecorated() || !_properties.get_undecorated()) { + window_style |= WS_OVERLAPPEDWINDOW; + } + + if (!_properties.has_origin()) { + _properties.set_origin(0, 0); + } + if (!_properties.has_size()) { + _properties.set_size(100, 100); + } + + RECT win_rect; + SetRect(&win_rect, + _properties.get_x_origin(), + _properties.get_y_origin(), + _properties.get_x_origin() + _properties.get_x_size(), + _properties.get_y_origin() + _properties.get_y_size()); + + // compute window size based on desired client area size + if (!AdjustWindowRect(&win_rect, window_style, FALSE)) { + windisplay_cat.error() + << "AdjustWindowRect failed!" << endl; + return false; + } + + // make sure origin is on screen; slide far bounds over if necessary + if (win_rect.left < 0) { + win_rect.right += abs(win_rect.left); + win_rect.left = 0; + } + if (win_rect.top < 0) { + win_rect.bottom += abs(win_rect.top); + win_rect.top = 0; + } + + string title; + if (_properties.has_title()) { + title = _properties.get_title(); + } + + register_window_class(); + HINSTANCE hinstance = GetModuleHandle(NULL); + _mwindow = CreateWindow(_window_class_name, title.c_str(), window_style, + win_rect.left, win_rect.top, + win_rect.right - win_rect.left, + win_rect.bottom - win_rect.top, + NULL, NULL, hinstance, 0); + + if (!_mwindow) { + windisplay_cat.error() + << "CreateWindow() failed!" << endl; + show_error_message(); + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::track_mouse_leaving +// Access: Private +// Description: Intended to be called whenever mouse motion is +// detected within the window, this indicates that the +// mouse is within the window and tells Windows that we +// want to be told when the mouse leaves the window. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +track_mouse_leaving(HWND hwnd) { + // Note: could use _TrackMouseEvent in comctrl32.dll (part of IE + // 3.0+) which emulates TrackMouseEvent on w95, but that requires + // another 500K of memory to hold that DLL, which is lame just to + // support w95, which probably has other issues anyway + if (_pfnTrackMouseEvent != NULL) { + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + hwnd, + 0 + }; + + // tell win32 to post WM_MOUSELEAVE msgs + BOOL bSucceeded = _pfnTrackMouseEvent(&tme); + + if ((!bSucceeded) && windisplay_cat.is_debug()) { + windisplay_cat.debug() + << "TrackMouseEvent failed!, LastError=" << GetLastError() << endl; + } + + _tracking_mouse_leaving = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::window_proc +// Access: Private +// Description: This is the nonstatic window_proc function. It is +// called to handle window events for this particular +// window. +//////////////////////////////////////////////////////////////////// +LONG WinGraphicsWindow:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + /* + cerr << ClockObject::get_global_clock()->get_real_time() + << " window_proc(" << (void *)this << ", " << hwnd << ", " + << msg << ", " << wparam << ", " << lparam << ")\n"; + */ + WindowProperties properties; + int button = -1; + + switch (msg) { + case WM_MOUSEMOVE: + if (!_tracking_mouse_leaving) { + // need to re-call TrackMouseEvent every time mouse re-enters window + track_mouse_leaving(hwnd); + } + set_cursor_in_window(); + handle_mouse_motion(translate_mouse(LOWORD(lparam)), + translate_mouse(HIWORD(lparam))); + break; + + case WM_MOUSELEAVE: + _tracking_mouse_leaving = false; + handle_mouse_exit(); + set_cursor_out_of_window(); + break; + + case WM_CREATE: + track_mouse_leaving(hwnd); + + // Assume the mouse cursor is within the window initially. It + // remains to be seen whether this is assumption does any harm. + set_cursor_in_window(); + break; + + case WM_CLOSE: + properties.set_open(false); + system_changed_properties(properties); + + // TODO: make sure we release the GSG properly. + break; + + case WM_ACTIVATE: + properties.set_minimized((wparam & 0xffff0000) != 0); + if ((wparam & 0xffff) != WA_INACTIVE) { + properties.set_foreground(true); + if (is_fullscreen()) { + // When a fullscreen window goes active, it automatically gets + // un-minimized. + ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN); + GdiFlush(); + SetWindowPos(_mwindow, HWND_TOP, 0,0,0,0, + SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER); + fullscreen_restored(properties); + } + } else { + properties.set_foreground(false); + if (is_fullscreen()) { + // When a fullscreen window goes inactive, it automatically + // gets minimized. + properties.set_minimized(true); + + // It seems order is important here. We must minimize the + // window before restoring the display settings, or risk + // losing the graphics context. + ShowWindow(_mwindow, SW_MINIMIZE); + GdiFlush(); + ChangeDisplaySettings(NULL, 0x0); + fullscreen_minimized(properties); + } + } + system_changed_properties(properties); + break; + + case WM_SIZE: + // for maximized, unmaximize, need to call resize code + // artificially since no WM_EXITSIZEMOVE is generated. + if (wparam == SIZE_MAXIMIZED) { + _maximized = true; + handle_reshape(); + + } else if (wparam == SIZE_RESTORED && _maximized) { + // SIZE_RESTORED might mean we restored to its original size + // before the maximize, but it might also be called while the + // user is resizing the window by hand. Checking the _maximized + // flag that we set above allows us to differentiate the two + // cases. + _maximized = false; + handle_reshape(); + } + break; + + case WM_EXITSIZEMOVE: + handle_reshape(); + break; + + case WM_LBUTTONDOWN: + button = 0; + // fall through + case WM_MBUTTONDOWN: + if (button < 0) { + button = 1; + } + // fall through + case WM_RBUTTONDOWN: + if (button < 0) { + button = 2; + } + SetCapture(hwnd); + handle_keypress(MouseButton::button(button), + translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + break; + + case WM_LBUTTONUP: + button = 0; + // fall through + case WM_MBUTTONUP: + if (button < 0) { + button = 1; + } + // fall through + case WM_RBUTTONUP: + if (button < 0) { + button = 2; + } + ReleaseCapture(); + handle_keyrelease(MouseButton::button(button)); + break; + + 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: + _ime_active = true; + break; + + case WM_IME_ENDCOMPOSITION: + _ime_active = false; + break; + + case WM_IME_COMPOSITION: + if (lparam & GCS_RESULTSTR) { + 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) { + show_error_message(); + } + for (int i = 0; i < wide_size; i++) { + _input_devices[0].keystroke(wide_result[i]); + } + } + + ImmReleaseContext(hwnd, hIMC); + 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[0].keystroke(wparam); + } + break; + + case WM_SYSKEYDOWN: + { + // Alt and F10 are sent as WM_SYSKEYDOWN instead of WM_KEYDOWN + // want to use defwindproc on Alt syskey so std windows cmd + // Alt-F4 works, etc + POINT point; + GetCursorPos(&point); + ScreenToClient(hwnd, &point); + handle_keypress(lookup_key(wparam), point.x, point.y); + if (wparam == VK_F10) { + // bypass default windproc F10 behavior (it activates the main + // menu, but we have none) + return 0; + } + } + break; + + case WM_SYSCOMMAND: + if (wparam == SC_KEYMENU) { + // if Alt is released (alone w/o other keys), defwindproc will + // send this command, which will 'activate' the title bar menu + // (we have none) and give focus to it. we dont want this to + // happen, so kill this msg + 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) && OpenClipboard(NULL)) { + // 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_KILLFOCUS: + // Record the current state of the keyboard when the focus is + // lost, so we can check it for changes when we regain focus. + GetKeyboardState(_keyboard_state); + break; + + case WM_SETFOCUS: + { + POINT point; + GetCursorPos(&point); + ScreenToClient(hwnd, &point); + + // When we lose focus, the app may miss key-up events for keys + // that were formerly held down (and vice-versa). Therefore, + // when focus is regained, compare the state of the keyboard to + // the last known state (stored above, when focus was lost) to + // regenerate the lost keyboard events. + BYTE new_keyboard_state[num_virtual_keys]; + GetKeyboardState(new_keyboard_state); + for (int i = 0; i < num_virtual_keys; i++) { + // Filter out these particular three. We don't want to test + // these, because these are virtual duplicates for + // VK_LSHIFT/VK_RSHIFT, etc.; and the left/right equivalent is + // also in the table. If we respect both VK_LSHIFT as well as + // VK_SHIFT, we'll generate two keyboard messages when + // VK_LSHIFT changes state. + if (i != VK_SHIFT && i != VK_CONTROL && i != VK_MENU) { + if (((new_keyboard_state[i] ^ _keyboard_state[i]) & 0x80) != 0) { + // This key has changed state. + if ((new_keyboard_state[i] & 0x80) != 0) { + // The key is now held down. + handle_keypress(lookup_key(i), point.x, point.y); + } else { + // The key is now released. + handle_keyrelease(lookup_key(i)); + } + } + } + } + + // Save the new keyboard state, just for good measure. This + // really shouldn't be necessary, but it protects against + // inadvertently getting WM_SETFOCUS twice in a row, for + // instance. + memcpy(_keyboard_state, new_keyboard_state, + sizeof(BYTE) * num_virtual_keys); + } + break; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::static_window_proc +// Access: Private, Static +// Description: This is attached to the window class for all +// WinGraphicsWindow windows; it is called to handle +// window events. +//////////////////////////////////////////////////////////////////// +LONG WINAPI WinGraphicsWindow:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + // Look up the window in our global map. + WindowHandles::const_iterator wi; + wi = _window_handles.find(hwnd); + if (wi != _window_handles.end()) { + // We found the window. + return (*wi).second->window_proc(hwnd, msg, wparam, lparam); + } + + // The window wasn't in the map; we must be creating it right now. + if (_creating_window != (WinGraphicsWindow *)NULL) { + return _creating_window->window_proc(hwnd, msg, wparam, lparam); + } + + // Oops, we weren't creating a window! Don't know how to handle the + // message, so just pass it on to Windows to deal with it. + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::process_1_event +// Access: Private, Static +// Description: Handles one event from the message queue. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +process_1_event() { + MSG msg; + + if (!GetMessage(&msg, NULL, 0, 0)) { + // WM_QUIT received. We need a cleaner way to deal with this. + // DestroyAllWindows(false); + exit(msg.wParam); // this will invoke AtExitFn + } + + // Translate virtual key messages + TranslateMessage(&msg); + // Call window_proc + DispatchMessage(&msg); +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::update_cursor_window +// Access: Private, Static +// Description: Changes _cursor_window from its current value to the +// indicated value. This also changes the cursor +// properties appropriately. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +update_cursor_window(WinGraphicsWindow *to_window) { + bool hide_cursor = false; + if (to_window == (WinGraphicsWindow *)NULL) { + // We are leaving a graphics window; we should restore the Win2000 + // effects. + if (_got_saved_params) { + SystemParametersInfo(SPI_SETMOUSETRAILS, NULL, + (PVOID)_saved_mouse_trails, NULL); + SystemParametersInfo(SPI_SETCURSORSHADOW, NULL, + (PVOID)_saved_cursor_shadow, NULL); + SystemParametersInfo(SPI_SETMOUSEVANISH, NULL, + (PVOID)_saved_mouse_vanish, NULL); + _got_saved_params = false; + } + + } else { + const WindowProperties &to_props = to_window->get_properties(); + hide_cursor = to_props.get_cursor_hidden(); + + // We are entering a graphics window; we should save and disable + // the Win2000 effects. These don't work at all well over a 3-D + // window. + + // These parameters are only defined for Win2000/XP, but they + // should just cause a silent error on earlier OS's, which is OK. + if (!_got_saved_params) { + SystemParametersInfo(SPI_GETMOUSETRAILS, NULL, + &_saved_mouse_trails, NULL); + SystemParametersInfo(SPI_GETCURSORSHADOW, NULL, + &_saved_cursor_shadow, NULL); + SystemParametersInfo(SPI_GETMOUSEVANISH, NULL, + &_saved_mouse_vanish, NULL); + _got_saved_params = true; + + SystemParametersInfo(SPI_SETMOUSETRAILS, NULL, (PVOID)0, NULL); + SystemParametersInfo(SPI_SETCURSORSHADOW, NULL, (PVOID)false, NULL); + SystemParametersInfo(SPI_SETMOUSEVANISH, NULL, (PVOID)false, NULL); + } + } + + if (hide_cursor) { + // We should hide the cursor in the new window. + if (!_cursor_hidden) { + ShowCursor(false); + _cursor_hidden = true; + } + } else { + // We should reveal the cursor in the new window. + if (_cursor_hidden) { + ShowCursor(true); + _cursor_hidden = false; + } + } + + _cursor_window = to_window; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::register_window_class +// Access: Private, Static +// Description: Registers a Window class for all WinGraphicsWindows. +// This only needs to be done once per session. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +register_window_class() { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + HINSTANCE instance = GetModuleHandle(NULL); + + // Clear before filling in window structure! + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = instance; + + // Might be nice to move these properties into the WindowProperties + // structure, so they don't have to be global for all windows. + string windows_icon_filename = get_icon_filename().to_os_specific(); + string windows_mono_cursor_filename = get_mono_cursor_filename().to_os_specific(); + + if (!windows_icon_filename.empty()) { + // Note: LoadImage seems to cause win2k internal heap corruption + // (outputdbgstr warnings) if icon is more than 8bpp + + // loads a .ico fmt file + wc.hIcon = (HICON)LoadImage(NULL, windows_icon_filename.c_str(), + IMAGE_ICON, 0, 0, LR_LOADFROMFILE); + + if (wc.hIcon == NULL) { + windisplay_cat.warning() + << "windows icon filename '" << windows_icon_filename + << "' not found!!\n"; + } + } else { + wc.hIcon = NULL; // use default app icon + } + + _loaded_custom_cursor = false; + if (!windows_mono_cursor_filename.empty()) { + // Note: LoadImage seems to cause win2k internal heap corruption + // (outputdbgstr warnings) if icon is more than 8bpp (because it + // was 'mapping' 16bpp colors to the device?) + + DWORD load_flags = LR_LOADFROMFILE; + + /* + if (_props._fullscreen) { + // I think cursors should use LR_CREATEDIBSECTION since they + // should not be mapped to the device palette (in the case of + // 256-color cursors) since they are not going to be used on the + // desktop + load_flags |= LR_CREATEDIBSECTION; + + // Of course, we can't make this determination here because one + // window class is used for all windows, fullscreen as well as + // desktop windows. + } + */ + + // loads a .cur fmt file + _mouse_cursor = (HCURSOR) LoadImage(NULL, windows_mono_cursor_filename.c_str(), IMAGE_CURSOR, 0, 0, load_flags); + + if (_mouse_cursor == NULL) { + windisplay_cat.warning() + << "windows cursor filename '" << windows_mono_cursor_filename + << "' not found!!\n"; + } else { + _loaded_custom_cursor = true; + } + } + + if (!_loaded_custom_cursor) { + _mouse_cursor = LoadCursor(NULL, IDC_ARROW); + } + + // even if cursor isnt visible, we need to load it so its visible + // in client-area window border + wc.hCursor = _mouse_cursor; + wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wc.lpszMenuName = NULL; + wc.lpszClassName = _window_class_name; + + if (!RegisterClass(&wc)) { + windisplay_cat.error() + << "could not register window class!" << endl; + return; + } + _window_class_registered = true; +} + +// dont pick any video modes < MIN_REFRESH_RATE Hz +#define MIN_REFRESH_RATE 60 +// EnumDisplaySettings may indicate 0 or 1 for refresh rate, which means use driver default rate (assume its >min_refresh_rate) +#define ACCEPTABLE_REFRESH_RATE(RATE) ((RATE >= MIN_REFRESH_RATE) || (RATE==0) || (RATE==1)) + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::find_acceptable_display_mode +// Access: Private, Static +// Description: Looks for a fullscreen mode that meets the specified +// size and bitdepth requirements. Returns true if a +// suitable mode is found, false otherwise. +//////////////////////////////////////////////////////////////////// +bool WinGraphicsWindow:: +find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, DWORD bpp, + DEVMODE &dm) { + int modenum = 0; + + while (1) { + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettings(NULL, modenum, &dm)) { + break; + } + + if ((dm.dmPelsWidth == dwWidth) && (dm.dmPelsHeight == dwHeight) && + (dm.dmBitsPerPel == bpp) && + ACCEPTABLE_REFRESH_RATE(dm.dmDisplayFrequency)) { + return true; + } + modenum++; + } + + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::show_error_message +// Access: Private, Static +// Description: Pops up a dialog box with the indicated Windows error +// message ID (or the last error message generated) for +// meaningful display to the user. +//////////////////////////////////////////////////////////////////// +void WinGraphicsWindow:: +show_error_message(DWORD message_id) { + LPTSTR message_buffer; + + if (message_id == 0) { + message_id = GetLastError(); + } + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, message_id, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language + (LPTSTR)&message_buffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER + 1024, NULL); + MessageBox(GetDesktopWindow(), message_buffer, _T(errorbox_title), MB_OK); + windisplay_cat.fatal() << "System error msg: " << message_buffer << endl; + LocalFree(message_buffer); +} + +//////////////////////////////////////////////////////////////////// +// Function: WinGraphicsWindow::lookup_key +// Access: Private +// Description: Translates the keycode reported by Windows to an +// appropriate Panda ButtonHandle. +//////////////////////////////////////////////////////////////////// +ButtonHandle WinGraphicsWindow:: +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_TAB: return KeyboardButton::tab(); + case VK_PRIOR: return KeyboardButton::page_up(); + case VK_NEXT: return KeyboardButton::page_down(); + case VK_HOME: return KeyboardButton::home(); + case VK_END: return KeyboardButton::end(); + case VK_F1: return KeyboardButton::f1(); + case VK_F2: return KeyboardButton::f2(); + case VK_F3: return KeyboardButton::f3(); + case VK_F4: return KeyboardButton::f4(); + case VK_F5: return KeyboardButton::f5(); + case VK_F6: return KeyboardButton::f6(); + case VK_F7: return KeyboardButton::f7(); + case VK_F8: return KeyboardButton::f8(); + case VK_F9: return KeyboardButton::f9(); + case VK_F10: return KeyboardButton::f10(); + case VK_F11: return KeyboardButton::f11(); + case VK_F12: return KeyboardButton::f12(); + case VK_INSERT: return KeyboardButton::insert(); + case VK_CAPITAL: return KeyboardButton::caps_lock(); + case VK_NUMLOCK: return KeyboardButton::num_lock(); + case VK_SCROLL: return KeyboardButton::scroll_lock(); + case VK_SNAPSHOT: return KeyboardButton::print_screen(); + + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + return KeyboardButton::shift(); + + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + return KeyboardButton::control(); + + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + return KeyboardButton::alt(); + + default: + int key = MapVirtualKey(wparam, 2); + 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; + } + return ButtonHandle::none(); +} diff --git a/panda/src/windisplay/winGraphicsWindow.h b/panda/src/windisplay/winGraphicsWindow.h index 5fccedcf66..70ee9548c0 100644 --- a/panda/src/windisplay/winGraphicsWindow.h +++ b/panda/src/windisplay/winGraphicsWindow.h @@ -1,164 +1,169 @@ -// Filename: winGraphicsWindow.h -// Created by: drose (20Dec02) -// -//////////////////////////////////////////////////////////////////// -// -// PANDA 3D SOFTWARE -// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved -// -// All use of this software is subject to the terms of the Panda 3d -// Software license. You should have received a copy of this license -// along with this source code; you will also find a current copy of -// the license at http://www.panda3d.org/license.txt . -// -// To contact the maintainers of this program write to -// panda3d@yahoogroups.com . -// -//////////////////////////////////////////////////////////////////// - -#ifndef WINGRAPHICSWINDOW_H -#define WINGRAPHICSWINDOW_H - -// include win32 defns for everything up to XP, and assume I'm smart -// enough to use GetProcAddress for backward compat on w95/w98 for -// newer fns -#define _WIN32_WINNT 0x0501 - -#define WINDOWS_LEAN_AND_MEAN -#include -#undef WINDOWS_LEAN_AND_MEAN - -#include "pandabase.h" -#include "graphicsWindow.h" - -class WinGraphicsPipe; - -//////////////////////////////////////////////////////////////////// -// Class : WinGraphicsWindow -// Description : An abstract base class for glGraphicsWindow and -// dxGraphicsWindow (and, in general, graphics windows -// that interface with the Microsoft Windows API). -// -// This class includes all the code for manipulating -// windows themselves: opening them, closing them, -// responding to user keyboard and mouse input, and so -// on. It does not make any 3-D rendering calls into -// the window; that is the province of the -// GraphicsStateGuardian. -//////////////////////////////////////////////////////////////////// -class EXPCL_PANDAWIN WinGraphicsWindow : public GraphicsWindow { -public: - WinGraphicsWindow(GraphicsPipe *pipe); - virtual ~WinGraphicsWindow(); - - virtual void begin_flip(); - - virtual void process_events(); - virtual void set_properties_now(WindowProperties &properties); - -protected: - virtual void close_window(); - virtual bool open_window(); - virtual void fullscreen_minimized(WindowProperties &properties); - virtual void fullscreen_restored(WindowProperties &properties); - - virtual bool do_reshape_request(int x_origin, int y_origin, - int x_size, int y_size); - - virtual void handle_reshape(); - virtual bool do_fullscreen_resize(int x_size, int y_size); - - void get_client_rect_screen(HWND hwnd, RECT *view_rect); - -private: - bool open_fullscreen_window(); - bool open_regular_window(); - void track_mouse_leaving(HWND hwnd); - - LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); - static LONG WINAPI - static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); - static void process_1_event(); - - INLINE void handle_mouse_motion(int x, int y); - INLINE void handle_mouse_exit(void); - INLINE void handle_keypress(ButtonHandle key, int x, int y); - INLINE void handle_keyrelease(ButtonHandle key); - ButtonHandle lookup_key(WPARAM wparam) const; - INLINE int translate_mouse(int pos) const; - INLINE void set_cursor_in_window(); - INLINE void set_cursor_out_of_window(); - static void update_cursor_window(WinGraphicsWindow *to_window); - - static void register_window_class(); - static bool find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, - DWORD bpp, DEVMODE &dm); - static void show_error_message(DWORD message_id = 0); - -protected: - HWND _mwindow; - -private: - bool _ime_open; - bool _ime_active; - bool _ime_composition_w; - bool _tracking_mouse_leaving; - bool _maximized; - DEVMODE _fullscreen_display_mode; - - static bool _got_dynamic_fns; - typedef BOOL (WINAPI *PFN_TRACKMOUSEEVENT)(LPTRACKMOUSEEVENT); - static PFN_TRACKMOUSEEVENT _pfnTrackMouseEvent; - -protected: - static bool _loaded_custom_cursor; - static HCURSOR _mouse_cursor; - static const char * const _window_class_name; - static bool _window_class_registered; - -private: - // We need this map to support per-window calls to window_proc(). - typedef map WindowHandles; - static WindowHandles _window_handles; - - // And we need a static pointer to the current WinGraphicsWindow we - // are creating at the moment, since CreateWindow() starts - // generating window events before it gives us the window handle. - static WinGraphicsWindow *_creating_window; - - // This tracks the current GraphicsWindow whose client area contains - // the mouse. There will only be one of these at a time, and - // storing the pointer here allows us to handle ambiguities in the - // order in which messages are passed from Windows to the various - // windows we manage. This pointer is used by - // set_cursor_in_window() to determine when it is time to call - // update_cursor() to hide the cursor (or do other related - // operations). - static WinGraphicsWindow *_cursor_window; - static bool _cursor_hidden; - static bool _got_saved_params; - static int _saved_mouse_trails; - static bool _saved_cursor_shadow; - static bool _saved_mouse_vanish; - -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - GraphicsWindow::init_type(); - register_type(_type_handle, "WinGraphicsWindow", - GraphicsWindow::get_class_type()); - } - virtual TypeHandle get_type() const { - return get_class_type(); - } - virtual TypeHandle force_init_type() {init_type(); return get_class_type();} - -private: - static TypeHandle _type_handle; -}; - -#include "winGraphicsWindow.I" - -#endif +// Filename: winGraphicsWindow.h +// Created by: drose (20Dec02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef WINGRAPHICSWINDOW_H +#define WINGRAPHICSWINDOW_H + +// include win32 defns for everything up to XP, and assume I'm smart +// enough to use GetProcAddress for backward compat on w95/w98 for +// newer fns +#define _WIN32_WINNT 0x0501 + +#define WINDOWS_LEAN_AND_MEAN +#include +#undef WINDOWS_LEAN_AND_MEAN + +#include "pandabase.h" +#include "graphicsWindow.h" + +class WinGraphicsPipe; + +//////////////////////////////////////////////////////////////////// +// Class : WinGraphicsWindow +// Description : An abstract base class for glGraphicsWindow and +// dxGraphicsWindow (and, in general, graphics windows +// that interface with the Microsoft Windows API). +// +// This class includes all the code for manipulating +// windows themselves: opening them, closing them, +// responding to user keyboard and mouse input, and so +// on. It does not make any 3-D rendering calls into +// the window; that is the province of the +// GraphicsStateGuardian. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAWIN WinGraphicsWindow : public GraphicsWindow { +public: + WinGraphicsWindow(GraphicsPipe *pipe); + virtual ~WinGraphicsWindow(); + + virtual void begin_flip(); + + virtual void process_events(); + virtual void set_properties_now(WindowProperties &properties); + +protected: + virtual void close_window(); + virtual bool open_window(); + virtual void fullscreen_minimized(WindowProperties &properties); + virtual void fullscreen_restored(WindowProperties &properties); + + virtual bool do_reshape_request(int x_origin, int y_origin, + int x_size, int y_size); + + virtual void handle_reshape(); + virtual bool do_fullscreen_resize(int x_size, int y_size); + + void get_client_rect_screen(HWND hwnd, RECT *view_rect); + +private: + bool open_fullscreen_window(); + bool open_regular_window(); + void track_mouse_leaving(HWND hwnd); + + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + static LONG WINAPI + static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + static void process_1_event(); + + INLINE void handle_mouse_motion(int x, int y); + INLINE void handle_mouse_exit(void); + INLINE void handle_keypress(ButtonHandle key, int x, int y); + INLINE void handle_keyrelease(ButtonHandle key); + ButtonHandle lookup_key(WPARAM wparam) const; + INLINE int translate_mouse(int pos) const; + INLINE void set_cursor_in_window(); + INLINE void set_cursor_out_of_window(); + static void update_cursor_window(WinGraphicsWindow *to_window); + + static void register_window_class(); + static bool find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, + DWORD bpp, DEVMODE &dm); + static void show_error_message(DWORD message_id = 0); + +protected: + HWND _mwindow; + +private: + bool _ime_open; + bool _ime_active; + bool _ime_composition_w; + bool _tracking_mouse_leaving; + bool _maximized; + DEVMODE _fullscreen_display_mode; + + static bool _got_dynamic_fns; + typedef BOOL (WINAPI *PFN_TRACKMOUSEEVENT)(LPTRACKMOUSEEVENT); + static PFN_TRACKMOUSEEVENT _pfnTrackMouseEvent; + + // This is used to remember the state of the keyboard when keyboard + // focus is lost. + static const int num_virtual_keys = 256; + BYTE _keyboard_state[num_virtual_keys]; + +protected: + static bool _loaded_custom_cursor; + static HCURSOR _mouse_cursor; + static const char * const _window_class_name; + static bool _window_class_registered; + +private: + // We need this map to support per-window calls to window_proc(). + typedef map WindowHandles; + static WindowHandles _window_handles; + + // And we need a static pointer to the current WinGraphicsWindow we + // are creating at the moment, since CreateWindow() starts + // generating window events before it gives us the window handle. + static WinGraphicsWindow *_creating_window; + + // This tracks the current GraphicsWindow whose client area contains + // the mouse. There will only be one of these at a time, and + // storing the pointer here allows us to handle ambiguities in the + // order in which messages are passed from Windows to the various + // windows we manage. This pointer is used by + // set_cursor_in_window() to determine when it is time to call + // update_cursor() to hide the cursor (or do other related + // operations). + static WinGraphicsWindow *_cursor_window; + static bool _cursor_hidden; + static bool _got_saved_params; + static int _saved_mouse_trails; + static bool _saved_cursor_shadow; + static bool _saved_mouse_vanish; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + GraphicsWindow::init_type(); + register_type(_type_handle, "WinGraphicsWindow", + GraphicsWindow::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "winGraphicsWindow.I" + +#endif