2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19 * All rights reserved.
21 * The Original Code is: all of this file.
23 * Contributors: Maarten Gribnau 05/2001
24 * Damien Plisson 09/2009
26 * ***** END GPL LICENSE BLOCK *****
29 #import <Cocoa/Cocoa.h>
31 /*For the currently not ported to Cocoa keyboard layout functions (64bit & 10.6 compatible)*/
32 #include <Carbon/Carbon.h>
35 #include <sys/types.h>
36 #include <sys/sysctl.h>
38 #include "GHOST_SystemCocoa.h"
40 #include "GHOST_DisplayManagerCocoa.h"
41 #include "GHOST_EventKey.h"
42 #include "GHOST_EventButton.h"
43 #include "GHOST_EventCursor.h"
44 #include "GHOST_EventWheel.h"
45 #include "GHOST_EventTrackpad.h"
46 #include "GHOST_EventDragnDrop.h"
47 #include "GHOST_EventString.h"
48 #include "GHOST_TimerManager.h"
49 #include "GHOST_TimerTask.h"
50 #include "GHOST_WindowManager.h"
51 #include "GHOST_WindowCocoa.h"
52 #ifdef WITH_INPUT_NDOF
53 #include "GHOST_NDOFManagerCocoa.h"
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();
600 #ifdef WITH_INPUT_NDOF
601 m_ndofManager = new GHOST_NDOFManagerCocoa(*this);
604 //ProcessSerialNumber psn;
606 //Carbon stuff to move window & menu to foreground
607 /*if (!GetCurrentProcess(&psn)) {
608 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
609 SetFrontProcess(&psn);
612 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
614 [NSApplication sharedApplication];
616 if ([NSApp mainMenu] == nil) {
617 NSMenu *mainMenubar = [[NSMenu alloc] init];
618 NSMenuItem *menuItem;
622 //Create the application menu
623 appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
625 [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
626 [appMenu addItem:[NSMenuItem separatorItem]];
628 menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
629 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
631 menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
632 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
634 [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
636 menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
637 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
639 menuItem = [[NSMenuItem alloc] init];
640 [menuItem setSubmenu:appMenu];
642 [mainMenubar addItem:menuItem];
644 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; //Needed for 10.5
647 //Create the window menu
648 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
650 menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
651 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
653 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
655 menuItem = [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
656 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
658 menuItem = [[NSMenuItem alloc] init];
659 [menuItem setSubmenu:windowMenu];
661 [mainMenubar addItem:menuItem];
664 [NSApp setMainMenu:mainMenubar];
665 [NSApp setWindowsMenu:windowMenu];
666 [windowMenu release];
669 if ([NSApp delegate] == nil) {
670 CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
671 [appDelegate setSystemCocoa:this];
672 [NSApp setDelegate:appDelegate];
675 [NSApp finishLaunching];
683 #pragma mark window management
685 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
687 //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
688 struct timeval currentTime;
690 gettimeofday(¤tTime, NULL);
692 //Return timestamp of system uptime
694 return ((currentTime.tv_sec*1000)+(currentTime.tv_usec/1000)-m_start_time);
698 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
700 //Note that OS X supports monitor hot plug
701 // We do not support multiple monitors at the moment
702 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
704 GHOST_TUns8 count = [[NSScreen screens] count];
711 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
713 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
714 //Get visible frame, that is frame excluding dock and top menu bar
715 NSRect frame = [[NSScreen mainScreen] visibleFrame];
717 //Returns max window contents (excluding title bar...)
718 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
719 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
721 width = contentRect.size.width;
722 height = contentRect.size.height;
728 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
729 const STR_String& title,
734 GHOST_TWindowState state,
735 GHOST_TDrawingContextType type,
737 const GHOST_TUns16 numOfAASamples,
738 const GHOST_TEmbedderWindowID parentWindow
741 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
742 GHOST_IWindow* window = 0;
744 //Get the available rect for including window contents
745 NSRect frame = [[NSScreen mainScreen] visibleFrame];
746 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
747 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
749 GHOST_TInt32 bottom = (contentRect.size.height - 1) - height - top;
751 //Ensures window top left is inside this available rect
752 left = left > contentRect.origin.x ? left : contentRect.origin.x;
753 bottom = bottom > contentRect.origin.y ? bottom : contentRect.origin.y;
755 window = new GHOST_WindowCocoa (this, title, left, bottom, width, height, state, type, stereoVisual, numOfAASamples);
758 if (window->getValid()) {
759 // Store the pointer to the window
760 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
761 m_windowManager->addWindow(window);
762 m_windowManager->setActiveWindow(window);
763 //Need to tell window manager the new window is the active one (Cocoa does not send the event activate upon window creation)
764 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window));
765 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
769 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
775 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): could not create window\n");
782 * @note : returns coordinates in Cocoa screen coordinates
784 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
786 NSPoint mouseLoc = [NSEvent mouseLocation];
788 // Returns the mouse location in screen coordinates
789 x = (GHOST_TInt32)mouseLoc.x;
790 y = (GHOST_TInt32)mouseLoc.y;
791 return GHOST_kSuccess;
795 * @note : expect Cocoa screen coordinates
797 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
799 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
800 if (!window) return GHOST_kFailure;
802 //Cursor and mouse dissociation placed here not to interfere with continuous grab
803 // (in cont. grab setMouseCursorPosition is directly called)
804 CGAssociateMouseAndMouseCursorPosition(false);
805 setMouseCursorPosition(x, y);
806 CGAssociateMouseAndMouseCursorPosition(true);
808 //Force mouse move event (not pushed by Cocoa)
809 pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, x, y));
810 m_outsideLoopEventProcessed = true;
812 return GHOST_kSuccess;
815 GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
817 float xf=(float)x, yf=(float)y;
818 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
819 if (!window) return GHOST_kFailure;
821 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
822 NSScreen *windowScreen = window->getScreen();
823 NSRect screenRect = [windowScreen frame];
825 //Set position relative to current screen
826 xf -= screenRect.origin.x;
827 yf -= screenRect.origin.y;
829 //Quartz Display Services uses the old coordinates (top left origin)
830 yf = screenRect.size.height -yf;
832 CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue], CGPointMake(xf, yf));
835 return GHOST_kSuccess;
839 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
841 keys.set(GHOST_kModifierKeyOS, (m_modifierMask & NSCommandKeyMask) ? true : false);
842 keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSAlternateKeyMask) ? true : false);
843 keys.set(GHOST_kModifierKeyLeftShift, (m_modifierMask & NSShiftKeyMask) ? true : false);
844 keys.set(GHOST_kModifierKeyLeftControl, (m_modifierMask & NSControlKeyMask) ? true : false);
846 return GHOST_kSuccess;
849 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
852 buttons.set(GHOST_kButtonMaskLeft, m_pressedMouseButtons & GHOST_kButtonMaskLeft);
853 buttons.set(GHOST_kButtonMaskRight, m_pressedMouseButtons & GHOST_kButtonMaskRight);
854 buttons.set(GHOST_kButtonMaskMiddle, m_pressedMouseButtons & GHOST_kButtonMaskMiddle);
855 buttons.set(GHOST_kButtonMaskButton4, m_pressedMouseButtons & GHOST_kButtonMaskButton4);
856 buttons.set(GHOST_kButtonMaskButton5, m_pressedMouseButtons & GHOST_kButtonMaskButton5);
857 return GHOST_kSuccess;
862 #pragma mark Event handlers
865 * The event queue polling function
867 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
869 bool anyProcessed = false;
872 // SetMouseCoalescingEnabled(false, NULL);
873 //TODO : implement timer ??
876 GHOST_TimerManager* timerMgr = getTimerManager();
879 GHOST_TUns64 next = timerMgr->nextFireTime();
882 if (next == GHOST_kFireTimeNever) {
883 timeOut = kEventDurationForever;
885 timeOut = (double)(next - getMilliSeconds())/1000.0;
890 ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
893 if (timerMgr->fireTimers(getMilliSeconds())) {
898 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
899 event = [NSApp nextEventMatchingMask:NSAnyEventMask
900 untilDate:[NSDate distantPast]
901 inMode:NSDefaultRunLoopMode
910 switch ([event type]) {
914 handleKeyEvent(event);
916 /* Support system-wide keyboard shortcuts, like Exposé, ...) =>included in always NSApp sendEvent */
917 /* if (([event modifierFlags] & NSCommandKeyMask) || [event type] == NSFlagsChanged) {
918 [NSApp sendEvent:event];
922 case NSLeftMouseDown:
924 case NSRightMouseDown:
927 case NSLeftMouseDragged:
928 case NSRightMouseDragged:
930 case NSOtherMouseDown:
932 case NSOtherMouseDragged:
933 case NSEventTypeMagnify:
934 case NSEventTypeRotate:
935 case NSEventTypeBeginGesture:
936 case NSEventTypeEndGesture:
937 handleMouseEvent(event);
941 case NSTabletProximity:
942 handleTabletEvent(event,[event type]);
945 /* Trackpad features, fired only from OS X 10.5.2
946 case NSEventTypeGesture:
947 case NSEventTypeSwipe:
953 NSAppKitDefined = 13,
954 NSSystemDefined = 14,
955 NSApplicationDefined = 15,
957 NSCursorUpdate = 17,*/
962 //Resend event to NSApp to ensure Mac wide events are handled
963 [NSApp sendEvent:event];
965 } while (event!= nil);
966 //} while (waitForEvent && !anyProcessed); Needed only for timer implementation
968 if (m_needDelayedApplicationBecomeActiveEventProcessing) handleApplicationBecomeActiveEvent();
970 if (m_outsideLoopEventProcessed) {
971 m_outsideLoopEventProcessed = false;
975 m_ignoreWindowSizedMessages = false;
980 //Note: called from NSApplication delegate
981 GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
983 //Update the modifiers key mask, as its status may have changed when the application was not active
984 //(that is when update events are sent to another application)
985 unsigned int modifiers;
986 GHOST_IWindow* window = m_windowManager->getActiveWindow();
989 m_needDelayedApplicationBecomeActiveEventProcessing = true;
990 return GHOST_kFailure;
992 else m_needDelayedApplicationBecomeActiveEventProcessing = false;
994 modifiers = [[[NSApplication sharedApplication] currentEvent] modifierFlags];
996 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
997 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
999 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1000 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1002 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1003 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1005 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1006 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1009 m_modifierMask = modifiers;
1011 m_outsideLoopEventProcessed = true;
1012 return GHOST_kSuccess;
1015 void GHOST_SystemCocoa::notifyExternalEventProcessed()
1017 m_outsideLoopEventProcessed = true;
1020 //Note: called from NSWindow delegate
1021 GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa* window)
1023 if (!validWindow(window)) {
1024 return GHOST_kFailure;
1028 case GHOST_kEventWindowClose:
1029 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
1031 case GHOST_kEventWindowActivate:
1032 m_windowManager->setActiveWindow(window);
1033 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
1034 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
1036 case GHOST_kEventWindowDeactivate:
1037 m_windowManager->setWindowInactive(window);
1038 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
1040 case GHOST_kEventWindowUpdate:
1041 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
1043 case GHOST_kEventWindowMove:
1044 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, window) );
1046 case GHOST_kEventWindowSize:
1047 if (!m_ignoreWindowSizedMessages)
1049 //Enforce only one resize message per event loop (coalescing all the live resize messages)
1050 window->updateDrawingContext();
1051 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
1052 //Mouse up event is trapped by the resizing event loop, so send it anyway to the window manager
1053 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonUp, window, convertButton(0)));
1054 m_ignoreWindowSizedMessages = true;
1058 return GHOST_kFailure;
1062 m_outsideLoopEventProcessed = true;
1063 return GHOST_kSuccess;
1066 //Note: called from NSWindow subclass
1067 GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,
1068 GHOST_WindowCocoa* window, int mouseX, int mouseY, void* data)
1070 if (!validWindow(window)) {
1071 return GHOST_kFailure;
1075 case GHOST_kEventDraggingEntered:
1076 case GHOST_kEventDraggingUpdated:
1077 case GHOST_kEventDraggingExited:
1078 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,NULL));
1081 case GHOST_kEventDraggingDropDone:
1083 GHOST_TUns8 * temp_buff;
1084 GHOST_TStringArray *strArray;
1085 NSArray *droppedArray;
1086 size_t pastedTextSize;
1087 NSString *droppedStr;
1088 GHOST_TEventDataPtr eventData;
1091 if (!data) return GHOST_kFailure;
1093 switch (draggedObjectType) {
1094 case GHOST_kDragnDropTypeFilenames:
1095 droppedArray = (NSArray*)data;
1097 strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
1098 if (!strArray) return GHOST_kFailure;
1100 strArray->count = [droppedArray count];
1101 if (strArray->count == 0) return GHOST_kFailure;
1103 strArray->strings = (GHOST_TUns8**) malloc(strArray->count*sizeof(GHOST_TUns8*));
1105 for (i=0;i<strArray->count;i++)
1107 droppedStr = [droppedArray objectAtIndex:i];
1109 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1110 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1113 strArray->count = i;
1117 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1118 temp_buff[pastedTextSize] = '\0';
1120 strArray->strings[i] = temp_buff;
1123 eventData = (GHOST_TEventDataPtr) strArray;
1126 case GHOST_kDragnDropTypeString:
1127 droppedStr = (NSString*)data;
1128 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1130 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1132 if (temp_buff == NULL) {
1133 return GHOST_kFailure;
1136 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1138 temp_buff[pastedTextSize] = '\0';
1140 eventData = (GHOST_TEventDataPtr) temp_buff;
1143 case GHOST_kDragnDropTypeBitmap:
1145 NSImage *droppedImg = (NSImage*)data;
1146 NSSize imgSize = [droppedImg size];
1148 GHOST_TUns8 *rasterRGB = NULL;
1149 GHOST_TUns8 *rasterRGBA = NULL;
1150 GHOST_TUns8 *toIBuf = NULL;
1151 int x, y, to_i, from_i;
1152 NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA,*bitmapImage=nil;
1153 NSEnumerator *enumerator;
1154 NSImageRep *representation;
1156 ibuf = IMB_allocImBuf (imgSize.width , imgSize.height, 32, IB_rect);
1158 [droppedImg release];
1159 return GHOST_kFailure;
1162 /*Get the bitmap of the image*/
1163 enumerator = [[droppedImg representations] objectEnumerator];
1164 while ((representation = [enumerator nextObject])) {
1165 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
1166 bitmapImage = (NSBitmapImageRep *)representation;
1170 if (bitmapImage == nil) return GHOST_kFailure;
1172 if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
1173 && ![bitmapImage isPlanar]) {
1174 /* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
1175 toIBuf = (GHOST_TUns8*)ibuf->rect;
1176 rasterRGB = (GHOST_TUns8*)[bitmapImage bitmapData];
1177 for (y = 0; y < imgSize.height; y++) {
1178 to_i = (imgSize.height-y-1)*imgSize.width;
1179 from_i = y*imgSize.width;
1180 memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*imgSize.width);
1184 /* Tell cocoa image resolution is same as current system one */
1185 [bitmapImage setSize:imgSize];
1187 /* Convert the image in a RGBA 32bit format */
1188 /* As Core Graphics does not support contextes with non premutliplied alpha,
1189 we need to get alpha key values in a separate batch */
1191 /* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
1192 blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1193 pixelsWide:imgSize.width
1194 pixelsHigh:imgSize.height
1195 bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
1196 colorSpaceName:NSDeviceRGBColorSpace
1197 bitmapFormat:(NSBitmapFormat)0
1198 bytesPerRow:4*imgSize.width
1199 bitsPerPixel:32/*RGB format padded to 32bits*/];
1201 [NSGraphicsContext saveGraphicsState];
1202 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
1204 [NSGraphicsContext restoreGraphicsState];
1206 rasterRGB = (GHOST_TUns8*)[blBitmapFormatImageRGB bitmapData];
1207 if (rasterRGB == NULL) {
1208 [bitmapImage release];
1209 [blBitmapFormatImageRGB release];
1210 [droppedImg release];
1211 return GHOST_kFailure;
1214 /* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
1215 blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1216 pixelsWide:imgSize.width
1217 pixelsHigh:imgSize.height
1218 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
1219 colorSpaceName:NSDeviceRGBColorSpace
1220 bitmapFormat:(NSBitmapFormat)0
1221 bytesPerRow:4*imgSize.width
1222 bitsPerPixel:32/* RGBA */];
1224 [NSGraphicsContext saveGraphicsState];
1225 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
1227 [NSGraphicsContext restoreGraphicsState];
1229 rasterRGBA = (GHOST_TUns8*)[blBitmapFormatImageRGBA bitmapData];
1230 if (rasterRGBA == NULL) {
1231 [bitmapImage release];
1232 [blBitmapFormatImageRGB release];
1233 [blBitmapFormatImageRGBA release];
1234 [droppedImg release];
1235 return GHOST_kFailure;
1238 /*Copy the image to ibuf, flipping it vertically*/
1239 toIBuf = (GHOST_TUns8*)ibuf->rect;
1240 for (y = 0; y < imgSize.height; y++) {
1241 for (x = 0; x < imgSize.width; x++) {
1242 to_i = (imgSize.height-y-1)*imgSize.width + x;
1243 from_i = y*imgSize.width + x;
1245 toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
1246 toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
1247 toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
1248 toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
1252 [blBitmapFormatImageRGB release];
1253 [blBitmapFormatImageRGBA release];
1254 [droppedImg release];
1257 eventData = (GHOST_TEventDataPtr) ibuf;
1262 return GHOST_kFailure;
1265 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,eventData));
1269 return GHOST_kFailure;
1271 m_outsideLoopEventProcessed = true;
1272 return GHOST_kSuccess;
1276 GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest()
1278 GHOST_Window* window = (GHOST_Window*)m_windowManager->getActiveWindow();
1280 //Discard quit event if we are in cursor grab sequence
1281 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1282 return GHOST_kExitCancel;
1284 //Check open windows if some changes are not saved
1285 if (m_windowManager->getAnyModifiedState())
1287 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved.\nDo you really want to quit ?",
1288 @"Cancel", @"Quit Anyway", nil);
1289 if (shouldQuit == NSAlertAlternateReturn)
1291 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1292 return GHOST_kExitNow;
1294 //Give back focus to the blender window if user selected cancel quit
1295 NSArray *windowsList = [NSApp orderedWindows];
1296 if ([windowsList count]) {
1297 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1298 //Handle the modifiers keyes changed state issue
1299 //as recovering from the quit dialog is like application
1300 //gaining focus back.
1301 //Main issue fixed is Cmd modifier not being cleared
1302 handleApplicationBecomeActiveEvent();
1308 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1309 m_outsideLoopEventProcessed = true;
1310 return GHOST_kExitNow;
1313 return GHOST_kExitCancel;
1316 bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr)
1318 NSString *filepath = (NSString*)filepathStr;
1319 int confirmOpen = NSAlertAlternateReturn;
1320 NSArray *windowsList;
1322 size_t filenameTextSize;
1323 GHOST_Window* window= (GHOST_Window*)m_windowManager->getActiveWindow();
1329 //Discard event if we are in cursor grab sequence, it'll lead to "stuck cursor" situation if the alert panel is raised
1330 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1331 return GHOST_kExitCancel;
1333 //Check open windows if some changes are not saved
1334 if (m_windowManager->getAnyModifiedState())
1336 confirmOpen = NSRunAlertPanel([NSString stringWithFormat:@"Opening %@",[filepath lastPathComponent]],
1337 @"Current document has not been saved.\nDo you really want to proceed?",
1338 @"Cancel", @"Open", nil);
1341 //Give back focus to the blender window
1342 windowsList = [NSApp orderedWindows];
1343 if ([windowsList count]) {
1344 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1347 if (confirmOpen == NSAlertAlternateReturn)
1349 filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1351 temp_buff = (char*) malloc(filenameTextSize+1);
1353 if (temp_buff == NULL) {
1354 return GHOST_kFailure;
1357 strncpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize);
1359 temp_buff[filenameTextSize] = '\0';
1361 pushEvent(new GHOST_EventString(getMilliSeconds(),GHOST_kEventOpenMainFile,window,(GHOST_TEventDataPtr) temp_buff));
1368 GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr, short eventType)
1370 NSEvent *event = (NSEvent *)eventPtr;
1371 GHOST_IWindow* window;
1373 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1375 //printf("\nW failure for event 0x%x",[event type]);
1376 return GHOST_kFailure;
1379 GHOST_TabletData& ct=((GHOST_WindowCocoa*)window)->GetCocoaTabletData();
1381 switch (eventType) {
1383 ct.Pressure = [event pressure];
1384 ct.Xtilt = [event tilt].x;
1385 ct.Ytilt = [event tilt].y;
1388 case NSTabletProximity:
1392 if ([event isEnteringProximity])
1394 //pointer is entering tablet area proximity
1395 switch ([event pointingDeviceType]) {
1396 case NSPenPointingDevice:
1397 ct.Active = GHOST_kTabletModeStylus;
1399 case NSEraserPointingDevice:
1400 ct.Active = GHOST_kTabletModeEraser;
1402 case NSCursorPointingDevice:
1403 case NSUnknownPointingDevice:
1405 ct.Active = GHOST_kTabletModeNone;
1409 // pointer is leaving - return to mouse
1410 ct.Active = GHOST_kTabletModeNone;
1415 GHOST_ASSERT(FALSE,"GHOST_SystemCocoa::handleTabletEvent : unknown event received");
1416 return GHOST_kFailure;
1419 return GHOST_kSuccess;
1423 GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
1425 NSEvent *event = (NSEvent *)eventPtr;
1426 GHOST_WindowCocoa* window;
1428 window = (GHOST_WindowCocoa*)m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1430 //printf("\nW failure for event 0x%x",[event type]);
1431 return GHOST_kFailure;
1434 switch ([event type])
1436 case NSLeftMouseDown:
1437 case NSRightMouseDown:
1438 case NSOtherMouseDown:
1439 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
1440 //Handle tablet events combined with mouse events
1441 switch ([event subtype]) {
1442 case NX_SUBTYPE_TABLET_POINT:
1443 handleTabletEvent(eventPtr, NSTabletPoint);
1445 case NX_SUBTYPE_TABLET_PROXIMITY:
1446 handleTabletEvent(eventPtr, NSTabletProximity);
1449 //No tablet event included : do nothing
1455 case NSRightMouseUp:
1456 case NSOtherMouseUp:
1457 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1458 //Handle tablet events combined with mouse events
1459 switch ([event subtype]) {
1460 case NX_SUBTYPE_TABLET_POINT:
1461 handleTabletEvent(eventPtr, NSTabletPoint);
1463 case NX_SUBTYPE_TABLET_PROXIMITY:
1464 handleTabletEvent(eventPtr, NSTabletProximity);
1467 //No tablet event included : do nothing
1472 case NSLeftMouseDragged:
1473 case NSRightMouseDragged:
1474 case NSOtherMouseDragged:
1475 //Handle tablet events combined with mouse events
1476 switch ([event subtype]) {
1477 case NX_SUBTYPE_TABLET_POINT:
1478 handleTabletEvent(eventPtr, NSTabletPoint);
1480 case NX_SUBTYPE_TABLET_PROXIMITY:
1481 handleTabletEvent(eventPtr, NSTabletProximity);
1484 //No tablet event included : do nothing
1489 switch (window->getCursorGrabMode()) {
1490 case GHOST_kGrabHide: //Cursor hidden grab operation : no cursor move
1492 GHOST_TInt32 x_warp, y_warp, x_accum, y_accum, x, y;
1494 window->getCursorGrabInitPos(x_warp, y_warp);
1496 window->getCursorGrabAccum(x_accum, y_accum);
1497 x_accum += [event deltaX];
1498 y_accum += -[event deltaY]; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1499 window->setCursorGrabAccum(x_accum, y_accum);
1501 window->clientToScreenIntern(x_warp+x_accum, y_warp+y_accum, x, y);
1502 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1505 case GHOST_kGrabWrap: //Wrap cursor at area/window boundaries
1507 NSPoint mousePos = [event locationInWindow];
1508 GHOST_TInt32 x_mouse= mousePos.x;
1509 GHOST_TInt32 y_mouse= mousePos.y;
1510 GHOST_TInt32 x_accum, y_accum, x_cur, y_cur, x, y;
1511 GHOST_Rect bounds, windowBounds, correctedBounds;
1513 /* fallback to window bounds */
1514 if(window->getCursorGrabBounds(bounds)==GHOST_kFailure)
1515 window->getClientBounds(bounds);
1517 //Switch back to Cocoa coordinates orientation (y=0 at botton,the same as blender internal btw!), and to client coordinates
1518 window->getClientBounds(windowBounds);
1519 window->screenToClient(bounds.m_l, bounds.m_b, correctedBounds.m_l, correctedBounds.m_t);
1520 window->screenToClient(bounds.m_r, bounds.m_t, correctedBounds.m_r, correctedBounds.m_b);
1521 correctedBounds.m_b = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_b;
1522 correctedBounds.m_t = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_t;
1524 //Update accumulation counts
1525 window->getCursorGrabAccum(x_accum, y_accum);
1526 x_accum += [event deltaX]-m_cursorDelta_x;
1527 y_accum += -[event deltaY]-m_cursorDelta_y; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1528 window->setCursorGrabAccum(x_accum, y_accum);
1531 //Warp mouse cursor if needed
1532 x_mouse += [event deltaX]-m_cursorDelta_x;
1533 y_mouse += -[event deltaY]-m_cursorDelta_y;
1534 correctedBounds.wrapPoint(x_mouse, y_mouse, 2);
1536 //Compensate for mouse moved event taking cursor position set into account
1537 m_cursorDelta_x = x_mouse-mousePos.x;
1538 m_cursorDelta_y = y_mouse-mousePos.y;
1540 //Set new cursor position
1541 window->clientToScreenIntern(x_mouse, y_mouse, x_cur, y_cur);
1542 setMouseCursorPosition(x_cur, y_cur); /* wrap */
1545 window->getCursorGrabInitPos(x_cur, y_cur);
1546 window->clientToScreenIntern(x_cur + x_accum, y_cur + y_accum, x, y);
1547 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1552 //Normal cursor operation: send mouse position in window
1553 NSPoint mousePos = [event locationInWindow];
1556 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1557 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1560 m_cursorDelta_y=0; //Mouse motion occurred between two cursor warps, so we can reset the delta counter
1568 /* Send trackpad event if inside a trackpad gesture, send wheel event otherwise */
1569 if (!m_hasMultiTouchTrackpad || !m_isGestureInProgress) {
1572 double deltaF = [event deltaY];
1574 if (deltaF == 0.0) deltaF = [event deltaX]; // make blender decide if it's horizontal scroll
1575 if (deltaF == 0.0) break; //discard trackpad delta=0 events
1577 delta = deltaF > 0.0 ? 1 : -1;
1578 pushEvent(new GHOST_EventWheel([event timestamp]*1000, window, delta));
1581 NSPoint mousePos = [event locationInWindow];
1583 double dx = [event deltaX];
1584 double dy = -[event deltaY];
1586 const double deltaMax = 50.0;
1588 if ((dx == 0) && (dy == 0)) break;
1590 /* Quadratic acceleration */
1591 dx = dx*(fabs(dx)+0.5);
1592 if (dx<0.0) dx-=0.5; else dx+=0.5;
1593 if (dx< -deltaMax) dx= -deltaMax; else if (dx>deltaMax) dx=deltaMax;
1595 dy = dy*(fabs(dy)+0.5);
1596 if (dy<0.0) dy-=0.5; else dy+=0.5;
1597 if (dy< -deltaMax) dy= -deltaMax; else if (dy>deltaMax) dy=deltaMax;
1599 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1602 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventScroll, x, y, dx, dy));
1607 case NSEventTypeMagnify:
1609 NSPoint mousePos = [event locationInWindow];
1611 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1612 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventMagnify, x, y,
1613 [event magnification]*250.0 + 0.1, 0));
1617 case NSEventTypeRotate:
1619 NSPoint mousePos = [event locationInWindow];
1621 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1622 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventRotate, x, y,
1623 -[event rotation] * 5.0, 0));
1625 case NSEventTypeBeginGesture:
1626 m_isGestureInProgress = true;
1628 case NSEventTypeEndGesture:
1629 m_isGestureInProgress = false;
1632 return GHOST_kFailure;
1636 return GHOST_kSuccess;
1640 GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1642 NSEvent *event = (NSEvent *)eventPtr;
1643 GHOST_IWindow* window;
1644 unsigned int modifiers;
1645 NSString *characters;
1646 NSData *convertedCharacters;
1648 unsigned char ascii;
1649 NSString* charsIgnoringModifiers;
1651 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1653 //printf("\nW failure for event 0x%x",[event type]);
1654 return GHOST_kFailure;
1657 char utf8_buf[6]= {'\0'};
1660 switch ([event type]) {
1664 charsIgnoringModifiers = [event charactersIgnoringModifiers];
1665 if ([charsIgnoringModifiers length]>0)
1666 keyCode = convertKey([event keyCode],
1667 [charsIgnoringModifiers characterAtIndex:0],
1668 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1670 keyCode = convertKey([event keyCode],0,
1671 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1673 /* handling both unicode or ascii */
1674 characters = [event characters];
1675 if ([characters length]>0) {
1676 convertedCharacters = [characters dataUsingEncoding:NSUTF8StringEncoding];
1678 for (int x = 0; x < [convertedCharacters length]; x++) {
1679 utf8_buf[x] = ((char*)[convertedCharacters bytes])[x];
1682 /* ascii is a subset of unicode */
1683 if ([convertedCharacters length] == 1) {
1684 ascii = utf8_buf[0];
1688 /* arrow keys should not have utf8 */
1689 if ((keyCode > 266) && (keyCode < 271))
1692 if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1693 break; //Cmd-Q is directly handled by Cocoa
1695 if ([event type] == NSKeyDown) {
1696 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyDown, window, keyCode, ascii, utf8_buf) );
1697 //printf("Key down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii, utf8_buf);
1699 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyUp, window, keyCode, 0, '\0') );
1700 //printf("Key up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n",[event keyCode],[charsIgnoringModifiers length]>0?[charsIgnoringModifiers characterAtIndex:0]:' ',keyCode,ascii,ascii, utf8_buf);
1704 case NSFlagsChanged:
1705 modifiers = [event modifierFlags];
1707 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1708 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1710 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1711 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1713 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1714 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1716 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1717 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1720 m_modifierMask = modifiers;
1724 return GHOST_kFailure;
1728 return GHOST_kSuccess;
1733 #pragma mark Clipboard get/set
1735 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1737 GHOST_TUns8 * temp_buff;
1738 size_t pastedTextSize;
1740 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1742 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1744 if (pasteBoard == nil) {
1749 NSArray *supportedTypes =
1750 [NSArray arrayWithObjects: NSStringPboardType, nil];
1752 NSString *bestType = [[NSPasteboard generalPasteboard]
1753 availableTypeFromArray:supportedTypes];
1755 if (bestType == nil) {
1760 NSString * textPasted = [pasteBoard stringForType:NSStringPboardType];
1762 if (textPasted == nil) {
1767 pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
1769 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1771 if (temp_buff == NULL) {
1776 strncpy((char*)temp_buff, [textPasted cStringUsingEncoding:NSISOLatin1StringEncoding], pastedTextSize);
1778 temp_buff[pastedTextSize] = '\0';
1789 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1791 NSString *textToCopy;
1793 if(selection) {return;} // for copying the selection, used on X11
1795 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1797 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1799 if (pasteBoard == nil) {
1804 NSArray *supportedTypes = [NSArray arrayWithObject:NSStringPboardType];
1806 [pasteBoard declareTypes:supportedTypes owner:nil];
1808 textToCopy = [NSString stringWithCString:buffer encoding:NSISOLatin1StringEncoding];
1810 [pasteBoard setString:textToCopy forType:NSStringPboardType];