3 * ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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.
19 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
20 * All rights reserved.
22 * The Original Code is: all of this file.
24 * Contributor(s): Maarten Gribnau 05/2001
25 * Damien Plisson 09/2009
27 * ***** END GPL LICENSE BLOCK *****
30 #import <Cocoa/Cocoa.h>
32 /*For the currently not ported to Cocoa keyboard layout functions (64bit & 10.6 compatible)*/
33 #include <Carbon/Carbon.h>
36 #include <sys/types.h>
37 #include <sys/sysctl.h>
39 #include "GHOST_SystemCocoa.h"
41 #include "GHOST_DisplayManagerCocoa.h"
42 #include "GHOST_EventKey.h"
43 #include "GHOST_EventButton.h"
44 #include "GHOST_EventCursor.h"
45 #include "GHOST_EventWheel.h"
46 #include "GHOST_EventNDOF.h"
47 #include "GHOST_EventTrackpad.h"
48 #include "GHOST_EventDragnDrop.h"
49 #include "GHOST_EventString.h"
51 #include "GHOST_TimerManager.h"
52 #include "GHOST_TimerTask.h"
53 #include "GHOST_WindowManager.h"
54 #include "GHOST_WindowCocoa.h"
55 #include "GHOST_NDOFManager.h"
56 #include "AssertMacros.h"
58 #pragma mark KeyMap, mouse converters
59 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
60 /* Keycodes not defined in Tiger */
66 * These constants are the virtual keycodes defined originally in
67 * Inside Mac Volume V, pg. V-191. They identify physical keys on a
68 * keyboard. Those constants with "ANSI" in the name are labeled
69 * according to the key position on an ANSI-standard US keyboard.
70 * For example, kVK_ANSI_A indicates the virtual keycode for the key
71 * with the letter 'A' in the US keyboard layout. Other keyboard
72 * layouts may have the 'A' key label on a different physical key;
73 * in this case, pressing 'A' will generate a different virtual
100 kVK_ANSI_Equal = 0x18,
103 kVK_ANSI_Minus = 0x1B,
106 kVK_ANSI_RightBracket = 0x1E,
109 kVK_ANSI_LeftBracket = 0x21,
114 kVK_ANSI_Quote = 0x27,
116 kVK_ANSI_Semicolon = 0x29,
117 kVK_ANSI_Backslash = 0x2A,
118 kVK_ANSI_Comma = 0x2B,
119 kVK_ANSI_Slash = 0x2C,
122 kVK_ANSI_Period = 0x2F,
123 kVK_ANSI_Grave = 0x32,
124 kVK_ANSI_KeypadDecimal = 0x41,
125 kVK_ANSI_KeypadMultiply = 0x43,
126 kVK_ANSI_KeypadPlus = 0x45,
127 kVK_ANSI_KeypadClear = 0x47,
128 kVK_ANSI_KeypadDivide = 0x4B,
129 kVK_ANSI_KeypadEnter = 0x4C,
130 kVK_ANSI_KeypadMinus = 0x4E,
131 kVK_ANSI_KeypadEquals = 0x51,
132 kVK_ANSI_Keypad0 = 0x52,
133 kVK_ANSI_Keypad1 = 0x53,
134 kVK_ANSI_Keypad2 = 0x54,
135 kVK_ANSI_Keypad3 = 0x55,
136 kVK_ANSI_Keypad4 = 0x56,
137 kVK_ANSI_Keypad5 = 0x57,
138 kVK_ANSI_Keypad6 = 0x58,
139 kVK_ANSI_Keypad7 = 0x59,
140 kVK_ANSI_Keypad8 = 0x5B,
141 kVK_ANSI_Keypad9 = 0x5C
144 /* keycodes for keys that are independent of keyboard layout*/
156 kVK_RightShift = 0x3C,
157 kVK_RightOption = 0x3D,
158 kVK_RightControl = 0x3E,
162 kVK_VolumeDown = 0x49,
183 kVK_ForwardDelete = 0x75,
189 kVK_LeftArrow = 0x7B,
190 kVK_RightArrow = 0x7C,
191 kVK_DownArrow = 0x7D,
195 /* ISO keyboards only*/
197 kVK_ISO_Section = 0x0A
200 /* JIS keyboards only*/
203 kVK_JIS_Underscore = 0x5E,
204 kVK_JIS_KeypadComma = 0x5F,
210 static GHOST_TButtonMask convertButton(int button)
214 return GHOST_kButtonMaskLeft;
216 return GHOST_kButtonMaskRight;
218 return GHOST_kButtonMaskMiddle;
220 return GHOST_kButtonMaskButton4;
222 return GHOST_kButtonMaskButton5;
224 return GHOST_kButtonMaskLeft;
229 * Converts Mac rawkey codes (same for Cocoa & Carbon)
230 * into GHOST key codes
231 * @param rawCode The raw physical key code
232 * @param recvChar the character ignoring modifiers (except for shift)
233 * @return Ghost key code
235 static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction)
238 //printf("\nrecvchar %c 0x%x",recvChar,recvChar);
240 /*Physical keycodes not used due to map changes in int'l keyboards
241 case kVK_ANSI_A: return GHOST_kKeyA;
242 case kVK_ANSI_B: return GHOST_kKeyB;
243 case kVK_ANSI_C: return GHOST_kKeyC;
244 case kVK_ANSI_D: return GHOST_kKeyD;
245 case kVK_ANSI_E: return GHOST_kKeyE;
246 case kVK_ANSI_F: return GHOST_kKeyF;
247 case kVK_ANSI_G: return GHOST_kKeyG;
248 case kVK_ANSI_H: return GHOST_kKeyH;
249 case kVK_ANSI_I: return GHOST_kKeyI;
250 case kVK_ANSI_J: return GHOST_kKeyJ;
251 case kVK_ANSI_K: return GHOST_kKeyK;
252 case kVK_ANSI_L: return GHOST_kKeyL;
253 case kVK_ANSI_M: return GHOST_kKeyM;
254 case kVK_ANSI_N: return GHOST_kKeyN;
255 case kVK_ANSI_O: return GHOST_kKeyO;
256 case kVK_ANSI_P: return GHOST_kKeyP;
257 case kVK_ANSI_Q: return GHOST_kKeyQ;
258 case kVK_ANSI_R: return GHOST_kKeyR;
259 case kVK_ANSI_S: return GHOST_kKeyS;
260 case kVK_ANSI_T: return GHOST_kKeyT;
261 case kVK_ANSI_U: return GHOST_kKeyU;
262 case kVK_ANSI_V: return GHOST_kKeyV;
263 case kVK_ANSI_W: return GHOST_kKeyW;
264 case kVK_ANSI_X: return GHOST_kKeyX;
265 case kVK_ANSI_Y: return GHOST_kKeyY;
266 case kVK_ANSI_Z: return GHOST_kKeyZ;*/
268 /* Numbers keys mapped to handle some int'l keyboard (e.g. French)*/
269 case kVK_ISO_Section: return GHOST_kKeyUnknown;
270 case kVK_ANSI_1: return GHOST_kKey1;
271 case kVK_ANSI_2: return GHOST_kKey2;
272 case kVK_ANSI_3: return GHOST_kKey3;
273 case kVK_ANSI_4: return GHOST_kKey4;
274 case kVK_ANSI_5: return GHOST_kKey5;
275 case kVK_ANSI_6: return GHOST_kKey6;
276 case kVK_ANSI_7: return GHOST_kKey7;
277 case kVK_ANSI_8: return GHOST_kKey8;
278 case kVK_ANSI_9: return GHOST_kKey9;
279 case kVK_ANSI_0: return GHOST_kKey0;
281 case kVK_ANSI_Keypad0: return GHOST_kKeyNumpad0;
282 case kVK_ANSI_Keypad1: return GHOST_kKeyNumpad1;
283 case kVK_ANSI_Keypad2: return GHOST_kKeyNumpad2;
284 case kVK_ANSI_Keypad3: return GHOST_kKeyNumpad3;
285 case kVK_ANSI_Keypad4: return GHOST_kKeyNumpad4;
286 case kVK_ANSI_Keypad5: return GHOST_kKeyNumpad5;
287 case kVK_ANSI_Keypad6: return GHOST_kKeyNumpad6;
288 case kVK_ANSI_Keypad7: return GHOST_kKeyNumpad7;
289 case kVK_ANSI_Keypad8: return GHOST_kKeyNumpad8;
290 case kVK_ANSI_Keypad9: return GHOST_kKeyNumpad9;
291 case kVK_ANSI_KeypadDecimal: return GHOST_kKeyNumpadPeriod;
292 case kVK_ANSI_KeypadEnter: return GHOST_kKeyNumpadEnter;
293 case kVK_ANSI_KeypadPlus: return GHOST_kKeyNumpadPlus;
294 case kVK_ANSI_KeypadMinus: return GHOST_kKeyNumpadMinus;
295 case kVK_ANSI_KeypadMultiply: return GHOST_kKeyNumpadAsterisk;
296 case kVK_ANSI_KeypadDivide: return GHOST_kKeyNumpadSlash;
297 case kVK_ANSI_KeypadClear: return GHOST_kKeyUnknown;
299 case kVK_F1: return GHOST_kKeyF1;
300 case kVK_F2: return GHOST_kKeyF2;
301 case kVK_F3: return GHOST_kKeyF3;
302 case kVK_F4: return GHOST_kKeyF4;
303 case kVK_F5: return GHOST_kKeyF5;
304 case kVK_F6: return GHOST_kKeyF6;
305 case kVK_F7: return GHOST_kKeyF7;
306 case kVK_F8: return GHOST_kKeyF8;
307 case kVK_F9: return GHOST_kKeyF9;
308 case kVK_F10: return GHOST_kKeyF10;
309 case kVK_F11: return GHOST_kKeyF11;
310 case kVK_F12: return GHOST_kKeyF12;
311 case kVK_F13: return GHOST_kKeyF13;
312 case kVK_F14: return GHOST_kKeyF14;
313 case kVK_F15: return GHOST_kKeyF15;
314 case kVK_F16: return GHOST_kKeyF16;
315 case kVK_F17: return GHOST_kKeyF17;
316 case kVK_F18: return GHOST_kKeyF18;
317 case kVK_F19: return GHOST_kKeyF19;
318 case kVK_F20: return GHOST_kKeyF20;
320 case kVK_UpArrow: return GHOST_kKeyUpArrow;
321 case kVK_DownArrow: return GHOST_kKeyDownArrow;
322 case kVK_LeftArrow: return GHOST_kKeyLeftArrow;
323 case kVK_RightArrow: return GHOST_kKeyRightArrow;
325 case kVK_Return: return GHOST_kKeyEnter;
326 case kVK_Delete: return GHOST_kKeyBackSpace;
327 case kVK_ForwardDelete: return GHOST_kKeyDelete;
328 case kVK_Escape: return GHOST_kKeyEsc;
329 case kVK_Tab: return GHOST_kKeyTab;
330 case kVK_Space: return GHOST_kKeySpace;
332 case kVK_Home: return GHOST_kKeyHome;
333 case kVK_End: return GHOST_kKeyEnd;
334 case kVK_PageUp: return GHOST_kKeyUpPage;
335 case kVK_PageDown: return GHOST_kKeyDownPage;
337 /*case kVK_ANSI_Minus: return GHOST_kKeyMinus;
338 case kVK_ANSI_Equal: return GHOST_kKeyEqual;
339 case kVK_ANSI_Comma: return GHOST_kKeyComma;
340 case kVK_ANSI_Period: return GHOST_kKeyPeriod;
341 case kVK_ANSI_Slash: return GHOST_kKeySlash;
342 case kVK_ANSI_Semicolon: return GHOST_kKeySemicolon;
343 case kVK_ANSI_Quote: return GHOST_kKeyQuote;
344 case kVK_ANSI_Backslash: return GHOST_kKeyBackslash;
345 case kVK_ANSI_LeftBracket: return GHOST_kKeyLeftBracket;
346 case kVK_ANSI_RightBracket: return GHOST_kKeyRightBracket;
347 case kVK_ANSI_Grave: return GHOST_kKeyAccentGrave;*/
352 return GHOST_kKeyUnknown;
355 /* alphanumerical or punctuation key that is remappable in int'l keyboards */
356 if ((recvChar >= 'A') && (recvChar <= 'Z')) {
357 return (GHOST_TKey) (recvChar - 'A' + GHOST_kKeyA);
358 } else if ((recvChar >= 'a') && (recvChar <= 'z')) {
359 return (GHOST_TKey) (recvChar - 'a' + GHOST_kKeyA);
361 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
362 KeyboardLayoutRef keyLayout;
363 UCKeyboardLayout *uchrData;
365 KLGetCurrentKeyboardLayout(&keyLayout);
366 KLGetKeyboardLayoutProperty(keyLayout, kKLuchrData, (const void **)
368 /*get actual character value of the "remappable" keys in int'l keyboards,
369 if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
370 then fallback on using the received charactersIgnoringModifiers */
373 UInt32 deadKeyState=0;
374 UniCharCount actualStrLength=0;
376 UCKeyTranslate(uchrData, rawCode, keyAction, 0,
377 LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
381 /* Leopard and Snow Leopard 64bit compatible API*/
382 CFDataRef uchrHandle; /*the keyboard layout*/
383 TISInputSourceRef kbdTISHandle;
385 kbdTISHandle = TISCopyCurrentKeyboardLayoutInputSource();
386 uchrHandle = (CFDataRef)TISGetInputSourceProperty(kbdTISHandle,kTISPropertyUnicodeKeyLayoutData);
387 CFRelease(kbdTISHandle);
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 */
394 UInt32 deadKeyState=0;
395 UniCharCount actualStrLength=0;
397 UCKeyTranslate((UCKeyboardLayout*)CFDataGetBytePtr(uchrHandle), rawCode, keyAction, 0,
398 LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
403 case '-': return GHOST_kKeyMinus;
404 case '=': return GHOST_kKeyEqual;
405 case ',': return GHOST_kKeyComma;
406 case '.': return GHOST_kKeyPeriod;
407 case '/': return GHOST_kKeySlash;
408 case ';': return GHOST_kKeySemicolon;
409 case '\'': return GHOST_kKeyQuote;
410 case '\\': return GHOST_kKeyBackslash;
411 case '[': return GHOST_kKeyLeftBracket;
412 case ']': return GHOST_kKeyRightBracket;
413 case '`': return GHOST_kKeyAccentGrave;
415 return GHOST_kKeyUnknown;
419 return GHOST_kKeyUnknown;
423 #pragma mark defines for 10.6 api not documented in 10.5
424 #ifndef MAC_OS_X_VERSION_10_6
426 /* The following event types are available on some hardware on 10.5.2 and later */
427 NSEventTypeGesture = 29,
428 NSEventTypeMagnify = 30,
429 NSEventTypeSwipe = 31,
430 NSEventTypeRotate = 18,
431 NSEventTypeBeginGesture = 19,
432 NSEventTypeEndGesture = 20
435 @interface NSEvent(GestureEvents)
436 /* This message is valid for events of type NSEventTypeMagnify, on 10.5.2 or later */
437 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
438 - (float)magnification; // change in magnification.
440 - (CGFloat)magnification; // change in magnification.
447 #pragma mark Utility functions
449 #define FIRSTFILEBUFLG 512
450 static bool g_hasFirstFile = false;
451 static char g_firstFileBuf[512];
453 //TODO:Need to investigate this. Function called too early in creator.c to have g_hasFirstFile == true
454 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) {
455 if (g_hasFirstFile) {
456 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1);
457 buf[FIRSTFILEBUFLG - 1] = '\0';
464 #if defined(WITH_QUICKTIME) && !defined(USE_QTKIT)
465 //Need to place this quicktime function in an ObjC file
466 //It is used to avoid memory leak when raising the quicktime "compression settings" standard dialog
470 extern int fromcocoa_request_qtcodec_settings(bContext *C, wmOperator *op);
473 int cocoa_request_qtcodec_settings(bContext *C, wmOperator *op)
476 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
478 result = fromcocoa_request_qtcodec_settings(C, op);
487 #pragma mark Cocoa objects
491 * ObjC object to capture applicationShouldTerminate, and send quit event
493 @interface CocoaAppDelegate : NSObject {
494 GHOST_SystemCocoa *systemCocoa;
496 - (void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa;
497 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
498 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
499 - (void)applicationWillTerminate:(NSNotification *)aNotification;
500 - (void)applicationWillBecomeActive:(NSNotification *)aNotification;
503 @implementation CocoaAppDelegate : NSObject
504 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
506 systemCocoa = sysCocoa;
509 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
511 return systemCocoa->handleOpenDocumentRequest(filename);
514 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
516 //TODO: implement graceful termination through Cocoa mechanism to avoid session log off to be cancelled
517 //Note that Cmd+Q is already handled by keyhandler
518 if (systemCocoa->handleQuitRequest() == GHOST_kExitNow)
519 return NSTerminateCancel;//NSTerminateNow;
521 return NSTerminateCancel;
524 // To avoid cancelling a log off process, we must use Cocoa termination process
525 // And this function is the only chance to perform clean up
526 // So WM_exit needs to be called directly, as the event loop will never run before termination
527 - (void)applicationWillTerminate:(NSNotification *)aNotification
529 /*G.afbreek = 0; //Let Cocoa perform the termination at the end
533 - (void)applicationWillBecomeActive:(NSNotification *)aNotification
535 systemCocoa->handleApplicationBecomeActiveEvent();
541 #pragma mark initialization/finalization
544 GHOST_SystemCocoa::GHOST_SystemCocoa()
547 struct timeval boottime;
549 char *rstring = NULL;
552 m_pressedMouseButtons =0;
553 m_isGestureInProgress = false;
556 m_outsideLoopEventProcessed = false;
557 m_needDelayedApplicationBecomeActiveEventProcessing = false;
558 m_displayManager = new GHOST_DisplayManagerCocoa ();
559 GHOST_ASSERT(m_displayManager, "GHOST_SystemCocoa::GHOST_SystemCocoa(): m_displayManager==0\n");
560 m_displayManager->initialize();
562 //NSEvent timeStamp is given in system uptime, state start date is boot time
564 mib[1] = KERN_BOOTTIME;
565 len = sizeof(struct timeval);
567 sysctl(mib, 2, &boottime, &len, NULL, 0);
568 m_start_time = ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
570 //Detect multitouch trackpad
573 sysctl( mib, 2, NULL, &len, NULL, 0 );
574 rstring = (char*)malloc( len );
575 sysctl( mib, 2, rstring, &len, NULL, 0 );
577 //Hack on MacBook revision, as multitouch avail. function missing
578 if (strstr(rstring,"MacBookAir") ||
579 (strstr(rstring,"MacBook") && (rstring[strlen(rstring)-3]>='5') && (rstring[strlen(rstring)-3]<='9')))
580 m_hasMultiTouchTrackpad = true;
581 else m_hasMultiTouchTrackpad = false;
586 m_ignoreWindowSizedMessages = false;
589 GHOST_SystemCocoa::~GHOST_SystemCocoa()
594 GHOST_TSuccess GHOST_SystemCocoa::init()
597 GHOST_TSuccess success = GHOST_System::init();
599 //ProcessSerialNumber psn;
601 //Carbon stuff to move window & menu to foreground
602 /*if (!GetCurrentProcess(&psn)) {
603 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
604 SetFrontProcess(&psn);
607 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
609 [NSApplication sharedApplication];
611 if ([NSApp mainMenu] == nil) {
612 NSMenu *mainMenubar = [[NSMenu alloc] init];
613 NSMenuItem *menuItem;
617 //Create the application menu
618 appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
620 [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
621 [appMenu addItem:[NSMenuItem separatorItem]];
623 menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
624 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
626 menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
627 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
629 [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
631 menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
632 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
634 menuItem = [[NSMenuItem alloc] init];
635 [menuItem setSubmenu:appMenu];
637 [mainMenubar addItem:menuItem];
639 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; //Needed for 10.5
642 //Create the window menu
643 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
645 menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
646 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
648 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
650 menuItem = [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
651 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
653 menuItem = [[NSMenuItem alloc] init];
654 [menuItem setSubmenu:windowMenu];
656 [mainMenubar addItem:menuItem];
659 [NSApp setMainMenu:mainMenubar];
660 [NSApp setWindowsMenu:windowMenu];
661 [windowMenu release];
664 if ([NSApp delegate] == nil) {
665 CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
666 [appDelegate setSystemCocoa:this];
667 [NSApp setDelegate:appDelegate];
670 [NSApp finishLaunching];
678 #pragma mark window management
680 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
682 //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
683 struct timeval currentTime;
685 gettimeofday(¤tTime, NULL);
687 //Return timestamp of system uptime
689 return ((currentTime.tv_sec*1000)+(currentTime.tv_usec/1000)-m_start_time);
693 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
695 //Note that OS X supports monitor hot plug
696 // We do not support multiple monitors at the moment
697 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
699 GHOST_TUns8 count = [[NSScreen screens] count];
706 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
708 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
709 //Get visible frame, that is frame excluding dock and top menu bar
710 NSRect frame = [[NSScreen mainScreen] visibleFrame];
712 //Returns max window contents (excluding title bar...)
713 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
714 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
716 width = contentRect.size.width;
717 height = contentRect.size.height;
723 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
724 const STR_String& title,
729 GHOST_TWindowState state,
730 GHOST_TDrawingContextType type,
732 const GHOST_TUns16 numOfAASamples,
733 const GHOST_TEmbedderWindowID parentWindow
736 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
737 GHOST_IWindow* window = 0;
739 //Get the available rect for including window contents
740 NSRect frame = [[NSScreen mainScreen] visibleFrame];
741 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
742 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
744 //Ensures window top left is inside this available rect
745 left = left > contentRect.origin.x ? left : contentRect.origin.x;
746 top = top > contentRect.origin.y ? top : contentRect.origin.y;
748 window = new GHOST_WindowCocoa (this, title, left, top, width, height, state, type, stereoVisual, numOfAASamples);
751 if (window->getValid()) {
752 // Store the pointer to the window
753 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
754 m_windowManager->addWindow(window);
755 m_windowManager->setActiveWindow(window);
756 //Need to tell window manager the new window is the active one (Cocoa does not send the event activate upon window creation)
757 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window));
758 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
762 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
768 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): could not create window\n");
774 GHOST_TSuccess GHOST_SystemCocoa::beginFullScreen(const GHOST_DisplaySetting& setting, GHOST_IWindow** window, const bool stereoVisual)
776 GHOST_IWindow* currentWindow = m_windowManager->getActiveWindow();
777 *window = currentWindow;
779 if(!currentWindow) return GHOST_kFailure;
781 return currentWindow->setState(GHOST_kWindowStateFullScreen);
784 GHOST_TSuccess GHOST_SystemCocoa::endFullScreen(void)
786 GHOST_IWindow* currentWindow = m_windowManager->getActiveWindow();
787 if(!currentWindow) return GHOST_kFailure;
789 return currentWindow->setState(GHOST_kWindowStateNormal);
795 * @note : returns coordinates in Cocoa screen coordinates
797 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
799 NSPoint mouseLoc = [NSEvent mouseLocation];
801 // Returns the mouse location in screen coordinates
802 x = (GHOST_TInt32)mouseLoc.x;
803 y = (GHOST_TInt32)mouseLoc.y;
804 return GHOST_kSuccess;
808 * @note : expect Cocoa screen coordinates
810 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
813 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
814 if (!window) return GHOST_kFailure;
816 //Cursor and mouse dissociation placed here not to interfere with continuous grab
817 // (in cont. grab setMouseCursorPosition is directly called)
818 CGAssociateMouseAndMouseCursorPosition(false);
819 setMouseCursorPosition(x, y);
820 CGAssociateMouseAndMouseCursorPosition(true);
822 //Force mouse move event (not pushed by Cocoa)
823 window->screenToClient(x, y, wx, wy);
824 pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, wx,wy));
825 m_outsideLoopEventProcessed = true;
827 return GHOST_kSuccess;
830 GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
832 float xf=(float)x, yf=(float)y;
833 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
834 if (!window) return GHOST_kFailure;
836 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
837 NSScreen *windowScreen = window->getScreen();
838 NSRect screenRect = [windowScreen frame];
840 //Set position relative to current screen
841 xf -= screenRect.origin.x;
842 yf -= screenRect.origin.y;
844 //Quartz Display Services uses the old coordinates (top left origin)
845 yf = screenRect.size.height -yf;
847 CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue], CGPointMake(xf, yf));
850 return GHOST_kSuccess;
854 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
856 keys.set(GHOST_kModifierKeyOS, (m_modifierMask & NSCommandKeyMask) ? true : false);
857 keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSAlternateKeyMask) ? true : false);
858 keys.set(GHOST_kModifierKeyLeftShift, (m_modifierMask & NSShiftKeyMask) ? true : false);
859 keys.set(GHOST_kModifierKeyLeftControl, (m_modifierMask & NSControlKeyMask) ? true : false);
861 return GHOST_kSuccess;
864 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
867 buttons.set(GHOST_kButtonMaskLeft, m_pressedMouseButtons & GHOST_kButtonMaskLeft);
868 buttons.set(GHOST_kButtonMaskRight, m_pressedMouseButtons & GHOST_kButtonMaskRight);
869 buttons.set(GHOST_kButtonMaskMiddle, m_pressedMouseButtons & GHOST_kButtonMaskMiddle);
870 buttons.set(GHOST_kButtonMaskButton4, m_pressedMouseButtons & GHOST_kButtonMaskButton4);
871 buttons.set(GHOST_kButtonMaskButton5, m_pressedMouseButtons & GHOST_kButtonMaskButton5);
872 return GHOST_kSuccess;
877 #pragma mark Event handlers
880 * The event queue polling function
882 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
884 bool anyProcessed = false;
887 // SetMouseCoalescingEnabled(false, NULL);
888 //TODO : implement timer ??
891 GHOST_TimerManager* timerMgr = getTimerManager();
894 GHOST_TUns64 next = timerMgr->nextFireTime();
897 if (next == GHOST_kFireTimeNever) {
898 timeOut = kEventDurationForever;
900 timeOut = (double)(next - getMilliSeconds())/1000.0;
905 ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
908 if (timerMgr->fireTimers(getMilliSeconds())) {
913 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
914 event = [NSApp nextEventMatchingMask:NSAnyEventMask
915 untilDate:[NSDate distantPast]
916 inMode:NSDefaultRunLoopMode
925 switch ([event type]) {
929 handleKeyEvent(event);
931 /* Support system-wide keyboard shortcuts, like Exposé, ...) =>included in always NSApp sendEvent */
932 /* if (([event modifierFlags] & NSCommandKeyMask) || [event type] == NSFlagsChanged) {
933 [NSApp sendEvent:event];
937 case NSLeftMouseDown:
939 case NSRightMouseDown:
942 case NSLeftMouseDragged:
943 case NSRightMouseDragged:
945 case NSOtherMouseDown:
947 case NSOtherMouseDragged:
948 case NSEventTypeMagnify:
949 case NSEventTypeRotate:
950 case NSEventTypeBeginGesture:
951 case NSEventTypeEndGesture:
952 handleMouseEvent(event);
956 case NSTabletProximity:
957 handleTabletEvent(event,[event type]);
960 /* Trackpad features, fired only from OS X 10.5.2
961 case NSEventTypeGesture:
962 case NSEventTypeSwipe:
968 NSAppKitDefined = 13,
969 NSSystemDefined = 14,
970 NSApplicationDefined = 15,
972 NSCursorUpdate = 17,*/
977 //Resend event to NSApp to ensure Mac wide events are handled
978 [NSApp sendEvent:event];
980 } while (event!= nil);
981 //} while (waitForEvent && !anyProcessed); Needed only for timer implementation
983 if (m_needDelayedApplicationBecomeActiveEventProcessing) handleApplicationBecomeActiveEvent();
985 if (m_outsideLoopEventProcessed) {
986 m_outsideLoopEventProcessed = false;
990 m_ignoreWindowSizedMessages = false;
995 //Note: called from NSApplication delegate
996 GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
998 //Update the modifiers key mask, as its status may have changed when the application was not active
999 //(that is when update events are sent to another application)
1000 unsigned int modifiers;
1001 GHOST_IWindow* window = m_windowManager->getActiveWindow();
1004 m_needDelayedApplicationBecomeActiveEventProcessing = true;
1005 return GHOST_kFailure;
1007 else m_needDelayedApplicationBecomeActiveEventProcessing = false;
1009 modifiers = [[[NSApplication sharedApplication] currentEvent] modifierFlags];
1011 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1012 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1014 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1015 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1017 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1018 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1020 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1021 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1024 m_modifierMask = modifiers;
1026 m_outsideLoopEventProcessed = true;
1027 return GHOST_kSuccess;
1030 //Note: called from NSWindow delegate
1031 GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa* window)
1033 if (!validWindow(window)) {
1034 return GHOST_kFailure;
1038 case GHOST_kEventWindowClose:
1039 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
1041 case GHOST_kEventWindowActivate:
1042 m_windowManager->setActiveWindow(window);
1043 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
1044 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
1046 case GHOST_kEventWindowDeactivate:
1047 m_windowManager->setWindowInactive(window);
1048 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
1050 case GHOST_kEventWindowUpdate:
1051 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
1053 case GHOST_kEventWindowMove:
1054 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, window) );
1056 case GHOST_kEventWindowSize:
1057 if (!m_ignoreWindowSizedMessages)
1059 //Enforce only one resize message per event loop (coalescing all the live resize messages)
1060 window->updateDrawingContext();
1061 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
1062 //Mouse up event is trapped by the resizing event loop, so send it anyway to the window manager
1063 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonUp, window, convertButton(0)));
1064 m_ignoreWindowSizedMessages = true;
1068 return GHOST_kFailure;
1072 m_outsideLoopEventProcessed = true;
1073 return GHOST_kSuccess;
1076 //Note: called from NSWindow subclass
1077 GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,
1078 GHOST_WindowCocoa* window, int mouseX, int mouseY, void* data)
1080 if (!validWindow(window)) {
1081 return GHOST_kFailure;
1085 case GHOST_kEventDraggingEntered:
1086 case GHOST_kEventDraggingUpdated:
1087 case GHOST_kEventDraggingExited:
1088 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,NULL));
1091 case GHOST_kEventDraggingDropDone:
1093 GHOST_TUns8 * temp_buff;
1094 GHOST_TStringArray *strArray;
1095 NSArray *droppedArray;
1096 size_t pastedTextSize;
1097 NSString *droppedStr;
1098 GHOST_TEventDataPtr eventData;
1101 if (!data) return GHOST_kFailure;
1103 switch (draggedObjectType) {
1104 case GHOST_kDragnDropTypeFilenames:
1105 droppedArray = (NSArray*)data;
1107 strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
1108 if (!strArray) return GHOST_kFailure;
1110 strArray->count = [droppedArray count];
1111 if (strArray->count == 0) return GHOST_kFailure;
1113 strArray->strings = (GHOST_TUns8**) malloc(strArray->count*sizeof(GHOST_TUns8*));
1115 for (i=0;i<strArray->count;i++)
1117 droppedStr = [droppedArray objectAtIndex:i];
1119 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1120 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1123 strArray->count = i;
1127 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1128 temp_buff[pastedTextSize] = '\0';
1130 strArray->strings[i] = temp_buff;
1133 eventData = (GHOST_TEventDataPtr) strArray;
1136 case GHOST_kDragnDropTypeString:
1137 droppedStr = (NSString*)data;
1138 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1140 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1142 if (temp_buff == NULL) {
1143 return GHOST_kFailure;
1146 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1148 temp_buff[pastedTextSize] = '\0';
1150 eventData = (GHOST_TEventDataPtr) temp_buff;
1153 case GHOST_kDragnDropTypeBitmap:
1155 NSImage *droppedImg = (NSImage*)data;
1156 NSSize imgSize = [droppedImg size];
1158 GHOST_TUns8 *rasterRGB = NULL;
1159 GHOST_TUns8 *rasterRGBA = NULL;
1160 GHOST_TUns8 *toIBuf = NULL;
1161 int x, y, to_i, from_i;
1162 NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA,*bitmapImage=nil;
1163 NSEnumerator *enumerator;
1164 NSImageRep *representation;
1166 ibuf = IMB_allocImBuf (imgSize.width , imgSize.height, 32, IB_rect, 0);
1168 [droppedImg release];
1169 return GHOST_kFailure;
1172 /*Get the bitmap of the image*/
1173 enumerator = [[droppedImg representations] objectEnumerator];
1174 while ((representation = [enumerator nextObject])) {
1175 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
1176 bitmapImage = (NSBitmapImageRep *)representation;
1180 if (bitmapImage == nil) return GHOST_kFailure;
1182 if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
1183 && ![bitmapImage isPlanar]) {
1184 /* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
1185 toIBuf = (GHOST_TUns8*)ibuf->rect;
1186 rasterRGB = (GHOST_TUns8*)[bitmapImage bitmapData];
1187 for (y = 0; y < imgSize.height; y++) {
1188 to_i = (imgSize.height-y-1)*imgSize.width;
1189 from_i = y*imgSize.width;
1190 memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*imgSize.width);
1194 /* Tell cocoa image resolution is same as current system one */
1195 [bitmapImage setSize:imgSize];
1197 /* Convert the image in a RGBA 32bit format */
1198 /* As Core Graphics does not support contextes with non premutliplied alpha,
1199 we need to get alpha key values in a separate batch */
1201 /* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
1202 blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1203 pixelsWide:imgSize.width
1204 pixelsHigh:imgSize.height
1205 bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
1206 colorSpaceName:NSDeviceRGBColorSpace
1207 bitmapFormat:(NSBitmapFormat)0
1208 bytesPerRow:4*imgSize.width
1209 bitsPerPixel:32/*RGB format padded to 32bits*/];
1211 [NSGraphicsContext saveGraphicsState];
1212 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
1214 [NSGraphicsContext restoreGraphicsState];
1216 rasterRGB = (GHOST_TUns8*)[blBitmapFormatImageRGB bitmapData];
1217 if (rasterRGB == NULL) {
1218 [bitmapImage release];
1219 [blBitmapFormatImageRGB release];
1220 [droppedImg release];
1221 return GHOST_kFailure;
1224 /* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
1225 blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1226 pixelsWide:imgSize.width
1227 pixelsHigh:imgSize.height
1228 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
1229 colorSpaceName:NSDeviceRGBColorSpace
1230 bitmapFormat:(NSBitmapFormat)0
1231 bytesPerRow:4*imgSize.width
1232 bitsPerPixel:32/* RGBA */];
1234 [NSGraphicsContext saveGraphicsState];
1235 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
1237 [NSGraphicsContext restoreGraphicsState];
1239 rasterRGBA = (GHOST_TUns8*)[blBitmapFormatImageRGBA bitmapData];
1240 if (rasterRGBA == NULL) {
1241 [bitmapImage release];
1242 [blBitmapFormatImageRGB release];
1243 [blBitmapFormatImageRGBA release];
1244 [droppedImg release];
1245 return GHOST_kFailure;
1248 /*Copy the image to ibuf, flipping it vertically*/
1249 toIBuf = (GHOST_TUns8*)ibuf->rect;
1250 for (y = 0; y < imgSize.height; y++) {
1251 for (x = 0; x < imgSize.width; x++) {
1252 to_i = (imgSize.height-y-1)*imgSize.width + x;
1253 from_i = y*imgSize.width + x;
1255 toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
1256 toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
1257 toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
1258 toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
1262 [blBitmapFormatImageRGB release];
1263 [blBitmapFormatImageRGBA release];
1264 [droppedImg release];
1267 eventData = (GHOST_TEventDataPtr) ibuf;
1272 return GHOST_kFailure;
1275 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,eventData));
1279 return GHOST_kFailure;
1281 m_outsideLoopEventProcessed = true;
1282 return GHOST_kSuccess;
1286 GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest()
1288 GHOST_Window* window = (GHOST_Window*)m_windowManager->getActiveWindow();
1290 //Discard quit event if we are in cursor grab sequence
1291 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1292 return GHOST_kExitCancel;
1294 //Check open windows if some changes are not saved
1295 if (m_windowManager->getAnyModifiedState())
1297 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved.\nDo you really want to quit ?",
1298 @"Cancel", @"Quit Anyway", nil);
1299 if (shouldQuit == NSAlertAlternateReturn)
1301 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1302 return GHOST_kExitNow;
1304 //Give back focus to the blender window if user selected cancel quit
1305 NSArray *windowsList = [NSApp orderedWindows];
1306 if ([windowsList count]) {
1307 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1308 //Handle the modifiers keyes changed state issue
1309 //as recovering from the quit dialog is like application
1310 //gaining focus back.
1311 //Main issue fixed is Cmd modifier not being cleared
1312 handleApplicationBecomeActiveEvent();
1318 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1319 m_outsideLoopEventProcessed = true;
1320 return GHOST_kExitNow;
1323 return GHOST_kExitCancel;
1326 bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr)
1328 NSString *filepath = (NSString*)filepathStr;
1329 int confirmOpen = NSAlertAlternateReturn;
1330 NSArray *windowsList;
1332 size_t filenameTextSize;
1333 GHOST_Window* window= (GHOST_Window*)m_windowManager->getActiveWindow();
1339 //Discard event if we are in cursor grab sequence, it'll lead to "stuck cursor" situation if the alert panel is raised
1340 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1341 return GHOST_kExitCancel;
1343 //Check open windows if some changes are not saved
1344 if (m_windowManager->getAnyModifiedState())
1346 confirmOpen = NSRunAlertPanel([NSString stringWithFormat:@"Opening %@",[filepath lastPathComponent]],
1347 @"Current document has not been saved.\nDo you really want to proceed?",
1348 @"Cancel", @"Open", nil);
1351 //Give back focus to the blender window
1352 windowsList = [NSApp orderedWindows];
1353 if ([windowsList count]) {
1354 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1357 if (confirmOpen == NSAlertAlternateReturn)
1359 filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
1361 temp_buff = (char*) malloc(filenameTextSize+1);
1363 if (temp_buff == NULL) {
1364 return GHOST_kFailure;
1367 strncpy(temp_buff, [filepath cStringUsingEncoding:NSISOLatin1StringEncoding], filenameTextSize);
1369 temp_buff[filenameTextSize] = '\0';
1371 pushEvent(new GHOST_EventString(getMilliSeconds(),GHOST_kEventOpenMainFile,window,(GHOST_TEventDataPtr) temp_buff));
1378 GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr, short eventType)
1380 NSEvent *event = (NSEvent *)eventPtr;
1381 GHOST_IWindow* window;
1383 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1385 //printf("\nW failure for event 0x%x",[event type]);
1386 return GHOST_kFailure;
1389 GHOST_TabletData& ct=((GHOST_WindowCocoa*)window)->GetCocoaTabletData();
1391 switch (eventType) {
1393 ct.Pressure = [event pressure];
1394 ct.Xtilt = [event tilt].x;
1395 ct.Ytilt = [event tilt].y;
1398 case NSTabletProximity:
1402 if ([event isEnteringProximity])
1404 //pointer is entering tablet area proximity
1405 switch ([event pointingDeviceType]) {
1406 case NSPenPointingDevice:
1407 ct.Active = GHOST_kTabletModeStylus;
1409 case NSEraserPointingDevice:
1410 ct.Active = GHOST_kTabletModeEraser;
1412 case NSCursorPointingDevice:
1413 case NSUnknownPointingDevice:
1415 ct.Active = GHOST_kTabletModeNone;
1419 // pointer is leaving - return to mouse
1420 ct.Active = GHOST_kTabletModeNone;
1425 GHOST_ASSERT(FALSE,"GHOST_SystemCocoa::handleTabletEvent : unknown event received");
1426 return GHOST_kFailure;
1429 return GHOST_kSuccess;
1433 GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
1435 NSEvent *event = (NSEvent *)eventPtr;
1436 GHOST_Window* window;
1438 window = (GHOST_Window*)m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1440 //printf("\nW failure for event 0x%x",[event type]);
1441 return GHOST_kFailure;
1444 switch ([event type])
1446 case NSLeftMouseDown:
1447 case NSRightMouseDown:
1448 case NSOtherMouseDown:
1449 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
1450 //Handle tablet events combined with mouse events
1451 switch ([event subtype]) {
1452 case NX_SUBTYPE_TABLET_POINT:
1453 handleTabletEvent(eventPtr, NSTabletPoint);
1455 case NX_SUBTYPE_TABLET_PROXIMITY:
1456 handleTabletEvent(eventPtr, NSTabletProximity);
1459 //No tablet event included : do nothing
1465 case NSRightMouseUp:
1466 case NSOtherMouseUp:
1467 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1468 //Handle tablet events combined with mouse events
1469 switch ([event subtype]) {
1470 case NX_SUBTYPE_TABLET_POINT:
1471 handleTabletEvent(eventPtr, NSTabletPoint);
1473 case NX_SUBTYPE_TABLET_PROXIMITY:
1474 handleTabletEvent(eventPtr, NSTabletProximity);
1477 //No tablet event included : do nothing
1482 case NSLeftMouseDragged:
1483 case NSRightMouseDragged:
1484 case NSOtherMouseDragged:
1485 //Handle tablet events combined with mouse events
1486 switch ([event subtype]) {
1487 case NX_SUBTYPE_TABLET_POINT:
1488 handleTabletEvent(eventPtr, NSTabletPoint);
1490 case NX_SUBTYPE_TABLET_PROXIMITY:
1491 handleTabletEvent(eventPtr, NSTabletProximity);
1494 //No tablet event included : do nothing
1499 switch (window->getCursorGrabMode()) {
1500 case GHOST_kGrabHide: //Cursor hidden grab operation : no cursor move
1502 GHOST_TInt32 x_warp, y_warp, x_accum, y_accum;
1504 window->getCursorGrabInitPos(x_warp, y_warp);
1506 window->getCursorGrabAccum(x_accum, y_accum);
1507 x_accum += [event deltaX];
1508 y_accum += -[event deltaY]; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1509 window->setCursorGrabAccum(x_accum, y_accum);
1511 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x_warp+x_accum, y_warp+y_accum));
1514 case GHOST_kGrabWrap: //Wrap cursor at area/window boundaries
1516 NSPoint mousePos = [event locationInWindow];
1517 GHOST_TInt32 x_mouse= mousePos.x;
1518 GHOST_TInt32 y_mouse= mousePos.y;
1519 GHOST_TInt32 x_accum, y_accum, x_cur, y_cur;
1520 GHOST_Rect bounds, windowBounds, correctedBounds;
1522 /* fallback to window bounds */
1523 if(window->getCursorGrabBounds(bounds)==GHOST_kFailure)
1524 window->getClientBounds(bounds);
1526 //Switch back to Cocoa coordinates orientation (y=0 at botton,the same as blender internal btw!), and to client coordinates
1527 window->getClientBounds(windowBounds);
1528 window->screenToClient(bounds.m_l,bounds.m_b, correctedBounds.m_l, correctedBounds.m_t);
1529 window->screenToClient(bounds.m_r, bounds.m_t, correctedBounds.m_r, correctedBounds.m_b);
1530 correctedBounds.m_b = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_b;
1531 correctedBounds.m_t = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_t;
1533 //Update accumulation counts
1534 window->getCursorGrabAccum(x_accum, y_accum);
1535 x_accum += [event deltaX]-m_cursorDelta_x;
1536 y_accum += -[event deltaY]-m_cursorDelta_y; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1537 window->setCursorGrabAccum(x_accum, y_accum);
1540 //Warp mouse cursor if needed
1541 x_mouse += [event deltaX]-m_cursorDelta_x;
1542 y_mouse += -[event deltaY]-m_cursorDelta_y;
1543 correctedBounds.wrapPoint(x_mouse, y_mouse, 2);
1545 //Compensate for mouse moved event taking cursor position set into account
1546 m_cursorDelta_x = x_mouse-mousePos.x;
1547 m_cursorDelta_y = y_mouse-mousePos.y;
1549 //Set new cursor position
1550 window->clientToScreen(x_mouse, y_mouse, x_cur, y_cur);
1551 setMouseCursorPosition(x_cur, y_cur); /* wrap */
1554 window->getCursorGrabInitPos(x_cur, y_cur);
1555 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x_cur + x_accum, y_cur + y_accum));
1560 //Normal cursor operation: send mouse position in window
1561 NSPoint mousePos = [event locationInWindow];
1562 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, mousePos.x, mousePos.y));
1564 m_cursorDelta_y=0; //Mouse motion occurred between two cursor warps, so we can reset the delta counter
1572 /* Send trackpad event if inside a trackpad gesture, send wheel event otherwise */
1573 if (!m_hasMultiTouchTrackpad || !m_isGestureInProgress) {
1576 double deltaF = [event deltaY];
1577 if (deltaF == 0.0) break; //discard trackpad delta=0 events
1579 delta = deltaF > 0.0 ? 1 : -1;
1580 pushEvent(new GHOST_EventWheel([event timestamp]*1000, window, delta));
1583 NSPoint mousePos = [event locationInWindow];
1584 double dx = [event deltaX];
1585 double dy = -[event deltaY];
1587 const double deltaMax = 50.0;
1589 if ((dx == 0) && (dy == 0)) break;
1591 /* Quadratic acceleration */
1592 dx = dx*(fabs(dx)+0.5);
1593 if (dx<0.0) dx-=0.5; else dx+=0.5;
1594 if (dx< -deltaMax) dx= -deltaMax; else if (dx>deltaMax) dx=deltaMax;
1596 dy = dy*(fabs(dy)+0.5);
1597 if (dy<0.0) dy-=0.5; else dy+=0.5;
1598 if (dy< -deltaMax) dy= -deltaMax; else if (dy>deltaMax) dy=deltaMax;
1600 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventScroll, mousePos.x, mousePos.y, dx, dy));
1605 case NSEventTypeMagnify:
1607 NSPoint mousePos = [event locationInWindow];
1608 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventMagnify, mousePos.x, mousePos.y,
1609 [event magnification]*250.0 + 0.1, 0));
1613 case NSEventTypeRotate:
1615 NSPoint mousePos = [event locationInWindow];
1616 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventRotate, mousePos.x, mousePos.y,
1617 -[event rotation] * 5.0, 0));
1619 case NSEventTypeBeginGesture:
1620 m_isGestureInProgress = true;
1622 case NSEventTypeEndGesture:
1623 m_isGestureInProgress = false;
1626 return GHOST_kFailure;
1630 return GHOST_kSuccess;
1634 GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1636 NSEvent *event = (NSEvent *)eventPtr;
1637 GHOST_IWindow* window;
1638 unsigned int modifiers;
1639 NSString *characters;
1640 NSData *convertedCharacters;
1642 unsigned char ascii;
1643 NSString* charsIgnoringModifiers;
1645 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1647 //printf("\nW failure for event 0x%x",[event type]);
1648 return GHOST_kFailure;
1651 switch ([event type]) {
1654 charsIgnoringModifiers = [event charactersIgnoringModifiers];
1655 if ([charsIgnoringModifiers length]>0)
1656 keyCode = convertKey([event keyCode],
1657 [charsIgnoringModifiers characterAtIndex:0],
1658 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1660 keyCode = convertKey([event keyCode],0,
1661 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1664 characters = [event characters];
1665 if ([characters length]>0) { //Check for dead keys
1666 //Convert characters to iso latin 1 encoding
1667 convertedCharacters = [characters dataUsingEncoding:NSISOLatin1StringEncoding];
1668 if ([convertedCharacters length]>0)
1669 ascii =((char*)[convertedCharacters bytes])[0];
1671 ascii = 0; //Character not available in iso latin 1 encoding
1676 if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1677 break; //Cmd-Q is directly handled by Cocoa
1679 if ([event type] == NSKeyDown) {
1680 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyDown, window, keyCode, ascii) );
1681 //printf("\nKey down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii);
1683 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyUp, window, keyCode, ascii) );
1684 //printf("\nKey up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii);
1688 case NSFlagsChanged:
1689 modifiers = [event modifierFlags];
1691 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1692 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1694 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1695 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1697 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1698 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1700 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1701 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1704 m_modifierMask = modifiers;
1708 return GHOST_kFailure;
1712 return GHOST_kSuccess;
1717 #pragma mark Clipboard get/set
1719 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1721 GHOST_TUns8 * temp_buff;
1722 size_t pastedTextSize;
1724 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1726 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1728 if (pasteBoard == nil) {
1733 NSArray *supportedTypes =
1734 [NSArray arrayWithObjects: NSStringPboardType, nil];
1736 NSString *bestType = [[NSPasteboard generalPasteboard]
1737 availableTypeFromArray:supportedTypes];
1739 if (bestType == nil) {
1744 NSString * textPasted = [pasteBoard stringForType:NSStringPboardType];
1746 if (textPasted == nil) {
1751 pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
1753 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1755 if (temp_buff == NULL) {
1760 strncpy((char*)temp_buff, [textPasted cStringUsingEncoding:NSISOLatin1StringEncoding], pastedTextSize);
1762 temp_buff[pastedTextSize] = '\0';
1773 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1775 NSString *textToCopy;
1777 if(selection) {return;} // for copying the selection, used on X11
1779 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1781 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1783 if (pasteBoard == nil) {
1788 NSArray *supportedTypes = [NSArray arrayWithObject:NSStringPboardType];
1790 [pasteBoard declareTypes:supportedTypes owner:nil];
1792 textToCopy = [NSString stringWithCString:buffer encoding:NSISOLatin1StringEncoding];
1794 [pasteBoard setString:textToCopy forType:NSStringPboardType];
1799 #pragma mark Base directories retrieval
1801 const GHOST_TUns8* GHOST_SystemCocoa::getSystemDir() const
1803 static GHOST_TUns8 tempPath[512] = "";
1804 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1808 paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, YES);
1810 if ([paths count] > 0)
1811 basePath = [paths objectAtIndex:0];
1817 strcpy((char*)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);
1823 const GHOST_TUns8* GHOST_SystemCocoa::getUserDir() const
1825 static GHOST_TUns8 tempPath[512] = "";
1826 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1830 paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
1832 if ([paths count] > 0)
1833 basePath = [paths objectAtIndex:0];
1839 strcpy((char*)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);
1845 const GHOST_TUns8* GHOST_SystemCocoa::getBinaryDir() const
1847 static GHOST_TUns8 tempPath[512] = "";
1848 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1851 basePath = [[NSBundle mainBundle] bundlePath];
1853 if (basePath == nil) {
1858 strcpy((char*)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);