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