36f5c5d1fbae5b443a09b6258fa4c4149654bb73
[blender.git] / intern / ghost / intern / GHOST_WindowWin32.cpp
1 /**
2  * $Id$
3  * ***** BEGIN GPL LICENSE BLOCK *****
4  *
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.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
20  * All rights reserved.
21  *
22  * The Original Code is: all of this file.
23  *
24  * Contributor(s): none yet.
25  *
26  * ***** END GPL LICENSE BLOCK *****
27  */
28
29 /**
30
31  * $Id$
32  * Copyright (C) 2001 NaN Technologies B.V.
33  * @author      Maarten Gribnau
34  * @date        May 10, 2001
35  */
36
37 #include <string.h>
38 #include "GHOST_WindowWin32.h"
39 #include "GHOST_SystemWin32.h"
40 #include "GHOST_DropTargetWin32.h"
41
42 // Need glew for some defines
43 #include <GL/glew.h>
44 #include <GL/wglew.h>
45 #include <math.h>
46
47 // MSVC6 still doesn't define M_PI
48 #ifndef M_PI
49 #define M_PI 3.1415926536
50 #endif
51
52 // Some more multisample defines
53 #define WGL_SAMPLE_BUFERS_ARB   0x2041
54 #define WGL_SAMPLES_ARB                 0x2042
55
56 // win64 doesn't define GWL_USERDATA
57 #ifdef WIN32
58 #ifndef GWL_USERDATA
59 #define GWL_USERDATA GWLP_USERDATA
60 #define GWL_WNDPROC GWLP_WNDPROC
61 #endif
62 #endif
63
64 LPCSTR GHOST_WindowWin32::s_windowClassName = "GHOST_WindowClass";
65 const int GHOST_WindowWin32::s_maxTitleLength = 128;
66 HGLRC GHOST_WindowWin32::s_firsthGLRc = NULL;
67 HDC GHOST_WindowWin32::s_firstHDC = NULL;
68
69 static int WeightPixelFormat(PIXELFORMATDESCRIPTOR& pfd);
70 static int EnumPixelFormats(HDC hdc);
71
72 /*
73  * Color and depth bit values are not to be trusted.
74  * For instance, on TNT2:
75  * When the screen color depth is set to 16 bit, we get 5 color bits
76  * and 16 depth bits.
77  * When the screen color depth is set to 32 bit, we get 8 color bits
78  * and 24 depth bits.
79  * Just to be safe, we request high waulity settings.
80  */
81 static PIXELFORMATDESCRIPTOR sPreferredFormat = {
82         sizeof(PIXELFORMATDESCRIPTOR),  /* size */
83         1,                              /* version */
84         PFD_SUPPORT_OPENGL |
85         PFD_DRAW_TO_WINDOW |
86         PFD_SWAP_COPY |                                 /* support swap copy */
87         PFD_DOUBLEBUFFER,               /* support double-buffering */
88         PFD_TYPE_RGBA,                  /* color type */
89         32,                             /* prefered color depth */
90         0, 0, 0, 0, 0, 0,               /* color bits (ignored) */
91         0,                              /* no alpha buffer */
92         0,                              /* alpha bits (ignored) */
93         0,                              /* no accumulation buffer */
94         0, 0, 0, 0,                     /* accum bits (ignored) */
95         32,                             /* depth buffer */
96         0,                              /* no stencil buffer */
97         0,                              /* no auxiliary buffers */
98         PFD_MAIN_PLANE,                 /* main layer */
99         0,                              /* reserved */
100         0, 0, 0                         /* no layer, visible, damage masks */
101 };
102
103 GHOST_WindowWin32::GHOST_WindowWin32(
104         GHOST_SystemWin32 * system,
105         const STR_String& title,
106         GHOST_TInt32 left,
107         GHOST_TInt32 top,
108         GHOST_TUns32 width,
109         GHOST_TUns32 height,
110         GHOST_TWindowState state,
111         GHOST_TDrawingContextType type,
112         const bool stereoVisual,
113         const GHOST_TUns16 numOfAASamples,
114         GHOST_TSuccess msEnabled,
115         int msPixelFormat)
116 :
117         GHOST_Window(title, left, top, width, height, state, GHOST_kDrawingContextTypeNone,
118         stereoVisual,numOfAASamples),
119         m_system(system),
120         m_hDC(0),
121         m_hGlRc(0),
122         m_hasMouseCaptured(false),
123         m_nPressedButtons(0),
124         m_customCursor(0),
125         m_wintab(NULL),
126         m_tabletData(NULL),
127         m_tablet(0),
128         m_maxPressure(0),
129         m_multisample(numOfAASamples),
130         m_multisampleEnabled(msEnabled),
131         m_msPixelFormat(msPixelFormat),
132         //For recreation
133         m_title(title),
134         m_left(left),
135         m_top(top),
136         m_width(width),
137         m_height(height),
138         m_normal_state(GHOST_kWindowStateNormal),
139         m_stereo(stereoVisual),
140         m_nextWindow(NULL)
141 {
142         if (state != GHOST_kWindowStateFullScreen) {
143                 RECT rect;
144                 MONITORINFO monitor;
145                 GHOST_TUns32 tw, th; 
146
147                 width += GetSystemMetrics(SM_CXSIZEFRAME)*2;
148                 height += GetSystemMetrics(SM_CYSIZEFRAME)*2 + GetSystemMetrics(SM_CYCAPTION);
149
150                 rect.left = left;
151                 rect.right = left + width;
152                 rect.top = top;
153                 rect.bottom = top + height;
154
155                 monitor.cbSize=sizeof(monitor);
156                 monitor.dwFlags=0;
157
158                 // take taskbar into account
159                 GetMonitorInfo(MonitorFromRect(&rect,MONITOR_DEFAULTTONEAREST),&monitor);
160
161                 th = monitor.rcWork.bottom - monitor.rcWork.top;
162                 tw = monitor.rcWork.right - monitor.rcWork.left;
163
164                 if(tw < width)
165                 {
166                         width = tw;
167                         left = monitor.rcWork.left;
168                 }
169                 else if(monitor.rcWork.right < left + (int)width)
170                         left = monitor.rcWork.right - width;
171                 else if(left < monitor.rcWork.left)
172                         left = monitor.rcWork.left;
173
174                 if(th < height)
175                 {
176                         height = th;
177                         top = monitor.rcWork.top;
178                 }
179                 else if(monitor.rcWork.bottom < top + (int)height)
180                         top = monitor.rcWork.bottom - height;
181                 else if(top < monitor.rcWork.top)
182                         top = monitor.rcWork.top;
183
184                 m_hWnd = ::CreateWindow(
185                         s_windowClassName,                      // pointer to registered class name
186                         title,                                          // pointer to window name
187                         WS_OVERLAPPEDWINDOW,            // window style
188                         left,                                   // horizontal position of window
189                         top,                                    // vertical position of window
190                         width,                                          // window width
191                         height,                                         // window height
192                         HWND_DESKTOP,                           // handle to parent or owner window
193                         0,                                                      // handle to menu or child-window identifier
194                         ::GetModuleHandle(0),           // handle to application instance
195                         0);                                                     // pointer to window-creation data
196         }
197         else {
198                 m_hWnd = ::CreateWindow(
199                         s_windowClassName,                      // pointer to registered class name
200                         title,                                          // pointer to window name
201                         WS_POPUP | WS_MAXIMIZE,         // window style
202                         left,                                           // horizontal position of window
203                         top,                                            // vertical position of window
204                         width,                                          // window width
205                         height,                                         // window height
206                         HWND_DESKTOP,                           // handle to parent or owner window
207                         0,                                                      // handle to menu or child-window identifier
208                         ::GetModuleHandle(0),           // handle to application instance
209                         0);                                                     // pointer to window-creation data
210         }
211         if (m_hWnd) {
212                 // Register this window as a droptarget. Requires m_hWnd to be valid.
213                 // Note that OleInitialize(0) has to be called prior to this. Done in GHOST_SystemWin32.
214                 m_dropTarget = new GHOST_DropTargetWin32(this, m_system);
215                 // Store a pointer to this class in the window structure
216                 ::SetWindowLongPtr(m_hWnd, GWL_USERDATA, (LONG_PTR)this);
217
218                 // Store the device context
219                 m_hDC = ::GetDC(m_hWnd);
220
221                 if(!s_firstHDC) {
222                         s_firstHDC = m_hDC;
223                 }
224
225                 // Show the window
226                 int nCmdShow;
227                 switch (state) {
228                         case GHOST_kWindowStateMaximized:
229                                 nCmdShow = SW_SHOWMAXIMIZED;
230                                 break;
231                         case GHOST_kWindowStateMinimized:
232                                 nCmdShow = SW_SHOWMINIMIZED;
233                                 break;
234                         case GHOST_kWindowStateNormal:
235                         default:
236                                 nCmdShow = SW_SHOWNORMAL;
237                                 break;
238                 }
239                 GHOST_TSuccess success;
240                 success = setDrawingContextType(type);
241
242                 if (success)
243                 {
244                         ::ShowWindow(m_hWnd, nCmdShow);
245                         // Force an initial paint of the window
246                         ::UpdateWindow(m_hWnd);
247                 }
248                 else
249                 {
250                         //invalidate the window
251                         m_hWnd = 0;
252                 }
253         }
254
255         m_wintab = ::LoadLibrary("Wintab32.dll");
256         if (m_wintab) {
257                 GHOST_WIN32_WTInfo fpWTInfo = ( GHOST_WIN32_WTInfo ) ::GetProcAddress( m_wintab, "WTInfoA" );
258                 GHOST_WIN32_WTOpen fpWTOpen = ( GHOST_WIN32_WTOpen ) ::GetProcAddress( m_wintab, "WTOpenA" );
259
260                 // let's see if we can initialize tablet here
261                 /* check if WinTab available. */
262                 if (fpWTInfo && fpWTInfo(0, 0, NULL)) {
263                         // Now init the tablet
264                         LOGCONTEXT lc;
265                         AXIS TabletX, TabletY, Pressure, Orientation[3]; /* The maximum tablet size, pressure and orientation (tilt) */
266
267                         // Open a Wintab context
268
269                         // Get default context information
270                         fpWTInfo( WTI_DEFCONTEXT, 0, &lc );
271
272                         // Open the context
273                         lc.lcPktData = PACKETDATA;
274                         lc.lcPktMode = PACKETMODE;
275                         lc.lcOptions |= CXO_MESSAGES | CXO_SYSTEM;
276
277                         /* Set the entire tablet as active */
278                         fpWTInfo(WTI_DEVICES,DVC_X,&TabletX);
279                         fpWTInfo(WTI_DEVICES,DVC_Y,&TabletY);
280
281                         /* get the max pressure, to divide into a float */
282                         BOOL pressureSupport = fpWTInfo (WTI_DEVICES, DVC_NPRESSURE, &Pressure);
283                         if (pressureSupport)
284                                 m_maxPressure = Pressure.axMax;
285                         else
286                                 m_maxPressure = 0;
287
288                         /* get the max tilt axes, to divide into floats */
289                         BOOL tiltSupport = fpWTInfo (WTI_DEVICES, DVC_ORIENTATION, &Orientation);
290                         if (tiltSupport) {
291                                 /* does the tablet support azimuth ([0]) and altitude ([1]) */
292                                 if (Orientation[0].axResolution && Orientation[1].axResolution) {
293                                         /* all this assumes the minimum is 0 */
294                                         m_maxAzimuth = Orientation[0].axMax;
295                                         m_maxAltitude = Orientation[1].axMax;
296                                 }
297                                 else {  /* no so dont do tilt stuff */
298                                         m_maxAzimuth = m_maxAltitude = 0;
299                                 }
300                         }
301
302                         if (fpWTOpen) {
303                                 m_tablet = fpWTOpen( m_hWnd, &lc, TRUE );
304                                 if (m_tablet) {
305                                         m_tabletData = new GHOST_TabletData();
306                                         m_tabletData->Active = GHOST_kTabletModeNone;
307                                 }
308                         }
309                 }
310         }
311 }
312
313
314 GHOST_WindowWin32::~GHOST_WindowWin32()
315 {
316         if (m_wintab) {
317                 GHOST_WIN32_WTClose fpWTClose = ( GHOST_WIN32_WTClose ) ::GetProcAddress( m_wintab, "WTClose" );
318                 if (fpWTClose) {
319                         if (m_tablet)
320                                 fpWTClose(m_tablet);
321                         if (m_tabletData)
322                                 delete m_tabletData;
323                                 m_tabletData = NULL;
324                 }
325         }
326         if (m_customCursor) {
327                 DestroyCursor(m_customCursor);
328                 m_customCursor = NULL;
329         }
330
331         ::wglMakeCurrent(NULL, NULL);
332         m_multisampleEnabled = GHOST_kFailure;
333         m_multisample = 0;
334         setDrawingContextType(GHOST_kDrawingContextTypeNone);
335         if (m_hDC && m_hDC != s_firstHDC) {
336                 ::ReleaseDC(m_hWnd, m_hDC);
337                 m_hDC = 0;
338         }
339         if (m_hWnd) {
340                 m_dropTarget->Release(); // frees itself.
341                 ::DestroyWindow(m_hWnd);
342                 m_hWnd = 0;
343         }
344 }
345
346 GHOST_Window *GHOST_WindowWin32::getNextWindow()
347 {
348         return m_nextWindow;
349 }
350
351 bool GHOST_WindowWin32::getValid() const
352 {
353         return m_hWnd != 0;
354 }
355
356 HWND GHOST_WindowWin32::getHWND() const
357 {
358         return m_hWnd;
359 }
360
361 void GHOST_WindowWin32::setTitle(const STR_String& title)
362 {
363         ::SetWindowText(m_hWnd, title);
364 }
365
366
367 void GHOST_WindowWin32::getTitle(STR_String& title) const
368 {
369         char buf[s_maxTitleLength];
370         ::GetWindowText(m_hWnd, buf, s_maxTitleLength);
371         STR_String temp (buf);
372         title = buf;
373 }
374
375
376 void GHOST_WindowWin32::getWindowBounds(GHOST_Rect& bounds) const
377 {
378         RECT rect;
379         ::GetWindowRect(m_hWnd, &rect);
380         bounds.m_b = rect.bottom;
381         bounds.m_l = rect.left;
382         bounds.m_r = rect.right;
383         bounds.m_t = rect.top;
384 }
385
386
387 void GHOST_WindowWin32::getClientBounds(GHOST_Rect& bounds) const
388 {
389         RECT rect;
390         GHOST_TWindowState state= this->getState();
391         LONG_PTR result = ::GetWindowLongPtr(m_hWnd, GWL_STYLE);
392         int sm_cysizeframe = GetSystemMetrics(SM_CYSIZEFRAME);
393         ::GetWindowRect(m_hWnd, &rect);
394
395         if((result & (WS_POPUP | WS_MAXIMIZE)) != (WS_POPUP | WS_MAXIMIZE)) {
396                 if(state==GHOST_kWindowStateMaximized) {
397                         // in maximized state we don't have borders on the window
398                         bounds.m_b = rect.bottom-GetSystemMetrics(SM_CYCAPTION)- sm_cysizeframe*2;
399                         bounds.m_l = rect.left + sm_cysizeframe;
400                         bounds.m_r = rect.right - sm_cysizeframe;
401                         bounds.m_t = rect.top;
402                 } else {
403                         bounds.m_b = rect.bottom-GetSystemMetrics(SM_CYCAPTION)-sm_cysizeframe*2;
404                         bounds.m_l = rect.left;
405                         bounds.m_r = rect.right-sm_cysizeframe*2;
406                         bounds.m_t = rect.top;
407                 }
408         } else {
409                 ::GetWindowRect(m_hWnd, &rect);
410                 bounds.m_b = rect.bottom;
411                 bounds.m_l = rect.left;
412                 bounds.m_r = rect.right;
413                 bounds.m_t = rect.top;
414         }
415 }
416
417
418 GHOST_TSuccess GHOST_WindowWin32::setClientWidth(GHOST_TUns32 width)
419 {
420         GHOST_TSuccess success;
421         GHOST_Rect cBnds, wBnds;
422         getClientBounds(cBnds);
423         if (cBnds.getWidth() != (GHOST_TInt32)width) {
424                 getWindowBounds(wBnds);
425                 int cx = wBnds.getWidth() + width - cBnds.getWidth();
426                 int cy = wBnds.getHeight();
427                 success =  ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ?
428                         GHOST_kSuccess : GHOST_kFailure;
429         }
430         else {
431                 success = GHOST_kSuccess;
432         }
433         return success;
434 }
435
436
437 GHOST_TSuccess GHOST_WindowWin32::setClientHeight(GHOST_TUns32 height)
438 {
439         GHOST_TSuccess success;
440         GHOST_Rect cBnds, wBnds;
441         getClientBounds(cBnds);
442         if (cBnds.getHeight() != (GHOST_TInt32)height) {
443                 getWindowBounds(wBnds);
444                 int cx = wBnds.getWidth();
445                 int cy = wBnds.getHeight() + height - cBnds.getHeight();
446                 success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ?
447                         GHOST_kSuccess : GHOST_kFailure;
448         }
449         else {
450                 success = GHOST_kSuccess;
451         }
452         return success;
453 }
454
455
456 GHOST_TSuccess GHOST_WindowWin32::setClientSize(GHOST_TUns32 width, GHOST_TUns32 height)
457 {
458         GHOST_TSuccess success;
459         GHOST_Rect cBnds, wBnds;
460         getClientBounds(cBnds);
461         if ((cBnds.getWidth() != (GHOST_TInt32)width) || (cBnds.getHeight() != (GHOST_TInt32)height)) {
462                 getWindowBounds(wBnds);
463                 int cx = wBnds.getWidth() + width - cBnds.getWidth();
464                 int cy = wBnds.getHeight() + height - cBnds.getHeight();
465                 success = ::SetWindowPos(m_hWnd, HWND_TOP, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOZORDER) ?
466                         GHOST_kSuccess : GHOST_kFailure;
467         }
468         else {
469                 success = GHOST_kSuccess;
470         }
471         return success;
472 }
473
474
475 GHOST_TWindowState GHOST_WindowWin32::getState() const
476 {
477         GHOST_TWindowState state;
478         if (::IsIconic(m_hWnd)) {
479                 state = GHOST_kWindowStateMinimized;
480         }
481         else if (::IsZoomed(m_hWnd)) {
482                 LONG_PTR result = ::GetWindowLongPtr(m_hWnd, GWL_STYLE);
483                 if((result & (WS_POPUP | WS_MAXIMIZE)) != (WS_POPUP | WS_MAXIMIZE))
484                         state = GHOST_kWindowStateMaximized;
485                 else
486                         state = GHOST_kWindowStateFullScreen;
487         }
488         else {
489                 state = GHOST_kWindowStateNormal;
490         }
491         return state;
492 }
493
494
495 void GHOST_WindowWin32::screenToClient(GHOST_TInt32 inX, GHOST_TInt32 inY, GHOST_TInt32& outX, GHOST_TInt32& outY) const
496 {
497         POINT point = { inX, inY };
498         ::ScreenToClient(m_hWnd, &point);
499         outX = point.x;
500         outY = point.y;
501 }
502
503
504 void GHOST_WindowWin32::clientToScreen(GHOST_TInt32 inX, GHOST_TInt32 inY, GHOST_TInt32& outX, GHOST_TInt32& outY) const
505 {
506         POINT point = { inX, inY };
507         ::ClientToScreen(m_hWnd, &point);
508         outX = point.x;
509         outY = point.y;
510 }
511
512
513 GHOST_TSuccess GHOST_WindowWin32::setState(GHOST_TWindowState state)
514 {
515         GHOST_TWindowState curstate = getState();
516         WINDOWPLACEMENT wp;
517         wp.length = sizeof(WINDOWPLACEMENT);
518         ::GetWindowPlacement(m_hWnd, &wp);
519
520         if (state == GHOST_kWindowStateNormal)
521                 state = m_normal_state;
522         switch (state) {
523         case GHOST_kWindowStateMinimized:
524                 wp.showCmd = SW_SHOWMINIMIZED;
525                 break;
526         case GHOST_kWindowStateMaximized:
527                 ShowWindow(m_hWnd, SW_HIDE);
528                 wp.showCmd = SW_SHOWMAXIMIZED;
529                 SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
530                 break;
531         case GHOST_kWindowStateFullScreen:
532                 if (curstate != state && curstate != GHOST_kWindowStateMinimized)
533                         m_normal_state = curstate;
534                 wp.showCmd = SW_SHOWMAXIMIZED;
535                 wp.ptMaxPosition.x = 0;
536                 wp.ptMaxPosition.y = 0;
537                 SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_POPUP | WS_MAXIMIZE);
538                 break;
539         case GHOST_kWindowStateNormal:
540         default:
541                 ShowWindow(m_hWnd, SW_HIDE);
542                 wp.showCmd = SW_SHOWNORMAL;
543                 SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
544                 break;
545         }
546         return ::SetWindowPlacement(m_hWnd, &wp) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
547 }
548
549
550 GHOST_TSuccess GHOST_WindowWin32::setOrder(GHOST_TWindowOrder order)
551 {
552         HWND hWndInsertAfter = order == GHOST_kWindowOrderTop ? HWND_TOP : HWND_BOTTOM;
553         return ::SetWindowPos(m_hWnd, hWndInsertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
554 }
555
556
557 GHOST_TSuccess GHOST_WindowWin32::swapBuffers()
558 {
559         return ::SwapBuffers(m_hDC) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
560 }
561
562
563 GHOST_TSuccess GHOST_WindowWin32::activateDrawingContext()
564 {
565         GHOST_TSuccess success;
566         if (m_drawingContextType == GHOST_kDrawingContextTypeOpenGL) {
567                 if (m_hDC && m_hGlRc) {
568                         success = ::wglMakeCurrent(m_hDC, m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
569                 }
570                 else {
571                         success = GHOST_kFailure;
572                 }
573         }
574         else {
575                 success = GHOST_kSuccess;
576         }
577         return success;
578 }
579
580
581 GHOST_TSuccess GHOST_WindowWin32::invalidate()
582 {
583         GHOST_TSuccess success;
584         if (m_hWnd) {
585                 success = ::InvalidateRect(m_hWnd, 0, FALSE) != 0 ? GHOST_kSuccess : GHOST_kFailure;
586         }
587         else {
588                 success = GHOST_kFailure;
589         }
590         return success;
591 }
592
593 GHOST_TSuccess GHOST_WindowWin32::initMultisample(PIXELFORMATDESCRIPTOR pfd)
594 {
595         int pixelFormat;
596         bool success;
597         UINT numFormats;
598         HDC hDC = GetDC(getHWND());
599         float fAttributes[] = {0, 0};
600
601         // The attributes to look for
602         int iAttributes[] = {
603                 WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
604                 WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
605                 WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
606                 WGL_COLOR_BITS_ARB, pfd.cColorBits,
607                 WGL_DEPTH_BITS_ARB, pfd.cDepthBits,
608                 WGL_STENCIL_BITS_ARB, pfd.cStencilBits,
609                 WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
610                 WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
611                 WGL_SAMPLES_ARB, m_multisample,
612                 0, 0
613         };
614
615         // Get the function
616         PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");  
617
618         if (!wglChoosePixelFormatARB)
619         {
620                 m_multisampleEnabled = GHOST_kFailure;
621                 return GHOST_kFailure;
622         }
623
624         // See if the format is valid
625         success = wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
626
627         if (success && numFormats >= 1)
628         {
629                 m_multisampleEnabled = GHOST_kSuccess;
630                 m_msPixelFormat = pixelFormat;
631                 return GHOST_kSuccess;
632         }
633         else
634         {
635                 // See if any formats are supported
636                 while (!success && iAttributes[19] != 0)
637                 {
638                         iAttributes[19] /= 2;
639
640                         success = wglChoosePixelFormatARB(m_hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
641
642                         if (success && numFormats >= 1)
643                         {
644                                 m_multisampleEnabled = GHOST_kSuccess;
645                                 m_msPixelFormat = pixelFormat;
646                                 return GHOST_kSuccess;
647                         }
648
649                         success = GHOST_kFailure;
650                 }
651         }
652
653         // No available pixel format...
654         return GHOST_kFailure;
655 }
656
657 GHOST_TSuccess GHOST_WindowWin32::installDrawingContext(GHOST_TDrawingContextType type)
658 {
659         GHOST_TSuccess success;
660         switch (type) {
661         case GHOST_kDrawingContextTypeOpenGL:
662                 {
663                 // If this window has multisample enabled, use the supplied format
664                 if (m_multisampleEnabled)
665                 {
666                         if (SetPixelFormat(m_hDC, m_msPixelFormat, &sPreferredFormat)==FALSE)
667                         {
668                                 success = GHOST_kFailure;
669                                 break;
670                         }
671
672                         // Create the context
673                         m_hGlRc = ::wglCreateContext(m_hDC);
674                         if (m_hGlRc) {
675                                 if (s_firsthGLRc) {
676                                         ::wglCopyContext(s_firsthGLRc, m_hGlRc, GL_ALL_ATTRIB_BITS);
677                                         wglShareLists(s_firsthGLRc, m_hGlRc);
678                                 } else {
679                                         s_firsthGLRc = m_hGlRc;
680                                 }
681
682                                 success = ::wglMakeCurrent(m_hDC, m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
683                         }
684                         else {
685                                 printf("Failed to get a context....\n");
686                                 success = GHOST_kFailure;
687                         }
688                 }
689                 else
690                 {
691                         if(m_stereoVisual)
692                                 sPreferredFormat.dwFlags |= PFD_STEREO;
693
694                         // Attempt to match device context pixel format to the preferred format
695                         int iPixelFormat = EnumPixelFormats(m_hDC);
696                         if (iPixelFormat == 0) {
697                                 success = GHOST_kFailure;
698                                 break;
699                         }
700                         if (::SetPixelFormat(m_hDC, iPixelFormat, &sPreferredFormat) == FALSE) {
701                                 success = GHOST_kFailure;
702                                 break;
703                         }
704                         // For debugging only: retrieve the pixel format chosen
705                         PIXELFORMATDESCRIPTOR preferredFormat;
706                         ::DescribePixelFormat(m_hDC, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &preferredFormat);
707
708                         // Create the context
709                         m_hGlRc = ::wglCreateContext(m_hDC);
710                         if (m_hGlRc) {
711                                 if (s_firsthGLRc) {
712                                         ::wglShareLists(s_firsthGLRc, m_hGlRc);
713                                 } else {
714                                         s_firsthGLRc = m_hGlRc;
715                                 }
716
717                                 success = ::wglMakeCurrent(m_hDC, m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
718                         }
719                         else {
720                                 printf("Failed to get a context....\n");
721                                 success = GHOST_kFailure;
722                         }
723                                         
724                         // Attempt to enable multisample
725                         if (m_multisample && WGL_ARB_multisample && !m_multisampleEnabled)
726                         {
727                                 success = initMultisample(preferredFormat);
728
729                                 if (success)
730                                 {
731
732                                         // Make sure we don't screw up the context
733                                         m_drawingContextType = GHOST_kDrawingContextTypeOpenGL;
734                                         removeDrawingContext();
735
736                                         // Create a new window
737                                         GHOST_TWindowState new_state = getState();
738
739                                         m_nextWindow = new GHOST_WindowWin32((GHOST_SystemWin32*)GHOST_ISystem::getSystem(),
740                                                                                                         m_title,
741                                                                                                         m_left,
742                                                                                                         m_top,
743                                                                                                         m_width,
744                                                                                                         m_height,
745                                                                                                         new_state,
746                                                                                                         type,
747                                                                                                         m_stereo,
748                                                                                                         m_multisample,
749                                                                                                         m_multisampleEnabled,
750                                                                                                         m_msPixelFormat);
751
752                                         // Return failure so we can trash this window.
753                                         success = GHOST_kFailure;
754                                         break;
755                                 }
756                         }
757                 }
758
759                 }
760                 break;
761
762         case GHOST_kDrawingContextTypeNone:
763                 success = GHOST_kSuccess;
764                 break;
765
766         default:
767                 success = GHOST_kFailure;
768         }
769         return success;
770 }
771
772 GHOST_TSuccess GHOST_WindowWin32::removeDrawingContext()
773 {
774         GHOST_TSuccess success;
775         switch (m_drawingContextType) {
776         case GHOST_kDrawingContextTypeOpenGL:
777                 if (m_hGlRc) {
778                         bool first = m_hGlRc == s_firsthGLRc;
779                         success = ::wglDeleteContext(m_hGlRc) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
780                         if (first) {
781                                 s_firsthGLRc = 0;
782                         }
783                         m_hGlRc = 0;
784                 }
785                 else {
786                         success = GHOST_kFailure;
787                 }
788                 break;
789         case GHOST_kDrawingContextTypeNone:
790                 success = GHOST_kSuccess;
791                 break;
792         default:
793                 success = GHOST_kFailure;
794         }
795         return success;
796 }
797
798 void GHOST_WindowWin32::lostMouseCapture()
799 {
800         if (m_hasMouseCaptured) {
801                 m_hasMouseCaptured = false;
802                 m_nPressedButtons = 0;
803         }
804 }
805
806 void GHOST_WindowWin32::registerMouseClickEvent(bool press)
807 {
808         if (press) {
809                 if (!m_hasMouseCaptured) {
810                         ::SetCapture(m_hWnd);
811                         m_hasMouseCaptured = true;
812                 }
813                 m_nPressedButtons++;
814         } else {
815                 if (m_nPressedButtons) {
816                         m_nPressedButtons--;
817                         if (!m_nPressedButtons) {
818                                 ::ReleaseCapture();
819                                 m_hasMouseCaptured = false;
820                         }
821                 }
822         }
823 }
824
825
826 void GHOST_WindowWin32::loadCursor(bool visible, GHOST_TStandardCursor cursor) const
827 {
828         if (!visible) {
829                 while (::ShowCursor(FALSE) >= 0);
830         }
831         else {
832                 while (::ShowCursor(TRUE) < 0);
833         }
834
835         if (cursor == GHOST_kStandardCursorCustom && m_customCursor) {
836                 ::SetCursor( m_customCursor );
837         } else {
838                 // Convert GHOST cursor to Windows OEM cursor
839                 bool success = true;
840                 LPCSTR id;
841                 switch (cursor) {
842                         case GHOST_kStandardCursorDefault:              id = IDC_ARROW;         break;
843                         case GHOST_kStandardCursorRightArrow:           id = IDC_ARROW;         break;
844                         case GHOST_kStandardCursorLeftArrow:            id = IDC_ARROW;         break;
845                         case GHOST_kStandardCursorInfo:                 id = IDC_SIZEALL;       break;  // Four-pointed arrow pointing north, south, east, and west
846                         case GHOST_kStandardCursorDestroy:              id = IDC_NO;            break;  // Slashed circle
847                         case GHOST_kStandardCursorHelp:                 id = IDC_HELP;          break;  // Arrow and question mark
848                         case GHOST_kStandardCursorCycle:                id = IDC_NO;            break;  // Slashed circle
849                         case GHOST_kStandardCursorSpray:                id = IDC_SIZEALL;       break;  // Four-pointed arrow pointing north, south, east, and west
850                         case GHOST_kStandardCursorWait:                 id = IDC_WAIT;          break;  // Hourglass
851                         case GHOST_kStandardCursorText:                 id = IDC_IBEAM;         break;  // I-beam
852                         case GHOST_kStandardCursorCrosshair:            id = IDC_CROSS;         break;  // Crosshair
853                         case GHOST_kStandardCursorUpDown:               id = IDC_SIZENS;        break;  // Double-pointed arrow pointing north and south
854                         case GHOST_kStandardCursorLeftRight:            id = IDC_SIZEWE;        break;  // Double-pointed arrow pointing west and east
855                         case GHOST_kStandardCursorTopSide:              id = IDC_UPARROW;       break;  // Vertical arrow
856                         case GHOST_kStandardCursorBottomSide:           id = IDC_SIZENS;        break;
857                         case GHOST_kStandardCursorLeftSide:             id = IDC_SIZEWE;        break;
858                         case GHOST_kStandardCursorTopLeftCorner:        id = IDC_SIZENWSE;      break;
859                         case GHOST_kStandardCursorTopRightCorner:       id = IDC_SIZENESW;      break;
860                         case GHOST_kStandardCursorBottomRightCorner:    id = IDC_SIZENWSE;      break;
861                         case GHOST_kStandardCursorBottomLeftCorner:     id = IDC_SIZENESW;      break;
862                         case GHOST_kStandardCursorPencil:               id = IDC_ARROW;         break;
863                         case GHOST_kStandardCursorCopy:                 id = IDC_ARROW;         break;
864                         default:
865                         success = false;
866                 }
867
868                 if (success) {
869                         ::SetCursor(::LoadCursor(0, id));
870                 }
871         }
872 }
873
874 GHOST_TSuccess GHOST_WindowWin32::setWindowCursorVisibility(bool visible)
875 {
876         if (::GetForegroundWindow() == m_hWnd) {
877                 loadCursor(visible, getCursorShape());
878         }
879
880         return GHOST_kSuccess;
881 }
882
883 GHOST_TSuccess GHOST_WindowWin32::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
884 {
885         if(mode != GHOST_kGrabDisable) {
886                 if(mode != GHOST_kGrabNormal) {
887                         m_system->getCursorPosition(m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]);
888                         setCursorGrabAccum(0, 0);
889
890                         if(mode == GHOST_kGrabHide)
891                                 setWindowCursorVisibility(false);
892                 }
893                 registerMouseClickEvent(true);
894         }
895         else {
896                 if (m_cursorGrab==GHOST_kGrabHide) {
897                         m_system->setCursorPosition(m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]);
898                         setWindowCursorVisibility(true);
899                 }
900                 if(m_cursorGrab != GHOST_kGrabNormal) {
901                         /* use to generate a mouse move event, otherwise the last event
902                          * blender gets can be outside the screen causing menus not to show
903                          * properly unless the user moves the mouse */
904                          GHOST_TInt32 pos[2];
905                          m_system->getCursorPosition(pos[0], pos[1]);
906                          m_system->setCursorPosition(pos[0], pos[1]);
907                 }
908
909                 /* Almost works without but important otherwise the mouse GHOST location can be incorrect on exit */
910                 setCursorGrabAccum(0, 0);
911                 m_cursorGrabBounds.m_l= m_cursorGrabBounds.m_r= -1; /* disable */
912                 registerMouseClickEvent(false);
913         }
914         
915         return GHOST_kSuccess;
916 }
917
918 GHOST_TSuccess GHOST_WindowWin32::setWindowCursorShape(GHOST_TStandardCursor cursorShape)
919 {
920         if (m_customCursor) {
921                 DestroyCursor(m_customCursor);
922                 m_customCursor = NULL;
923         }
924
925         if (::GetForegroundWindow() == m_hWnd) {
926                 loadCursor(getCursorVisibility(), cursorShape);
927         }
928
929         return GHOST_kSuccess;
930 }
931
932 void GHOST_WindowWin32::processWin32TabletInitEvent()
933 {
934         if (m_wintab) {
935                 GHOST_WIN32_WTInfo fpWTInfo = ( GHOST_WIN32_WTInfo ) ::GetProcAddress( m_wintab, "WTInfoA" );
936
937                 // let's see if we can initialize tablet here
938                 /* check if WinTab available. */
939                 if (fpWTInfo) {
940                         AXIS Pressure, Orientation[3]; /* The maximum tablet size */
941
942                         BOOL pressureSupport = fpWTInfo (WTI_DEVICES, DVC_NPRESSURE, &Pressure);
943                         if (pressureSupport)
944                                 m_maxPressure = Pressure.axMax;
945                         else
946                                 m_maxPressure = 0;
947
948                         BOOL tiltSupport = fpWTInfo (WTI_DEVICES, DVC_ORIENTATION, &Orientation);
949                         if (tiltSupport) {
950                                 /* does the tablet support azimuth ([0]) and altitude ([1]) */
951                                 if (Orientation[0].axResolution && Orientation[1].axResolution) {
952                                         m_maxAzimuth = Orientation[0].axMax;
953                                         m_maxAltitude = Orientation[1].axMax;
954                                 }
955                                 else {  /* no so dont do tilt stuff */
956                                         m_maxAzimuth = m_maxAltitude = 0;
957                                 }
958                         }
959
960                         m_tabletData->Active = GHOST_kTabletModeNone;
961                 }
962         }
963 }
964
965 void GHOST_WindowWin32::processWin32TabletEvent(WPARAM wParam, LPARAM lParam)
966 {
967         PACKET pkt;
968         if (m_wintab) {
969                 GHOST_WIN32_WTPacket fpWTPacket = ( GHOST_WIN32_WTPacket ) ::GetProcAddress( m_wintab, "WTPacket" );
970                 if (fpWTPacket) {
971                         if (fpWTPacket((HCTX)lParam, wParam, &pkt)) {
972                                 if (m_tabletData) {
973                                         switch (pkt.pkCursor) {
974                                                 case 0: /* first device */
975                                                 case 3: /* second device */
976                                                         m_tabletData->Active = GHOST_kTabletModeNone; /* puck - not yet supported */
977                                                         break;
978                                                 case 1:
979                                                 case 4:
980                                                         m_tabletData->Active = GHOST_kTabletModeStylus; /* stylus */
981                                                         break;
982                                                 case 2:
983                                                 case 5:
984                                                         m_tabletData->Active = GHOST_kTabletModeEraser; /* eraser */
985                                                         break;
986                                         }
987                                         if (m_maxPressure > 0) {
988                                                 m_tabletData->Pressure = (float)pkt.pkNormalPressure / (float)m_maxPressure;
989                                         } else {
990                                                 m_tabletData->Pressure = 1.0f;
991                                         }
992
993                                         if ((m_maxAzimuth > 0) && (m_maxAltitude > 0)) {
994                                                 ORIENTATION ort = pkt.pkOrientation;
995                                                 float vecLen;
996                                                 float altRad, azmRad;   /* in radians */
997
998                                                 /*
999                                                 from the wintab spec:
1000                                                 orAzimuth       Specifies the clockwise rotation of the
1001                                                 cursor about the z axis through a full circular range.
1002
1003                                                 orAltitude      Specifies the angle with the x-y plane
1004                                                 through a signed, semicircular range.  Positive values
1005                                                 specify an angle upward toward the positive z axis;
1006                                                 negative values specify an angle downward toward the negative z axis.
1007
1008                                                 wintab.h defines .orAltitude as a UINT but documents .orAltitude
1009                                                 as positive for upward angles and negative for downward angles.
1010                                                 WACOM uses negative altitude values to show that the pen is inverted;
1011                                                 therefore we cast .orAltitude as an (int) and then use the absolute value.
1012                                                 */
1013
1014                                                 /* convert raw fixed point data to radians */
1015                                                 altRad = (float)((fabs((float)ort.orAltitude)/(float)m_maxAltitude) * M_PI/2.0);
1016                                                 azmRad = (float)(((float)ort.orAzimuth/(float)m_maxAzimuth) * M_PI*2.0);
1017
1018                                                 /* find length of the stylus' projected vector on the XY plane */
1019                                                 vecLen = cos(altRad);
1020
1021                                                 /* from there calculate X and Y components based on azimuth */
1022                                                 m_tabletData->Xtilt = sin(azmRad) * vecLen;
1023                                                 m_tabletData->Ytilt = (float)(sin(M_PI/2.0 - azmRad) * vecLen);
1024
1025                                         } else {
1026                                                 m_tabletData->Xtilt = 0.0f;
1027                                                 m_tabletData->Ytilt = 0.0f;
1028                                         }
1029                                 }
1030                         }
1031                 }
1032         }
1033 }
1034
1035 /** Reverse the bits in a GHOST_TUns8 */
1036 static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch)
1037 {
1038         ch= ((ch>>1)&0x55) | ((ch<<1)&0xAA);
1039         ch= ((ch>>2)&0x33) | ((ch<<2)&0xCC);
1040         ch= ((ch>>4)&0x0F) | ((ch<<4)&0xF0);
1041         return ch;
1042 }
1043
1044 /** Reverse the bits in a GHOST_TUns16 */
1045 static GHOST_TUns16 uns16ReverseBits(GHOST_TUns16 shrt)
1046 {
1047         shrt= ((shrt>>1)&0x5555) | ((shrt<<1)&0xAAAA);
1048         shrt= ((shrt>>2)&0x3333) | ((shrt<<2)&0xCCCC);
1049         shrt= ((shrt>>4)&0x0F0F) | ((shrt<<4)&0xF0F0);
1050         shrt= ((shrt>>8)&0x00FF) | ((shrt<<8)&0xFF00);
1051         return shrt;
1052 }
1053 GHOST_TSuccess GHOST_WindowWin32::setWindowCustomCursorShape(GHOST_TUns8 bitmap[16][2],
1054                                         GHOST_TUns8 mask[16][2], int hotX, int hotY)
1055 {
1056         return setWindowCustomCursorShape((GHOST_TUns8*)bitmap, (GHOST_TUns8*)mask,
1057                                                                         16, 16, hotX, hotY, 0, 1);
1058 }
1059
1060 GHOST_TSuccess GHOST_WindowWin32::setWindowCustomCursorShape(GHOST_TUns8 *bitmap,
1061                                         GHOST_TUns8 *mask, int sizeX, int sizeY, int hotX, int hotY,
1062                                         int fg_color, int bg_color)
1063 {
1064         GHOST_TUns32 andData[32];
1065         GHOST_TUns32 xorData[32];
1066         GHOST_TUns32 fullBitRow, fullMaskRow;
1067         int x, y, cols;
1068
1069         cols=sizeX/8; /* Num of whole bytes per row (width of bm/mask) */
1070         if (sizeX%8) cols++;
1071
1072         if (m_customCursor) {
1073                 DestroyCursor(m_customCursor);
1074                 m_customCursor = NULL;
1075         }
1076
1077         memset(&andData, 0xFF, sizeof(andData));
1078         memset(&xorData, 0, sizeof(xorData));
1079
1080         for (y=0; y<sizeY; y++) {
1081                 fullBitRow=0;
1082                 fullMaskRow=0;
1083                 for (x=cols-1; x>=0; x--){
1084                         fullBitRow<<=8;
1085                         fullMaskRow<<=8;
1086                         fullBitRow  |= uns8ReverseBits(bitmap[cols*y + x]);
1087                         fullMaskRow |= uns8ReverseBits(  mask[cols*y + x]);
1088                 }
1089                 xorData[y]= fullBitRow & fullMaskRow;
1090                 andData[y]= ~fullMaskRow;
1091         }
1092
1093         m_customCursor = ::CreateCursor(::GetModuleHandle(0), hotX, hotY, 32, 32, andData, xorData);
1094         if (!m_customCursor) {
1095                 return GHOST_kFailure;
1096         }
1097
1098         if (::GetForegroundWindow() == m_hWnd) {
1099                 loadCursor(getCursorVisibility(), GHOST_kStandardCursorCustom);
1100         }
1101
1102         return GHOST_kSuccess;
1103 }
1104
1105
1106 /*  Ron Fosner's code for weighting pixel formats and forcing software.
1107         See http://www.opengl.org/resources/faq/technical/weight.cpp */
1108
1109 static int WeightPixelFormat(PIXELFORMATDESCRIPTOR& pfd) {
1110         int weight = 0;
1111
1112         /* assume desktop color depth is 32 bits per pixel */
1113
1114         /* cull unusable pixel formats */
1115         /* if no formats can be found, can we determine why it was rejected? */
1116         if( !(pfd.dwFlags & PFD_SUPPORT_OPENGL) ||
1117                 !(pfd.dwFlags & PFD_DRAW_TO_WINDOW) ||
1118                 !(pfd.dwFlags & PFD_DOUBLEBUFFER) || /* Blender _needs_ this */
1119                 ( pfd.cDepthBits <= 8 ) ||
1120                 !(pfd.iPixelType == PFD_TYPE_RGBA))
1121                 return 0;
1122
1123         weight = 1;  /* it's usable */
1124
1125         /* the bigger the depth buffer the better */
1126         /* give no weight to a 16-bit depth buffer, because those are crap */
1127         weight += pfd.cDepthBits - 16;
1128
1129         weight += pfd.cColorBits - 8;
1130
1131         /* want swap copy capability -- it matters a lot */
1132         if(pfd.dwFlags & PFD_SWAP_COPY) weight += 16;
1133
1134         /* but if it's a generic (not accelerated) view, it's really bad */
1135         if(pfd.dwFlags & PFD_GENERIC_FORMAT) weight /= 10;
1136
1137         return weight;
1138 }
1139
1140 /* A modification of Ron Fosner's replacement for ChoosePixelFormat */
1141 /* returns 0 on error, else returns the pixel format number to be used */
1142 static int EnumPixelFormats(HDC hdc) {
1143         int iPixelFormat;
1144         int i, n, w, weight = 0;
1145         PIXELFORMATDESCRIPTOR pfd;
1146
1147         /* we need a device context to do anything */
1148         if(!hdc) return 0;
1149
1150         iPixelFormat = 1; /* careful! PFD numbers are 1 based, not zero based */
1151
1152         /* obtain detailed information about
1153         the device context's first pixel format */
1154         n = 1+::DescribePixelFormat(hdc, iPixelFormat,
1155                 sizeof(PIXELFORMATDESCRIPTOR), &pfd);
1156
1157         /* choose a pixel format using the useless Windows function in case
1158                 we come up empty handed */
1159         iPixelFormat = ::ChoosePixelFormat( hdc, &sPreferredFormat );
1160
1161         if(!iPixelFormat) return 0; /* couldn't find one to use */
1162
1163         for(i=1; i<=n; i++) { /* not the idiom, but it's right */
1164                 ::DescribePixelFormat( hdc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd );
1165                 w = WeightPixelFormat(pfd);
1166                 // be strict on stereo
1167                 if (!((sPreferredFormat.dwFlags ^ pfd.dwFlags) & PFD_STEREO))   {
1168                         if(w > weight) {
1169                                 weight = w;
1170                                 iPixelFormat = i;
1171                         }
1172                 }
1173         }
1174         if (weight == 0) {
1175                 // we could find the correct stereo setting, just find any suitable format
1176                 for(i=1; i<=n; i++) { /* not the idiom, but it's right */
1177                         ::DescribePixelFormat( hdc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd );
1178                         w = WeightPixelFormat(pfd);
1179                         if(w > weight) {
1180                                 weight = w;
1181                                 iPixelFormat = i;
1182                         }
1183                 }
1184         }
1185         return iPixelFormat;
1186 }
1187
1188