code cleanup: add getCursorGrabModeIsWarp(), makes the intent more clear where we...
[blender.git] / intern / ghost / intern / GHOST_SystemCocoa.mm
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributors: Maarten Gribnau 05/2001
24  *               Damien Plisson 09/2009
25  *
26  * ***** END GPL LICENSE BLOCK *****
27  */
28
29 #import <Cocoa/Cocoa.h>
30
31 /*For the currently not ported to Cocoa keyboard layout functions (64bit & 10.6 compatible)*/
32 #include <Carbon/Carbon.h>
33
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/sysctl.h>
37
38 #include "GHOST_SystemCocoa.h"
39
40 #include "GHOST_DisplayManagerCocoa.h"
41 #include "GHOST_EventKey.h"
42 #include "GHOST_EventButton.h"
43 #include "GHOST_EventCursor.h"
44 #include "GHOST_EventWheel.h"
45 #include "GHOST_EventTrackpad.h"
46 #include "GHOST_EventDragnDrop.h"
47 #include "GHOST_EventString.h"
48 #include "GHOST_TimerManager.h"
49 #include "GHOST_TimerTask.h"
50 #include "GHOST_WindowManager.h"
51 #include "GHOST_WindowCocoa.h"
52 #ifdef WITH_INPUT_NDOF
53 #include "GHOST_NDOFManagerCocoa.h"
54 #endif
55
56 #include "AssertMacros.h"
57
58 #pragma mark KeyMap, mouse converters
59 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
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                         } else if ((recvChar >= 'a') && (recvChar <= 'z')) {
359                                 return (GHOST_TKey) (recvChar - 'a' + GHOST_kKeyA);
360                         } else {
361 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
362                                 KeyboardLayoutRef keyLayout;
363                                 UCKeyboardLayout *uchrData;
364                                 
365                                 KLGetCurrentKeyboardLayout(&keyLayout);
366                                 KLGetKeyboardLayoutProperty(keyLayout, kKLuchrData, (const void **)
367                                                                                         &uchrData);
368                                 /*get actual character value of the "remappable" keys in int'l keyboards,
369                                  if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
370                                  then fallback on using the received charactersIgnoringModifiers */
371                                 if (uchrData)
372                                 {
373                                         UInt32 deadKeyState=0;
374                                         UniCharCount actualStrLength=0;
375                                         
376                                         UCKeyTranslate(uchrData, rawCode, keyAction, 0,
377                                                                    LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
378                                         
379                                 }                               
380 #else
381                                 /* Leopard and Snow Leopard 64bit compatible API*/
382                                 CFDataRef uchrHandle; /*the keyboard layout*/
383                                 TISInputSourceRef kbdTISHandle;
384                                 
385                                 kbdTISHandle = TISCopyCurrentKeyboardLayoutInputSource();
386                                 uchrHandle = (CFDataRef)TISGetInputSourceProperty(kbdTISHandle,kTISPropertyUnicodeKeyLayoutData);
387                                 CFRelease(kbdTISHandle);
388                                 
389                                 /*get actual character value of the "remappable" keys in int'l keyboards,
390                                  if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
391                                  then fallback on using the received charactersIgnoringModifiers */
392                                 if (uchrHandle)
393                                 {
394                                         UInt32 deadKeyState=0;
395                                         UniCharCount actualStrLength=0;
396                                         
397                                         UCKeyTranslate((UCKeyboardLayout*)CFDataGetBytePtr(uchrHandle), rawCode, keyAction, 0,
398                                                                    LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
399                                         
400                                 }
401 #endif
402                                 switch (recvChar) {
403                                         case '-':       return GHOST_kKeyMinus;
404                                         case '=':       return GHOST_kKeyEqual;
405                                         case ',':       return GHOST_kKeyComma;
406                                         case '.':       return GHOST_kKeyPeriod;
407                                         case '/':       return GHOST_kKeySlash;
408                                         case ';':       return GHOST_kKeySemicolon;
409                                         case '\'':      return GHOST_kKeyQuote;
410                                         case '\\':      return GHOST_kKeyBackslash;
411                                         case '[':       return GHOST_kKeyLeftBracket;
412                                         case ']':       return GHOST_kKeyRightBracket;
413                                         case '`':       return GHOST_kKeyAccentGrave;
414                                         default:
415                                                 return GHOST_kKeyUnknown;
416                                 }
417                         }
418         }
419         return GHOST_kKeyUnknown;
420 }
421
422
423 #pragma mark defines for 10.6 api not documented in 10.5
424 #ifndef MAC_OS_X_VERSION_10_6
425 enum {
426         /* The following event types are available on some hardware on 10.5.2 and later */
427         NSEventTypeGesture          = 29,
428         NSEventTypeMagnify          = 30,
429         NSEventTypeSwipe            = 31,
430         NSEventTypeRotate           = 18,
431         NSEventTypeBeginGesture     = 19,
432         NSEventTypeEndGesture       = 20
433 };
434
435 @interface NSEvent(GestureEvents)
436 /* This message is valid for events of type NSEventTypeMagnify, on 10.5.2 or later */
437 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
438 - (float)magnification;       // change in magnification.
439 #else
440 - (CGFloat)magnification;       // change in magnification.
441 #endif
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         } else {
461                 return 0; 
462         }
463 }
464
465 #if defined(WITH_QUICKTIME) && !defined(USE_QTKIT)
466 //Need to place this quicktime function in an ObjC file
467 //It is used to avoid memory leak when raising the quicktime "compression settings" standard dialog
468 extern "C" {
469         struct bContext;
470         struct wmOperator;
471         extern int fromcocoa_request_qtcodec_settings(bContext *C, wmOperator *op);
472
473
474 int cocoa_request_qtcodec_settings(bContext *C, wmOperator *op)
475 {
476         int result;
477         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
478         
479         result = fromcocoa_request_qtcodec_settings(C, op);
480         
481         [pool drain];
482         return result;
483 }
484 };
485 #endif
486
487
488 #pragma mark Cocoa objects
489
490 /**
491  * CocoaAppDelegate
492  * ObjC object to capture applicationShouldTerminate, and send quit event
493  **/
494 @interface CocoaAppDelegate : NSObject {
495         GHOST_SystemCocoa *systemCocoa;
496 }
497 - (void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa;
498 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
499 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
500 - (void)applicationWillTerminate:(NSNotification *)aNotification;
501 - (void)applicationWillBecomeActive:(NSNotification *)aNotification;
502 @end
503
504 @implementation CocoaAppDelegate : NSObject
505 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
506 {
507         systemCocoa = sysCocoa;
508 }
509
510 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
511 {
512         return systemCocoa->handleOpenDocumentRequest(filename);
513 }
514
515 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
516 {
517         //TODO: implement graceful termination through Cocoa mechanism to avoid session log off to be cancelled
518         //Note that Cmd+Q is already handled by keyhandler
519     if (systemCocoa->handleQuitRequest() == GHOST_kExitNow)
520                 return NSTerminateCancel;//NSTerminateNow;
521         else
522                 return NSTerminateCancel;
523 }
524
525 // To avoid cancelling a log off process, we must use Cocoa termination process
526 // And this function is the only chance to perform clean up
527 // So WM_exit needs to be called directly, as the event loop will never run before termination
528 - (void)applicationWillTerminate:(NSNotification *)aNotification
529 {
530         /*G.afbreek = 0; //Let Cocoa perform the termination at the end
531         WM_exit(C);*/
532 }
533
534 - (void)applicationWillBecomeActive:(NSNotification *)aNotification
535 {
536         systemCocoa->handleApplicationBecomeActiveEvent();
537 }
538 @end
539
540
541
542 #pragma mark initialization/finalization
543
544
545 GHOST_SystemCocoa::GHOST_SystemCocoa()
546 {
547         int mib[2];
548         struct timeval boottime;
549         size_t len;
550         char *rstring = NULL;
551         
552         m_modifierMask =0;
553         m_pressedMouseButtons =0;
554         m_isGestureInProgress = false;
555         m_cursorDelta_x=0;
556         m_cursorDelta_y=0;
557         m_outsideLoopEventProcessed = false;
558         m_needDelayedApplicationBecomeActiveEventProcessing = false;
559         m_displayManager = new GHOST_DisplayManagerCocoa ();
560         GHOST_ASSERT(m_displayManager, "GHOST_SystemCocoa::GHOST_SystemCocoa(): m_displayManager==0\n");
561         m_displayManager->initialize();
562
563         //NSEvent timeStamp is given in system uptime, state start date is boot time
564         mib[0] = CTL_KERN;
565         mib[1] = KERN_BOOTTIME;
566         len = sizeof(struct timeval);
567         
568         sysctl(mib, 2, &boottime, &len, NULL, 0);
569         m_start_time = ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
570         
571         //Detect multitouch trackpad
572         mib[0] = CTL_HW;
573         mib[1] = HW_MODEL;
574         sysctl( mib, 2, NULL, &len, NULL, 0 );
575         rstring = (char*)malloc( len );
576         sysctl( mib, 2, rstring, &len, NULL, 0 );
577         
578         //Hack on MacBook revision, as multitouch avail. function missing
579         if (strstr(rstring,"MacBookAir") ||
580                 (strstr(rstring,"MacBook") && (rstring[strlen(rstring)-3]>='5') && (rstring[strlen(rstring)-3]<='9')))
581                 m_hasMultiTouchTrackpad = true;
582         else m_hasMultiTouchTrackpad = false;
583         
584         free( rstring );
585         rstring = NULL;
586         
587         m_ignoreWindowSizedMessages = false;
588 }
589
590 GHOST_SystemCocoa::~GHOST_SystemCocoa()
591 {
592 }
593
594
595 GHOST_TSuccess GHOST_SystemCocoa::init()
596 {
597         
598     GHOST_TSuccess success = GHOST_System::init();
599     if (success) {
600
601 #ifdef WITH_INPUT_NDOF
602                 m_ndofManager = new GHOST_NDOFManagerCocoa(*this);
603 #endif
604
605                 //ProcessSerialNumber psn;
606                 
607                 //Carbon stuff to move window & menu to foreground
608                 /*if (!GetCurrentProcess(&psn)) {
609                         TransformProcessType(&psn, kProcessTransformToForegroundApplication);
610                         SetFrontProcess(&psn);
611                 }*/
612                 
613                 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
614                 if (NSApp == nil) {
615                         [NSApplication sharedApplication];
616                         
617                         if ([NSApp mainMenu] == nil) {
618                                 NSMenu *mainMenubar = [[NSMenu alloc] init];
619                                 NSMenuItem *menuItem;
620                                 NSMenu *windowMenu;
621                                 NSMenu *appMenu;
622                                 
623                                 //Create the application menu
624                                 appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
625                                 
626                                 [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
627                                 [appMenu addItem:[NSMenuItem separatorItem]];
628                                 
629                                 menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
630                                 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
631                                  
632                                 menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
633                                 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
634                                 
635                                 [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
636                                 
637                                 menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
638                                 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
639                                 
640                                 menuItem = [[NSMenuItem alloc] init];
641                                 [menuItem setSubmenu:appMenu];
642                                 
643                                 [mainMenubar addItem:menuItem];
644                                 [menuItem release];
645                                 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; //Needed for 10.5
646                                 [appMenu release];
647                                 
648                                 //Create the window menu
649                                 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
650                                 
651                                 menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
652                                 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
653                                 
654                                 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
655                                 
656                                 menuItem = [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
657                                 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
658                                 
659                                 menuItem = [[NSMenuItem alloc] init];
660                                 [menuItem setSubmenu:windowMenu];
661                                 
662                                 [mainMenubar addItem:menuItem];
663                                 [menuItem release];
664                                 
665                                 [NSApp setMainMenu:mainMenubar];
666                                 [NSApp setWindowsMenu:windowMenu];
667                                 [windowMenu release];
668                         }
669                 }
670                 if ([NSApp delegate] == nil) {
671                         CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
672                         [appDelegate setSystemCocoa:this];
673                         [NSApp setDelegate:appDelegate];
674                 }
675                 
676                 [NSApp finishLaunching];
677                 
678                 [pool drain];
679     }
680     return success;
681 }
682
683
684 #pragma mark window management
685
686 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
687 {
688         //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
689         struct timeval currentTime;
690         
691         gettimeofday(&currentTime, NULL);
692         
693         //Return timestamp of system uptime
694         
695         return ((currentTime.tv_sec*1000)+(currentTime.tv_usec/1000)-m_start_time);
696 }
697
698
699 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
700 {
701         //Note that OS X supports monitor hot plug
702         // We do not support multiple monitors at the moment
703         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
704
705         GHOST_TUns8 count = [[NSScreen screens] count];
706
707         [pool drain];
708         return count;
709 }
710
711
712 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
713 {
714         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
715         //Get visible frame, that is frame excluding dock and top menu bar
716         NSRect frame = [[NSScreen mainScreen] visibleFrame];
717         
718         //Returns max window contents (excluding title bar...)
719         NSRect contentRect = [NSWindow contentRectForFrameRect:frame
720                                                                                                  styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
721         
722         width = contentRect.size.width;
723         height = contentRect.size.height;
724
725         [pool drain];
726 }
727
728
729 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
730         const STR_String& title, 
731         GHOST_TInt32 left,
732         GHOST_TInt32 top,
733         GHOST_TUns32 width,
734         GHOST_TUns32 height,
735         GHOST_TWindowState state,
736         GHOST_TDrawingContextType type,
737         bool stereoVisual,
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         }
769         else {
770                         GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
771             delete window;
772             window = 0;
773         }
774     }
775         else {
776                 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): could not create window\n");
777         }
778         [pool drain];
779     return window;
780 }
781
782 /**
783  * @note : returns coordinates in Cocoa screen coordinates
784  */
785 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
786 {
787     NSPoint mouseLoc = [NSEvent mouseLocation];
788         
789     // Returns the mouse location in screen coordinates
790     x = (GHOST_TInt32)mouseLoc.x;
791     y = (GHOST_TInt32)mouseLoc.y;
792     return GHOST_kSuccess;
793 }
794
795 /**
796  * @note : expect Cocoa screen coordinates
797  */
798 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
799 {
800         GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
801         if (!window) return GHOST_kFailure;
802
803         //Cursor and mouse dissociation placed here not to interfere with continuous grab
804         // (in cont. grab setMouseCursorPosition is directly called)
805         CGAssociateMouseAndMouseCursorPosition(false);
806         setMouseCursorPosition(x, y);
807         CGAssociateMouseAndMouseCursorPosition(true);
808         
809         //Force mouse move event (not pushed by Cocoa)
810         pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, x, y));
811         m_outsideLoopEventProcessed = true;
812         
813         return GHOST_kSuccess;
814 }
815
816 GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
817 {
818         float xf=(float)x, yf=(float)y;
819         GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
820         if (!window) return GHOST_kFailure;
821
822         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
823         NSScreen *windowScreen = window->getScreen();
824         NSRect screenRect = [windowScreen frame];
825         
826         //Set position relative to current screen
827         xf -= screenRect.origin.x;
828         yf -= screenRect.origin.y;
829         
830         //Quartz Display Services uses the old coordinates (top left origin)
831         yf = screenRect.size.height -yf;
832
833         CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue], CGPointMake(xf, yf));
834
835         [pool drain];
836     return GHOST_kSuccess;
837 }
838
839
840 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
841 {
842         keys.set(GHOST_kModifierKeyOS, (m_modifierMask & NSCommandKeyMask) ? true : false);
843         keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSAlternateKeyMask) ? true : false);
844         keys.set(GHOST_kModifierKeyLeftShift, (m_modifierMask & NSShiftKeyMask) ? true : false);
845         keys.set(GHOST_kModifierKeyLeftControl, (m_modifierMask & NSControlKeyMask) ? true : false);
846         
847     return GHOST_kSuccess;
848 }
849
850 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
851 {
852         buttons.clear();
853     buttons.set(GHOST_kButtonMaskLeft, m_pressedMouseButtons & GHOST_kButtonMaskLeft);
854         buttons.set(GHOST_kButtonMaskRight, m_pressedMouseButtons & GHOST_kButtonMaskRight);
855         buttons.set(GHOST_kButtonMaskMiddle, m_pressedMouseButtons & GHOST_kButtonMaskMiddle);
856         buttons.set(GHOST_kButtonMaskButton4, m_pressedMouseButtons & GHOST_kButtonMaskButton4);
857         buttons.set(GHOST_kButtonMaskButton5, m_pressedMouseButtons & GHOST_kButtonMaskButton5);
858     return GHOST_kSuccess;
859 }
860
861
862
863 #pragma mark Event handlers
864
865 /**
866  * The event queue polling function
867  */
868 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
869 {
870         bool anyProcessed = false;
871         NSEvent *event;
872         
873         //      SetMouseCoalescingEnabled(false, NULL);
874         //TODO : implement timer ??
875         
876         /*do {
877                 GHOST_TimerManager* timerMgr = getTimerManager();
878                 
879                 if (waitForEvent) {
880                 GHOST_TUns64 next = timerMgr->nextFireTime();
881                 double timeOut;
882
883                 if (next == GHOST_kFireTimeNever) {
884                 timeOut = kEventDurationForever;
885                 } else {
886                 timeOut = (double)(next - getMilliSeconds())/1000.0;
887                 if (timeOut < 0.0)
888                 timeOut = 0.0;
889                 }
890
891                 ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
892                 }
893
894                 if (timerMgr->fireTimers(getMilliSeconds())) {
895                 anyProcessed = true;
896                 }
897                 */
898                 
899                 do {
900                         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
901                         event = [NSApp nextEventMatchingMask:NSAnyEventMask
902                                                                            untilDate:[NSDate distantPast]
903                                                                                   inMode:NSDefaultRunLoopMode
904                                                                                  dequeue:YES];
905                         if (event==nil) {
906                                 [pool drain];
907                                 break;
908                         }
909                         
910                         anyProcessed = true;
911                         
912                         switch ([event type]) {
913                                 case NSKeyDown:
914                                 case NSKeyUp:
915                                 case NSFlagsChanged:
916                                         handleKeyEvent(event);
917                                         
918                                         /* Support system-wide keyboard shortcuts, like Expos√©, ...) =>included in always NSApp sendEvent */
919                                         /*              if (([event modifierFlags] & NSCommandKeyMask) || [event type] == NSFlagsChanged) {
920                                          [NSApp sendEvent:event];
921                                          }*/
922                                         break;
923                                         
924                                 case NSLeftMouseDown:
925                                 case NSLeftMouseUp:
926                                 case NSRightMouseDown:
927                                 case NSRightMouseUp:
928                                 case NSMouseMoved:
929                                 case NSLeftMouseDragged:
930                                 case NSRightMouseDragged:
931                                 case NSScrollWheel:
932                                 case NSOtherMouseDown:
933                                 case NSOtherMouseUp:
934                                 case NSOtherMouseDragged:
935                                 case NSEventTypeMagnify:
936                                 case NSEventTypeRotate:
937                                 case NSEventTypeBeginGesture:
938                                 case NSEventTypeEndGesture:
939                                         handleMouseEvent(event);
940                                         break;
941                                         
942                                 case NSTabletPoint:
943                                 case NSTabletProximity:
944                                         handleTabletEvent(event,[event type]);
945                                         break;
946                                         
947                                         /* Trackpad features, fired only from OS X 10.5.2
948                                          case NSEventTypeGesture:
949                                          case NSEventTypeSwipe:
950                                          break; */
951                                         
952                                         /*Unused events
953                                          NSMouseEntered       = 8,
954                                          NSMouseExited        = 9,
955                                          NSAppKitDefined      = 13,
956                                          NSSystemDefined      = 14,
957                                          NSApplicationDefined = 15,
958                                          NSPeriodic           = 16,
959                                          NSCursorUpdate       = 17,*/
960                                         
961                                 default:
962                                         break;
963                         }
964                         //Resend event to NSApp to ensure Mac wide events are handled
965                         [NSApp sendEvent:event];
966                         [pool drain];
967                 } while (event!= nil);          
968         //} while (waitForEvent && !anyProcessed); Needed only for timer implementation
969         
970         if (m_needDelayedApplicationBecomeActiveEventProcessing) handleApplicationBecomeActiveEvent();
971         
972         if (m_outsideLoopEventProcessed) {
973                 m_outsideLoopEventProcessed = false;
974                 return true;
975         }
976         
977         m_ignoreWindowSizedMessages = false;
978         
979     return anyProcessed;
980 }
981
982 //Note: called from NSApplication delegate
983 GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
984 {
985         //Update the modifiers key mask, as its status may have changed when the application was not active
986         //(that is when update events are sent to another application)
987         unsigned int modifiers;
988         GHOST_IWindow* window = m_windowManager->getActiveWindow();
989         
990         if (!window) {
991                 m_needDelayedApplicationBecomeActiveEventProcessing = true;
992                 return GHOST_kFailure;
993         }
994         else m_needDelayedApplicationBecomeActiveEventProcessing = false;
995
996         modifiers = [[[NSApplication sharedApplication] currentEvent] modifierFlags];
997         
998         if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
999                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1000         }
1001         if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1002                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1003         }
1004         if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1005                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1006         }
1007         if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1008                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1009         }
1010         
1011         m_modifierMask = modifiers;
1012         
1013         m_outsideLoopEventProcessed = true;
1014         return GHOST_kSuccess;
1015 }
1016
1017 void GHOST_SystemCocoa::notifyExternalEventProcessed()
1018 {
1019         m_outsideLoopEventProcessed = true;
1020 }
1021
1022 //Note: called from NSWindow delegate
1023 GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa* window)
1024 {
1025         if (!validWindow(window)) {
1026                 return GHOST_kFailure;
1027         }
1028                 switch(eventType) 
1029                 {
1030                         case GHOST_kEventWindowClose:
1031                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
1032                                 break;
1033                         case GHOST_kEventWindowActivate:
1034                                 m_windowManager->setActiveWindow(window);
1035                                 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
1036                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
1037                                 break;
1038                         case GHOST_kEventWindowDeactivate:
1039                                 m_windowManager->setWindowInactive(window);
1040                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
1041                                 break;
1042                         case GHOST_kEventWindowUpdate:
1043                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
1044                                 break;
1045                         case GHOST_kEventWindowMove:
1046                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, window) );
1047                                 break;
1048                         case GHOST_kEventWindowSize:
1049                                 if (!m_ignoreWindowSizedMessages)
1050                                 {
1051                                         //Enforce only one resize message per event loop (coalescing all the live resize messages)                                      
1052                                         window->updateDrawingContext();
1053                                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
1054                                         //Mouse up event is trapped by the resizing event loop, so send it anyway to the window manager
1055                                         pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonUp, window, convertButton(0)));
1056                                         m_ignoreWindowSizedMessages = true;
1057                                 }
1058                                 break;
1059                         default:
1060                                 return GHOST_kFailure;
1061                                 break;
1062                 }
1063         
1064         m_outsideLoopEventProcessed = true;
1065         return GHOST_kSuccess;
1066 }
1067
1068 //Note: called from NSWindow subclass
1069 GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,
1070                                                                    GHOST_WindowCocoa* window, int mouseX, int mouseY, void* data)
1071 {
1072         if (!validWindow(window)) {
1073                 return GHOST_kFailure;
1074         }
1075         switch(eventType) 
1076         {
1077                 case GHOST_kEventDraggingEntered:
1078                 case GHOST_kEventDraggingUpdated:
1079                 case GHOST_kEventDraggingExited:
1080                         pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,NULL));
1081                         break;
1082                         
1083                 case GHOST_kEventDraggingDropDone:
1084                 {
1085                         GHOST_TUns8 * temp_buff;
1086                         GHOST_TStringArray *strArray;
1087                         NSArray *droppedArray;
1088                         size_t pastedTextSize;  
1089                         NSString *droppedStr;
1090                         GHOST_TEventDataPtr eventData;
1091                         int i;
1092
1093                         if (!data) return GHOST_kFailure;
1094                         
1095                         switch (draggedObjectType) {
1096                                 case GHOST_kDragnDropTypeFilenames:
1097                                         droppedArray = (NSArray*)data;
1098                                         
1099                                         strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
1100                                         if (!strArray) return GHOST_kFailure;
1101                                         
1102                                         strArray->count = [droppedArray count];
1103                                         if (strArray->count == 0) return GHOST_kFailure;
1104                                         
1105                                         strArray->strings = (GHOST_TUns8**) malloc(strArray->count*sizeof(GHOST_TUns8*));
1106                                         
1107                                         for (i=0;i<strArray->count;i++)
1108                                         {
1109                                                 droppedStr = [droppedArray objectAtIndex:i];
1110                                                 
1111                                                 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1112                                                 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1); 
1113                                         
1114                                                 if (!temp_buff) {
1115                                                         strArray->count = i;
1116                                                         break;
1117                                                 }
1118                                         
1119                                                 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1120                                                 temp_buff[pastedTextSize] = '\0';
1121                                                 
1122                                                 strArray->strings[i] = temp_buff;
1123                                         }
1124
1125                                         eventData = (GHOST_TEventDataPtr) strArray;     
1126                                         break;
1127                                         
1128                                 case GHOST_kDragnDropTypeString:
1129                                         droppedStr = (NSString*)data;
1130                                         pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1131                                         
1132                                         temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1); 
1133                                         
1134                                         if (temp_buff == NULL) {
1135                                                 return GHOST_kFailure;
1136                                         }
1137                                         
1138                                         strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1139                                         
1140                                         temp_buff[pastedTextSize] = '\0';
1141                                         
1142                                         eventData = (GHOST_TEventDataPtr) temp_buff;
1143                                         break;
1144                                 
1145                                 case GHOST_kDragnDropTypeBitmap:
1146                                 {
1147                                         NSImage *droppedImg = (NSImage*)data;
1148                                         NSSize imgSize = [droppedImg size];
1149                                         ImBuf *ibuf = NULL;
1150                                         GHOST_TUns8 *rasterRGB = NULL;
1151                                         GHOST_TUns8 *rasterRGBA = NULL;
1152                                         GHOST_TUns8 *toIBuf = NULL;
1153                                         int x, y, to_i, from_i;
1154                                         NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA,*bitmapImage=nil;
1155                                         NSEnumerator *enumerator;
1156                                         NSImageRep *representation;
1157                                         
1158                                         ibuf = IMB_allocImBuf (imgSize.width , imgSize.height, 32, IB_rect);
1159                                         if (!ibuf) {
1160                                                 [droppedImg release];
1161                                                 return GHOST_kFailure;
1162                                         }
1163                                         
1164                                         /*Get the bitmap of the image*/
1165                                         enumerator = [[droppedImg representations] objectEnumerator];
1166                                         while ((representation = [enumerator nextObject])) {
1167                                                 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
1168                                                         bitmapImage = (NSBitmapImageRep *)representation;
1169                                                         break;
1170                                                 }
1171                                         }
1172                                         if (bitmapImage == nil) return GHOST_kFailure;
1173                                         
1174                                         if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
1175                                                 && ![bitmapImage isPlanar]) {
1176                                                 /* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
1177                                                 toIBuf = (GHOST_TUns8*)ibuf->rect;
1178                                                 rasterRGB = (GHOST_TUns8*)[bitmapImage bitmapData];
1179                                                 for (y = 0; y < imgSize.height; y++) {
1180                                                         to_i = (imgSize.height-y-1)*imgSize.width;
1181                                                         from_i = y*imgSize.width;
1182                                                         memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*imgSize.width);
1183                                                 }
1184                                         }
1185                                         else {
1186                                                 /* Tell cocoa image resolution is same as current system one */
1187                                                 [bitmapImage setSize:imgSize];
1188                                                 
1189                                                 /* Convert the image in a RGBA 32bit format */
1190                                                 /* As Core Graphics does not support contextes with non premutliplied alpha,
1191                                                  we need to get alpha key values in a separate batch */
1192                                                 
1193                                                 /* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
1194                                                 blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1195                                                                                                                                                                                  pixelsWide:imgSize.width 
1196                                                                                                                                                                                  pixelsHigh:imgSize.height
1197                                                                                                                                                                           bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
1198                                                                                                                                                                          colorSpaceName:NSDeviceRGBColorSpace 
1199                                                                                                                                                                            bitmapFormat:(NSBitmapFormat)0
1200                                                                                                                                                                                 bytesPerRow:4*imgSize.width
1201                                                                                                                                                                            bitsPerPixel:32/*RGB format padded to 32bits*/];
1202                                                 
1203                                                 [NSGraphicsContext saveGraphicsState];
1204                                                 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
1205                                                 [bitmapImage draw];
1206                                                 [NSGraphicsContext restoreGraphicsState];
1207                                                 
1208                                                 rasterRGB = (GHOST_TUns8*)[blBitmapFormatImageRGB bitmapData];
1209                                                 if (rasterRGB == NULL) {
1210                                                         [bitmapImage release];
1211                                                         [blBitmapFormatImageRGB release];
1212                                                         [droppedImg release];
1213                                                         return GHOST_kFailure;
1214                                                 }
1215                                                 
1216                                                 /* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
1217                                                 blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1218                                                                                                                                                                                   pixelsWide:imgSize.width
1219                                                                                                                                                                                   pixelsHigh:imgSize.height
1220                                                                                                                                                                            bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
1221                                                                                                                                                                           colorSpaceName:NSDeviceRGBColorSpace
1222                                                                                                                                                                                 bitmapFormat:(NSBitmapFormat)0
1223                                                                                                                                                                                  bytesPerRow:4*imgSize.width
1224                                                                                                                                                                                 bitsPerPixel:32/* RGBA */];
1225                                                 
1226                                                 [NSGraphicsContext saveGraphicsState];
1227                                                 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
1228                                                 [bitmapImage draw];
1229                                                 [NSGraphicsContext restoreGraphicsState];
1230                                                 
1231                                                 rasterRGBA = (GHOST_TUns8*)[blBitmapFormatImageRGBA bitmapData];
1232                                                 if (rasterRGBA == NULL) {
1233                                                         [bitmapImage release];
1234                                                         [blBitmapFormatImageRGB release];
1235                                                         [blBitmapFormatImageRGBA release];
1236                                                         [droppedImg release];
1237                                                         return GHOST_kFailure;
1238                                                 }
1239                                                 
1240                                                 /*Copy the image to ibuf, flipping it vertically*/
1241                                                 toIBuf = (GHOST_TUns8*)ibuf->rect;
1242                                                 for (y = 0; y < imgSize.height; y++) {
1243                                                         for (x = 0; x < imgSize.width; x++) {
1244                                                                 to_i = (imgSize.height-y-1)*imgSize.width + x;
1245                                                                 from_i = y*imgSize.width + x;
1246                                                                 
1247                                                                 toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
1248                                                                 toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
1249                                                                 toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
1250                                                                 toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
1251                                                         }
1252                                                 }
1253                                                 
1254                                                 [blBitmapFormatImageRGB release];
1255                                                 [blBitmapFormatImageRGBA release];
1256                                                 [droppedImg release];
1257                                         }
1258                                         
1259                                         eventData = (GHOST_TEventDataPtr) ibuf;
1260                                 }
1261                                         break;
1262                                         
1263                                 default:
1264                                         return GHOST_kFailure;
1265                                         break;
1266                         }
1267                         pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,eventData));
1268                 }
1269                         break;
1270                 default:
1271                         return GHOST_kFailure;
1272         }
1273         m_outsideLoopEventProcessed = true;
1274         return GHOST_kSuccess;
1275 }
1276
1277
1278 GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest()
1279 {
1280         GHOST_Window* window = (GHOST_Window*)m_windowManager->getActiveWindow();
1281         
1282         //Discard quit event if we are in cursor grab sequence
1283         if (window && window->getCursorGrabModeIsWarp())
1284                 return GHOST_kExitCancel;
1285         
1286         //Check open windows if some changes are not saved
1287         if (m_windowManager->getAnyModifiedState())
1288         {
1289                 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved.\nDo you really want to quit ?",
1290                                                                                  @"Cancel", @"Quit Anyway", nil);
1291                 if (shouldQuit == NSAlertAlternateReturn)
1292                 {
1293                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1294                         return GHOST_kExitNow;
1295                 } else {
1296                         //Give back focus to the blender window if user selected cancel quit
1297                         NSArray *windowsList = [NSApp orderedWindows];
1298                         if ([windowsList count]) {
1299                                 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1300                                 //Handle the modifiers keyes changed state issue
1301                                 //as recovering from the quit dialog is like application
1302                                 //gaining focus back.
1303                                 //Main issue fixed is Cmd modifier not being cleared
1304                                 handleApplicationBecomeActiveEvent();
1305                         }
1306                 }
1307
1308         }
1309         else {
1310                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1311                 m_outsideLoopEventProcessed = true;
1312                 return GHOST_kExitNow;
1313         }
1314         
1315         return GHOST_kExitCancel;
1316 }
1317
1318 bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr)
1319 {
1320         NSString *filepath = (NSString*)filepathStr;
1321         int confirmOpen = NSAlertAlternateReturn;
1322         NSArray *windowsList;
1323         char * temp_buff;
1324         size_t filenameTextSize;        
1325         GHOST_Window* window= (GHOST_Window*)m_windowManager->getActiveWindow();
1326         
1327         if (!window) {
1328                 return NO;
1329         }       
1330         
1331         //Discard event if we are in cursor grab sequence, it'll lead to "stuck cursor" situation if the alert panel is raised
1332         if (window && window->getCursorGrabModeIsWarp())
1333                 return GHOST_kExitCancel;
1334
1335         //Check open windows if some changes are not saved
1336         if (m_windowManager->getAnyModifiedState())
1337         {
1338                 confirmOpen = NSRunAlertPanel([NSString stringWithFormat:@"Opening %@",[filepath lastPathComponent]],
1339                                                                                  @"Current document has not been saved.\nDo you really want to proceed?",
1340                                                                                  @"Cancel", @"Open", nil);
1341         }
1342
1343         //Give back focus to the blender window
1344         windowsList = [NSApp orderedWindows];
1345         if ([windowsList count]) {
1346                 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1347         }
1348
1349         if (confirmOpen == NSAlertAlternateReturn)
1350         {
1351                 filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1352                 
1353                 temp_buff = (char*) malloc(filenameTextSize+1); 
1354                 
1355                 if (temp_buff == NULL) {
1356                         return GHOST_kFailure;
1357                 }
1358                 
1359                 strncpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize);
1360                 
1361                 temp_buff[filenameTextSize] = '\0';
1362
1363                 pushEvent(new GHOST_EventString(getMilliSeconds(),GHOST_kEventOpenMainFile,window,(GHOST_TEventDataPtr) temp_buff));
1364
1365                 return YES;
1366         }
1367         else return NO;
1368 }
1369
1370 GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr, short eventType)
1371 {
1372         NSEvent *event = (NSEvent *)eventPtr;
1373         GHOST_IWindow* window;
1374         
1375         window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1376         if (!window) {
1377                 //printf("\nW failure for event 0x%x",[event type]);
1378                 return GHOST_kFailure;
1379         }
1380         
1381         GHOST_TabletData& ct=((GHOST_WindowCocoa*)window)->GetCocoaTabletData();
1382         
1383         switch (eventType) {
1384                 case NSTabletPoint:
1385                         ct.Pressure = [event pressure];
1386                         ct.Xtilt = [event tilt].x;
1387                         ct.Ytilt = [event tilt].y;
1388                         break;
1389                 
1390                 case NSTabletProximity:
1391                         ct.Pressure = 0;
1392                         ct.Xtilt = 0;
1393                         ct.Ytilt = 0;
1394                         if ([event isEnteringProximity])
1395                         {
1396                                 //pointer is entering tablet area proximity
1397                                 switch ([event pointingDeviceType]) {
1398                                         case NSPenPointingDevice:
1399                                                 ct.Active = GHOST_kTabletModeStylus;
1400                                                 break;
1401                                         case NSEraserPointingDevice:
1402                                                 ct.Active = GHOST_kTabletModeEraser;
1403                                                 break;
1404                                         case NSCursorPointingDevice:
1405                                         case NSUnknownPointingDevice:
1406                                         default:
1407                                                 ct.Active = GHOST_kTabletModeNone;
1408                                                 break;
1409                                 }
1410                         } else {
1411                                 // pointer is leaving - return to mouse
1412                                 ct.Active = GHOST_kTabletModeNone;
1413                         }
1414                         break;
1415                 
1416                 default:
1417                         GHOST_ASSERT(FALSE,"GHOST_SystemCocoa::handleTabletEvent : unknown event received");
1418                         return GHOST_kFailure;
1419                         break;
1420         }
1421         return GHOST_kSuccess;
1422 }
1423
1424
1425 GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
1426 {
1427         NSEvent *event = (NSEvent *)eventPtr;
1428     GHOST_WindowCocoa* window;
1429         
1430         window = (GHOST_WindowCocoa*)m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1431         if (!window) {
1432                 //printf("\nW failure for event 0x%x",[event type]);
1433                 return GHOST_kFailure;
1434         }
1435         
1436         switch ([event type])
1437     {
1438                 case NSLeftMouseDown:
1439                 case NSRightMouseDown:
1440                 case NSOtherMouseDown:
1441                         pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
1442                         //Handle tablet events combined with mouse events
1443                         switch ([event subtype]) {
1444                                 case NX_SUBTYPE_TABLET_POINT:
1445                                         handleTabletEvent(eventPtr, NSTabletPoint);
1446                                         break;
1447                                 case NX_SUBTYPE_TABLET_PROXIMITY:
1448                                         handleTabletEvent(eventPtr, NSTabletProximity);
1449                                         break;
1450                                 default:
1451                                         //No tablet event included : do nothing
1452                                         break;
1453                         }
1454                         break;
1455                                                 
1456                 case NSLeftMouseUp:
1457                 case NSRightMouseUp:
1458                 case NSOtherMouseUp:
1459                         pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1460                         //Handle tablet events combined with mouse events
1461                         switch ([event subtype]) {
1462                                 case NX_SUBTYPE_TABLET_POINT:
1463                                         handleTabletEvent(eventPtr, NSTabletPoint);
1464                                         break;
1465                                 case NX_SUBTYPE_TABLET_PROXIMITY:
1466                                         handleTabletEvent(eventPtr, NSTabletProximity);
1467                                         break;
1468                                 default:
1469                                         //No tablet event included : do nothing
1470                                         break;
1471                         }
1472                         break;
1473                         
1474                 case NSLeftMouseDragged:
1475                 case NSRightMouseDragged:
1476                 case NSOtherMouseDragged:                               
1477                         //Handle tablet events combined with mouse events
1478                         switch ([event subtype]) {
1479                                 case NX_SUBTYPE_TABLET_POINT:
1480                                         handleTabletEvent(eventPtr, NSTabletPoint);
1481                                         break;
1482                                 case NX_SUBTYPE_TABLET_PROXIMITY:
1483                                         handleTabletEvent(eventPtr, NSTabletProximity);
1484                                         break;
1485                                 default:
1486                                         //No tablet event included : do nothing
1487                                         break;
1488                         }
1489                         
1490                 case NSMouseMoved:
1491                                 GHOST_TGrabCursorMode grab_mode = window->getCursorGrabMode();
1492
1493                                 /* TODO: CHECK IF THIS IS A TABLET EVENT */
1494                                 bool is_tablet = false;
1495
1496                                 if (is_tablet && window->getCursorGrabModeIsWarp()) {
1497                                         grab_mode = GHOST_kGrabDisable;
1498                                 }
1499
1500                                 switch (grab_mode) {
1501                                         case GHOST_kGrabHide: //Cursor hidden grab operation : no cursor move
1502                                         {
1503                                                 GHOST_TInt32 x_warp, y_warp, x_accum, y_accum, x, y;
1504                                                 
1505                                                 window->getCursorGrabInitPos(x_warp, y_warp);
1506                                                 
1507                                                 window->getCursorGrabAccum(x_accum, y_accum);
1508                                                 x_accum += [event deltaX];
1509                                                 y_accum += -[event deltaY]; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1510                                                 window->setCursorGrabAccum(x_accum, y_accum);
1511                                                 
1512                                                 window->clientToScreenIntern(x_warp+x_accum, y_warp+y_accum, x, y);
1513                                                 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1514                                         }
1515                                                 break;
1516                                         case GHOST_kGrabWrap: //Wrap cursor at area/window boundaries
1517                                         {
1518                                                 NSPoint mousePos = [event locationInWindow];
1519                                                 GHOST_TInt32 x_mouse= mousePos.x;
1520                                                 GHOST_TInt32 y_mouse= mousePos.y;
1521                                                 GHOST_TInt32 x_accum, y_accum, x_cur, y_cur, x, y;
1522                                                 GHOST_Rect bounds, windowBounds, correctedBounds;
1523                                                 
1524                                                 /* fallback to window bounds */
1525                                                 if(window->getCursorGrabBounds(bounds)==GHOST_kFailure)
1526                                                         window->getClientBounds(bounds);
1527                                                 
1528                                                 //Switch back to Cocoa coordinates orientation (y=0 at botton,the same as blender internal btw!), and to client coordinates
1529                                                 window->getClientBounds(windowBounds);
1530                                                 window->screenToClient(bounds.m_l, bounds.m_b, correctedBounds.m_l, correctedBounds.m_t);
1531                                                 window->screenToClient(bounds.m_r, bounds.m_t, correctedBounds.m_r, correctedBounds.m_b);
1532                                                 correctedBounds.m_b = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_b;
1533                                                 correctedBounds.m_t = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_t;
1534                                                 
1535                                                 //Update accumulation counts
1536                                                 window->getCursorGrabAccum(x_accum, y_accum);
1537                                                 x_accum += [event deltaX]-m_cursorDelta_x;
1538                                                 y_accum += -[event deltaY]-m_cursorDelta_y; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1539                                                 window->setCursorGrabAccum(x_accum, y_accum);
1540                                                 
1541                                                 
1542                                                 //Warp mouse cursor if needed
1543                                                 x_mouse += [event deltaX]-m_cursorDelta_x;
1544                                                 y_mouse += -[event deltaY]-m_cursorDelta_y;
1545                                                 correctedBounds.wrapPoint(x_mouse, y_mouse, 2);
1546                                                 
1547                                                 //Compensate for mouse moved event taking cursor position set into account
1548                                                 m_cursorDelta_x = x_mouse-mousePos.x;
1549                                                 m_cursorDelta_y = y_mouse-mousePos.y;
1550                                                 
1551                                                 //Set new cursor position
1552                                                 window->clientToScreenIntern(x_mouse, y_mouse, x_cur, y_cur);
1553                                                 setMouseCursorPosition(x_cur, y_cur); /* wrap */
1554                                                 
1555                                                 //Post event
1556                                                 window->getCursorGrabInitPos(x_cur, y_cur);
1557                                                 window->clientToScreenIntern(x_cur + x_accum, y_cur + y_accum, x, y);
1558                                                 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1559                                         }
1560                                                 break;
1561                                         default:
1562                                         {
1563                                                 //Normal cursor operation: send mouse position in window
1564                                                 NSPoint mousePos = [event locationInWindow];
1565                                                 GHOST_TInt32 x, y;
1566
1567                                                 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1568                                                 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1569
1570                                                 m_cursorDelta_x=0;
1571                                                 m_cursorDelta_y=0; //Mouse motion occurred between two cursor warps, so we can reset the delta counter
1572                                         }
1573                                                 break;
1574                                 }
1575                                 break;
1576                         
1577                 case NSScrollWheel:
1578                         {
1579                                 /* Send trackpad event if inside a trackpad gesture, send wheel event otherwise */
1580                                 if (!m_hasMultiTouchTrackpad || !m_isGestureInProgress) {
1581                                         GHOST_TInt32 delta;
1582                                         
1583                                         double deltaF = [event deltaY];
1584
1585                                         if (deltaF == 0.0) deltaF = [event deltaX]; // make blender decide if it's horizontal scroll
1586                                         if (deltaF == 0.0) break; //discard trackpad delta=0 events
1587                                         
1588                                         delta = deltaF > 0.0 ? 1 : -1;
1589                                         pushEvent(new GHOST_EventWheel([event timestamp]*1000, window, delta));
1590                                 }
1591                                 else {
1592                                         NSPoint mousePos = [event locationInWindow];
1593                                         GHOST_TInt32 x, y;
1594                                         double dx = [event deltaX];
1595                                         double dy = -[event deltaY];
1596                                         
1597                                         const double deltaMax = 50.0;
1598                                         
1599                                         if ((dx == 0) && (dy == 0)) break;
1600                                         
1601                                         /* Quadratic acceleration */
1602                                         dx = dx*(fabs(dx)+0.5);
1603                                         if (dx<0.0) dx-=0.5; else dx+=0.5;
1604                                         if (dx< -deltaMax) dx= -deltaMax; else if (dx>deltaMax) dx=deltaMax;
1605                                         
1606                                         dy = dy*(fabs(dy)+0.5);
1607                                         if (dy<0.0) dy-=0.5; else dy+=0.5;
1608                                         if (dy< -deltaMax) dy= -deltaMax; else if (dy>deltaMax) dy=deltaMax;
1609
1610                                         window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1611                                         dy = -dy;
1612
1613                                         pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventScroll, x, y, dx, dy));
1614                                 }
1615                         }
1616                         break;
1617                         
1618                 case NSEventTypeMagnify:
1619                         {
1620                                 NSPoint mousePos = [event locationInWindow];
1621                                 GHOST_TInt32 x, y;
1622                                 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1623                                 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventMagnify, x, y,
1624                                                                                                   [event magnification]*125.0 + 0.1, 0));
1625                         }
1626                         break;
1627
1628                 case NSEventTypeRotate:
1629                         {
1630                                 NSPoint mousePos = [event locationInWindow];
1631                                 GHOST_TInt32 x, y;
1632                                 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1633                                 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventRotate, x, y,
1634                                                                                                   -[event rotation] * 5.0, 0));
1635                         }
1636                 case NSEventTypeBeginGesture:
1637                         m_isGestureInProgress = true;
1638                         break;
1639                 case NSEventTypeEndGesture:
1640                         m_isGestureInProgress = false;
1641                         break;
1642                 default:
1643                         return GHOST_kFailure;
1644                         break;
1645                 }
1646         
1647         return GHOST_kSuccess;
1648 }
1649
1650
1651 GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1652 {
1653         NSEvent *event = (NSEvent *)eventPtr;
1654         GHOST_IWindow* window;
1655         unsigned int modifiers;
1656         NSString *characters;
1657         NSData *convertedCharacters;
1658         GHOST_TKey keyCode;
1659         unsigned char ascii;
1660         NSString* charsIgnoringModifiers;
1661
1662         window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1663         if (!window) {
1664                 //printf("\nW failure for event 0x%x",[event type]);
1665                 return GHOST_kFailure;
1666         }
1667
1668         char utf8_buf[6]= {'\0'};
1669         ascii = 0;
1670         
1671         switch ([event type]) {
1672
1673                 case NSKeyDown:
1674                 case NSKeyUp:
1675                         charsIgnoringModifiers = [event charactersIgnoringModifiers];
1676                         if ([charsIgnoringModifiers length]>0)
1677                                 keyCode = convertKey([event keyCode],
1678                                                                          [charsIgnoringModifiers characterAtIndex:0],
1679                                                                          [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1680                         else
1681                                 keyCode = convertKey([event keyCode],0,
1682                                                                          [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1683
1684                         /* handling both unicode or ascii */
1685                         characters = [event characters];
1686                         if ([characters length]>0) {
1687                                 convertedCharacters = [characters dataUsingEncoding:NSUTF8StringEncoding];
1688                                 
1689                                 for (int x = 0; x < [convertedCharacters length]; x++) {
1690                                         utf8_buf[x] = ((char*)[convertedCharacters bytes])[x];
1691                                 }
1692
1693                                 /* ascii is a subset of unicode */
1694                                 if ([convertedCharacters length] == 1) {
1695                                         ascii = utf8_buf[0];
1696                                 }
1697                         }
1698
1699                         /* arrow keys should not have utf8 */
1700                         if ((keyCode > 266) && (keyCode < 271))
1701                                 utf8_buf[0] = '\0';
1702
1703                         if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1704                                 break; //Cmd-Q is directly handled by Cocoa
1705
1706                         if ([event type] == NSKeyDown) {
1707                                 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyDown, window, keyCode, ascii, utf8_buf) );
1708                                 //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);
1709                         } else {
1710                                 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyUp, window, keyCode, 0, '\0') );
1711                                 //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);
1712                         }
1713                         break;
1714         
1715                 case NSFlagsChanged: 
1716                         modifiers = [event modifierFlags];
1717                         
1718                         if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1719                                 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1720                         }
1721                         if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1722                                 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1723                         }
1724                         if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1725                                 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1726                         }
1727                         if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1728                                 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1729                         }
1730                         
1731                         m_modifierMask = modifiers;
1732                         break;
1733                         
1734                 default:
1735                         return GHOST_kFailure;
1736                         break;
1737         }
1738         
1739         return GHOST_kSuccess;
1740 }
1741
1742
1743
1744 #pragma mark Clipboard get/set
1745
1746 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1747 {
1748         GHOST_TUns8 * temp_buff;
1749         size_t pastedTextSize;  
1750         
1751         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1752         
1753         NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1754         
1755         if (pasteBoard == nil) {
1756                 [pool drain];
1757                 return NULL;
1758         }
1759         
1760         NSArray *supportedTypes =
1761                 [NSArray arrayWithObjects: NSStringPboardType, nil];
1762         
1763         NSString *bestType = [[NSPasteboard generalPasteboard]
1764                                                   availableTypeFromArray:supportedTypes];
1765         
1766         if (bestType == nil) {
1767                 [pool drain];
1768                 return NULL;
1769         }
1770         
1771         NSString * textPasted = [pasteBoard stringForType:NSStringPboardType];
1772
1773         if (textPasted == nil) {
1774                 [pool drain];
1775                 return NULL;
1776         }
1777         
1778         pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1779         
1780         temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1); 
1781
1782         if (temp_buff == NULL) {
1783                 [pool drain];
1784                 return NULL;
1785         }
1786         
1787         strncpy((char*)temp_buff, [textPasted cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1788         
1789         temp_buff[pastedTextSize] = '\0';
1790         
1791         [pool drain];
1792
1793         if(temp_buff) {
1794                 return temp_buff;
1795         } else {
1796                 return NULL;
1797         }
1798 }
1799
1800 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1801 {
1802         NSString *textToCopy;
1803         
1804         if(selection) {return;} // for copying the selection, used on X11
1805
1806         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1807                 
1808         NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1809         
1810         if (pasteBoard == nil) {
1811                 [pool drain];
1812                 return;
1813         }
1814         
1815         NSArray *supportedTypes = [NSArray arrayWithObject:NSStringPboardType];
1816         
1817         [pasteBoard declareTypes:supportedTypes owner:nil];
1818         
1819         textToCopy = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
1820         
1821         [pasteBoard setString:textToCopy forType:NSStringPboardType];
1822         
1823         [pool drain];
1824 }
1825