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