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