Browse Source

better tracking of key events when focus is lost

David Rose 23 years ago
parent
commit
78a3290325
2 changed files with 1489 additions and 1476 deletions
  1. 1320 1312
      panda/src/windisplay/winGraphicsWindow.cxx
  2. 169 164
      panda/src/windisplay/winGraphicsWindow.h

+ 1320 - 1312
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
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#include "winGraphicsWindow.h"
-#include "config_windisplay.h"
-#include "winGraphicsPipe.h"
-
-#include "graphicsPipe.h"
-#include "keyboardButton.h"
-#include "mouseButton.h"
-#include "clockObject.h"
-
-#include <tchar.h>
-
-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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "winGraphicsWindow.h"
+#include "config_windisplay.h"
+#include "winGraphicsPipe.h"
+
+#include "graphicsPipe.h"
+#include "keyboardButton.h"
+#include "mouseButton.h"
+#include "clockObject.h"
+
+#include <tchar.h>
+
+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();
+}

+ 169 - 164
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
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#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 <windows.h>
-#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<HWND, WinGraphicsWindow *> 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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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 <windows.h>
+#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<HWND, WinGraphicsWindow *> 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