4582dfb2a4970750108645584758dbd1da16ce47
[blender-staging.git] / intern / ghost / intern / GHOST_SystemCocoa.mm
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributors: Maarten Gribnau 05/2001
24  *               Damien Plisson 09/2009
25  *               Jens Verwiebe   10/2014
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 #import <Cocoa/Cocoa.h>
31
32 /*For the currently not ported to Cocoa keyboard layout functions (64bit & 10.6 compatible)*/
33 #include <Carbon/Carbon.h>
34
35 #include <sys/time.h>
36 #include <sys/types.h>
37 #include <sys/sysctl.h>
38
39 #include "GHOST_SystemCocoa.h"
40
41 #include "GHOST_DisplayManagerCocoa.h"
42 #include "GHOST_EventKey.h"
43 #include "GHOST_EventButton.h"
44 #include "GHOST_EventCursor.h"
45 #include "GHOST_EventWheel.h"
46 #include "GHOST_EventTrackpad.h"
47 #include "GHOST_EventDragnDrop.h"
48 #include "GHOST_EventString.h"
49 #include "GHOST_TimerManager.h"
50 #include "GHOST_TimerTask.h"
51 #include "GHOST_WindowManager.h"
52 #include "GHOST_WindowCocoa.h"
53
54 #ifdef WITH_INPUT_NDOF
55   #include "GHOST_NDOFManagerCocoa.h"
56 #endif
57
58 #include "AssertMacros.h"
59
60
61 #pragma mark KeyMap, mouse converters
62
63 static GHOST_TButtonMask convertButton(int button)
64 {
65         switch (button) {
66                 case 0:
67                         return GHOST_kButtonMaskLeft;
68                 case 1:
69                         return GHOST_kButtonMaskRight;
70                 case 2:
71                         return GHOST_kButtonMaskMiddle;
72                 case 3:
73                         return GHOST_kButtonMaskButton4;
74                 case 4:
75                         return GHOST_kButtonMaskButton5;
76                 case 5:
77                         return GHOST_kButtonMaskButton6;
78                 case 6:
79                         return GHOST_kButtonMaskButton7;
80                 default:
81                         return GHOST_kButtonMaskLeft;
82         }
83 }
84
85 /**
86  * Converts Mac rawkey codes (same for Cocoa & Carbon)
87  * into GHOST key codes
88  * \param rawCode The raw physical key code
89  * \param recvChar the character ignoring modifiers (except for shift)
90  * \return Ghost key code
91  */
92 static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction) 
93 {
94         //printf("\nrecvchar %c 0x%x",recvChar,recvChar);
95         switch (rawCode) {
96                 /*Physical keycodes not used due to map changes in int'l keyboards
97                 case kVK_ANSI_A:        return GHOST_kKeyA;
98                 case kVK_ANSI_B:        return GHOST_kKeyB;
99                 case kVK_ANSI_C:        return GHOST_kKeyC;
100                 case kVK_ANSI_D:        return GHOST_kKeyD;
101                 case kVK_ANSI_E:        return GHOST_kKeyE;
102                 case kVK_ANSI_F:        return GHOST_kKeyF;
103                 case kVK_ANSI_G:        return GHOST_kKeyG;
104                 case kVK_ANSI_H:        return GHOST_kKeyH;
105                 case kVK_ANSI_I:        return GHOST_kKeyI;
106                 case kVK_ANSI_J:        return GHOST_kKeyJ;
107                 case kVK_ANSI_K:        return GHOST_kKeyK;
108                 case kVK_ANSI_L:        return GHOST_kKeyL;
109                 case kVK_ANSI_M:        return GHOST_kKeyM;
110                 case kVK_ANSI_N:        return GHOST_kKeyN;
111                 case kVK_ANSI_O:        return GHOST_kKeyO;
112                 case kVK_ANSI_P:        return GHOST_kKeyP;
113                 case kVK_ANSI_Q:        return GHOST_kKeyQ;
114                 case kVK_ANSI_R:        return GHOST_kKeyR;
115                 case kVK_ANSI_S:        return GHOST_kKeyS;
116                 case kVK_ANSI_T:        return GHOST_kKeyT;
117                 case kVK_ANSI_U:        return GHOST_kKeyU;
118                 case kVK_ANSI_V:        return GHOST_kKeyV;
119                 case kVK_ANSI_W:        return GHOST_kKeyW;
120                 case kVK_ANSI_X:        return GHOST_kKeyX;
121                 case kVK_ANSI_Y:        return GHOST_kKeyY;
122                 case kVK_ANSI_Z:        return GHOST_kKeyZ;*/
123
124                 /* Numbers keys mapped to handle some int'l keyboard (e.g. French)*/
125                 case kVK_ISO_Section: return    GHOST_kKeyUnknown;
126                 case kVK_ANSI_1:        return GHOST_kKey1;
127                 case kVK_ANSI_2:        return GHOST_kKey2;
128                 case kVK_ANSI_3:        return GHOST_kKey3;
129                 case kVK_ANSI_4:        return GHOST_kKey4;
130                 case kVK_ANSI_5:        return GHOST_kKey5;
131                 case kVK_ANSI_6:        return GHOST_kKey6;
132                 case kVK_ANSI_7:        return GHOST_kKey7;
133                 case kVK_ANSI_8:        return GHOST_kKey8;
134                 case kVK_ANSI_9:        return GHOST_kKey9;
135                 case kVK_ANSI_0:        return GHOST_kKey0;
136
137                 case kVK_ANSI_Keypad0:                  return GHOST_kKeyNumpad0;
138                 case kVK_ANSI_Keypad1:                  return GHOST_kKeyNumpad1;
139                 case kVK_ANSI_Keypad2:                  return GHOST_kKeyNumpad2;
140                 case kVK_ANSI_Keypad3:                  return GHOST_kKeyNumpad3;
141                 case kVK_ANSI_Keypad4:                  return GHOST_kKeyNumpad4;
142                 case kVK_ANSI_Keypad5:                  return GHOST_kKeyNumpad5;
143                 case kVK_ANSI_Keypad6:                  return GHOST_kKeyNumpad6;
144                 case kVK_ANSI_Keypad7:                  return GHOST_kKeyNumpad7;
145                 case kVK_ANSI_Keypad8:                  return GHOST_kKeyNumpad8;
146                 case kVK_ANSI_Keypad9:                  return GHOST_kKeyNumpad9;
147                 case kVK_ANSI_KeypadDecimal:    return GHOST_kKeyNumpadPeriod;
148                 case kVK_ANSI_KeypadEnter:              return GHOST_kKeyNumpadEnter;
149                 case kVK_ANSI_KeypadPlus:               return GHOST_kKeyNumpadPlus;
150                 case kVK_ANSI_KeypadMinus:              return GHOST_kKeyNumpadMinus;
151                 case kVK_ANSI_KeypadMultiply:   return GHOST_kKeyNumpadAsterisk;
152                 case kVK_ANSI_KeypadDivide:     return GHOST_kKeyNumpadSlash;
153                 case kVK_ANSI_KeypadClear:              return GHOST_kKeyUnknown;
154
155                 case kVK_F1:                            return GHOST_kKeyF1;
156                 case kVK_F2:                            return GHOST_kKeyF2;
157                 case kVK_F3:                            return GHOST_kKeyF3;
158                 case kVK_F4:                            return GHOST_kKeyF4;
159                 case kVK_F5:                            return GHOST_kKeyF5;
160                 case kVK_F6:                            return GHOST_kKeyF6;
161                 case kVK_F7:                            return GHOST_kKeyF7;
162                 case kVK_F8:                            return GHOST_kKeyF8;
163                 case kVK_F9:                            return GHOST_kKeyF9;
164                 case kVK_F10:                           return GHOST_kKeyF10;
165                 case kVK_F11:                           return GHOST_kKeyF11;
166                 case kVK_F12:                           return GHOST_kKeyF12;
167                 case kVK_F13:                           return GHOST_kKeyF13;
168                 case kVK_F14:                           return GHOST_kKeyF14;
169                 case kVK_F15:                           return GHOST_kKeyF15;
170                 case kVK_F16:                           return GHOST_kKeyF16;
171                 case kVK_F17:                           return GHOST_kKeyF17;
172                 case kVK_F18:                           return GHOST_kKeyF18;
173                 case kVK_F19:                           return GHOST_kKeyF19;
174                 case kVK_F20:                           return GHOST_kKeyF20;
175
176                 case kVK_UpArrow:                       return GHOST_kKeyUpArrow;
177                 case kVK_DownArrow:                     return GHOST_kKeyDownArrow;
178                 case kVK_LeftArrow:                     return GHOST_kKeyLeftArrow;
179                 case kVK_RightArrow:            return GHOST_kKeyRightArrow;
180
181                 case kVK_Return:                        return GHOST_kKeyEnter;
182                 case kVK_Delete:                        return GHOST_kKeyBackSpace;
183                 case kVK_ForwardDelete:         return GHOST_kKeyDelete;
184                 case kVK_Escape:                        return GHOST_kKeyEsc;
185                 case kVK_Tab:                           return GHOST_kKeyTab;
186                 case kVK_Space:                         return GHOST_kKeySpace;
187
188                 case kVK_Home:                          return GHOST_kKeyHome;
189                 case kVK_End:                           return GHOST_kKeyEnd;
190                 case kVK_PageUp:                        return GHOST_kKeyUpPage;
191                 case kVK_PageDown:                      return GHOST_kKeyDownPage;
192
193                 /*case kVK_ANSI_Minus:          return GHOST_kKeyMinus;
194                 case kVK_ANSI_Equal:            return GHOST_kKeyEqual;
195                 case kVK_ANSI_Comma:            return GHOST_kKeyComma;
196                 case kVK_ANSI_Period:           return GHOST_kKeyPeriod;
197                 case kVK_ANSI_Slash:            return GHOST_kKeySlash;
198                 case kVK_ANSI_Semicolon:        return GHOST_kKeySemicolon;
199                 case kVK_ANSI_Quote:            return GHOST_kKeyQuote;
200                 case kVK_ANSI_Backslash:        return GHOST_kKeyBackslash;
201                 case kVK_ANSI_LeftBracket:      return GHOST_kKeyLeftBracket;
202                 case kVK_ANSI_RightBracket:     return GHOST_kKeyRightBracket;
203                 case kVK_ANSI_Grave:            return GHOST_kKeyAccentGrave;*/
204
205                 case kVK_VolumeUp:
206                 case kVK_VolumeDown:
207                 case kVK_Mute:
208                         return GHOST_kKeyUnknown;
209
210                 default:
211                 {
212                         /* alphanumerical or punctuation key that is remappable in int'l keyboards */
213                         if ((recvChar >= 'A') && (recvChar <= 'Z')) {
214                                 return (GHOST_TKey) (recvChar - 'A' + GHOST_kKeyA);
215                         }
216                         else if ((recvChar >= 'a') && (recvChar <= 'z')) {
217                                 return (GHOST_TKey) (recvChar - 'a' + GHOST_kKeyA);
218                         }
219                         else {
220                                 /* Leopard and Snow Leopard 64bit compatible API*/
221                                 CFDataRef uchrHandle; /*the keyboard layout*/
222                                 TISInputSourceRef kbdTISHandle;
223
224                                 kbdTISHandle = TISCopyCurrentKeyboardLayoutInputSource();
225                                 uchrHandle = (CFDataRef)TISGetInputSourceProperty(kbdTISHandle,kTISPropertyUnicodeKeyLayoutData);
226                                 CFRelease(kbdTISHandle);
227
228                                 /*get actual character value of the "remappable" keys in int'l keyboards,
229                                 if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
230                                 then fallback on using the received charactersIgnoringModifiers */
231                                 if (uchrHandle) {
232                                         UInt32 deadKeyState=0;
233                                         UniCharCount actualStrLength=0;
234                                         
235                                         UCKeyTranslate((UCKeyboardLayout*)CFDataGetBytePtr(uchrHandle), rawCode, keyAction, 0,
236                                                        LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
237                                 }
238
239                                 switch (recvChar) {
240                                         case '-':       return GHOST_kKeyMinus;
241                                         case '+':       return GHOST_kKeyPlus;
242                                         case '=':       return GHOST_kKeyEqual;
243                                         case ',':       return GHOST_kKeyComma;
244                                         case '.':       return GHOST_kKeyPeriod;
245                                         case '/':       return GHOST_kKeySlash;
246                                         case ';':       return GHOST_kKeySemicolon;
247                                         case '\'':      return GHOST_kKeyQuote;
248                                         case '\\':      return GHOST_kKeyBackslash;
249                                         case '[':       return GHOST_kKeyLeftBracket;
250                                         case ']':       return GHOST_kKeyRightBracket;
251                                         case '`':       return GHOST_kKeyAccentGrave;
252                                         default:
253                                                 return GHOST_kKeyUnknown;
254                                 }
255                         }
256                 }
257         }
258         return GHOST_kKeyUnknown;
259 }
260
261 #pragma mark Utility functions
262
263 #define FIRSTFILEBUFLG 512
264 static bool g_hasFirstFile = false;
265 static char g_firstFileBuf[512];
266
267 //TODO:Need to investigate this. Function called too early in creator.c to have g_hasFirstFile == true
268 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG])
269 {
270         if (g_hasFirstFile) {
271                 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1);
272                 buf[FIRSTFILEBUFLG - 1] = '\0';
273                 return 1;
274         }
275         else {
276                 return 0;
277         }
278 }
279
280 #pragma mark Cocoa objects
281
282 /**
283  * CocoaAppDelegate
284  * ObjC object to capture applicationShouldTerminate, and send quit event
285  **/
286 #if defined(__clang_major__) && __clang_major__ <= 7
287 /* FIXME(merwin & Juicyfruit): long-term fix for proper protocol to use
288  * merwin thinks NSApplicationDelegate is the correct protocol here. Has been around since 10.6 so we should be good... what's the problem?
289  * https://developer.apple.com/reference/appkit/nsapplicationdelegate?language=objc
290  */
291 @interface CocoaAppDelegate : NSObject <NSFileManagerDelegate> {
292 #else
293 /* for Xcode 8 */
294 @interface CocoaAppDelegate : NSObject <NSApplicationDelegate> {
295 #endif
296
297         GHOST_SystemCocoa *systemCocoa;
298 }
299 - (void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa;
300 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
301 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
302 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
303 - (void)applicationWillTerminate:(NSNotification *)aNotification;
304 - (void)applicationWillBecomeActive:(NSNotification *)aNotification;
305 - (void)toggleFullScreen:(NSNotification *)notification;
306 @end
307
308 @implementation CocoaAppDelegate : NSObject
309 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
310 {
311         systemCocoa = sysCocoa;
312 }
313
314 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
315 {
316         // raise application to front, convenient when starting from the terminal
317         // and important for launching the animation player. we call this after the
318         // application finishes launching, as doing it earlier can make us end up
319         // with a frontmost window but an inactive application
320         [NSApp activateIgnoringOtherApps:YES];
321 }
322
323 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
324 {
325         return systemCocoa->handleOpenDocumentRequest(filename);
326 }
327
328 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
329 {
330         //TODO: implement graceful termination through Cocoa mechanism to avoid session log off to be canceled
331         //Note that Cmd+Q is already handled by keyhandler
332         if (systemCocoa->handleQuitRequest() == GHOST_kExitNow)
333                 return NSTerminateCancel;//NSTerminateNow;
334         else
335                 return NSTerminateCancel;
336 }
337
338 // To avoid canceling a log off process, we must use Cocoa termination process
339 // And this function is the only chance to perform clean up
340 // So WM_exit needs to be called directly, as the event loop will never run before termination
341 - (void)applicationWillTerminate:(NSNotification *)aNotification
342 {
343         /*G.is_break = FALSE; //Let Cocoa perform the termination at the end
344         WM_exit(C);*/
345 }
346
347 - (void)applicationWillBecomeActive:(NSNotification *)aNotification
348 {
349         systemCocoa->handleApplicationBecomeActiveEvent();
350 }
351
352 - (void)toggleFullScreen:(NSNotification *)notification
353 {
354 }
355
356 @end
357
358
359 #pragma mark initialization/finalization
360
361 GHOST_SystemCocoa::GHOST_SystemCocoa()
362 {
363         int mib[2];
364         struct timeval boottime;
365         size_t len;
366         char *rstring = NULL;
367
368         m_modifierMask =0;
369         m_outsideLoopEventProcessed = false;
370         m_needDelayedApplicationBecomeActiveEventProcessing = false;
371         m_displayManager = new GHOST_DisplayManagerCocoa ();
372         GHOST_ASSERT(m_displayManager, "GHOST_SystemCocoa::GHOST_SystemCocoa(): m_displayManager==0\n");
373         m_displayManager->initialize();
374
375         //NSEvent timeStamp is given in system uptime, state start date is boot time
376         mib[0] = CTL_KERN;
377         mib[1] = KERN_BOOTTIME;
378         len = sizeof(struct timeval);
379
380         sysctl(mib, 2, &boottime, &len, NULL, 0);
381         m_start_time = ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
382
383         //Detect multitouch trackpad
384         mib[0] = CTL_HW;
385         mib[1] = HW_MODEL;
386         sysctl( mib, 2, NULL, &len, NULL, 0 );
387         rstring = (char*)malloc( len );
388         sysctl( mib, 2, rstring, &len, NULL, 0 );
389
390         free( rstring );
391         rstring = NULL;
392
393         m_ignoreWindowSizedMessages = false;
394         m_ignoreMomentumScroll = false;
395         m_multiTouchScroll = false;
396 }
397
398 GHOST_SystemCocoa::~GHOST_SystemCocoa()
399 {
400 }
401
402
403 GHOST_TSuccess GHOST_SystemCocoa::init()
404 {
405         GHOST_TSuccess success = GHOST_System::init();
406         if (success) {
407
408 #ifdef WITH_INPUT_NDOF
409                 m_ndofManager = new GHOST_NDOFManagerCocoa(*this);
410 #endif
411
412                 //ProcessSerialNumber psn;
413
414                 //Carbon stuff to move window & menu to foreground
415                 /*if (!GetCurrentProcess(&psn)) {
416                         TransformProcessType(&psn, kProcessTransformToForegroundApplication);
417                         SetFrontProcess(&psn);
418                 }*/
419
420                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
421                 [NSApplication sharedApplication]; // initializes       NSApp
422
423                 if ([NSApp mainMenu] == nil) {
424                         NSMenu *mainMenubar = [[NSMenu alloc] init];
425                         NSMenuItem *menuItem;
426                         NSMenu *windowMenu;
427                         NSMenu *appMenu;
428
429                         //Create the application menu
430                         appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
431
432                         [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
433                         [appMenu addItem:[NSMenuItem separatorItem]];
434
435                         menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
436                         [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
437
438                         menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
439                         [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
440
441                         [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
442
443                         menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
444                         [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
445
446                         menuItem = [[NSMenuItem alloc] init];
447                         [menuItem setSubmenu:appMenu];
448
449                         [mainMenubar addItem:menuItem];
450                         [menuItem release];
451                         [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; //Needed for 10.5
452                         [appMenu release];
453
454                         //Create the window menu
455                         windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
456
457                         menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
458                         [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
459
460                         [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
461
462                         menuItem = [windowMenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f" ];
463                         [menuItem setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask];
464
465                         menuItem = [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
466                         [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
467
468                         menuItem = [[NSMenuItem alloc] init];
469                         [menuItem setSubmenu:windowMenu];
470
471                         [mainMenubar addItem:menuItem];
472                         [menuItem release];
473
474                         [NSApp setMainMenu:mainMenubar];
475                         [NSApp setWindowsMenu:windowMenu];
476                         [windowMenu release];
477                 }
478
479                 if ([NSApp delegate] == nil) {
480                         CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
481                         [appDelegate setSystemCocoa:this];
482                         [NSApp setDelegate:appDelegate];
483                 }
484
485                 [NSApp finishLaunching];
486                 
487                 [pool drain];
488         }
489         return success;
490 }
491
492
493 #pragma mark window management
494
495 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
496 {
497         //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
498         struct timeval currentTime;
499
500         gettimeofday(&currentTime, NULL);
501
502         //Return timestamp of system uptime
503
504         return ((currentTime.tv_sec*1000)+(currentTime.tv_usec/1000)-m_start_time);
505 }
506
507
508 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
509 {
510         //Note that OS X supports monitor hot plug
511         // We do not support multiple monitors at the moment
512         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
513
514         GHOST_TUns8 count = [[NSScreen screens] count];
515
516         [pool drain];
517         return count;
518 }
519
520
521 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
522 {
523         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
524         //Get visible frame, that is frame excluding dock and top menu bar
525         NSRect frame = [[NSScreen mainScreen] visibleFrame];
526
527         //Returns max window contents (excluding title bar...)
528         NSRect contentRect = [NSWindow contentRectForFrameRect:frame
529                                                                                                  styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
530
531         width = contentRect.size.width;
532         height = contentRect.size.height;
533
534         [pool drain];
535 }
536
537 void GHOST_SystemCocoa::getAllDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
538 {
539         /* TODO! */
540         getMainDisplayDimensions(width, height);
541 }
542
543 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
544         const STR_String& title, 
545         GHOST_TInt32 left,
546         GHOST_TInt32 top,
547         GHOST_TUns32 width,
548         GHOST_TUns32 height,
549         GHOST_TWindowState state,
550         GHOST_TDrawingContextType type,
551         GHOST_GLSettings glSettings,
552         const bool exclusive,
553         const GHOST_TEmbedderWindowID parentWindow
554 )
555 {
556         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
557         GHOST_IWindow* window = NULL;
558
559         //Get the available rect for including window contents
560         NSRect frame = [[NSScreen mainScreen] visibleFrame];
561         NSRect contentRect = [NSWindow contentRectForFrameRect:frame
562                                                                                                  styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
563
564         GHOST_TInt32 bottom = (contentRect.size.height - 1) - height - top;
565
566         //Ensures window top left is inside this available rect
567         left = left > contentRect.origin.x ? left : contentRect.origin.x;
568         // Add contentRect.origin.y to respect docksize
569         bottom = bottom > contentRect.origin.y ? bottom + contentRect.origin.y : contentRect.origin.y;
570
571         window = new GHOST_WindowCocoa(this, title, left, bottom, width, height, state, type, glSettings.flags & GHOST_glStereoVisual, glSettings.numOfAASamples, glSettings.flags & GHOST_glDebugContext);
572
573         if (window->getValid()) {
574                 // Store the pointer to the window
575                 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
576                 m_windowManager->addWindow(window);
577                 m_windowManager->setActiveWindow(window);
578                 //Need to tell window manager the new window is the active one (Cocoa does not send the event activate upon window creation)
579                 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window));
580                 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
581         }
582         else {
583                 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
584                 delete window;
585                 window = NULL;
586         }
587
588         [pool drain];
589         return window;
590 }
591
592 /**
593  * \note : returns coordinates in Cocoa screen coordinates
594  */
595 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
596 {
597         NSPoint mouseLoc = [NSEvent mouseLocation];
598
599         // Returns the mouse location in screen coordinates
600         x = (GHOST_TInt32)mouseLoc.x;
601         y = (GHOST_TInt32)mouseLoc.y;
602         return GHOST_kSuccess;
603 }
604
605 /**
606  * \note : expect Cocoa screen coordinates
607  */
608 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
609 {
610         GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
611         if (!window) return GHOST_kFailure;
612
613         //Cursor and mouse dissociation placed here not to interfere with continuous grab
614         // (in cont. grab setMouseCursorPosition is directly called)
615         CGAssociateMouseAndMouseCursorPosition(false);
616         setMouseCursorPosition(x, y);
617         CGAssociateMouseAndMouseCursorPosition(true);
618
619         //Force mouse move event (not pushed by Cocoa)
620         pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, x, y));
621         m_outsideLoopEventProcessed = true;
622
623         return GHOST_kSuccess;
624 }
625
626 GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
627 {
628         float xf=(float)x, yf=(float)y;
629         GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
630         if (!window) return GHOST_kFailure;
631
632         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
633         NSScreen *windowScreen = window->getScreen();
634         NSRect screenRect = [windowScreen frame];
635
636         //Set position relative to current screen
637         xf -= screenRect.origin.x;
638         yf -= screenRect.origin.y;
639
640         //Quartz Display Services uses the old coordinates (top left origin)
641         yf = screenRect.size.height -yf;
642
643         CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue], CGPointMake(xf, yf));
644
645         // See https://stackoverflow.com/a/17559012. By default, hardware events
646         // will be suppressed for 500ms after a synthetic mouse event. For unknown
647         // reasons CGEventSourceSetLocalEventsSuppressionInterval does not work,
648         // however calling CGAssociateMouseAndMouseCursorPosition also removes the
649         // delay, even if this is undocumented.
650         CGAssociateMouseAndMouseCursorPosition(true);
651
652         [pool drain];
653         return GHOST_kSuccess;
654 }
655
656
657 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
658 {
659         keys.set(GHOST_kModifierKeyOS, (m_modifierMask & NSCommandKeyMask) ? true : false);
660         keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSAlternateKeyMask) ? true : false);
661         keys.set(GHOST_kModifierKeyLeftShift, (m_modifierMask & NSShiftKeyMask) ? true : false);
662         keys.set(GHOST_kModifierKeyLeftControl, (m_modifierMask & NSControlKeyMask) ? true : false);
663
664         return GHOST_kSuccess;
665 }
666
667 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
668 {
669         UInt32 button_state = GetCurrentEventButtonState();
670
671         buttons.clear();
672         buttons.set(GHOST_kButtonMaskLeft, button_state & (1 << 0));
673         buttons.set(GHOST_kButtonMaskRight, button_state & (1 << 1));
674         buttons.set(GHOST_kButtonMaskMiddle, button_state & (1 << 2));
675         buttons.set(GHOST_kButtonMaskButton4, button_state & (1 << 3));
676         buttons.set(GHOST_kButtonMaskButton5, button_state & (1 << 4));
677         return GHOST_kSuccess;
678 }
679
680
681 #pragma mark Event handlers
682
683 /**
684  * The event queue polling function
685  */
686 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
687 {
688         bool anyProcessed = false;
689         NSEvent *event;
690
691         //      SetMouseCoalescingEnabled(false, NULL);
692         //TODO : implement timer ??
693 #if 0
694         do {
695                 GHOST_TimerManager* timerMgr = getTimerManager();
696
697                 if (waitForEvent) {
698                         GHOST_TUns64 next = timerMgr->nextFireTime();
699                         double timeOut;
700
701                         if (next == GHOST_kFireTimeNever) {
702                                 timeOut = kEventDurationForever;
703                         }
704                         else {
705                                 timeOut = (double)(next - getMilliSeconds())/1000.0;
706                                 if (timeOut < 0.0)
707                                         timeOut = 0.0;
708                         }
709
710                         ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
711                 }
712
713                 if (timerMgr->fireTimers(getMilliSeconds())) {
714                         anyProcessed = true;
715                 }
716 #endif
717                 do {
718                         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
719                         event = [NSApp nextEventMatchingMask:NSAnyEventMask
720                                                    untilDate:[NSDate distantPast]
721                                                       inMode:NSDefaultRunLoopMode
722                                                      dequeue:YES];
723                         if (event==nil) {
724                                 [pool drain];
725                                 break;
726                         }
727
728                         anyProcessed = true;
729
730                         // Send event to NSApp to ensure Mac wide events are handled,
731                         // this will send events to CocoaWindow which will call back
732                         // to handleKeyEvent, handleMouseEvent and handleTabletEvent
733
734                         // There is on special exception for ctrl+(shift)+tab. We do not
735                         // get keyDown events delivered to the view because they are
736                         // special hotkeys to switch between views, so override directly
737
738                         if ([event type] == NSKeyDown &&
739                            [event keyCode] == kVK_Tab &&
740                            ([event modifierFlags] & NSControlKeyMask)) {
741                                 handleKeyEvent(event);
742                         }
743                         else {
744                                 // For some reason NSApp is swallowing the key up events when modifier
745                                 // key is pressed, even if there seems to be no apparent reason to do
746                                 // so, as a workaround we always handle these up events.
747                                 if ([event type] == NSKeyUp && ([event modifierFlags] & (NSCommandKeyMask | NSAlternateKeyMask)))
748                                         handleKeyEvent(event);
749
750                                 [NSApp sendEvent:event];
751                         }
752
753                         [pool drain];
754                 } while (event != nil);
755 #if 0
756         } while (waitForEvent && !anyProcessed); // Needed only for timer implementation
757 #endif
758
759         if (m_needDelayedApplicationBecomeActiveEventProcessing) handleApplicationBecomeActiveEvent();
760
761         if (m_outsideLoopEventProcessed) {
762                 m_outsideLoopEventProcessed = false;
763                 return true;
764         }
765
766         m_ignoreWindowSizedMessages = false;
767
768         return anyProcessed;
769 }
770
771 //Note: called from NSApplication delegate
772 GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
773 {
774         //Update the modifiers key mask, as its status may have changed when the application was not active
775         //(that is when update events are sent to another application)
776         unsigned int modifiers;
777         GHOST_IWindow* window = m_windowManager->getActiveWindow();
778
779         if (!window) {
780                 m_needDelayedApplicationBecomeActiveEventProcessing = true;
781                 return GHOST_kFailure;
782         }
783         else m_needDelayedApplicationBecomeActiveEventProcessing = false;
784
785         modifiers = [[[NSApplication sharedApplication] currentEvent] modifierFlags];
786
787         if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
788                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift));
789         }
790         if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
791                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl));
792         }
793         if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
794                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt));
795         }
796         if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
797                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyOS));
798         }
799
800         m_modifierMask = modifiers;
801
802         m_outsideLoopEventProcessed = true;
803         return GHOST_kSuccess;
804 }
805
806 void GHOST_SystemCocoa::notifyExternalEventProcessed()
807 {
808         m_outsideLoopEventProcessed = true;
809 }
810
811 //Note: called from NSWindow delegate
812 GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa* window)
813 {
814         NSArray *windowsList;
815         windowsList = [NSApp orderedWindows];
816         if (!validWindow(window)) {
817                 return GHOST_kFailure;
818         }
819         switch (eventType) {
820                 case GHOST_kEventWindowClose:
821                         // check for index of mainwindow as it would quit blender without dialog and discard
822                         if ([windowsList count] > 1  && window->getCocoaWindow() != [windowsList objectAtIndex:[windowsList count] - 1]) {
823                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
824                         }
825                         else {
826                                 handleQuitRequest(); // -> quit dialog
827                         }
828                         break;
829                 case GHOST_kEventWindowActivate:
830                         m_windowManager->setActiveWindow(window);
831                         window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
832                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
833                         break;
834                 case GHOST_kEventWindowDeactivate:
835                         m_windowManager->setWindowInactive(window);
836                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
837                         break;
838                 case GHOST_kEventWindowUpdate:
839                         if (m_nativePixel) {
840                                 window->setNativePixelSize();
841                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventNativeResolutionChange, window) );
842                         }
843                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
844                         break;
845                 case GHOST_kEventWindowMove:
846                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, window) );
847                         break;
848                 case GHOST_kEventWindowSize:
849                         if (!m_ignoreWindowSizedMessages) {
850                                 //Enforce only one resize message per event loop (coalescing all the live resize messages)
851                                 window->updateDrawingContext();
852                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
853                                 //Mouse up event is trapped by the resizing event loop, so send it anyway to the window manager
854                                 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonUp, window, GHOST_kButtonMaskLeft));
855                                 //m_ignoreWindowSizedMessages = true;
856                         }
857                         break;
858                 case GHOST_kEventNativeResolutionChange:
859
860                         if (m_nativePixel) {
861                                 window->setNativePixelSize();
862                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventNativeResolutionChange, window) );
863                         }
864
865                 default:
866                         return GHOST_kFailure;
867                         break;
868         }
869
870         m_outsideLoopEventProcessed = true;
871         return GHOST_kSuccess;
872 }
873
874 //Note: called from NSWindow subclass
875 GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,
876                                                       GHOST_WindowCocoa* window, int mouseX, int mouseY, void* data)
877 {
878         if (!validWindow(window)) {
879                 return GHOST_kFailure;
880         }
881         switch (eventType) {
882                 case GHOST_kEventDraggingEntered:
883                 case GHOST_kEventDraggingUpdated:
884                 case GHOST_kEventDraggingExited:
885                         pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,NULL));
886                         break;
887
888                 case GHOST_kEventDraggingDropDone:
889                 {
890                         GHOST_TUns8 * temp_buff;
891                         GHOST_TStringArray *strArray;
892                         NSArray *droppedArray;
893                         size_t pastedTextSize;
894                         NSString *droppedStr;
895                         GHOST_TEventDataPtr eventData;
896                         int i;
897
898                         if (!data) return GHOST_kFailure;
899
900                         switch (draggedObjectType) {
901                                 case GHOST_kDragnDropTypeFilenames:
902                                         droppedArray = (NSArray*)data;
903
904                                         strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
905                                         if (!strArray) return GHOST_kFailure;
906
907                                         strArray->count = [droppedArray count];
908                                         if (strArray->count == 0) {
909                                                 free(strArray);
910                                                 return GHOST_kFailure;
911                                         }
912
913                                         strArray->strings = (GHOST_TUns8**) malloc(strArray->count*sizeof(GHOST_TUns8*));
914
915                                         for (i=0;i<strArray->count;i++)
916                                         {
917                                                 droppedStr = [droppedArray objectAtIndex:i];
918
919                                                 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
920                                                 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1); 
921
922                                                 if (!temp_buff) {
923                                                         strArray->count = i;
924                                                         break;
925                                                 }
926
927                                                 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
928                                                 temp_buff[pastedTextSize] = '\0';
929
930                                                 strArray->strings[i] = temp_buff;
931                                         }
932
933                                         eventData = (GHOST_TEventDataPtr) strArray;
934                                         break;
935
936                                 case GHOST_kDragnDropTypeString:
937                                         droppedStr = (NSString*)data;
938                                         pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
939
940                                         temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1); 
941
942                                         if (temp_buff == NULL) {
943                                                 return GHOST_kFailure;
944                                         }
945
946                                         strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
947
948                                         temp_buff[pastedTextSize] = '\0';
949
950                                         eventData = (GHOST_TEventDataPtr) temp_buff;
951                                         break;
952
953                                 case GHOST_kDragnDropTypeBitmap:
954                                 {
955                                         NSImage *droppedImg = (NSImage*)data;
956                                         NSSize imgSize = [droppedImg size];
957                                         ImBuf *ibuf = NULL;
958                                         GHOST_TUns8 *rasterRGB = NULL;
959                                         GHOST_TUns8 *rasterRGBA = NULL;
960                                         GHOST_TUns8 *toIBuf = NULL;
961                                         int x, y, to_i, from_i;
962                                         NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA,*bitmapImage=nil;
963                                         NSEnumerator *enumerator;
964                                         NSImageRep *representation;
965
966                                         ibuf = IMB_allocImBuf (imgSize.width, imgSize.height, 32, IB_rect);
967                                         if (!ibuf) {
968                                                 [droppedImg release];
969                                                 return GHOST_kFailure;
970                                         }
971
972                                         /*Get the bitmap of the image*/
973                                         enumerator = [[droppedImg representations] objectEnumerator];
974                                         while ((representation = [enumerator nextObject])) {
975                                                 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
976                                                         bitmapImage = (NSBitmapImageRep *)representation;
977                                                         break;
978                                                 }
979                                         }
980                                         if (bitmapImage == nil) return GHOST_kFailure;
981
982                                         if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
983                                                 && ![bitmapImage isPlanar]) {
984                                                 /* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
985                                                 toIBuf = (GHOST_TUns8*)ibuf->rect;
986                                                 rasterRGB = (GHOST_TUns8*)[bitmapImage bitmapData];
987                                                 for (y = 0; y < imgSize.height; y++) {
988                                                         to_i = (imgSize.height-y-1)*imgSize.width;
989                                                         from_i = y*imgSize.width;
990                                                         memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*imgSize.width);
991                                                 }
992                                         }
993                                         else {
994                                                 /* Tell cocoa image resolution is same as current system one */
995                                                 [bitmapImage setSize:imgSize];
996
997                                                 /* Convert the image in a RGBA 32bit format */
998                                                 /* As Core Graphics does not support contextes with non premutliplied alpha,
999                                                  we need to get alpha key values in a separate batch */
1000
1001                                                 /* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
1002                                                 blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1003                                                                                                                  pixelsWide:imgSize.width 
1004                                                                                                                  pixelsHigh:imgSize.height
1005                                                                                                               bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
1006                                                                                                              colorSpaceName:NSDeviceRGBColorSpace 
1007                                                                                                                bitmapFormat:(NSBitmapFormat)0
1008                                                                                                                 bytesPerRow:4*imgSize.width
1009                                                                                                                bitsPerPixel:32/*RGB format padded to 32bits*/];
1010
1011                                                 [NSGraphicsContext saveGraphicsState];
1012                                                 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
1013                                                 [bitmapImage draw];
1014                                                 [NSGraphicsContext restoreGraphicsState];
1015
1016                                                 rasterRGB = (GHOST_TUns8*)[blBitmapFormatImageRGB bitmapData];
1017                                                 if (rasterRGB == NULL) {
1018                                                         [bitmapImage release];
1019                                                         [blBitmapFormatImageRGB release];
1020                                                         [droppedImg release];
1021                                                         return GHOST_kFailure;
1022                                                 }
1023
1024                                                 /* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
1025                                                 blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1026                                                                                                                   pixelsWide:imgSize.width
1027                                                                                                                   pixelsHigh:imgSize.height
1028                                                                                                                bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
1029                                                                                                               colorSpaceName:NSDeviceRGBColorSpace
1030                                                                                                                 bitmapFormat:(NSBitmapFormat)0
1031                                                                                                                  bytesPerRow:4*imgSize.width
1032                                                                                                                 bitsPerPixel:32/* RGBA */];
1033
1034                                                 [NSGraphicsContext saveGraphicsState];
1035                                                 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
1036                                                 [bitmapImage draw];
1037                                                 [NSGraphicsContext restoreGraphicsState];
1038
1039                                                 rasterRGBA = (GHOST_TUns8*)[blBitmapFormatImageRGBA bitmapData];
1040                                                 if (rasterRGBA == NULL) {
1041                                                         [bitmapImage release];
1042                                                         [blBitmapFormatImageRGB release];
1043                                                         [blBitmapFormatImageRGBA release];
1044                                                         [droppedImg release];
1045                                                         return GHOST_kFailure;
1046                                                 }
1047
1048                                                 /*Copy the image to ibuf, flipping it vertically*/
1049                                                 toIBuf = (GHOST_TUns8*)ibuf->rect;
1050                                                 for (y = 0; y < imgSize.height; y++) {
1051                                                         for (x = 0; x < imgSize.width; x++) {
1052                                                                 to_i = (imgSize.height-y-1)*imgSize.width + x;
1053                                                                 from_i = y*imgSize.width + x;
1054
1055                                                                 toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
1056                                                                 toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
1057                                                                 toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
1058                                                                 toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
1059                                                         }
1060                                                 }
1061
1062                                                 [blBitmapFormatImageRGB release];
1063                                                 [blBitmapFormatImageRGBA release];
1064                                                 [droppedImg release];
1065                                         }
1066
1067                                         eventData = (GHOST_TEventDataPtr) ibuf;
1068
1069                                         break;
1070                                 }
1071                                 default:
1072                                         return GHOST_kFailure;
1073                                         break;
1074                         }
1075                         pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,eventData));
1076
1077                         break;
1078                 }
1079                 default:
1080                         return GHOST_kFailure;
1081         }
1082         m_outsideLoopEventProcessed = true;
1083         return GHOST_kSuccess;
1084 }
1085
1086
1087 GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest()
1088 {
1089         GHOST_Window* window = (GHOST_Window*)m_windowManager->getActiveWindow();
1090
1091         //Discard quit event if we are in cursor grab sequence
1092         if (window && window->getCursorGrabModeIsWarp())
1093                 return GHOST_kExitCancel;
1094
1095         //Check open windows if some changes are not saved
1096         if (m_windowManager->getAnyModifiedState())
1097         {
1098                 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved.\nDo you really want to quit?",
1099                                                  @"Cancel", @"Quit Anyway", nil);
1100                 if (shouldQuit == NSAlertAlternateReturn)
1101                 {
1102                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1103                         return GHOST_kExitNow;
1104                 }
1105                 else {
1106                         //Give back focus to the blender window if user selected cancel quit
1107                         NSArray *windowsList = [NSApp orderedWindows];
1108                         if ([windowsList count]) {
1109                                 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1110                                 //Handle the modifiers keyes changed state issue
1111                                 //as recovering from the quit dialog is like application
1112                                 //gaining focus back.
1113                                 //Main issue fixed is Cmd modifier not being cleared
1114                                 handleApplicationBecomeActiveEvent();
1115                         }
1116                 }
1117         }
1118         else {
1119                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1120                 m_outsideLoopEventProcessed = true;
1121                 return GHOST_kExitNow;
1122         }
1123
1124         return GHOST_kExitCancel;
1125 }
1126
1127 bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr)
1128 {
1129         NSString *filepath = (NSString*)filepathStr;
1130         int confirmOpen = NSAlertAlternateReturn;
1131         NSArray *windowsList;
1132         char * temp_buff;
1133         size_t filenameTextSize;
1134         GHOST_Window* window= (GHOST_Window*)m_windowManager->getActiveWindow();
1135
1136         if (!window) {
1137                 return NO;
1138         }       
1139
1140         //Discard event if we are in cursor grab sequence, it'll lead to "stuck cursor" situation if the alert panel is raised
1141         if (window && window->getCursorGrabModeIsWarp())
1142                 return GHOST_kExitCancel;
1143
1144         //Check open windows if some changes are not saved
1145         if (m_windowManager->getAnyModifiedState())
1146         {
1147                 confirmOpen = NSRunAlertPanel([NSString stringWithFormat:@"Opening %@",[filepath lastPathComponent]],
1148                                               @"Current document has not been saved.\nDo you really want to proceed?",
1149                                               @"Cancel", @"Open", nil);
1150         }
1151
1152         //Give back focus to the blender window
1153         windowsList = [NSApp orderedWindows];
1154         if ([windowsList count]) {
1155                 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1156         }
1157
1158         if (confirmOpen == NSAlertAlternateReturn)
1159         {
1160                 filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1161
1162                 temp_buff = (char*) malloc(filenameTextSize+1); 
1163
1164                 if (temp_buff == NULL) {
1165                         return GHOST_kFailure;
1166                 }
1167
1168                 strncpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize);
1169
1170                 temp_buff[filenameTextSize] = '\0';
1171
1172                 pushEvent(new GHOST_EventString(getMilliSeconds(),GHOST_kEventOpenMainFile,window,(GHOST_TEventDataPtr) temp_buff));
1173
1174                 return YES;
1175         }
1176         else return NO;
1177 }
1178
1179 GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr, short eventType)
1180 {
1181         NSEvent *event = (NSEvent *)eventPtr;
1182         GHOST_IWindow* window;
1183
1184         window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1185         if (!window) {
1186                 //printf("\nW failure for event 0x%x",[event type]);
1187                 return GHOST_kFailure;
1188         }
1189
1190         GHOST_TabletData& ct=((GHOST_WindowCocoa*)window)->GetCocoaTabletData();
1191
1192         switch (eventType) {
1193                 case NSTabletPoint:
1194                         // workaround 2 cornercases:
1195                         // 1. if [event isEnteringProximity] was not triggered since program-start
1196                         // 2. device is not sending [event pointingDeviceType], due no eraser
1197                         if (ct.Active == GHOST_kTabletModeNone)
1198                                 ct.Active = GHOST_kTabletModeStylus;
1199
1200                         ct.Pressure = [event pressure];
1201                         ct.Xtilt = [event tilt].x;
1202                         ct.Ytilt = [event tilt].y;
1203                         break;
1204
1205                 case NSTabletProximity:
1206                         ct.Pressure = 0;
1207                         ct.Xtilt = 0;
1208                         ct.Ytilt = 0;
1209                         if ([event isEnteringProximity])
1210                         {
1211                                 //pointer is entering tablet area proximity
1212                                 switch ([event pointingDeviceType]) {
1213                                         case NSPenPointingDevice:
1214                                                 ct.Active = GHOST_kTabletModeStylus;
1215                                                 break;
1216                                         case NSEraserPointingDevice:
1217                                                 ct.Active = GHOST_kTabletModeEraser;
1218                                                 break;
1219                                         case NSCursorPointingDevice:
1220                                         case NSUnknownPointingDevice:
1221                                         default:
1222                                                 ct.Active = GHOST_kTabletModeNone;
1223                                                 break;
1224                                 }
1225                         }
1226                         else {
1227                                 // pointer is leaving - return to mouse
1228                                 ct.Active = GHOST_kTabletModeNone;
1229                         }
1230                         break;
1231                 
1232                 default:
1233                         GHOST_ASSERT(FALSE,"GHOST_SystemCocoa::handleTabletEvent : unknown event received");
1234                         return GHOST_kFailure;
1235                         break;
1236         }
1237         return GHOST_kSuccess;
1238 }
1239
1240 bool GHOST_SystemCocoa::handleTabletEvent(void *eventPtr)
1241 {
1242         NSEvent *event = (NSEvent *)eventPtr;
1243
1244         switch ([event subtype]) {
1245                 case NSTabletPointEventSubtype:
1246                         handleTabletEvent(eventPtr, NSTabletPoint);
1247                         return true;
1248                 case NSTabletProximityEventSubtype:
1249                         handleTabletEvent(eventPtr, NSTabletProximity);
1250                         return true;
1251                 default:
1252                         //No tablet event included : do nothing
1253                         return false;
1254         }
1255 }
1256
1257 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
1258 enum {
1259         NSEventPhaseNone = 0,
1260         NSEventPhaseBegan = 0x1 << 0,
1261         NSEventPhaseStationary = 0x1 << 1,
1262         NSEventPhaseChanged = 0x1 << 2,
1263         NSEventPhaseEnded = 0x1 << 3,
1264         NSEventPhaseCancelled = 0x1 << 4,
1265 };
1266 typedef NSUInteger NSEventPhase;
1267
1268 @interface NSEvent (AvailableOn1070AndLater)
1269 - (BOOL)hasPreciseScrollingDeltas;
1270 - (CGFloat)scrollingDeltaX;
1271 - (CGFloat)scrollingDeltaY;
1272 - (NSEventPhase)momentumPhase;
1273 - (BOOL)isDirectionInvertedFromDevice;
1274 - (NSEventPhase)phase;
1275 @end
1276 #endif
1277
1278 GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
1279 {
1280         NSEvent *event = (NSEvent *)eventPtr;
1281         GHOST_WindowCocoa* window;
1282         CocoaWindow *cocoawindow;
1283
1284         /* [event window] returns other windows if mouse-over, that's OSX input standard
1285            however, if mouse exits window(s), the windows become inactive, until you click.
1286            We then fall back to the active window from ghost */
1287         window = (GHOST_WindowCocoa*)m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1288         if (!window) {
1289                 window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
1290                 if (!window) {
1291                         //printf("\nW failure for event 0x%x",[event type]);
1292                         return GHOST_kFailure;
1293                 }
1294         }
1295
1296         cocoawindow = (CocoaWindow *)window->getOSWindow();
1297
1298         switch ([event type]) {
1299                 case NSLeftMouseDown:
1300                         pushEvent(new GHOST_EventButton([event timestamp] * 1000, GHOST_kEventButtonDown, window, GHOST_kButtonMaskLeft));
1301                         handleTabletEvent(event); //Handle tablet events combined with mouse events
1302                         break;
1303                 case NSRightMouseDown:
1304                         pushEvent(new GHOST_EventButton([event timestamp] * 1000, GHOST_kEventButtonDown, window, GHOST_kButtonMaskRight));
1305                         handleTabletEvent(event); //Handle tablet events combined with mouse events
1306                         break;
1307                 case NSOtherMouseDown:
1308                         pushEvent(new GHOST_EventButton([event timestamp] * 1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
1309                         handleTabletEvent(event); //Handle tablet events combined with mouse events
1310                         break;
1311
1312                 case NSLeftMouseUp:
1313                         pushEvent(new GHOST_EventButton([event timestamp] * 1000, GHOST_kEventButtonUp, window, GHOST_kButtonMaskLeft));
1314                         handleTabletEvent(event); //Handle tablet events combined with mouse events
1315                         break;
1316                 case NSRightMouseUp:
1317                         pushEvent(new GHOST_EventButton([event timestamp] * 1000, GHOST_kEventButtonUp, window, GHOST_kButtonMaskRight));
1318                         handleTabletEvent(event); //Handle tablet events combined with mouse events
1319                         break;
1320                 case NSOtherMouseUp:
1321                         pushEvent(new GHOST_EventButton([event timestamp] * 1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1322                         handleTabletEvent(event); //Handle tablet events combined with mouse events
1323                         break;
1324
1325                 case NSLeftMouseDragged:
1326                 case NSRightMouseDragged:
1327                 case NSOtherMouseDragged:
1328                         //Handle tablet events combined with mouse events
1329                         handleTabletEvent(event);
1330
1331                 case NSMouseMoved: 
1332                         {
1333                                 GHOST_TGrabCursorMode grab_mode = window->getCursorGrabMode();
1334
1335                                 /* TODO: CHECK IF THIS IS A TABLET EVENT */
1336                                 bool is_tablet = false;
1337
1338                                 if (is_tablet && window->getCursorGrabModeIsWarp()) {
1339                                         grab_mode = GHOST_kGrabDisable;
1340                                 }
1341
1342                                 switch (grab_mode) {
1343                                         case GHOST_kGrabHide: //Cursor hidden grab operation : no cursor move
1344                                         {
1345                                                 GHOST_TInt32 x_warp, y_warp, x_accum, y_accum, x, y;
1346
1347                                                 window->getCursorGrabInitPos(x_warp, y_warp);
1348                                                 window->screenToClientIntern(x_warp, y_warp, x_warp, y_warp);
1349
1350                                                 window->getCursorGrabAccum(x_accum, y_accum);
1351                                                 x_accum += [event deltaX];
1352                                                 y_accum += -[event deltaY]; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1353                                                 window->setCursorGrabAccum(x_accum, y_accum);
1354
1355                                                 window->clientToScreenIntern(x_warp+x_accum, y_warp+y_accum, x, y);
1356                                                 pushEvent(new GHOST_EventCursor([event timestamp] * 1000, GHOST_kEventCursorMove, window, x, y));
1357                                                 break;
1358                                         }
1359                                         case GHOST_kGrabWrap: //Wrap cursor at area/window boundaries
1360                                         {
1361                                                 NSPoint mousePos = [cocoawindow mouseLocationOutsideOfEventStream];
1362                                                 GHOST_TInt32 x_mouse = mousePos.x;
1363                                                 GHOST_TInt32 y_mouse = mousePos.y;
1364                                                 GHOST_Rect bounds, windowBounds, correctedBounds;
1365
1366                                                 /* fallback to window bounds */
1367                                                 if (window->getCursorGrabBounds(bounds) == GHOST_kFailure)
1368                                                         window->getClientBounds(bounds);
1369
1370                                                 //Switch back to Cocoa coordinates orientation (y=0 at botton,the same as blender internal btw!), and to client coordinates
1371                                                 window->getClientBounds(windowBounds);
1372                                                 window->screenToClient(bounds.m_l, bounds.m_b, correctedBounds.m_l, correctedBounds.m_t);
1373                                                 window->screenToClient(bounds.m_r, bounds.m_t, correctedBounds.m_r, correctedBounds.m_b);
1374                                                 correctedBounds.m_b = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_b;
1375                                                 correctedBounds.m_t = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_t;
1376
1377                                                 //Get accumulation from previous mouse warps
1378                                                 GHOST_TInt32 x_accum, y_accum;
1379                                                 window->getCursorGrabAccum(x_accum, y_accum);
1380
1381                                                 //Warp mouse cursor if needed
1382                                                 GHOST_TInt32 warped_x_mouse = x_mouse;
1383                                                 GHOST_TInt32 warped_y_mouse = y_mouse;
1384                                                 correctedBounds.wrapPoint(warped_x_mouse, warped_y_mouse, 4);
1385
1386                                                 //Set new cursor position
1387                                                 if (x_mouse != warped_x_mouse || y_mouse != warped_y_mouse) {
1388                                                         GHOST_TInt32 warped_x, warped_y;
1389                                                         window->clientToScreenIntern(warped_x_mouse, warped_y_mouse, warped_x, warped_y);
1390                                                         setMouseCursorPosition(warped_x, warped_y); /* wrap */
1391                                                         window->setCursorGrabAccum(x_accum + (x_mouse - warped_x_mouse), y_accum + (y_mouse - warped_y_mouse));
1392                                                 }
1393
1394                                                 //Generate event
1395                                                 GHOST_TInt32 x, y;
1396                                                 window->clientToScreenIntern(x_mouse + x_accum, y_mouse + y_accum, x, y);
1397                                                 pushEvent(new GHOST_EventCursor([event timestamp] * 1000, GHOST_kEventCursorMove, window, x, y));
1398                                                 break;
1399                                         }
1400                                         default:
1401                                         {
1402                                                 //Normal cursor operation: send mouse position in window
1403                                                 NSPoint mousePos = [cocoawindow mouseLocationOutsideOfEventStream];
1404                                                 GHOST_TInt32 x, y;
1405
1406                                                 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1407                                                 pushEvent(new GHOST_EventCursor([event timestamp] * 1000, GHOST_kEventCursorMove, window, x, y));
1408                                                 break;
1409                                         }
1410                                 }
1411                         }
1412                         break;
1413
1414                 case NSScrollWheel:
1415                         {
1416                                 NSEventPhase momentumPhase = NSEventPhaseNone;
1417                                 NSEventPhase phase = NSEventPhaseNone;
1418
1419                                 if ([event respondsToSelector:@selector(momentumPhase)])
1420                                         momentumPhase = [event momentumPhase];
1421                                 if ([event respondsToSelector:@selector(phase)])
1422                                         phase = [event phase];
1423
1424                                 /* when pressing a key while momentum scrolling continues after
1425                                  * lifting fingers off the trackpad, the action can unexpectedly
1426                                  * change from e.g. scrolling to zooming. this works around the
1427                                  * issue by ignoring momentum scroll after a key press */
1428                                 if (momentumPhase) {
1429                                         if (m_ignoreMomentumScroll)
1430                                                 break;
1431                                 }
1432                                 else {
1433                                         m_ignoreMomentumScroll = false;
1434                                 }
1435
1436                                 /* we assume phases are only set for gestures from trackpad or magic
1437                                  * mouse events. note that using tablet at the same time may not work
1438                                  * since this is a static variable */
1439                                 if (phase == NSEventPhaseBegan)
1440                                         m_multiTouchScroll = true;
1441                                 else if (phase == NSEventPhaseEnded)
1442                                         m_multiTouchScroll = false;
1443
1444                                 /* standard scrollwheel case, if no swiping happened, and no momentum (kinetic scroll) works */
1445                                 if (!m_multiTouchScroll && momentumPhase == NSEventPhaseNone) {
1446                                         GHOST_TInt32 delta;
1447
1448                                         double deltaF = [event deltaY];
1449
1450                                         if (deltaF == 0.0) deltaF = [event deltaX]; // make blender decide if it's horizontal scroll
1451                                         if (deltaF == 0.0) break; //discard trackpad delta=0 events
1452
1453                                         delta = deltaF > 0.0 ? 1 : -1;
1454                                         pushEvent(new GHOST_EventWheel([event timestamp] * 1000, window, delta));
1455                                 }
1456                                 else {
1457                                         NSPoint mousePos = [cocoawindow mouseLocationOutsideOfEventStream];
1458                                         GHOST_TInt32 x, y;
1459                                         double dx;
1460                                         double dy;
1461
1462 #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
1463                                         /* with 10.7 nice scrolling deltas are supported */
1464                                         dx = [event scrollingDeltaX];
1465                                         dy = [event scrollingDeltaY];
1466
1467                                         /* however, wacom tablet (intuos5) needs old deltas, it then has momentum and phase at zero */
1468                                         if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) {
1469                                                 dx = [event deltaX];
1470                                                 dy = [event deltaY];
1471                                         }
1472 #else
1473                                         /* trying to pretend you have nice scrolls... */
1474                                         dx = [event deltaX];
1475                                         dy = -[event deltaY];
1476                                         const double deltaMax = 50.0;
1477
1478                                         if ((dx == 0) && (dy == 0)) break;
1479
1480                                         /* Quadratic acceleration */
1481                                         dx = dx*(fabs(dx) + 0.5);
1482                                         if (dx < 0.0) dx -= 0.5;
1483                                         else          dx += 0.5;
1484                                         if      (dx < -deltaMax) dx = -deltaMax;
1485                                         else if (dx >  deltaMax) dx =  deltaMax;
1486
1487                                         dy = dy*(fabs(dy) + 0.5);
1488                                         if (dy < 0.0) dy -= 0.5;
1489                                         else          dy += 0.5;
1490                                         if      (dy < -deltaMax) dy= -deltaMax;
1491                                         else if (dy >  deltaMax) dy=  deltaMax;
1492
1493                                         dy = -dy;
1494 #endif
1495                                         window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1496
1497                                         pushEvent(new GHOST_EventTrackpad([event timestamp] * 1000, window, GHOST_kTrackpadEventScroll, x, y, dx, dy));
1498                                 }
1499                         }
1500                         break;
1501
1502                 case NSEventTypeMagnify:
1503                         {
1504                                 NSPoint mousePos = [cocoawindow mouseLocationOutsideOfEventStream];
1505                                 GHOST_TInt32 x, y;
1506                                 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1507                                 pushEvent(new GHOST_EventTrackpad([event timestamp] * 1000, window, GHOST_kTrackpadEventMagnify, x, y,
1508                                                                   [event magnification] * 125.0 + 0.1, 0));
1509                         }
1510                         break;
1511
1512                 case NSEventTypeRotate:
1513                         {
1514                                 NSPoint mousePos = [cocoawindow mouseLocationOutsideOfEventStream];
1515                                 GHOST_TInt32 x, y;
1516                                 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1517                                 pushEvent(new GHOST_EventTrackpad([event timestamp] * 1000, window, GHOST_kTrackpadEventRotate, x, y,
1518                                                                   [event rotation] * 5.0, 0));
1519                         }
1520                 default:
1521                         return GHOST_kFailure;
1522                         break;
1523                 }
1524
1525         return GHOST_kSuccess;
1526 }
1527
1528
1529 GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1530 {
1531         NSEvent *event = (NSEvent *)eventPtr;
1532         GHOST_IWindow* window;
1533         unsigned int modifiers;
1534         NSString *characters;
1535         NSData *convertedCharacters;
1536         GHOST_TKey keyCode;
1537         unsigned char ascii;
1538         NSString* charsIgnoringModifiers;
1539
1540         window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1541         if (!window) {
1542                 //printf("\nW failure for event 0x%x",[event type]);
1543                 return GHOST_kFailure;
1544         }
1545
1546         char utf8_buf[6]= {'\0'};
1547         ascii = 0;
1548
1549         switch ([event type]) {
1550
1551                 case NSKeyDown:
1552                 case NSKeyUp:
1553                         charsIgnoringModifiers = [event charactersIgnoringModifiers];
1554                         if ([charsIgnoringModifiers length] > 0) {
1555                                 keyCode = convertKey([event keyCode],
1556                                                      [charsIgnoringModifiers characterAtIndex:0],
1557                                                      [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1558                         }
1559                         else {
1560                                 keyCode = convertKey([event keyCode],0,
1561                                                      [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1562                         }
1563
1564                         /* handling both unicode or ascii */
1565                         characters = [event characters];
1566                         if ([characters length] > 0) {
1567                                 convertedCharacters = [characters dataUsingEncoding:NSUTF8StringEncoding];
1568
1569                                 for (int x = 0; x < [convertedCharacters length]; x++) {
1570                                         utf8_buf[x] = ((char*)[convertedCharacters bytes])[x];
1571                                 }
1572                         }
1573
1574                         /* arrow keys should not have utf8 */
1575                         if ((keyCode > 266) && (keyCode < 271))
1576                                 utf8_buf[0] = '\0';
1577
1578                         /* F keys should not have utf8 */
1579                         if ((keyCode >= GHOST_kKeyF1) && (keyCode <= GHOST_kKeyF20))
1580                                 utf8_buf[0] = '\0';
1581
1582                         /* no text with command key pressed */
1583                         if (m_modifierMask & NSCommandKeyMask)
1584                                 utf8_buf[0] = '\0';
1585
1586                         if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1587                                 break; //Cmd-Q is directly handled by Cocoa
1588
1589                         /* ascii is a subset of unicode */
1590                         if (utf8_buf[0] && !utf8_buf[1]) {
1591                                 ascii = utf8_buf[0];
1592                         }
1593
1594                         if ([event type] == NSKeyDown) {
1595                                 pushEvent(new GHOST_EventKey([event timestamp] * 1000, GHOST_kEventKeyDown, window, keyCode, ascii, utf8_buf));
1596                                 //printf("Key down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii, utf8_buf);
1597                         }
1598                         else {
1599                                 pushEvent(new GHOST_EventKey([event timestamp] * 1000, GHOST_kEventKeyUp, window, keyCode, 0, NULL));
1600                                 //printf("Key up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii, utf8_buf);
1601                         }
1602                         m_ignoreMomentumScroll = true;
1603                         break;
1604
1605                 case NSFlagsChanged: 
1606                         modifiers = [event modifierFlags];
1607                         
1608                         if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1609                                 pushEvent(new GHOST_EventKey([event timestamp] * 1000, (modifiers & NSShiftKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift));
1610                         }
1611                         if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1612                                 pushEvent(new GHOST_EventKey([event timestamp] * 1000, (modifiers & NSControlKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl));
1613                         }
1614                         if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1615                                 pushEvent(new GHOST_EventKey([event timestamp] * 1000, (modifiers & NSAlternateKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt));
1616                         }
1617                         if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1618                                 pushEvent(new GHOST_EventKey([event timestamp] * 1000, (modifiers & NSCommandKeyMask) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, GHOST_kKeyOS));
1619                         }
1620
1621                         m_modifierMask = modifiers;
1622                         m_ignoreMomentumScroll = true;
1623                         break;
1624
1625                 default:
1626                         return GHOST_kFailure;
1627                         break;
1628         }
1629
1630         return GHOST_kSuccess;
1631 }
1632
1633
1634 #pragma mark Clipboard get/set
1635
1636 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1637 {
1638         GHOST_TUns8 * temp_buff;
1639         size_t pastedTextSize;
1640
1641         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1642
1643         NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1644
1645         if (pasteBoard == nil) {
1646                 [pool drain];
1647                 return NULL;
1648         }
1649
1650         NSArray *supportedTypes =
1651                 [NSArray arrayWithObjects: NSStringPboardType, nil];
1652
1653         NSString *bestType = [[NSPasteboard generalPasteboard] availableTypeFromArray:supportedTypes];
1654
1655         if (bestType == nil) {
1656                 [pool drain];
1657                 return NULL;
1658         }
1659
1660         NSString *textPasted = [pasteBoard stringForType:NSStringPboardType];
1661
1662         if (textPasted == nil) {
1663                 [pool drain];
1664                 return NULL;
1665         }
1666
1667         pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1668
1669         temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1); 
1670
1671         if (temp_buff == NULL) {
1672                 [pool drain];
1673                 return NULL;
1674         }
1675
1676         strncpy((char*)temp_buff, [textPasted cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1677
1678         temp_buff[pastedTextSize] = '\0';
1679
1680         [pool drain];
1681
1682         if (temp_buff) {
1683                 return temp_buff;
1684         }
1685         else {
1686                 return NULL;
1687         }
1688 }
1689
1690 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1691 {
1692         NSString *textToCopy;
1693
1694         if (selection) return;  // for copying the selection, used on X11
1695
1696         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1697
1698         NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1699
1700         if (pasteBoard == nil) {
1701                 [pool drain];
1702                 return;
1703         }
1704
1705         NSArray *supportedTypes = [NSArray arrayWithObject:NSStringPboardType];
1706
1707         [pasteBoard declareTypes:supportedTypes owner:nil];
1708
1709         textToCopy = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
1710
1711         [pasteBoard setString:textToCopy forType:NSStringPboardType];
1712
1713         [pool drain];
1714 }