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