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