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