Cocoa / Mac:
[blender-staging.git] / intern / ghost / intern / GHOST_WindowCocoa.mm
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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, 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):      Maarten Gribnau 05/2001
25                                         Damien Plisson 10/2009
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 #include <Cocoa/Cocoa.h>
31
32 #ifndef MAC_OS_X_VERSION_10_6
33 //Use of the SetSystemUIMode function (64bit compatible)
34 #include <Carbon/Carbon.h>
35 #endif
36
37 /***** Multithreaded opengl code : uncomment for enabling
38 #include <OpenGL/OpenGL.h>
39 */
40  
41 #include "GHOST_WindowCocoa.h"
42 #include "GHOST_SystemCocoa.h"
43 #include "GHOST_Debug.h"
44
45
46 // Pixel Format Attributes for the windowed NSOpenGLContext
47 static NSOpenGLPixelFormatAttribute pixelFormatAttrsWindow[] =
48 {
49         NSOpenGLPFADoubleBuffer,
50         NSOpenGLPFAAccelerated,
51         //NSOpenGLPFAAllowOfflineRenderers,   // Removed to allow 10.4 builds, and 2 GPUs rendering is not used anyway
52         NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute) 32,
53         (NSOpenGLPixelFormatAttribute) 0
54 };
55
56 #pragma mark Cocoa window delegate object
57 /* live resize ugly patch
58 extern "C" {
59         struct bContext;
60         typedef struct bContext bContext;
61         bContext* ghostC;
62         extern int wm_window_timer(const bContext *C);
63         extern void wm_window_process_events(const bContext *C);
64         extern void wm_event_do_handlers(bContext *C);
65         extern void wm_event_do_notifiers(bContext *C);
66         extern void wm_draw_update(bContext *C);
67 };*/
68 @interface CocoaWindowDelegate : NSObject
69 #ifdef MAC_OS_X_VERSION_10_6
70 <NSWindowDelegate>
71 #endif
72 {
73         GHOST_SystemCocoa *systemCocoa;
74         GHOST_WindowCocoa *associatedWindow;
75 }
76
77 - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa;
78 - (void)windowWillClose:(NSNotification *)notification;
79 - (void)windowDidBecomeKey:(NSNotification *)notification;
80 - (void)windowDidResignKey:(NSNotification *)notification;
81 - (void)windowDidUpdate:(NSNotification *)notification;
82 - (void)windowDidResize:(NSNotification *)notification;
83 @end
84
85 @implementation CocoaWindowDelegate : NSObject
86 - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa
87 {
88         systemCocoa = sysCocoa;
89         associatedWindow = winCocoa;
90 }
91
92 - (void)windowWillClose:(NSNotification *)notification
93 {
94         systemCocoa->handleWindowEvent(GHOST_kEventWindowClose, associatedWindow);
95 }
96
97 - (void)windowDidBecomeKey:(NSNotification *)notification
98 {
99         systemCocoa->handleWindowEvent(GHOST_kEventWindowActivate, associatedWindow);
100 }
101
102 - (void)windowDidResignKey:(NSNotification *)notification
103 {
104         //The window is no more key when its own view becomes fullscreen
105         //but ghost doesn't know the view/window difference, so hide this fact
106         if (associatedWindow->getState() != GHOST_kWindowStateFullScreen)
107                 systemCocoa->handleWindowEvent(GHOST_kEventWindowDeactivate, associatedWindow);
108 }
109
110 - (void)windowDidUpdate:(NSNotification *)notification
111 {
112         systemCocoa->handleWindowEvent(GHOST_kEventWindowUpdate, associatedWindow);
113 }
114
115 - (void)windowDidResize:(NSNotification *)notification
116 {
117 #ifdef MAC_OS_X_VERSION_10_6
118         //if (![[notification object] inLiveResize]) {
119                 //Send event only once, at end of resize operation (when user has released mouse button)
120 #endif
121                 systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, associatedWindow);
122 #ifdef MAC_OS_X_VERSION_10_6
123         //}
124 #endif
125         /* Live resize ugly patch. Needed because live resize runs in a modal loop, not letting main loop run
126          if ([[notification object] inLiveResize]) {
127                 systemCocoa->dispatchEvents();
128                 wm_window_timer(ghostC);
129                 wm_event_do_handlers(ghostC);
130                 wm_event_do_notifiers(ghostC);
131                 wm_draw_update(ghostC);
132         }*/
133 }
134 @end
135
136 #pragma mark NSWindow subclass
137 //We need to subclass it to tell that even borderless (fullscreen), it can become key (receive user events)
138 @interface CocoaWindow: NSWindow
139 {
140
141 }
142 @end
143 @implementation CocoaWindow
144
145 -(BOOL)canBecomeKeyWindow
146 {
147         return YES;
148 }
149
150 @end
151
152
153
154 #pragma mark NSOpenGLView subclass
155 //We need to subclass it in order to give Cocoa the feeling key events are trapped
156 @interface CocoaOpenGLView : NSOpenGLView
157 {
158 }
159 @end
160 @implementation CocoaOpenGLView
161
162 - (BOOL)acceptsFirstResponder
163 {
164     return YES;
165 }
166
167 //The trick to prevent Cocoa from complaining (beeping)
168 - (void)keyDown:(NSEvent *)theEvent
169 {}
170
171 - (BOOL)isOpaque
172 {
173     return YES;
174 }
175
176 @end
177
178
179 #pragma mark initialization / finalization
180
181 NSOpenGLContext* GHOST_WindowCocoa::s_firstOpenGLcontext = nil;
182
183 GHOST_WindowCocoa::GHOST_WindowCocoa(
184         GHOST_SystemCocoa *systemCocoa,
185         const STR_String& title,
186         GHOST_TInt32 left,
187         GHOST_TInt32 top,
188         GHOST_TUns32 width,
189         GHOST_TUns32 height,
190         GHOST_TWindowState state,
191         GHOST_TDrawingContextType type,
192         const bool stereoVisual
193 ) :
194         GHOST_Window(title, left, top, width, height, state, GHOST_kDrawingContextTypeNone),
195         m_customCursor(0)
196 {
197         m_systemCocoa = systemCocoa;
198         m_fullScreen = false;
199         
200         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
201         
202
203         //Creates the window
204         NSRect rect;
205         NSSize  minSize;
206         
207         rect.origin.x = left;
208         rect.origin.y = top;
209         rect.size.width = width;
210         rect.size.height = height;
211         
212         m_window = [[CocoaWindow alloc] initWithContentRect:rect
213                                                                                    styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask
214                                                                                          backing:NSBackingStoreBuffered defer:NO];
215         if (m_window == nil) {
216                 [pool drain];
217                 return;
218         }
219         
220         //Forbid to resize the window below the blender defined minimum one
221         minSize.width = 320;
222         minSize.height = 240;
223         [m_window setContentMinSize:minSize];
224         
225         setTitle(title);
226         
227                         
228         //Creates the OpenGL View inside the window
229         NSOpenGLPixelFormat *pixelFormat =
230         [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttrsWindow];
231         
232         m_openGLView = [[CocoaOpenGLView alloc] initWithFrame:rect
233                                                                                                  pixelFormat:pixelFormat];
234         
235         [pixelFormat release];
236         
237         m_openGLContext = [m_openGLView openGLContext]; //This context will be replaced by the proper one just after
238         
239         [m_window setContentView:m_openGLView];
240         [m_window setInitialFirstResponder:m_openGLView];
241         
242         [m_window setReleasedWhenClosed:NO]; //To avoid bad pointer exception in case of user closing the window
243         
244         [m_window makeKeyAndOrderFront:nil];
245         
246         setDrawingContextType(type);
247         updateDrawingContext();
248         activateDrawingContext();
249         
250         m_tablet.Active = GHOST_kTabletModeNone;
251         
252         CocoaWindowDelegate *windowDelegate = [[CocoaWindowDelegate alloc] init];
253         [windowDelegate setSystemAndWindowCocoa:systemCocoa windowCocoa:this];
254         [m_window setDelegate:windowDelegate];
255         
256         [m_window setAcceptsMouseMovedEvents:YES];
257         
258         if (state == GHOST_kWindowStateFullScreen)
259                 setState(GHOST_kWindowStateFullScreen);
260                 
261         [pool drain];
262 }
263
264
265 GHOST_WindowCocoa::~GHOST_WindowCocoa()
266 {
267         if (m_customCursor) delete m_customCursor;
268
269     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
270         [m_openGLView release];
271         
272         if (m_window) {
273                 [m_window close];
274                 [[m_window delegate] release];
275                 [m_window release];
276                 m_window = nil;
277         }
278         
279         //Check for other blender opened windows and make the frontmost key
280         NSArray *windowsList = [NSApp orderedWindows];
281         if ([windowsList count]) {
282                 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
283         }
284         [pool drain];
285 }
286
287 #pragma mark accessors
288
289 bool GHOST_WindowCocoa::getValid() const
290 {
291     bool valid;
292     if (!m_fullScreen) {
293         valid = (m_window != 0); //&& ::IsValidWindowPtr(m_windowRef);
294     }
295     else {
296         valid = true;
297     }
298     return valid;
299 }
300
301
302 void GHOST_WindowCocoa::setTitle(const STR_String& title)
303 {
304     GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setTitle(): window invalid")
305         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
306
307         NSString *windowTitle = [[NSString alloc] initWithUTF8String:title];
308         
309         //Set associated file if applicable
310         if ([windowTitle hasPrefix:@"Blender"])
311         {
312                 NSRange fileStrRange;
313                 NSString *associatedFileName;
314                 int len;
315                 
316                 fileStrRange.location = [windowTitle rangeOfString:@"["].location+1;
317                 len = [windowTitle rangeOfString:@"]"].location - fileStrRange.location;
318         
319                 if (len >0)
320                 {
321                         fileStrRange.length = len;
322                         associatedFileName = [windowTitle substringWithRange:fileStrRange];
323                         @try {
324                                 [m_window setRepresentedFilename:associatedFileName];
325                         }
326                         @catch (NSException * e) {
327                                 printf("\nInvalid file path given in window title");
328                         }
329                         [m_window setTitle:[associatedFileName lastPathComponent]];
330                 }
331                 else {
332                         [m_window setTitle:windowTitle];
333                         [m_window setRepresentedFilename:@""];
334                 }
335
336         } else {
337                 [m_window setTitle:windowTitle];
338                 [m_window setRepresentedFilename:@""];
339         }
340
341         
342         [windowTitle release];
343         [pool drain];
344 }
345
346
347 void GHOST_WindowCocoa::getTitle(STR_String& title) const
348 {
349     GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getTitle(): window invalid")
350
351         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
352
353         NSString *windowTitle = [m_window title];
354
355         if (windowTitle != nil) {
356                 title = [windowTitle UTF8String];               
357         }
358         
359         [pool drain];
360 }
361
362
363 void GHOST_WindowCocoa::getWindowBounds(GHOST_Rect& bounds) const
364 {
365         NSRect rect;
366         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getWindowBounds(): window invalid")
367
368         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
369         
370         NSRect screenSize = [[m_window screen] visibleFrame];
371
372         rect = [m_window frame];
373
374         bounds.m_b = screenSize.size.height - (rect.origin.y -screenSize.origin.y);
375         bounds.m_l = rect.origin.x -screenSize.origin.x;
376         bounds.m_r = rect.origin.x-screenSize.origin.x + rect.size.width;
377         bounds.m_t = screenSize.size.height - (rect.origin.y + rect.size.height -screenSize.origin.y);
378         
379         [pool drain];
380 }
381
382
383 void GHOST_WindowCocoa::getClientBounds(GHOST_Rect& bounds) const
384 {
385         NSRect rect;
386         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getClientBounds(): window invalid")
387         
388         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
389         
390         if (!m_fullScreen)
391         {
392                 NSRect screenSize = [[m_window screen] visibleFrame];
393
394                 //Max window contents as screen size (excluding title bar...)
395                 NSRect contentRect = [CocoaWindow contentRectForFrameRect:screenSize
396                                                                                                          styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)];
397
398                 rect = [m_window contentRectForFrameRect:[m_window frame]];
399                 
400                 bounds.m_b = contentRect.size.height - (rect.origin.y -contentRect.origin.y);
401                 bounds.m_l = rect.origin.x -contentRect.origin.x;
402                 bounds.m_r = rect.origin.x-contentRect.origin.x + rect.size.width;
403                 bounds.m_t = contentRect.size.height - (rect.origin.y + rect.size.height -contentRect.origin.y);
404         }
405         else {
406                 NSRect screenSize = [[m_window screen] frame];
407                 
408                 bounds.m_b = screenSize.origin.y + screenSize.size.height;
409                 bounds.m_l = screenSize.origin.x;
410                 bounds.m_r = screenSize.origin.x + screenSize.size.width;
411                 bounds.m_t = screenSize.origin.y;
412         }
413         [pool drain];
414 }
415
416
417 GHOST_TSuccess GHOST_WindowCocoa::setClientWidth(GHOST_TUns32 width)
418 {
419         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setClientWidth(): window invalid")
420         GHOST_Rect cBnds, wBnds;
421         getClientBounds(cBnds);
422         if (((GHOST_TUns32)cBnds.getWidth()) != width) {
423                 NSSize size;
424                 size.width=width;
425                 size.height=cBnds.getHeight();
426                 [m_window setContentSize:size];
427         }
428         return GHOST_kSuccess;
429 }
430
431
432 GHOST_TSuccess GHOST_WindowCocoa::setClientHeight(GHOST_TUns32 height)
433 {
434         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setClientHeight(): window invalid")
435         GHOST_Rect cBnds, wBnds;
436         getClientBounds(cBnds);
437         if (((GHOST_TUns32)cBnds.getHeight()) != height) {
438                 NSSize size;
439                 size.width=cBnds.getWidth();
440                 size.height=height;
441                 [m_window setContentSize:size];
442         }
443         return GHOST_kSuccess;
444 }
445
446
447 GHOST_TSuccess GHOST_WindowCocoa::setClientSize(GHOST_TUns32 width, GHOST_TUns32 height)
448 {
449         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setClientSize(): window invalid")
450         GHOST_Rect cBnds, wBnds;
451         getClientBounds(cBnds);
452         if ((((GHOST_TUns32)cBnds.getWidth()) != width) ||
453             (((GHOST_TUns32)cBnds.getHeight()) != height)) {
454                 NSSize size;
455                 size.width=width;
456                 size.height=height;
457                 [m_window setContentSize:size];
458         }
459         return GHOST_kSuccess;
460 }
461
462
463 GHOST_TWindowState GHOST_WindowCocoa::getState() const
464 {
465         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getState(): window invalid")
466         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
467         GHOST_TWindowState state;
468         if (m_fullScreen) {
469                 state = GHOST_kWindowStateFullScreen;
470         } 
471         else if ([m_window isMiniaturized]) {
472                 state = GHOST_kWindowStateMinimized;
473         }
474         else if ([m_window isZoomed]) {
475                 state = GHOST_kWindowStateMaximized;
476         }
477         else {
478                 state = GHOST_kWindowStateNormal;
479         }
480         [pool drain];
481         return state;
482 }
483
484
485 void GHOST_WindowCocoa::screenToClient(GHOST_TInt32 inX, GHOST_TInt32 inY, GHOST_TInt32& outX, GHOST_TInt32& outY) const
486 {
487         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::screenToClient(): window invalid")
488         
489         NSPoint screenCoord;
490         NSPoint baseCoord;
491         
492         screenCoord.x = inX;
493         screenCoord.y = inY;
494         
495         baseCoord = [m_window convertScreenToBase:screenCoord];
496         
497         outX = baseCoord.x;
498         outY = baseCoord.y;
499 }
500
501
502 void GHOST_WindowCocoa::clientToScreen(GHOST_TInt32 inX, GHOST_TInt32 inY, GHOST_TInt32& outX, GHOST_TInt32& outY) const
503 {
504         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::clientToScreen(): window invalid")
505         
506         NSPoint screenCoord;
507         NSPoint baseCoord;
508         
509         baseCoord.x = inX;
510         baseCoord.y = inY;
511         
512         screenCoord = [m_window convertBaseToScreen:baseCoord];
513         
514         outX = screenCoord.x;
515         outY = screenCoord.y;
516 }
517
518
519 NSScreen* GHOST_WindowCocoa::getScreen()
520 {
521         return [m_window screen];
522 }
523
524
525 /**
526  * @note Fullscreen switch is not actual fullscreen with display capture. As this capture removes all OS X window manager features.
527  * Instead, the menu bar and the dock are hidden, and the window is made borderless and enlarged.
528  * Thus, process switch, exposé, spaces, ... still work in fullscreen mode
529  */
530 GHOST_TSuccess GHOST_WindowCocoa::setState(GHOST_TWindowState state)
531 {
532         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setState(): window invalid")
533     switch (state) {
534                 case GHOST_kWindowStateMinimized:
535             [m_window miniaturize:nil];
536             break;
537                 case GHOST_kWindowStateMaximized:
538                         [m_window zoom:nil];
539                         break;
540                 
541                 case GHOST_kWindowStateFullScreen:
542                         if (!m_fullScreen)
543                         {
544                                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
545                         
546                                 //This status change needs to be done before Cocoa call to enter fullscreen mode
547                                 //to give window delegate hint not to forward its deactivation to ghost wm that doesn't know view/window difference
548                                 m_fullScreen = true;
549
550 #ifdef MAC_OS_X_VERSION_10_6
551                                 //10.6 provides Cocoa functions to autoshow menu bar, and to change a window style
552                                 //Hide menu & dock if needed
553                                 if ([[m_window screen] isEqual:[NSScreen mainScreen]])
554                                 {
555                                         [NSApp setPresentationOptions:(NSApplicationPresentationHideDock | NSApplicationPresentationAutoHideMenuBar)];
556                                 }
557                                 //Make window borderless and enlarge it
558                                 [m_window setStyleMask:NSBorderlessWindowMask];
559                                 [m_window setFrame:[[m_window screen] frame] display:YES];
560                                 [m_window makeFirstResponder:m_openGLView];
561 #else
562                                 //With 10.5, we need to create a new window to change its style to borderless
563                                 //Hide menu & dock if needed
564                                 if ([[m_window screen] isEqual:[NSScreen mainScreen]])
565                                 {
566                                         //Cocoa function in 10.5 does not allow to set the menu bar in auto-show mode [NSMenu setMenuBarVisible:NO];
567                                         //One of the very few 64bit compatible Carbon function
568                                         SetSystemUIMode(kUIModeAllHidden,kUIOptionAutoShowMenuBar);
569                                 }
570                                 //Create a fullscreen borderless window
571                                 CocoaWindow *tmpWindow = [[CocoaWindow alloc]
572                                                                                   initWithContentRect:[[m_window screen] frame]
573                                                                                   styleMask:NSBorderlessWindowMask
574                                                                                   backing:NSBackingStoreBuffered
575                                                                                   defer:YES];
576                                 //Copy current window parameters
577                                 [tmpWindow setTitle:[m_window title]];
578                                 [tmpWindow setRepresentedFilename:[m_window representedFilename]];
579                                 [tmpWindow setReleasedWhenClosed:NO];
580                                 [tmpWindow setAcceptsMouseMovedEvents:YES];
581                                 [tmpWindow setDelegate:[m_window delegate]];
582                                 
583                                 //Assign the openGL view to the new window
584                                 [tmpWindow setContentView:m_openGLView];
585                                 
586                                 //Show the new window
587                                 [tmpWindow makeKeyAndOrderFront:nil];
588                                 //Close and release old window
589                                 [m_window setDelegate:nil]; // To avoid the notification of "window closed" event
590                                 [m_window close];
591                                 [m_window release];
592                                 m_window = tmpWindow;
593 #endif
594                         
595                                 //Tell WM of view new size
596                                 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, this);
597                                 
598                                 [pool drain];
599                                 }
600                         break;
601                 case GHOST_kWindowStateNormal:
602         default:
603                         if (m_fullScreen)
604                         {
605                                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
606                                 m_fullScreen = false;
607
608                                 //Exit fullscreen
609 #ifdef MAC_OS_X_VERSION_10_6
610                                 //Show again menu & dock if needed
611                                 if ([[m_window screen] isEqual:[NSScreen mainScreen]])
612                                 {
613                                         [NSApp setPresentationOptions:NSApplicationPresentationDefault];
614                                 }
615                                 //Make window normal and resize it
616                                 [m_window setStyleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)];
617                                 [m_window setFrame:[[m_window screen] visibleFrame] display:YES];
618                                 [m_window makeFirstResponder:m_openGLView];
619 #else
620                                 //With 10.5, we need to create a new window to change its style to borderless
621                                 //Show menu & dock if needed
622                                 if ([[m_window screen] isEqual:[NSScreen mainScreen]])
623                                 {
624                                         //Cocoa function in 10.5 does not allow to set the menu bar in auto-show mode [NSMenu setMenuBarVisible:YES];
625                                         SetSystemUIMode(kUIModeNormal, 0); //One of the very few 64bit compatible Carbon function
626                                 }
627                                 //Create a fullscreen borderless window
628                                 CocoaWindow *tmpWindow = [[CocoaWindow alloc]
629                                                                                   initWithContentRect:[[m_window screen] frame]
630                                                                                                         styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
631                                                                                                           backing:NSBackingStoreBuffered
632                                                                                                                 defer:YES];
633                                 //Copy current window parameters
634                                 [tmpWindow setTitle:[m_window title]];
635                                 [tmpWindow setRepresentedFilename:[m_window representedFilename]];
636                                 [tmpWindow setReleasedWhenClosed:NO];
637                                 [tmpWindow setAcceptsMouseMovedEvents:YES];
638                                 [tmpWindow setDelegate:[m_window delegate]];
639                                 
640                                 //Assign the openGL view to the new window
641                                 [tmpWindow setContentView:m_openGLView];
642                                 
643                                 //Show the new window
644                                 [tmpWindow makeKeyAndOrderFront:nil];
645                                 //Close and release old window
646                                 [m_window setDelegate:nil]; // To avoid the notification of "window closed" event
647                                 [m_window close];
648                                 [m_window release];
649                                 m_window = tmpWindow;
650 #endif
651                         
652                                 //Tell WM of view new size
653                                 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, this);
654                                 
655                                 [pool drain];
656                         }
657             else if ([m_window isMiniaturized])
658                                 [m_window deminiaturize:nil];
659                         else if ([m_window isZoomed])
660                                 [m_window zoom:nil];
661             break;
662     }
663     return GHOST_kSuccess;
664 }
665
666 GHOST_TSuccess GHOST_WindowCocoa::setModifiedState(bool isUnsavedChanges)
667 {
668         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
669         
670         [m_window setDocumentEdited:isUnsavedChanges];
671         
672         [pool drain];
673         return GHOST_Window::setModifiedState(isUnsavedChanges);
674 }
675
676
677
678 GHOST_TSuccess GHOST_WindowCocoa::setOrder(GHOST_TWindowOrder order)
679 {
680         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setOrder(): window invalid")
681     if (order == GHOST_kWindowOrderTop) {
682                 [m_window orderFront:nil];
683     }
684     else {
685                 [m_window orderBack:nil];
686     }
687     return GHOST_kSuccess;
688 }
689
690 #pragma mark Drawing context
691
692 /*#define  WAIT_FOR_VSYNC 1*/
693
694 GHOST_TSuccess GHOST_WindowCocoa::swapBuffers()
695 {
696     if (m_drawingContextType == GHOST_kDrawingContextTypeOpenGL) {
697         if (m_openGLContext != nil) {
698                         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
699                         [m_openGLContext flushBuffer];
700                         [pool drain];
701             return GHOST_kSuccess;
702         }
703     }
704     return GHOST_kFailure;
705 }
706
707 GHOST_TSuccess GHOST_WindowCocoa::updateDrawingContext()
708 {
709         if (m_drawingContextType == GHOST_kDrawingContextTypeOpenGL) {
710                 if (m_openGLContext != nil) {
711                         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
712                         [m_openGLContext update];
713                         [pool drain];
714                         return GHOST_kSuccess;
715                 }
716         }
717         return GHOST_kFailure;
718 }
719
720 GHOST_TSuccess GHOST_WindowCocoa::activateDrawingContext()
721 {
722         if (m_drawingContextType == GHOST_kDrawingContextTypeOpenGL) {
723                 if (m_openGLContext != nil) {
724                         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
725                         [m_openGLContext makeCurrentContext];
726                         [pool drain];
727                         return GHOST_kSuccess;
728                 }
729         }
730         return GHOST_kFailure;
731 }
732
733
734 GHOST_TSuccess GHOST_WindowCocoa::installDrawingContext(GHOST_TDrawingContextType type)
735 {
736         GHOST_TSuccess success = GHOST_kFailure;
737         
738         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
739         
740         NSOpenGLPixelFormat *pixelFormat;
741         NSOpenGLContext *tmpOpenGLContext;
742         
743         /***** Multithreaded opengl code : uncomment for enabling
744         CGLContextObj cglCtx;
745         */
746          
747         switch (type) {
748                 case GHOST_kDrawingContextTypeOpenGL:
749                         if (!getValid()) break;
750                                         
751                         pixelFormat = [m_openGLView pixelFormat];
752                         tmpOpenGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
753                                                                                                                           shareContext:s_firstOpenGLcontext];
754                         if (tmpOpenGLContext == nil) {
755                                 success = GHOST_kFailure;
756                                 break;
757                         }
758                         
759                         //Switch openGL to multhreaded mode
760                         /******* Multithreaded opengl code : uncomment for enabling
761                         cglCtx = (CGLContextObj)[tmpOpenGLContext CGLContextObj];
762                         if (CGLEnable(cglCtx, kCGLCEMPEngine) == kCGLNoError)
763                                 printf("\nSwitched openGL to multithreaded mode");
764                          */
765                         
766                         if (!s_firstOpenGLcontext) s_firstOpenGLcontext = tmpOpenGLContext;
767 #ifdef WAIT_FOR_VSYNC
768                                 /* wait for vsync, to avoid tearing artifacts */
769                                 [tmpOpenGLContext setValues:1 forParameter:NSOpenGLCPSwapInterval];
770 #endif
771                                 [m_openGLView setOpenGLContext:tmpOpenGLContext];
772                                 [tmpOpenGLContext setView:m_openGLView];
773                                 
774                                 m_openGLContext = tmpOpenGLContext;
775                         break;
776                 
777                 case GHOST_kDrawingContextTypeNone:
778                         success = GHOST_kSuccess;
779                         break;
780                 
781                 default:
782                         break;
783         }
784         [pool drain];
785         return success;
786 }
787
788
789 GHOST_TSuccess GHOST_WindowCocoa::removeDrawingContext()
790 {
791         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
792         switch (m_drawingContextType) {
793                 case GHOST_kDrawingContextTypeOpenGL:
794                         if (m_openGLContext)
795                         {
796                                 [m_openGLView clearGLContext];
797                                 if (s_firstOpenGLcontext == m_openGLContext) s_firstOpenGLcontext = nil;
798                                 m_openGLContext = nil;
799                         }
800                         [pool drain];
801                         return GHOST_kSuccess;
802                 case GHOST_kDrawingContextTypeNone:
803                         [pool drain];
804                         return GHOST_kSuccess;
805                         break;
806                 default:
807                         [pool drain];
808                         return GHOST_kFailure;
809         }
810 }
811
812
813 GHOST_TSuccess GHOST_WindowCocoa::invalidate()
814 {
815         GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::invalidate(): window invalid")
816         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
817         [m_openGLView setNeedsDisplay:YES];
818         [pool drain];
819         return GHOST_kSuccess;
820 }
821
822 #pragma mark Cursor handling
823
824 void GHOST_WindowCocoa::loadCursor(bool visible, GHOST_TStandardCursor cursor) const
825 {
826         static bool systemCursorVisible = true;
827         
828         NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
829
830         NSCursor *tmpCursor =nil;
831         
832         if (visible != systemCursorVisible) {
833                 if (visible) {
834                         [NSCursor unhide];
835                         systemCursorVisible = true;
836                 }
837                 else {
838                         [NSCursor hide];
839                         systemCursorVisible = false;
840                 }
841         }
842
843         if (cursor == GHOST_kStandardCursorCustom && m_customCursor) {
844                 tmpCursor = m_customCursor;
845         } else {
846                 switch (cursor) {
847                         case GHOST_kStandardCursorDestroy:
848                                 tmpCursor = [NSCursor disappearingItemCursor];
849                                 break;
850                         case GHOST_kStandardCursorText:
851                                 tmpCursor = [NSCursor IBeamCursor];
852                                 break;
853                         case GHOST_kStandardCursorCrosshair:
854                                 tmpCursor = [NSCursor crosshairCursor];
855                                 break;
856                         case GHOST_kStandardCursorUpDown:
857                                 tmpCursor = [NSCursor resizeUpDownCursor];
858                                 break;
859                         case GHOST_kStandardCursorLeftRight:
860                                 tmpCursor = [NSCursor resizeLeftRightCursor];
861                                 break;
862                         case GHOST_kStandardCursorTopSide:
863                                 tmpCursor = [NSCursor resizeUpCursor];
864                                 break;
865                         case GHOST_kStandardCursorBottomSide:
866                                 tmpCursor = [NSCursor resizeDownCursor];
867                                 break;
868                         case GHOST_kStandardCursorLeftSide:
869                                 tmpCursor = [NSCursor resizeLeftCursor];
870                                 break;
871                         case GHOST_kStandardCursorRightSide:
872                                 tmpCursor = [NSCursor resizeRightCursor];
873                                 break;
874                         case GHOST_kStandardCursorRightArrow:
875                         case GHOST_kStandardCursorInfo:
876                         case GHOST_kStandardCursorLeftArrow:
877                         case GHOST_kStandardCursorHelp:
878                         case GHOST_kStandardCursorCycle:
879                         case GHOST_kStandardCursorSpray:
880                         case GHOST_kStandardCursorWait:
881                         case GHOST_kStandardCursorTopLeftCorner:
882                         case GHOST_kStandardCursorTopRightCorner:
883                         case GHOST_kStandardCursorBottomRightCorner:
884                         case GHOST_kStandardCursorBottomLeftCorner:
885                         case GHOST_kStandardCursorDefault:
886                         default:
887                                 tmpCursor = [NSCursor arrowCursor];
888                                 break;
889                 };
890         }
891         [tmpCursor set];
892         [pool drain];
893 }
894
895
896
897 GHOST_TSuccess GHOST_WindowCocoa::setWindowCursorVisibility(bool visible)
898 {
899         if ([m_window isVisible]) {
900                 loadCursor(visible, getCursorShape());
901         }
902         
903         return GHOST_kSuccess;
904 }
905
906
907 GHOST_TSuccess GHOST_WindowCocoa::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
908 {
909         GHOST_TSuccess err = GHOST_kSuccess;
910         
911         if (mode != GHOST_kGrabDisable)
912         {
913                 //No need to perform grab without warp as it is always on in OS X
914                 if(mode != GHOST_kGrabNormal) {
915                         GHOST_TInt32 x_old,y_old;
916
917                         m_systemCocoa->getCursorPosition(x_old,y_old);
918                         screenToClient(x_old, y_old, m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]);
919                         //Warp position is stored in client (window base) coordinates
920                         setCursorGrabAccum(0, 0);
921                         
922                         if(mode == GHOST_kGrabHide) {
923                                 setWindowCursorVisibility(false);
924                         }
925                         
926                         //Dissociate cursor position even for warp mode, to allow mouse acceleration to work even when warping the cursor
927                         err = CGAssociateMouseAndMouseCursorPosition(false) == kCGErrorSuccess ? GHOST_kSuccess : GHOST_kFailure;
928                 }
929         }
930         else {
931                 if(m_cursorGrab==GHOST_kGrabHide)
932                 {
933                         //No need to set again cursor position, as it has not changed for Cocoa
934                         setWindowCursorVisibility(true);
935                 }
936                 
937                 err = CGAssociateMouseAndMouseCursorPosition(true) == kCGErrorSuccess ? GHOST_kSuccess : GHOST_kFailure;
938                 /* Almost works without but important otherwise the mouse GHOST location can be incorrect on exit */
939                 setCursorGrabAccum(0, 0);
940                 m_cursorGrabBounds.m_l= m_cursorGrabBounds.m_r= -1; /* disable */
941         }
942         return err;
943 }
944         
945 GHOST_TSuccess GHOST_WindowCocoa::setWindowCursorShape(GHOST_TStandardCursor shape)
946 {
947         if (m_customCursor) {
948                 [m_customCursor release];
949                 m_customCursor = nil;
950         }
951
952         if ([m_window isVisible]) {
953                 loadCursor(getCursorVisibility(), shape);
954         }
955         
956         return GHOST_kSuccess;
957 }
958
959 /** Reverse the bits in a GHOST_TUns8
960 static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch)
961 {
962         ch= ((ch>>1)&0x55) | ((ch<<1)&0xAA);
963         ch= ((ch>>2)&0x33) | ((ch<<2)&0xCC);
964         ch= ((ch>>4)&0x0F) | ((ch<<4)&0xF0);
965         return ch;
966 }
967 */
968
969
970 /** Reverse the bits in a GHOST_TUns16 */
971 static GHOST_TUns16 uns16ReverseBits(GHOST_TUns16 shrt)
972 {
973         shrt= ((shrt>>1)&0x5555) | ((shrt<<1)&0xAAAA);
974         shrt= ((shrt>>2)&0x3333) | ((shrt<<2)&0xCCCC);
975         shrt= ((shrt>>4)&0x0F0F) | ((shrt<<4)&0xF0F0);
976         shrt= ((shrt>>8)&0x00FF) | ((shrt<<8)&0xFF00);
977         return shrt;
978 }
979
980 GHOST_TSuccess GHOST_WindowCocoa::setWindowCustomCursorShape(GHOST_TUns8 *bitmap, GHOST_TUns8 *mask,
981                                         int sizex, int sizey, int hotX, int hotY, int fg_color, int bg_color)
982 {
983         int y,nbUns16;
984         NSPoint hotSpotPoint;
985         NSBitmapImageRep *cursorImageRep;
986         NSImage *cursorImage;
987         NSSize imSize;
988         GHOST_TUns16 *cursorBitmap;
989         
990         
991         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
992         
993         if (m_customCursor) {
994                 [m_customCursor release];
995                 m_customCursor = nil;
996         }
997         
998
999         cursorImageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
1000                                                                                                                          pixelsWide:sizex
1001                                                                                                                          pixelsHigh:sizey
1002                                                                                                                   bitsPerSample:1 
1003                                                                                                                 samplesPerPixel:2
1004                                                                                                                            hasAlpha:YES
1005                                                                                                                            isPlanar:YES
1006                                                                                                                  colorSpaceName:NSDeviceWhiteColorSpace
1007                                                                                                                         bytesPerRow:(sizex/8 + (sizex%8 >0 ?1:0))
1008                                                                                                                    bitsPerPixel:1];
1009         
1010         
1011         cursorBitmap = (GHOST_TUns16*)[cursorImageRep bitmapData];
1012         nbUns16 = [cursorImageRep bytesPerPlane]/2;
1013         
1014         for (y=0; y<nbUns16; y++) {
1015 #if !defined(__LITTLE_ENDIAN__)
1016                 cursorBitmap[y] = ~uns16ReverseBits((bitmap[2*y]<<0) | (bitmap[2*y+1]<<8));
1017                 cursorBitmap[nbUns16+y] = uns16ReverseBits((mask[2*y]<<0) | (mask[2*y+1]<<8));
1018 #else
1019                 cursorBitmap[y] = ~uns16ReverseBits((bitmap[2*y+1]<<0) | (bitmap[2*y]<<8));
1020                 cursorBitmap[nbUns16+y] = uns16ReverseBits((mask[2*y+1]<<0) | (mask[2*y]<<8));
1021 #endif
1022                 
1023         }
1024         
1025         
1026         imSize.width = sizex;
1027         imSize.height= sizey;
1028         cursorImage = [[NSImage alloc] initWithSize:imSize];
1029         [cursorImage addRepresentation:cursorImageRep];
1030         
1031         hotSpotPoint.x = hotX;
1032         hotSpotPoint.y = hotY;
1033         
1034         //foreground and background color parameter is not handled for now (10.6)
1035         m_customCursor = [[NSCursor alloc] initWithImage:cursorImage
1036                                                                                          hotSpot:hotSpotPoint];
1037         
1038         [cursorImageRep release];
1039         [cursorImage release];
1040         
1041         if ([m_window isVisible]) {
1042                 loadCursor(getCursorVisibility(), GHOST_kStandardCursorCustom);
1043         }
1044         [pool drain];
1045         return GHOST_kSuccess;
1046 }
1047
1048 GHOST_TSuccess GHOST_WindowCocoa::setWindowCustomCursorShape(GHOST_TUns8 bitmap[16][2], 
1049                                                                                                 GHOST_TUns8 mask[16][2], int hotX, int hotY)
1050 {
1051         return setWindowCustomCursorShape((GHOST_TUns8*)bitmap, (GHOST_TUns8*) mask, 16, 16, hotX, hotY, 0, 1);
1052 }