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