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