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