3 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version. The Blender
9 * Foundation also sells licenses for use in proprietary software under
10 * the Blender License. See http://www.blender.org/BL/ for information
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
23 * All rights reserved.
25 * The Original Code is: all of this file.
27 * Contributor(s): none yet.
29 * ***** END GPL/BL DUAL LICENSE BLOCK *****
35 * Copyright (C) 2001 NaN Technologies B.V.
36 * @author Maarten Gribnau
45 #include "GHOST_WindowWin32.h"
48 LPCSTR GHOST_WindowWin32::s_windowClassName = "GHOST_WindowClass";
49 const int GHOST_WindowWin32::s_maxTitleLength = 128;
50 HGLRC GHOST_WindowWin32::s_firsthGLRc = NULL;
52 static int WeightPixelFormat(PIXELFORMATDESCRIPTOR& pfd);
53 static int EnumPixelFormats(HDC hdc);
56 * Color and depth bit values are not to be trusted.
57 * For instance, on TNT2:
58 * When the screen color depth is set to 16 bit, we get 5 color bits
60 * When the screen color depth is set to 32 bit, we get 8 color bits
62 * Just to be safe, we request high waulity settings.
64 static PIXELFORMATDESCRIPTOR sPreferredFormat = {
65 sizeof(PIXELFORMATDESCRIPTOR), /* size */
69 PFD_SWAP_COPY | /* support swap copy */
70 PFD_DOUBLEBUFFER, /* support double-buffering */
71 PFD_TYPE_RGBA, /* color type */
72 32, /* prefered color depth */
73 0, 0, 0, 0, 0, 0, /* color bits (ignored) */
74 0, /* no alpha buffer */
75 0, /* alpha bits (ignored) */
76 0, /* no accumulation buffer */
77 0, 0, 0, 0, /* accum bits (ignored) */
78 32, /* depth buffer */
79 0, /* no stencil buffer */
80 0, /* no auxiliary buffers */
81 PFD_MAIN_PLANE, /* main layer */
83 0, 0, 0 /* no layer, visible, damage masks */
86 GHOST_WindowWin32::GHOST_WindowWin32(
87 const STR_String& title,
92 GHOST_TWindowState state,
93 GHOST_TDrawingContextType type,
94 const bool stereoVisual)
96 GHOST_Window(title, left, top, width, height, state, GHOST_kDrawingContextTypeNone,
100 m_hasMouseCaptured(false),
101 m_nPressedButtons(0),
104 if (state != GHOST_kWindowStateFullScreen) {
105 /* Convert client size into window size */
106 width += GetSystemMetrics(SM_CXSIZEFRAME)*2;
107 height += GetSystemMetrics(SM_CYSIZEFRAME)*2 + GetSystemMetrics(SM_CYCAPTION);
109 m_hWnd = ::CreateWindow(
110 s_windowClassName, // pointer to registered class name
111 title, // pointer to window name
112 WS_OVERLAPPEDWINDOW, // window style
113 left, // horizontal position of window
114 top, // vertical position of window
115 width, // window width
116 height, // window height
117 0, // handle to parent or owner window
118 0, // handle to menu or child-window identifier
119 ::GetModuleHandle(0), // handle to application instance
120 0); // pointer to window-creation data
123 m_hWnd = ::CreateWindow(
124 s_windowClassName, // pointer to registered class name
125 title, // pointer to window name
126 WS_POPUP | WS_MAXIMIZE, // window style
127 left, // horizontal position of window
128 top, // vertical position of window
129 width, // window width
130 height, // window height
131 0, // handle to parent or owner window
132 0, // handle to menu or child-window identifier
133 ::GetModuleHandle(0), // handle to application instance
134 0); // pointer to window-creation data
137 // Store a pointer to this class in the window structure
138 LONG result = ::SetWindowLong(m_hWnd, GWL_USERDATA, (LONG)this);
140 // Store the device context
141 m_hDC = ::GetDC(m_hWnd);
146 case GHOST_kWindowStateMaximized:
147 nCmdShow = SW_SHOWMAXIMIZED;
149 case GHOST_kWindowStateMinimized:
150 nCmdShow = SW_SHOWMINIMIZED;
152 case GHOST_kWindowStateNormal:
154 nCmdShow = SW_SHOWNORMAL;
157 setDrawingContextType(type);
158 ::ShowWindow(m_hWnd, nCmdShow);
159 // Force an initial paint of the window
160 ::UpdateWindow(m_hWnd);
165 GHOST_WindowWin32::~GHOST_WindowWin32()
167 if (m_customCursor) {
168 DestroyCursor(m_customCursor);
169 m_customCursor = NULL;
172 setDrawingContextType(GHOST_kDrawingContextTypeNone);
174 ::ReleaseDC(m_hWnd, m_hDC);
178 ::DestroyWindow(m_hWnd);
183 bool GHOST_WindowWin32::getValid() const
189 void GHOST_WindowWin32::setTitle(const STR_String& title)
191 ::SetWindowText(m_hWnd, title);
195 void GHOST_WindowWin32::getTitle(STR_String& title) const
197 char buf[s_maxTitleLength];
198 ::GetWindowText(m_hWnd, buf, s_maxTitleLength);
199 STR_String temp (buf);
204 void GHOST_WindowWin32::getWindowBounds(GHOST_Rect& bounds) const
207 ::GetWindowRect(m_hWnd, &rect);
208 bounds.m_b = rect.bottom;
209 bounds.m_l = rect.left;
210 bounds.m_r = rect.right;
211 bounds.m_t = rect.top;
215 void GHOST_WindowWin32::getClientBounds(GHOST_Rect& bounds) const
218 ::GetClientRect(m_hWnd, &rect);
219 bounds.m_b = rect.bottom;
220 bounds.m_l = rect.left;
221 bounds.m_r = rect.right;
222 bounds.m_t = rect.top;
226 GHOST_TSuccess GHOST_WindowWin32::setClientWidth(GHOST_TUns32 width)
228 GHOST_TSuccess success;
229 GHOST_Rect cBnds, wBnds;
230 getClientBounds(cBnds);
231 if (cBnds.getWidth() != width) {
232 getWindowBounds(wBnds);
233 int cx = wBnds.getWidth() + width - cBnds.getWidth();
234 int cy = wBnds.getHeight();
235 success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ?
236 GHOST_kSuccess : GHOST_kFailure;
239 success = GHOST_kSuccess;
245 GHOST_TSuccess GHOST_WindowWin32::setClientHeight(GHOST_TUns32 height)
247 GHOST_TSuccess success;
248 GHOST_Rect cBnds, wBnds;
249 getClientBounds(cBnds);
250 if (cBnds.getHeight() != height) {
251 getWindowBounds(wBnds);
252 int cx = wBnds.getWidth();
253 int cy = wBnds.getHeight() + height - cBnds.getHeight();
254 success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ?
255 GHOST_kSuccess : GHOST_kFailure;
258 success = GHOST_kSuccess;
264 GHOST_TSuccess GHOST_WindowWin32::setClientSize(GHOST_TUns32 width, GHOST_TUns32 height)
266 GHOST_TSuccess success;
267 GHOST_Rect cBnds, wBnds;
268 getClientBounds(cBnds);
269 if ((cBnds.getWidth() != width) || (cBnds.getHeight() != height)) {
270 getWindowBounds(wBnds);
271 int cx = wBnds.getWidth() + width - cBnds.getWidth();
272 int cy = wBnds.getHeight() + height - cBnds.getHeight();
273 success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ?
274 GHOST_kSuccess : GHOST_kFailure;
277 success = GHOST_kSuccess;
283 GHOST_TWindowState GHOST_WindowWin32::getState() const
285 GHOST_TWindowState state;
286 if (::IsIconic(m_hWnd)) {
287 state = GHOST_kWindowStateMinimized;
289 else if (::IsZoomed(m_hWnd)) {
290 state = GHOST_kWindowStateMaximized;
293 state = GHOST_kWindowStateNormal;
299 void GHOST_WindowWin32::screenToClient(GHOST_TInt32 inX, GHOST_TInt32 inY, GHOST_TInt32& outX, GHOST_TInt32& outY) const
301 POINT point = { inX, inY };
302 ::ScreenToClient(m_hWnd, &point);
308 void GHOST_WindowWin32::clientToScreen(GHOST_TInt32 inX, GHOST_TInt32 inY, GHOST_TInt32& outX, GHOST_TInt32& outY) const
310 POINT point = { inX, inY };
311 ::ClientToScreen(m_hWnd, &point);
317 GHOST_TSuccess GHOST_WindowWin32::setState(GHOST_TWindowState state)
320 wp.length = sizeof(WINDOWPLACEMENT);
321 ::GetWindowPlacement(m_hWnd, &wp);
323 case GHOST_kWindowStateMinimized:
324 wp.showCmd = SW_SHOWMINIMIZED;
326 case GHOST_kWindowStateMaximized:
327 ShowWindow(m_hWnd, SW_HIDE); //fe. HACK!
328 //Solves redraw problems when switching from fullscreen to normal.
330 wp.showCmd = SW_SHOWMAXIMIZED;
331 SetWindowLong(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
333 case GHOST_kWindowStateFullScreen:
334 wp.showCmd = SW_SHOWMAXIMIZED;
335 SetWindowLong(m_hWnd, GWL_STYLE, WS_POPUP | WS_MAXIMIZE);
337 case GHOST_kWindowStateNormal:
339 wp.showCmd = SW_SHOWNORMAL;
342 return ::SetWindowPlacement(m_hWnd, &wp) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
346 GHOST_TSuccess GHOST_WindowWin32::setOrder(GHOST_TWindowOrder order)
348 HWND hWndInsertAfter = order == GHOST_kWindowOrderTop ? HWND_TOP : HWND_BOTTOM;
349 return ::SetWindowPos(m_hWnd, hWndInsertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
353 GHOST_TSuccess GHOST_WindowWin32::swapBuffers()
355 // adding a glFinish() here is to prevent Geforce in 'full scene antialias' mode
356 // from antialising the Blender window. Officially a swapbuffers does a glFinish
357 // itself, so this feels really like a hack... but it won't harm. (ton)
359 return ::SwapBuffers(m_hDC) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
363 GHOST_TSuccess GHOST_WindowWin32::activateDrawingContext()
365 GHOST_TSuccess success;
366 if (m_drawingContextType == GHOST_kDrawingContextTypeOpenGL) {
367 if (m_hDC && m_hGlRc) {
368 success = ::wglMakeCurrent(m_hDC, m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
371 success = GHOST_kFailure;
375 success = GHOST_kSuccess;
381 GHOST_TSuccess GHOST_WindowWin32::invalidate()
383 GHOST_TSuccess success;
385 success = ::InvalidateRect(m_hWnd, 0, FALSE) != 0 ? GHOST_kSuccess : GHOST_kFailure;
388 success = GHOST_kFailure;
394 GHOST_TSuccess GHOST_WindowWin32::installDrawingContext(GHOST_TDrawingContextType type)
396 GHOST_TSuccess success;
398 case GHOST_kDrawingContextTypeOpenGL:
401 sPreferredFormat.dwFlags |= PFD_STEREO;
403 // Attempt to match device context pixel format to the preferred format
404 int iPixelFormat = EnumPixelFormats(m_hDC);
405 if (iPixelFormat == 0) {
406 success = GHOST_kFailure;
409 if (::SetPixelFormat(m_hDC, iPixelFormat, &sPreferredFormat) == FALSE) {
410 success = GHOST_kFailure;
413 // For debugging only: retrieve the pixel format chosen
414 PIXELFORMATDESCRIPTOR preferredFormat;
415 ::DescribePixelFormat(m_hDC, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &preferredFormat);
416 // Create the context
417 m_hGlRc = ::wglCreateContext(m_hDC);
420 wglShareLists(s_firsthGLRc, m_hGlRc);
422 s_firsthGLRc = m_hGlRc;
425 success = ::wglMakeCurrent(m_hDC, m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
428 success = GHOST_kFailure;
433 case GHOST_kDrawingContextTypeNone:
434 success = GHOST_kSuccess;
438 success = GHOST_kFailure;
444 GHOST_TSuccess GHOST_WindowWin32::removeDrawingContext()
446 GHOST_TSuccess success;
447 switch (m_drawingContextType) {
448 case GHOST_kDrawingContextTypeOpenGL:
450 success = ::wglDeleteContext(m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
451 if (m_hGlRc == s_firsthGLRc) {
457 success = GHOST_kFailure;
460 case GHOST_kDrawingContextTypeNone:
461 success = GHOST_kSuccess;
464 success = GHOST_kFailure;
469 void GHOST_WindowWin32::lostMouseCapture()
471 if (m_hasMouseCaptured) {
472 m_hasMouseCaptured = false;
473 m_nPressedButtons = 0;
477 void GHOST_WindowWin32::registerMouseClickEvent(bool press)
480 if (!m_hasMouseCaptured) {
481 ::SetCapture(m_hWnd);
482 m_hasMouseCaptured = true;
486 if (m_nPressedButtons) {
488 if (!m_nPressedButtons) {
490 m_hasMouseCaptured = false;
497 void GHOST_WindowWin32::loadCursor(bool visible, GHOST_TStandardCursor cursor) const
500 while (::ShowCursor(FALSE) >= 0);
503 while (::ShowCursor(TRUE) < 0);
506 if (cursor == GHOST_kStandardCursorCustom && m_customCursor) {
507 ::SetCursor( m_customCursor );
509 // Convert GHOST cursor to Windows OEM cursor
513 case GHOST_kStandardCursorDefault: id = IDC_ARROW; break;
514 case GHOST_kStandardCursorRightArrow: id = IDC_ARROW; break;
515 case GHOST_kStandardCursorLeftArrow: id = IDC_ARROW; break;
516 case GHOST_kStandardCursorInfo: id = IDC_SIZEALL; break; // Four-pointed arrow pointing north, south, east, and west
517 case GHOST_kStandardCursorDestroy: id = IDC_NO; break; // Slashed circle
518 case GHOST_kStandardCursorHelp: id = IDC_HELP; break; // Arrow and question mark
519 case GHOST_kStandardCursorCycle: id = IDC_NO; break; // Slashed circle
520 case GHOST_kStandardCursorSpray: id = IDC_SIZEALL; break; // Four-pointed arrow pointing north, south, east, and west
521 case GHOST_kStandardCursorWait: id = IDC_WAIT; break; // Hourglass
522 case GHOST_kStandardCursorText: id = IDC_IBEAM; break; // I-beam
523 case GHOST_kStandardCursorCrosshair: id = IDC_CROSS; break; // Crosshair
524 case GHOST_kStandardCursorUpDown: id = IDC_SIZENS; break; // Double-pointed arrow pointing north and south
525 case GHOST_kStandardCursorLeftRight: id = IDC_SIZEWE; break; // Double-pointed arrow pointing west and east
526 case GHOST_kStandardCursorTopSide: id = IDC_UPARROW; break; // Vertical arrow
527 case GHOST_kStandardCursorBottomSide: id = IDC_SIZENS; break;
528 case GHOST_kStandardCursorLeftSide: id = IDC_SIZEWE; break;
529 case GHOST_kStandardCursorTopLeftCorner: id = IDC_SIZENWSE; break;
530 case GHOST_kStandardCursorTopRightCorner: id = IDC_SIZENESW; break;
531 case GHOST_kStandardCursorBottomRightCorner: id = IDC_SIZENWSE; break;
532 case GHOST_kStandardCursorBottomLeftCorner: id = IDC_SIZENESW; break;
533 case GHOST_kStandardCursorPencil: id = IDC_ARROW; break;
539 HCURSOR hCursor = ::SetCursor(::LoadCursor(0, id));
544 GHOST_TSuccess GHOST_WindowWin32::setWindowCursorVisibility(bool visible)
546 if (::GetForegroundWindow() == m_hWnd) {
547 loadCursor(visible, getCursorShape());
550 return GHOST_kSuccess;
553 GHOST_TSuccess GHOST_WindowWin32::setWindowCursorShape(GHOST_TStandardCursor cursorShape)
555 if (m_customCursor) {
556 DestroyCursor(m_customCursor);
557 m_customCursor = NULL;
560 if (::GetForegroundWindow() == m_hWnd) {
561 loadCursor(getCursorVisibility(), cursorShape);
564 return GHOST_kSuccess;
567 /** Reverse the bits in a GHOST_TUns8 */
568 static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch)
570 ch= ((ch>>1)&0x55) | ((ch<<1)&0xAA);
571 ch= ((ch>>2)&0x33) | ((ch<<2)&0xCC);
572 ch= ((ch>>4)&0x0F) | ((ch<<4)&0xF0);
576 /** Reverse the bits in a GHOST_TUns16 */
577 static GHOST_TUns16 uns16ReverseBits(GHOST_TUns16 shrt)
579 shrt= ((shrt>>1)&0x5555) | ((shrt<<1)&0xAAAA);
580 shrt= ((shrt>>2)&0x3333) | ((shrt<<2)&0xCCCC);
581 shrt= ((shrt>>4)&0x0F0F) | ((shrt<<4)&0xF0F0);
582 shrt= ((shrt>>8)&0x00FF) | ((shrt<<8)&0xFF00);
585 GHOST_TSuccess GHOST_WindowWin32::setWindowCustomCursorShape(GHOST_TUns8 bitmap[16][2],
586 GHOST_TUns8 mask[16][2], int hotX, int hotY)
588 return setWindowCustomCursorShape((GHOST_TUns8*)bitmap, (GHOST_TUns8*)mask,
589 16, 16, hotX, hotY, 0, 1);
592 GHOST_TSuccess GHOST_WindowWin32::setWindowCustomCursorShape(GHOST_TUns8 *bitmap,
593 GHOST_TUns8 *mask, int sizeX, int sizeY, int hotX, int hotY,
594 int fg_color, int bg_color)
596 GHOST_TUns32 andData[32];
597 GHOST_TUns32 xorData[32];
598 GHOST_TUns32 fullBitRow, fullMaskRow;
601 cols=sizeX/8; /* Num of whole bytes per row (width of bm/mask) */
604 if (m_customCursor) {
605 DestroyCursor(m_customCursor);
606 m_customCursor = NULL;
609 memset(&andData, 0xFF, sizeof(andData));
610 memset(&xorData, 0, sizeof(xorData));
612 for (y=0; y<sizeY; y++) {
615 for (x=cols-1; x>=0; x--){
618 fullBitRow |= uns8ReverseBits(bitmap[cols*y + x]);
619 fullMaskRow |= uns8ReverseBits( mask[cols*y + x]);
621 xorData[y]= fullBitRow & fullMaskRow;
622 andData[y]= ~fullMaskRow;
625 m_customCursor = ::CreateCursor(::GetModuleHandle(0), hotX, hotY, 32, 32, andData, xorData);
626 if (!m_customCursor) {
627 return GHOST_kFailure;
630 if (::GetForegroundWindow() == m_hWnd) {
631 loadCursor(getCursorVisibility(), GHOST_kStandardCursorCustom);
634 return GHOST_kSuccess;
638 /* Ron Fosner's code for weighting pixel formats and forcing software.
639 See http://www.opengl.org/resources/faq/technical/weight.cpp */
641 static int WeightPixelFormat(PIXELFORMATDESCRIPTOR& pfd) {
644 /* assume desktop color depth is 32 bits per pixel */
646 /* cull unusable pixel formats */
647 /* if no formats can be found, can we determine why it was rejected? */
648 if( !(pfd.dwFlags & PFD_SUPPORT_OPENGL) ||
649 !(pfd.dwFlags & PFD_DRAW_TO_WINDOW) ||
650 !(pfd.dwFlags & PFD_DOUBLEBUFFER) || /* Blender _needs_ this */
651 ( pfd.cDepthBits <= 8 ) ||
652 !(pfd.iPixelType == PFD_TYPE_RGBA) )
655 weight = 1; /* it's usable */
657 /* the bigger the depth buffer the better */
658 /* give no weight to a 16-bit depth buffer, because those are crap */
659 weight += pfd.cDepthBits - 16;
661 weight += pfd.cColorBits - 8;
663 /* want swap copy capability -- it matters a lot */
664 if(pfd.dwFlags & PFD_SWAP_COPY) weight += 16;
666 /* but if it's a generic (not accelerated) view, it's really bad */
667 if(pfd.dwFlags & PFD_GENERIC_FORMAT) weight /= 10;
672 /* A modification of Ron Fosner's replacement for ChoosePixelFormat */
673 /* returns 0 on error, else returns the pixel format number to be used */
674 static int EnumPixelFormats(HDC hdc) {
676 int i, n, w, weight = 0;
677 PIXELFORMATDESCRIPTOR pfd, pfd_fallback;
679 /* we need a device context to do anything */
682 iPixelFormat = 1; /* careful! PFD numbers are 1 based, not zero based */
684 /* obtain detailed information about
685 the device context's first pixel format */
686 n = 1+::DescribePixelFormat(hdc, iPixelFormat,
687 sizeof(PIXELFORMATDESCRIPTOR), &pfd);
689 /* choose a pixel format using the useless Windows function in case
690 we come up empty handed */
691 iPixelFormat = ::ChoosePixelFormat( hdc, &sPreferredFormat );
693 if(!iPixelFormat) return 0; /* couldn't find one to use */
695 for(i=1; i<=n; i++) { /* not the idiom, but it's right */
696 ::DescribePixelFormat( hdc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd );
697 w = WeightPixelFormat(pfd);