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