panda3d/panda/src/windisplay/winGraphicsWindow.cxx

2655 lines
93 KiB
C++

// Filename: winGraphicsWindow.cxx
// Created by: drose (20Dec02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "winGraphicsWindow.h"
#include "config_windisplay.h"
#include "winGraphicsPipe.h"
#include "graphicsPipe.h"
#include "keyboardButton.h"
#include "mouseButton.h"
#include "clockObject.h"
#include "config_util.h"
#include "throw_event.h"
#include "nativeWindowHandle.h"
#include <tchar.h>
#define WANT_NEW_FOCUS_MANAGMENT
TypeHandle WinGraphicsWindow::_type_handle;
TypeHandle WinGraphicsWindow::WinWindowHandle::_type_handle;
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;
WinGraphicsWindow::IconFilenames WinGraphicsWindow::_icon_filenames;
WinGraphicsWindow::IconFilenames WinGraphicsWindow::_cursor_filenames;
WinGraphicsWindow::WindowClasses WinGraphicsWindow::_window_classes;
int WinGraphicsWindow::_window_class_index = 0;
static const char * const errorbox_title = "Panda3D Error";
////////////////////////////////////////////////////////////////////
//
// These static variables contain pointers to the Raw Input
// functions, which are dynamically extracted from USER32.DLL
//
////////////////////////////////////////////////////////////////////
typedef WINUSERAPI UINT (WINAPI *tGetRawInputDeviceList)
(OUT PRAWINPUTDEVICELIST pRawInputDeviceList, IN OUT PUINT puiNumDevices, IN UINT cbSize);
typedef WINUSERAPI UINT(WINAPI *tGetRawInputData)
(IN HRAWINPUT hRawInput, IN UINT uiCommand, OUT LPVOID pData, IN OUT PUINT pcbSize, IN UINT cbSizeHeader);
typedef WINUSERAPI UINT(WINAPI *tGetRawInputDeviceInfoA)
(IN HANDLE hDevice, IN UINT uiCommand, OUT LPVOID pData, IN OUT PUINT pcbSize);
typedef WINUSERAPI BOOL (WINAPI *tRegisterRawInputDevices)
(IN PCRAWINPUTDEVICE pRawInputDevices, IN UINT uiNumDevices, IN UINT cbSize);
static tGetRawInputDeviceList pGetRawInputDeviceList;
static tGetRawInputData pGetRawInputData;
static tGetRawInputDeviceInfoA pGetRawInputDeviceInfoA;
static tRegisterRawInputDevices pRegisterRawInputDevices;
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
WinGraphicsWindow::
WinGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
const string &name,
const FrameBufferProperties &fb_prop,
const WindowProperties &win_prop,
int flags,
GraphicsStateGuardian *gsg,
GraphicsOutput *host) :
GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
{
initialize_input_devices();
_hWnd = (HWND)0;
_ime_open = false;
_ime_active = false;
_tracking_mouse_leaving = false;
_maximized = false;
_cursor = 0;
memset(_keyboard_state, 0, sizeof(BYTE) * num_virtual_keys);
_lost_keypresses = false;
_lshift_down = false;
_rshift_down = false;
_lcontrol_down = false;
_rcontrol_down = false;
_lalt_down = false;
_ralt_down = false;
_hparent = NULL;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
WinGraphicsWindow::
~WinGraphicsWindow() {
if (_window_handle != (WindowHandle *)NULL) {
DCAST(WinWindowHandle, _window_handle)->clear_window();
}
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::move_pointer
// Access: Published, Virtual
// Description: Forces the pointer to the indicated position within
// the window, if possible.
//
// Returns true if successful, false on failure. This
// may fail if the mouse is not currently within the
// window, or if the API doesn't support this operation.
////////////////////////////////////////////////////////////////////
bool WinGraphicsWindow::
move_pointer(int device, int x, int y) {
// First, indicate that the IME is no longer active, so that it won't
// send the string through WM_IME_COMPOSITION. But we still leave
// _ime_open true, so that it also won't send the string through WM_CHAR.
_ime_active = false;
// Note: this is not thread-safe; it should be called only from App.
// Probably not an issue.
if (device == 0) {
// Move the system mouse pointer.
if (!_properties.get_foreground() )
// !_input_devices[0].get_pointer().get_in_window())
{
// If the window doesn't have input focus, or the mouse isn't
// currently within the window, forget it.
return false;
}
RECT view_rect;
get_client_rect_screen(_hWnd, &view_rect);
SetCursorPos(view_rect.left + x, view_rect.top + y);
_input_devices[0].set_pointer_in_window(x, y);
return true;
} else {
// Move a raw mouse.
if ((device < 1)||(device >= (int)_input_devices.size())) {
return false;
}
_input_devices[device].set_pointer_in_window(x, y);
return true;
}
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::close_ime
// Access: Published, Virtual
// Description: Forces the ime window to close, if any
//
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
close_ime() {
// Check if the ime window is open
if (!_ime_open)
return;
HIMC hIMC = ImmGetContext(_hWnd);
if (hIMC != 0) {
if (!ImmSetOpenStatus(hIMC, false)) {
windisplay_cat.debug() << "ImmSetOpenStatus failed\n";
}
ImmReleaseContext(_hWnd, hIMC);
}
_ime_open = false;
windisplay_cat.debug() << "success: closed ime window\n";
return;
}
////////////////////////////////////////////////////////////////////
// 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;
}
if (properties.has_title()) {
string title = properties.get_title();
_properties.set_title(title);
SetWindowText(_hWnd, title.c_str());
properties.clear_title();
}
if (properties.has_cursor_hidden()) {
bool hide_cursor = properties.get_cursor_hidden();
_properties.set_cursor_hidden(hide_cursor);
if (_cursor_window == this) {
hide_or_show_cursor(hide_cursor);
}
properties.clear_cursor_hidden();
}
if (properties.has_cursor_filename()) {
Filename filename = properties.get_cursor_filename();
_properties.set_cursor_filename(filename);
_cursor = get_cursor(filename);
if (_cursor == 0) {
_cursor = LoadCursor(NULL, IDC_ARROW);
}
if (_cursor_window == this) {
SetCursor(_cursor);
}
properties.clear_cursor_filename();
}
if (properties.has_z_order()) {
WindowProperties::ZOrder last_z_order = _properties.get_z_order();
_properties.set_z_order(properties.get_z_order());
adjust_z_order(last_z_order, properties.get_z_order());
properties.clear_z_order();
}
if (properties.has_foreground() && properties.get_foreground()) {
if (!SetActiveWindow(_hWnd)) {
windisplay_cat.warning()
<< "SetForegroundWindow() failed!\n";
} else {
_properties.set_foreground(true);
}
properties.clear_foreground();
}
}
////////////////////////////////////////////////////////////////////
// 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(_hWnd);
if (is_fullscreen()) {
// revert to default display mode.
ChangeDisplaySettings(NULL, 0x0);
}
// Remove the window handle from our global map.
_window_handles.erase(_hWnd);
_hWnd = (HWND)0;
GraphicsWindow::close_window();
}
////////////////////////////////////////////////////////////////////
// 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() {
if (_properties.has_cursor_filename()) {
_cursor = get_cursor(_properties.get_cursor_filename());
}
if (_cursor == 0) {
_cursor = LoadCursor(NULL, IDC_ARROW);
}
bool want_foreground = (!_properties.has_foreground() || _properties.get_foreground());
HWND old_foreground_window = GetForegroundWindow();
// 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 = open_graphic_window(is_fullscreen());
_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(_hWnd, this));
// move window to top of zorder.
SetWindowPos(_hWnd, HWND_TOP, 0,0,0,0,
SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
// need to do twice to override any minimized flags in StartProcessInfo
ShowWindow(_hWnd, SW_SHOWNORMAL);
ShowWindow(_hWnd, SW_SHOWNORMAL);
HWND new_foreground_window = _hWnd;
if (!want_foreground) {
// If we specifically requested the window not to be on top,
// restore the previous foreground window (if we can).
new_foreground_window = old_foreground_window;
}
if (!SetActiveWindow(new_foreground_window)) {
windisplay_cat.warning()
<< "SetActiveWindow() failed!\n";
}
// Let's aggressively call SetForegroundWindow() in addition to
// SetActiveWindow(). It seems to work in some cases to make the
// window come to the top, where SetActiveWindow doesn't work.
if (!SetForegroundWindow(new_foreground_window)) {
windisplay_cat.warning()
<< "SetForegroundWindow() failed!\n";
}
// Determine the initial open status of the IME.
_ime_open = false;
_ime_active = false;
HIMC hIMC = ImmGetContext(_hWnd);
if (hIMC != 0) {
_ime_open = (ImmGetOpenStatus(hIMC) != 0);
ImmReleaseContext(_hWnd, hIMC);
}
// Registers to receive the WM_INPUT messages
if ((pRegisterRawInputDevices)&&(_input_devices.size() > 1)) {
RAWINPUTDEVICE Rid;
Rid.usUsagePage = 0x01;
Rid.usUsage = 0x02;
Rid.dwFlags = 0;// RIDEV_NOLEGACY; // adds HID mouse and also ignores legacy mouse messages
Rid.hwndTarget = _hWnd;
pRegisterRawInputDevices(&Rid, 1, sizeof (Rid));
}
// Create a WindowHandle for ourselves
_window_handle = NativeWindowHandle::make_win(_hWnd);
// Actually, we want a WinWindowHandle.
_window_handle = new WinWindowHandle(this, *_window_handle);
// And tell our parent window that we're now its child.
if (_parent_window_handle != (WindowHandle *)NULL) {
_parent_window_handle->attach_child(_window_handle);
}
// set us as the focus window for keyboard input
set_focus();
return true;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::initialize_input_devices
// Access: Private
// Description: Creates the array of input devices. The first
// one is always the system mouse and keyboard.
// Each subsequent one is a raw mouse device. Also
// initializes a parallel array, _input_device_handle,
// with the win32 handle of each raw input device.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
initialize_input_devices() {
UINT nInputDevices;
PRAWINPUTDEVICELIST pRawInputDeviceList;
nassertv(_input_devices.size() == 0);
// Clear the handle array, and set up the system keyboard/mouse
memset(_input_device_handle, 0, sizeof(_input_device_handle));
GraphicsWindowInputDevice device =
GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
add_input_device(device);
// Try initializing the Raw Input function pointers.
if (pRegisterRawInputDevices==0) {
HMODULE user32 = LoadLibrary("user32.dll");
if (user32) {
pRegisterRawInputDevices = (tRegisterRawInputDevices)GetProcAddress(user32,"RegisterRawInputDevices");
pGetRawInputDeviceList = (tGetRawInputDeviceList) GetProcAddress(user32,"GetRawInputDeviceList");
pGetRawInputDeviceInfoA = (tGetRawInputDeviceInfoA) GetProcAddress(user32,"GetRawInputDeviceInfoA");
pGetRawInputData = (tGetRawInputData) GetProcAddress(user32,"GetRawInputData");
}
}
if (pRegisterRawInputDevices==0) return;
if (pGetRawInputDeviceList==0) return;
if (pGetRawInputDeviceInfoA==0) return;
if (pGetRawInputData==0) return;
// Get the number of devices.
if (pGetRawInputDeviceList(NULL, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) != 0)
return;
// Allocate the array to hold the DeviceList
pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(sizeof(RAWINPUTDEVICELIST) * nInputDevices);
if (pRawInputDeviceList==0) return;
// Fill the Array
if (pGetRawInputDeviceList(pRawInputDeviceList, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) == -1)
return;
// Loop through all raw devices and find the raw mice
for (int i = 0; i < (int)nInputDevices; i++) {
if (pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE) {
// Fetch information about specified mouse device.
UINT nSize;
if (pGetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)0, &nSize) != 0)
return;
char *psName = (char*)alloca(sizeof(TCHAR) * nSize);
if (psName == 0) return;
if (pGetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)psName, &nSize) < 0)
return;
// If it's not an RDP mouse, add it to the list of raw mice.
if (strncmp(psName,"\\??\\Root#RDP_MOU#0000#",22)!=0) {
if (_input_devices.size() < 32) {
if (strncmp(psName,"\\??\\",4)==0) psName += 4;
char *pound1 = strchr(psName,'#');
char *pound2 = pound1 ? strchr(pound1+1,'#') : 0;
char *pound3 = pound2 ? strchr(pound2+1,'#') : 0;
if (pound3) *pound3 = 0;
for (char *p = psName; *p; p++) {
if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) {
*p = '_';
}
}
if (pound2) *pound2 = '.';
_input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice;
GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName);
device.set_pointer_in_window(0,0);
add_input_device(device);
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// 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::do_reshape_request
// 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, bool has_origin,
int x_size, int y_size) {
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "Got reshape request (" << x_origin << ", " << y_origin
<< ", " << has_origin << ", " << x_size << ", " << y_size << ")\n";
}
if (!is_fullscreen()) {
if (has_origin) {
// A coordinate of -2 means to center the window in its client area.
if (x_origin == -2) {
x_origin = 0.5 * (_pipe->get_display_width() - x_size);
}
if (y_origin == -2) {
y_origin = 0.5 * (_pipe->get_display_height() - y_size);
}
_properties.set_origin(x_origin, y_origin);
if (x_origin = -1) {
x_origin = CW_USEDEFAULT;
}
if (y_origin = -1) {
y_origin = CW_USEDEFAULT;
}
}
// 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(_hWnd, &wi);
AdjustWindowRectEx(&view_rect, wi.dwStyle, FALSE, wi.dwExStyle);
UINT flags = SWP_NOZORDER | SWP_NOSENDCHANGING;
if (has_origin) {
x_origin = view_rect.left;
y_origin = view_rect.top;
} else {
x_origin = CW_USEDEFAULT;
y_origin = CW_USEDEFAULT;
flags |= SWP_NOMOVE;
}
SetWindowPos(_hWnd, NULL, x_origin, y_origin,
view_rect.right - view_rect.left,
view_rect.bottom - view_rect.top,
flags);
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(_hWnd, &view_rect);
ClientToScreen(_hWnd, (POINT*)&view_rect.left); // translates top,left pnt
ClientToScreen(_hWnd, (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";
}
adjust_z_order();
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) {
HWND hDesktopWindow = GetDesktopWindow();
HDC scrnDC = GetDC(hDesktopWindow);
DWORD dwFullScreenBitDepth = GetDeviceCaps(scrnDC, BITSPIXEL);
ReleaseDC(hDesktopWindow, scrnDC);
// resize will always leave screen bitdepth unchanged
// allowing resizing of lowvidmem cards to > 640x480. why? I'll
// assume check was already done by caller, so he knows what he
// wants
DEVMODE dm;
if (!find_acceptable_display_mode(x_size, y_size,
dwFullScreenBitDepth, dm)) {
windisplay_cat.error()
<< "window resize(" << x_size << ", " << y_size
<< ") failed, no compatible fullscreen display mode found!\n";
return false;
}
// this causes WM_SIZE msg to be produced
SetWindowPos(_hWnd, NULL, 0,0, x_size, y_size,
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSENDCHANGING);
int chg_result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (chg_result != DISP_CHANGE_SUCCESSFUL) {
windisplay_cat.error()
<< "resize ChangeDisplaySettings failed (error code: "
<< chg_result << ") for specified res (" << x_size << " x "
<< y_size << " x " << dwFullScreenBitDepth << "), "
<< dm.dmDisplayFrequency << "Hz\n";
return false;
}
_fullscreen_display_mode = dm;
windisplay_cat.info()
<< "Resized fullscreen window to " << x_size << ", " << y_size
<< " bitdepth " << dwFullScreenBitDepth << ", "
<< dm.dmDisplayFrequency << "Hz\n";
_properties.set_size(x_size, y_size);
set_size_and_recalc(x_size, y_size);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::reconsider_fullscreen_size
// Access: Protected, Virtual
// Description: Called before creating a fullscreen window to give
// the driver a chance to adjust the particular
// resolution request, if necessary.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
reconsider_fullscreen_size(DWORD &, DWORD &, DWORD &) {
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::support_overlay_window
// Access: Protected, Virtual
// Description: Some windows graphics contexts (e.g. DirectX)
// require special support to enable the displaying of
// an overlay window (particularly the IME window) over
// the fullscreen graphics window. This is a hook for
// the window to enable or disable that mode when
// necessary.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
support_overlay_window(bool) {
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::open_graphic_window
// Access: Private
// Description: Creates a regular or fullscreen window.
////////////////////////////////////////////////////////////////////
bool WinGraphicsWindow::
open_graphic_window(bool fullscreen) {
// 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_CLIPCHILDREN | WS_CLIPSIBLINGS;
if (fullscreen){
window_style |= WS_SYSMENU;
} else if (!_properties.get_undecorated()) {
window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
if (!_properties.get_fixed_size()) {
window_style |= (WS_SIZEBOX | WS_MAXIMIZEBOX);
} else {
window_style |= WS_BORDER;
}
}
string title;
if (_properties.has_title()) {
title = _properties.get_title();
}
if (!_properties.has_size()) {
//Just pick a conservative default size if one isn't specified.
_properties.set_size(640, 480);
}
int x_size = _properties.get_x_size();
int y_size = _properties.get_y_size();
int x_origin = 0;
int y_origin = 0;
if (!fullscreen && _properties.has_origin()) {
x_origin = _properties.get_x_origin();
y_origin = _properties.get_y_origin();
// A coordinate of -2 means to center the window in its client area.
if (x_origin == -2) {
x_origin = 0.5 * (_pipe->get_display_width() - x_size);
}
if (y_origin == -2) {
y_origin = 0.5 * (_pipe->get_display_height() - y_size);
}
_properties.set_origin(x_origin, y_origin);
if (x_origin = -1) {
x_origin = CW_USEDEFAULT;
}
if (y_origin = -1) {
y_origin = CW_USEDEFAULT;
}
}
int clientAreaWidth = x_size;
int clientAreaHeight = y_size;
if (!fullscreen){
RECT win_rect;
SetRect(&win_rect, x_origin, y_origin,
x_origin + x_size, y_origin + 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;
}
if (_properties.has_origin()) {
x_origin = win_rect.left;
y_origin = win_rect.top;
} else {
x_origin = CW_USEDEFAULT;
y_origin = CW_USEDEFAULT;
}
clientAreaWidth = win_rect.right - win_rect.left;
clientAreaHeight = win_rect.bottom - win_rect.top;
}
const WindowClass &wclass = register_window_class(_properties);
HINSTANCE hinstance = GetModuleHandle(NULL);
_hparent = NULL;
if (!fullscreen){
WindowHandle *window_handle = _properties.get_parent_window();
if (window_handle != NULL) {
windisplay_cat.info()
<< "Got parent_window " << *window_handle << "\n";
WindowHandle::OSHandle *os_handle = window_handle->get_os_handle();
if (os_handle != NULL) {
windisplay_cat.info()
<< "os_handle type " << os_handle->get_type() << "\n";
if (os_handle->is_of_type(NativeWindowHandle::WinHandle::get_class_type())) {
NativeWindowHandle::WinHandle *win_handle = DCAST(NativeWindowHandle::WinHandle, os_handle);
_hparent = win_handle->get_handle();
} else if (os_handle->is_of_type(NativeWindowHandle::IntHandle::get_class_type())) {
NativeWindowHandle::IntHandle *int_handle = DCAST(NativeWindowHandle::IntHandle, os_handle);
_hparent = (HWND)int_handle->get_handle();
}
}
}
_parent_window_handle = window_handle;
} else {
_parent_window_handle = NULL;
}
if (!_hparent) { // This can be a regular window or a fullscreen window
_hWnd = CreateWindow(wclass._name.c_str(), title.c_str(), window_style,
x_origin, y_origin,
clientAreaWidth,
clientAreaHeight,
NULL, NULL, hinstance, 0);
} else { // This is a regular window with a parent
x_origin = 0;
y_origin = 0;
if (!fullscreen && _properties.has_origin()) {
x_origin = _properties.get_x_origin();
y_origin = _properties.get_y_origin();
}
_hWnd = CreateWindow(wclass._name.c_str(), title.c_str(),
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ,
x_origin, y_origin,
x_size, y_size,
_hparent, NULL, hinstance, 0);
if (_hWnd) {
// join our keyboard state with the parents
// Actually, let's not. Is there really any reason to do this?
// It causes problems with the browser plugin--it deadlocks when
// the parent process is waiting on the child process.
//AttachThreadInput(GetWindowThreadProcessId(_hparent,NULL), GetCurrentThreadId(),TRUE);
WindowProperties properties;
properties.set_foreground(true);
system_changed_properties(properties);
}
}
if (!_hWnd) {
windisplay_cat.error()
<< "CreateWindow() failed!" << endl;
show_error_message();
return false;
}
// 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.
if (fullscreen){
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 dwWidth = _properties.get_x_size();
DWORD dwHeight = _properties.get_y_size();
DWORD dwFullScreenBitDepth = cur_bitdepth;
DEVMODE dm;
reconsider_fullscreen_size(dwWidth, dwHeight, dwFullScreenBitDepth);
if (!find_acceptable_display_mode(dwWidth, dwHeight, dwFullScreenBitDepth, dm)) {
windisplay_cat.error()
<< "Videocard has no supported display resolutions at specified res ("
<< dwWidth << " x " << dwHeight << " x " << dwFullScreenBitDepth <<")\n";
return false;
}
dm.dmPelsWidth = dwWidth;
dm.dmPelsHeight = dwHeight;
dm.dmBitsPerPel = dwFullScreenBitDepth;
int chg_result = ChangeDisplaySettings(&dm, 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::adjust_z_order
// Access: Private
// Description: Adjusts the Z-order of a window after it has been
// moved.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
adjust_z_order() {
WindowProperties::ZOrder z_order = _properties.get_z_order();
adjust_z_order(z_order, z_order);
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::adjust_z_order
// Access: Private
// Description: Adjusts the Z-order of a window after it has been
// moved.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
adjust_z_order(WindowProperties::ZOrder last_z_order,
WindowProperties::ZOrder this_z_order) {
HWND order;
bool do_change = false;
switch (this_z_order) {
case WindowProperties::Z_bottom:
order = HWND_BOTTOM;
do_change = true;
break;
case WindowProperties::Z_normal:
if ((last_z_order != WindowProperties::Z_normal) &&
// If we aren't changing the window order, don't move it to
// the top.
(last_z_order != WindowProperties::Z_bottom ||
_properties.get_foreground())
// If the window was previously on the bottom, but it doesn't
// have focus now, don't move it to the top; it will get moved
// the next time we get focus.
) {
order = HWND_TOP;
do_change = true;
}
break;
case WindowProperties::Z_top:
order = HWND_TOPMOST;
do_change = true;
break;
}
if (do_change) {
BOOL result = SetWindowPos(_hWnd, order, 0,0,0,0,
SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
if (!result) {
windisplay_cat.warning()
<< "SetWindowPos failed.\n";
}
}
}
////////////////////////////////////////////////////////////////////
// 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
WinGraphicsPipe *winpipe;
DCAST_INTO_V(winpipe, _pipe);
if (winpipe->_pfnTrackMouseEvent != NULL) {
TRACKMOUSEEVENT tme = {
sizeof(TRACKMOUSEEVENT),
TME_LEAVE,
hwnd,
0
};
// tell win32 to post WM_MOUSELEAVE msgs
BOOL bSucceeded = winpipe->_pfnTrackMouseEvent(&tme);
if ((!bSucceeded) && windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "TrackMouseEvent failed!, LastError=" << GetLastError() << endl;
}
_tracking_mouse_leaving = true;
}
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::set_focus
// Access: Private
// Description: Attempts to set this window as the "focus" window, so
// that keyboard events come here.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
set_focus() {
if (SetFocus(_hWnd) == NULL && GetLastError() != 0) {
// If the SetFocus() request failed, maybe we're running in the
// plugin environment on Vista, with UAC enabled. In this case,
// we're not allowed to assign focus to the Panda window for some
// stupid reason. So instead, we have to ask the parent window
// (in the browser process) to proxy our keyboard events for us.
if (_parent_window_handle != NULL && _window_handle != NULL) {
_parent_window_handle->request_keyboard_focus(_window_handle);
} else {
// Otherwise, something is wrong.
windisplay_cat.error()
<< "SetFocus failed: " << GetLastError() << "\n";
}
}
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::receive_windows_message
// Access: Public
// Description: This is called to receive a keyboard event generated
// by proxy by another window in a parent process. This
// hacky system is used in the web plugin system to
// allow the Panda window to receive keyboard events on
// Vista, which doesn't allow the Panda window to set
// keyboard focus to itself.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
receive_windows_message(unsigned int msg, int wparam, int lparam) {
// Well, we'll just deliver this directly to window_proc(),
// supplying our own window handle. For the most part, we don't
// care about the window handle anyway, but this might become an
// issue for the IME. TODO: investigate IME issues.
window_proc(_hWnd, msg, wparam, lparam);
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::window_proc
// Access: Public, Virtual
// 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) {
if (windisplay_cat.is_spam()) {
windisplay_cat.spam()
<< 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();
if(handle_mouse_motion(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))))
return 0;
break;
case WM_INPUT:
handle_raw_input((HRAWINPUT)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);
ClearToBlack(hwnd, _properties);
POINT cpos;
GetCursorPos(&cpos);
ScreenToClient(hwnd, &cpos);
RECT clientRect;
GetClientRect(hwnd, &clientRect);
if (PtInRect(&clientRect,cpos)) {
set_cursor_in_window(); // should window focus be true as well?
} else {
set_cursor_out_of_window();
}
}
break;
/*
case WM_SHOWWINDOW:
// You'd think WM_SHOWWINDOW would be just the thing for embedded
// windows, but it turns out it's not sent to the child windows
// when the parent is minimized. I guess it's only sent for an
// explicit call to ShowWindow, phooey.
{
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "WM_SHOWWINDOW: " << hwnd << ", " << wparam << "\n";
}
if (wparam) {
// Window is being shown.
properties.set_minimized(false);
} else {
// Window is being hidden.
properties.set_minimized(true);
}
system_changed_properties(properties);
}
break;
*/
case WM_CLOSE:
// This is a message from the system indicating that the user
// has requested to close the window (e.g. alt-f4).
{
string close_request_event = get_close_request_event();
if (!close_request_event.empty()) {
// In this case, the app has indicated a desire to intercept
// the request and process it directly.
throw_event(close_request_event);
return 0;
} else {
// In this case, the default case, the app does not intend
// to service the request, so we do by closing the window.
close_window();
properties.set_open(false);
system_changed_properties(properties);
// TODO: make sure we release the GSG properly.
}
}
break;
case WM_CHILDACTIVATE:
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "WM_CHILDACTIVATE: " << hwnd << "\n";
}
break;
case WM_ACTIVATE:
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "WM_ACTIVATE: " << hwnd << ", " << wparam << ", " << lparam << "\n";
}
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(_hWnd, 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(_hWnd, SW_MINIMIZE);
GdiFlush();
ChangeDisplaySettings(NULL, 0x0);
fullscreen_minimized(properties);
}
}
adjust_z_order();
system_changed_properties(properties);
break;
case WM_SIZE:
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "WM_SIZE: " << hwnd << ", " << wparam << "\n";
}
// 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_WINDOWPOSCHANGED:
adjust_z_order();
break;
case WM_LBUTTONDOWN:
if (_lost_keypresses) {
resend_lost_keypresses();
}
SetCapture(hwnd);
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
_input_devices[0].button_down(MouseButton::button(0), get_message_time());
// A button-click in the window means to grab the keyboard focus.
set_focus();
break;
case WM_MBUTTONDOWN:
if (_lost_keypresses) {
resend_lost_keypresses();
}
SetCapture(hwnd);
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
_input_devices[0].button_down(MouseButton::button(1), get_message_time());
// A button-click in the window means to grab the keyboard focus.
set_focus();
break;
case WM_RBUTTONDOWN:
if (_lost_keypresses) {
resend_lost_keypresses();
}
SetCapture(hwnd);
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
_input_devices[0].button_down(MouseButton::button(2), get_message_time());
// A button-click in the window means to grab the keyboard focus.
set_focus();
break;
case WM_XBUTTONDOWN:
{
if (_lost_keypresses) {
resend_lost_keypresses();
}
SetCapture(hwnd);
int whichButton = GET_XBUTTON_WPARAM(wparam);
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
if (whichButton == XBUTTON1) {
_input_devices[0].button_down(MouseButton::button(3), get_message_time());
} else if (whichButton == XBUTTON2) {
_input_devices[0].button_down(MouseButton::button(4), get_message_time());
}
}
break;
case WM_LBUTTONUP:
if (_lost_keypresses) {
resend_lost_keypresses();
}
ReleaseCapture();
_input_devices[0].button_up(MouseButton::button(0), get_message_time());
break;
case WM_MBUTTONUP:
if (_lost_keypresses) {
resend_lost_keypresses();
}
ReleaseCapture();
_input_devices[0].button_up(MouseButton::button(1), get_message_time());
break;
case WM_RBUTTONUP:
if (_lost_keypresses) {
resend_lost_keypresses();
}
ReleaseCapture();
_input_devices[0].button_up(MouseButton::button(2), get_message_time());
break;
case WM_XBUTTONUP:
{
if (_lost_keypresses) {
resend_lost_keypresses();
}
ReleaseCapture();
int whichButton = GET_XBUTTON_WPARAM(wparam);
if (whichButton == XBUTTON1) {
_input_devices[0].button_up(MouseButton::button(3), get_message_time());
} else if (whichButton == XBUTTON2) {
_input_devices[0].button_up(MouseButton::button(4), get_message_time());
}
}
break;
case WM_MOUSEWHEEL:
{
int delta = GET_WHEEL_DELTA_WPARAM(wparam);
POINT point;
GetCursorPos(&point);
ScreenToClient(hwnd, &point);
double time = get_message_time();
if (delta >= 0) {
while (delta > 0) {
handle_keypress(MouseButton::wheel_up(), point.x, point.y, time);
handle_keyrelease(MouseButton::wheel_up(), time);
delta -= WHEEL_DELTA;
}
} else {
while (delta < 0) {
handle_keypress(MouseButton::wheel_down(), point.x, point.y, time);
handle_keyrelease(MouseButton::wheel_down(), time);
delta += WHEEL_DELTA;
}
}
return 0;
}
break;
case WM_IME_SETCONTEXT:
if (!ime_hide)
break;
windisplay_cat.debug() << "hwnd = " << hwnd << " and GetFocus = " << GetFocus() << endl;
_ime_hWnd = ImmGetDefaultIMEWnd(hwnd);
if (::SendMessage(_ime_hWnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
//if (::SendMessage(hwnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
windisplay_cat.debug() << "SendMessage failed for " << _ime_hWnd << endl;
else
windisplay_cat.debug() << "SendMessage Succeeded for " << _ime_hWnd << endl;
windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
lparam &= ~ISC_SHOWUIALL;
if (ImmIsUIMessage(_ime_hWnd, msg, wparam, lparam))
windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
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.
}
if (ime_hide) {
//if (0) {
COMPOSITIONFORM comf;
CANDIDATEFORM canf;
ImmGetCompositionWindow(hIMC, &comf);
ImmGetCandidateWindow(hIMC, 0, &canf);
windisplay_cat.debug() <<
"comf style " << comf.dwStyle <<
" comf point: x" << comf.ptCurrentPos.x << ",y " << comf.ptCurrentPos.y <<
" comf rect: l " << comf.rcArea.left << ",t " << comf.rcArea.top << ",r " <<
comf.rcArea.right << ",b " << comf.rcArea.bottom << endl;
windisplay_cat.debug() <<
"canf style " << canf.dwStyle <<
" canf point: x" << canf.ptCurrentPos.x << ",y " << canf.ptCurrentPos.y <<
" canf rect: l " << canf.rcArea.left << ",t " << canf.rcArea.top << ",r " <<
canf.rcArea.right << ",b " << canf.rcArea.bottom << endl;
comf.dwStyle = CFS_POINT;
comf.ptCurrentPos.x = 2000;
comf.ptCurrentPos.y = 2000;
canf.dwStyle = CFS_EXCLUDE;
canf.dwIndex = 0;
canf.ptCurrentPos.x = 0;
canf.ptCurrentPos.y = 0;
canf.rcArea.left = 0;
canf.rcArea.top = 0;
canf.rcArea.right = 640;
canf.rcArea.bottom = 480;
#if 0
comf.rcArea.left = 200;
comf.rcArea.top = 200;
comf.rcArea.right = 0;
comf.rcArea.bottom = 0;
#endif
if (ImmSetCompositionWindow(hIMC, &comf))
windisplay_cat.debug() << "set composition form: success\n";
for (int i=0; i<3; ++i) {
if (ImmSetCandidateWindow(hIMC, &canf))
windisplay_cat.debug() << "set candidate form: success\n";
canf.dwIndex++;
}
}
ImmReleaseContext(hwnd, hIMC);
}
break;
case WM_IME_STARTCOMPOSITION:
support_overlay_window(true);
_ime_active = true;
break;
case WM_IME_ENDCOMPOSITION:
support_overlay_window(false);
_ime_active = false;
if (ime_aware) {
wstring ws;
_input_devices[0].candidate(ws, 0, 0, 0);
}
break;
case WM_IME_COMPOSITION:
if (ime_aware) {
// If the ime window is not marked as active at this point, we
// must be in the process of closing it down (in close_ime), and
// we don't want to send the current composition string in that
// case. But we do need to return 0 to tell windows not to try
// to send the composition string through WM_CHAR messages.
if (!_ime_active) {
return 0;
}
HIMC hIMC = ImmGetContext(hwnd);
nassertr(hIMC != 0, 0);
DWORD result_size = 0;
static const int ime_buffer_size = 256;
static const int ime_buffer_size_bytes = ime_buffer_size / sizeof(wchar_t);
wchar_t ime_buffer[ime_buffer_size];
size_t cursor_pos, delta_start;
if (lparam & GCS_RESULTSTR) {
result_size = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR,
ime_buffer, ime_buffer_size_bytes);
size_t num_chars = result_size / sizeof(wchar_t);
for (size_t i = 0; i < num_chars; ++i) {
_input_devices[0].keystroke(ime_buffer[i]);
}
}
if (lparam & GCS_COMPSTR) {
result_size = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, NULL, 0);
cursor_pos = result_size & 0xffff;
result_size = ImmGetCompositionStringW(hIMC, GCS_DELTASTART, NULL, 0);
delta_start = result_size & 0xffff;
result_size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, ime_buffer, ime_buffer_size);
size_t num_chars = result_size / sizeof(wchar_t);
_input_devices[0].candidate(wstring(ime_buffer, num_chars),
min(cursor_pos, delta_start),
max(cursor_pos, delta_start),
cursor_pos);
}
ImmReleaseContext(hwnd, hIMC);
}
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:
if (_lost_keypresses) {
resend_lost_keypresses();
}
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "keydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
}
{
// 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,
get_message_time());
// wparam does not contain left/right information for SHIFT,
// CONTROL, or ALT, so we have to track their status and do
// the right thing. We'll send the left/right specific key
// event along with the general key event.
//
// Key repeating is not being handled consistently for LALT
// and RALT, but from comments below, it's only being handled
// the way it is for backspace, so we'll leave it as is.
if (wparam == VK_MENU) {
if ((GetKeyState(VK_LMENU) & 0x8000) != 0 && ! _lalt_down) {
handle_keypress(KeyboardButton::lalt(), point.x, point.y,
get_message_time());
_lalt_down = true;
}
if ((GetKeyState(VK_RMENU) & 0x8000) != 0 && ! _ralt_down) {
handle_keypress(KeyboardButton::ralt(), point.x, point.y,
get_message_time());
_ralt_down = true;
}
}
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.
// Note that the WM_SYSKEYUP message for Alt has already
// been sent (if it is going to be), so ignoring this
// special message does no harm.
return 0;
}
break;
case WM_KEYDOWN:
if (_lost_keypresses) {
resend_lost_keypresses();
}
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "keydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
}
// If this bit is not zero, this is just a keyrepeat echo; we
// ignore these for handle_keypress (we respect keyrepeat only
// for handle_keystroke).
if ((lparam & 0x40000000) == 0) {
POINT point;
GetCursorPos(&point);
ScreenToClient(hwnd, &point);
handle_keypress(lookup_key(wparam), point.x, point.y,
get_message_time());
// wparam does not contain left/right information for SHIFT,
// CONTROL, or ALT, so we have to track their status and do
// the right thing. We'll send the left/right specific key
// event along with the general key event.
if (wparam == VK_SHIFT) {
if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0 && ! _lshift_down) {
handle_keypress(KeyboardButton::lshift(), point.x, point.y,
get_message_time());
_lshift_down = true;
}
if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0 && ! _rshift_down) {
handle_keypress(KeyboardButton::rshift(), point.x, point.y,
get_message_time());
_rshift_down = true;
}
} else if(wparam == VK_CONTROL) {
if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0 && ! _lcontrol_down) {
handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
get_message_time());
_lcontrol_down = true;
}
if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0 && ! _rcontrol_down) {
handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
get_message_time());
_rcontrol_down = true;
}
}
// 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();
}
}
} else {
// Actually, for now we'll respect the repeat anyway, just
// so we support backspace properly. Rethink later.
POINT point;
GetCursorPos(&point);
ScreenToClient(hwnd, &point);
handle_keypress(lookup_key(wparam), point.x, point.y,
get_message_time());
// wparam does not contain left/right information for SHIFT,
// CONTROL, or ALT, so we have to track their status and do
// the right thing. We'll send the left/right specific key
// event along with the general key event.
//
// If the user presses LSHIFT and then RSHIFT, the RSHIFT event
// will come in with the keyrepeat flag on (i.e. it will end up
// in this block). The logic below should detect this correctly
// and only send the RSHIFT event. Note that the CONTROL event
// will be sent twice, once for each keypress. Since keyrepeats
// are currently being sent simply as additional keypress events,
// that should be okay for now.
if (wparam == VK_SHIFT) {
if (((GetKeyState(VK_LSHIFT) & 0x8000) != 0) && ! _lshift_down ) {
handle_keypress(KeyboardButton::lshift(), point.x, point.y,
get_message_time());
_lshift_down = true;
} else if (((GetKeyState(VK_RSHIFT) & 0x8000) != 0) && ! _rshift_down ) {
handle_keypress(KeyboardButton::rshift(), point.x, point.y,
get_message_time());
_rshift_down = true;
} else {
if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0) {
handle_keypress(KeyboardButton::lshift(), point.x, point.y,
get_message_time());
}
if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0) {
handle_keypress(KeyboardButton::rshift(), point.x, point.y,
get_message_time());
}
}
} else if(wparam == VK_CONTROL) {
if (((GetKeyState(VK_LCONTROL) & 0x8000) != 0) && ! _lcontrol_down ) {
handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
get_message_time());
_lcontrol_down = true;
} else if (((GetKeyState(VK_RCONTROL) & 0x8000) != 0) && ! _rcontrol_down ) {
handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
get_message_time());
_rcontrol_down = true;
} else {
if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0) {
handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
get_message_time());
}
if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0) {
handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
get_message_time());
}
}
}
}
break;
case WM_SYSKEYUP:
case WM_KEYUP:
if (_lost_keypresses) {
resend_lost_keypresses();
}
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "keyup: " << wparam << " (" << lookup_key(wparam) << ")\n";
}
handle_keyrelease(lookup_key(wparam), get_message_time());
// wparam does not contain left/right information for SHIFT,
// CONTROL, or ALT, so we have to track their status and do
// the right thing. We'll send the left/right specific key
// event along with the general key event.
if (wparam == VK_SHIFT) {
if ((GetKeyState(VK_LSHIFT) & 0x8000) == 0 && _lshift_down) {
handle_keyrelease(KeyboardButton::lshift(), get_message_time());
_lshift_down = false;
}
if ((GetKeyState(VK_RSHIFT) & 0x8000) == 0 && _rshift_down) {
handle_keyrelease(KeyboardButton::rshift(), get_message_time());
_rshift_down = false;
}
} else if(wparam == VK_CONTROL) {
if ((GetKeyState(VK_LCONTROL) & 0x8000) == 0 && _lcontrol_down) {
handle_keyrelease(KeyboardButton::lcontrol(), get_message_time());
_lcontrol_down = false;
}
if ((GetKeyState(VK_RCONTROL) & 0x8000) == 0 && _rcontrol_down) {
handle_keyrelease(KeyboardButton::rcontrol(), get_message_time());
_rcontrol_down = false;
}
} else if(wparam == VK_MENU) {
if ((GetKeyState(VK_LMENU) & 0x8000) == 0 && _lalt_down) {
handle_keyrelease(KeyboardButton::lalt(), get_message_time());
_lalt_down = false;
}
if ((GetKeyState(VK_RMENU) & 0x8000) == 0 && _ralt_down) {
handle_keyrelease(KeyboardButton::ralt(), get_message_time());
_ralt_down = false;
}
}
break;
case WM_KILLFOCUS:
if (windisplay_cat.is_debug())
{
windisplay_cat.debug()
<< "killfocus\n";
}
#ifndef WANT_NEW_FOCUS_MANAGMENT
if (!_lost_keypresses)
{
// 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);
if (windisplay_cat.is_debug()) {
// Report the set of keys that are held down at the time of
// the killfocus event.
for (int i = 0; i < num_virtual_keys; i++)
{
if (i != VK_SHIFT && i != VK_CONTROL && i != VK_MENU)
{
if ((_keyboard_state[i] & 0x80) != 0)
{
windisplay_cat.debug()
<< "on killfocus, key is down: " << i
<< " (" << lookup_key(i) << ")\n";
}
}
}
}
if (!hold_keys_across_windows)
{
// If we don't want to remember the keystate while the
// window focus is lost, then generate a keyup event
// right now for each key currently held.
double message_time = get_message_time();
for (int i = 0; i < num_virtual_keys; i++)
{
if (i != VK_SHIFT && i != VK_CONTROL && i != VK_MENU)
{
if ((_keyboard_state[i] & 0x80) != 0)
{
handle_keyrelease(lookup_key(i), message_time);
_keyboard_state[i] &= ~0x80;
}
}
}
}
// Now set the flag indicating that some keypresses from now
// on may be lost.
_lost_keypresses = true;
}
#else // WANT_NEW_FOCUS_MANAGMENT
{
double message_time = get_message_time();
int i;
for (i = 0; i < num_virtual_keys; i++) {
ButtonHandle bh = lookup_key(i);
if(bh != ButtonHandle::none()) {
handle_keyrelease(bh,message_time);
}
}
memset(_keyboard_state, 0, sizeof(BYTE) * num_virtual_keys);
// Also up the mouse buttons.
for (i = 0; i < MouseButton::num_mouse_buttons; ++i) {
handle_keyrelease(MouseButton::button(i), message_time);
}
handle_keyrelease(MouseButton::wheel_up(), message_time);
handle_keyrelease(MouseButton::wheel_down(), message_time);
_lost_keypresses = true;
}
#endif // WANT_NEW_FOCUS_MANAGMENT
properties.set_foreground(false);
system_changed_properties(properties);
break;
case WM_SETFOCUS:
// You would think that this would be a good time to call
// resend_lost_keypresses(), but it turns out that we get
// WM_SETFOCUS slightly before Windows starts resending key
// up/down events to us.
// In particular, if the user restored focus using alt-tab,
// then at this point the keyboard state will indicate that
// both the alt and tab keys are held down. However, there is
// a small window of opportunity for the user to release these
// keys before Windows starts telling us about keyup events.
// Thus, if we record the fact that alt and tab are being held
// down now, we may miss the keyup events for them, and they
// can get "stuck" down.
// So we have to defer calling resend_lost_keypresses() until
// we know Windows is ready to send us key up/down events. I
// don't know when we can guarantee that, except when we
// actually do start to receive key up/down events, so that
// call is made there.
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "setfocus\n";
}
if (_lost_keypresses) {
resend_lost_keypresses();
}
properties.set_foreground(true);
system_changed_properties(properties);
break;
case PM_ACTIVE:
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "PM_ACTIVE\n";
}
properties.set_foreground(true);
system_changed_properties(properties);
break;
case PM_INACTIVE:
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "PM_INACTIVE\n";
}
properties.set_foreground(false);
system_changed_properties(properties);
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::resend_lost_keypresses
// Access: Private, Static
// Description: Called when the keyboard focus has been restored to
// the window after it has been lost for a time, this
// rechecks the keyboard state and generates key up/down
// messages for keys that have changed state in the
// meantime.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
resend_lost_keypresses() {
_lost_keypresses = false;
return;
nassertv(_lost_keypresses);
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "resending lost keypresses\n";
}
BYTE new_keyboard_state[num_virtual_keys];
GetKeyboardState(new_keyboard_state);
double message_time = get_message_time();
#ifndef WANT_NEW_FOCUS_MANAGMENT
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.
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "key has gone down: " << i << " (" << lookup_key(i) << ")\n";
}
// Roger
//handle_keyresume(lookup_key(i), message_time);
// resume does not seem to work and sending the pointer position seems to
// weird ot some cursor controls
ButtonHandle key = lookup_key(i);
if (key != ButtonHandle::none())
_input_devices[0].button_down(key, message_time);
} else {
// The key is now released.
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "key has gone up: " << i << " (" << lookup_key(i) << ")\n";
}
handle_keyrelease(lookup_key(i), message_time);
}
} else {
// This key is in the same state.
if (windisplay_cat.is_debug()) {
if ((new_keyboard_state[i] & 0x80) != 0) {
windisplay_cat.debug()
<< "key is still down: " << i << " (" << lookup_key(i) << ")\n";
}
}
}
}
}
#else // WANT_NEW_FOCUS_MANAGMENT
for (int i = 0; i < num_virtual_keys; i++)
{
if ((new_keyboard_state[i] & 0x80) != 0)
{
ButtonHandle key = lookup_key(i);
if (key != ButtonHandle::none())
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "resending key: " << " (" << key << ")\n";
}
_input_devices[0].button_down(key, message_time);
}
}
#endif // WANT_NEW_FOCUS_MANAGMENT
// Keypresses are no longer lost.
_lost_keypresses = false;
}
////////////////////////////////////////////////////////////////////
// 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);
}
SetCursor(to_window->_cursor);
}
hide_or_show_cursor(hide_cursor);
_cursor_window = to_window;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::hide_or_show_cursor
// Access: Private, Static
// Description: Hides or shows the mouse cursor according to the
// indicated parameter. This is normally called when
// the mouse wanders into or out of a window with the
// cursor_hidden properties.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
hide_or_show_cursor(bool hide_cursor) {
if (hide_cursor) {
if (!_cursor_hidden) {
ShowCursor(false);
_cursor_hidden = true;
}
} else {
if (_cursor_hidden) {
ShowCursor(true);
_cursor_hidden = false;
}
}
}
// 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)) {
cout << "[FS FOUND] " << dwWidth << "x" << dwHeight << "@" << bpp << endl;
// We want to modify the current DEVMODE rather than using a fresh one in order
// to work around a Windows 7 bug.
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
if (0 != EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm)){
dm.dmPelsWidth = dwWidth;
dm.dmPelsHeight = dwHeight;
dm.dmBitsPerPel = bpp;
return true;
} else {
windisplay_cat.error()
<< "Couldn't retrieve active device mode.\n";
return false;
}
}
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_PAUSE: return KeyboardButton::pause();
case VK_SNAPSHOT: return KeyboardButton::print_screen();
case VK_SHIFT: return KeyboardButton::shift();
case VK_LSHIFT: return KeyboardButton::lshift();
case VK_RSHIFT: return KeyboardButton::rshift();
case VK_CONTROL: return KeyboardButton::control();
case VK_LCONTROL: return KeyboardButton::lcontrol();
case VK_RCONTROL: return KeyboardButton::rcontrol();
case VK_MENU: return KeyboardButton::alt();
case VK_LMENU: return KeyboardButton::lalt();
case VK_RMENU: return KeyboardButton::ralt();
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();
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::handle_raw_input
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
handle_raw_input(HRAWINPUT hraw) {
LPBYTE lpb;
UINT dwSize;
if (hraw == 0) {
return;
}
if (pGetRawInputData(hraw, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)) == -1) {
return;
}
lpb = (LPBYTE)alloca(sizeof(LPBYTE) * dwSize);
if (lpb == NULL) {
return;
}
if (pGetRawInputData(hraw, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) {
return;
}
RAWINPUT *raw = (RAWINPUT *)lpb;
if (raw->header.hDevice == 0) {
return;
}
for (int i = 1; i < (int)(_input_devices.size()); ++i) {
if (_input_device_handle[i] == raw->header.hDevice) {
int adjx = raw->data.mouse.lLastX;
int adjy = raw->data.mouse.lLastY;
if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) {
_input_devices[i].set_pointer_in_window(adjx, adjy);
} else {
int oldx = _input_devices[i].get_raw_pointer().get_x();
int oldy = _input_devices[i].get_raw_pointer().get_y();
_input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy);
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) {
_input_devices[i].button_down(MouseButton::button(0), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) {
_input_devices[i].button_up(MouseButton::button(0), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) {
_input_devices[i].button_down(MouseButton::button(2), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) {
_input_devices[i].button_up(MouseButton::button(2), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) {
_input_devices[i].button_down(MouseButton::button(1), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) {
_input_devices[i].button_up(MouseButton::button(1), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) {
_input_devices[i].button_down(MouseButton::button(3), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) {
_input_devices[i].button_up(MouseButton::button(3), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) {
_input_devices[i].button_down(MouseButton::button(4), get_message_time());
}
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) {
_input_devices[i].button_up(MouseButton::button(4), get_message_time());
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::handle_mouse_motion
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
bool WinGraphicsWindow::
handle_mouse_motion(int x, int y) {
_input_devices[0].set_pointer_in_window(x, y);
return false;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::handle_mouse_exit
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::
handle_mouse_exit() {
// note: 'mouse_motion' is considered the 'entry' event
_input_devices[0].set_pointer_out_of_window();
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::get_icon
// Access: Private, Static
// Description: Loads and returns an HICON corresponding to the
// indicated filename. If the file cannot be loaded,
// returns 0.
////////////////////////////////////////////////////////////////////
HICON WinGraphicsWindow::
get_icon(const Filename &filename) {
// First, look for the unresolved filename in our index.
IconFilenames::iterator fi = _icon_filenames.find(filename);
if (fi != _icon_filenames.end()) {
return (HICON)((*fi).second);
}
// If it wasn't found, resolve the filename and search for that.
// Since we have to use a Windows call to load the image from a
// filename, we can't load a virtual file and we can't use the
// virtual file system.
Filename resolved = filename;
if (!resolved.resolve_filename(get_model_path())) {
// The filename doesn't exist along the search path.
if (resolved.is_fully_qualified() && resolved.exists()) {
// But it does exist locally, so accept it.
} else {
windisplay_cat.warning()
<< "Could not find icon filename " << filename << "\n";
return 0;
}
}
fi = _icon_filenames.find(resolved);
if (fi != _icon_filenames.end()) {
_icon_filenames[filename] = (*fi).second;
return (HICON)((*fi).second);
}
Filename os = resolved.to_os_specific();
HANDLE h = LoadImage(NULL, os.c_str(),
IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
if (h == 0) {
windisplay_cat.warning()
<< "windows icon filename '" << os << "' could not be loaded!!\n";
}
_icon_filenames[filename] = h;
_icon_filenames[resolved] = h;
return (HICON)h;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::get_cursor
// Access: Private, Static
// Description: Loads and returns an HCURSOR corresponding to the
// indicated filename. If the file cannot be loaded,
// returns 0.
////////////////////////////////////////////////////////////////////
HCURSOR WinGraphicsWindow::
get_cursor(const Filename &filename) {
// First, look for the unresolved filename in our index.
IconFilenames::iterator fi = _cursor_filenames.find(filename);
if (fi != _cursor_filenames.end()) {
return (HCURSOR)((*fi).second);
}
// If it wasn't found, resolve the filename and search for that.
// Since we have to use a Windows call to load the image from a
// filename, we can't load a virtual file and we can't use the
// virtual file system.
Filename resolved = filename;
if (!resolved.resolve_filename(get_model_path())) {
// The filename doesn't exist.
windisplay_cat.warning()
<< "Could not find cursor filename " << filename << "\n";
return 0;
}
fi = _cursor_filenames.find(resolved);
if (fi != _cursor_filenames.end()) {
_cursor_filenames[filename] = (*fi).second;
return (HCURSOR)((*fi).second);
}
Filename os = resolved.to_os_specific();
HANDLE h = LoadImage(NULL, os.c_str(),
IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
if (h == 0) {
windisplay_cat.warning()
<< "windows cursor filename '" << os << "' could not be loaded!!\n";
show_error_message();
}
_cursor_filenames[filename] = h;
_cursor_filenames[resolved] = h;
return (HCURSOR)h;
}
static HCURSOR get_cursor(const Filename &filename);
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::register_window_class
// Access: Private, Static
// Description: Registers a Window class appropriate for the
// indicated properties. This class may be shared by
// multiple windows.
////////////////////////////////////////////////////////////////////
const WinGraphicsWindow::WindowClass &WinGraphicsWindow::
register_window_class(const WindowProperties &props) {
pair<WindowClasses::iterator, bool> found =
_window_classes.insert(WindowClass(props));
WindowClass &wclass = (*found.first);
if (!found.second) {
// We have already created a window class.
return wclass;
}
// We have not yet created this window class.
ostringstream wclass_name;
wclass_name << "WinGraphicsWindow" << _window_class_index;
_window_class_index++;
wclass._name = wclass_name.str();
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;
wc.hIcon = wclass._icon;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = wclass._name.c_str();
if (!RegisterClass(&wc)) {
windisplay_cat.error()
<< "could not register window class " << wclass._name << "!" << endl;
return wclass;
}
return wclass;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::WinWindowHandle::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
WinGraphicsWindow::WinWindowHandle::
WinWindowHandle(WinGraphicsWindow *window, const WindowHandle &copy) :
WindowHandle(copy),
_window(window)
{
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::WinWindowHandle::clear_window
// Access: Public
// Description: Should be called by the WinGraphicsWindow's
// destructor, so that we don't end up with a floating
// pointer should this object persist beyond the
// lifespan of its window.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::WinWindowHandle::
clear_window() {
_window = NULL;
}
////////////////////////////////////////////////////////////////////
// Function: WinGraphicsWindow::WinWindowHandle::receive_windows_message
// Access: Public, Virtual
// Description: Called on a child handle to deliver a keyboard button
// event generated in the parent window.
////////////////////////////////////////////////////////////////////
void WinGraphicsWindow::WinWindowHandle::
receive_windows_message(unsigned int msg, int wparam, int lparam) {
if (_window != NULL) {
_window->receive_windows_message(msg, wparam, lparam);
}
}
// pops up MsgBox w/system error msg
void PrintErrorMessage(DWORD msgID) {
LPTSTR pMessageBuffer;
if (msgID==PRINT_LAST_ERROR)
msgID=GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,msgID,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
(LPTSTR) &pMessageBuffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
1024, NULL);
MessageBox(GetDesktopWindow(),pMessageBuffer,_T(errorbox_title),MB_OK);
windisplay_cat.fatal() << "System error msg: " << pMessageBuffer << endl;
LocalFree( pMessageBuffer );
}
void
ClearToBlack(HWND hWnd, const WindowProperties &props) {
if (!props.has_origin()) {
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "Skipping ClearToBlack, no origin specified yet.\n";
}
return;
}
if (windisplay_cat.is_debug()) {
windisplay_cat.debug()
<< "ClearToBlack(" << hWnd << ", " << props << ")\n";
}
// clear to black
HDC hDC=GetDC(hWnd); // GetDC is not particularly fast. if this needs to be super-quick, we should cache GetDC's hDC
RECT clrRect = {
props.get_x_origin(), props.get_y_origin(),
props.get_x_origin() + props.get_x_size(),
props.get_y_origin() + props.get_y_size()
};
FillRect(hDC,&clrRect,(HBRUSH)GetStockObject(BLACK_BRUSH));
ReleaseDC(hWnd,hDC);
GdiFlush();
}
////////////////////////////////////////////////////////////////////
// Function: get_client_rect_screen
// Description: Fills view_rect with the coordinates of the client
// area of the indicated window, converted to screen
// coordinates.
////////////////////////////////////////////////////////////////////
void 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;
}