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 * Contributors: 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_EventTrackpad.h"
47 #include "GHOST_EventDragnDrop.h"
48 #include "GHOST_EventString.h"
49 #include "GHOST_TimerManager.h"
50 #include "GHOST_TimerTask.h"
51 #include "GHOST_WindowManager.h"
52 #include "GHOST_WindowCocoa.h"
53 #ifdef WITH_INPUT_NDOF
54 #include "GHOST_NDOFManagerCocoa.h"
57 #include "AssertMacros.h"
59 #pragma mark KeyMap, mouse converters
60 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
61 /* Keycodes not defined in Tiger */
67 * These constants are the virtual keycodes defined originally in
68 * Inside Mac Volume V, pg. V-191. They identify physical keys on a
69 * keyboard. Those constants with "ANSI" in the name are labeled
70 * according to the key position on an ANSI-standard US keyboard.
71 * For example, kVK_ANSI_A indicates the virtual keycode for the key
72 * with the letter 'A' in the US keyboard layout. Other keyboard
73 * layouts may have the 'A' key label on a different physical key;
74 * in this case, pressing 'A' will generate a different virtual
101 kVK_ANSI_Equal = 0x18,
104 kVK_ANSI_Minus = 0x1B,
107 kVK_ANSI_RightBracket = 0x1E,
110 kVK_ANSI_LeftBracket = 0x21,
115 kVK_ANSI_Quote = 0x27,
117 kVK_ANSI_Semicolon = 0x29,
118 kVK_ANSI_Backslash = 0x2A,
119 kVK_ANSI_Comma = 0x2B,
120 kVK_ANSI_Slash = 0x2C,
123 kVK_ANSI_Period = 0x2F,
124 kVK_ANSI_Grave = 0x32,
125 kVK_ANSI_KeypadDecimal = 0x41,
126 kVK_ANSI_KeypadMultiply = 0x43,
127 kVK_ANSI_KeypadPlus = 0x45,
128 kVK_ANSI_KeypadClear = 0x47,
129 kVK_ANSI_KeypadDivide = 0x4B,
130 kVK_ANSI_KeypadEnter = 0x4C,
131 kVK_ANSI_KeypadMinus = 0x4E,
132 kVK_ANSI_KeypadEquals = 0x51,
133 kVK_ANSI_Keypad0 = 0x52,
134 kVK_ANSI_Keypad1 = 0x53,
135 kVK_ANSI_Keypad2 = 0x54,
136 kVK_ANSI_Keypad3 = 0x55,
137 kVK_ANSI_Keypad4 = 0x56,
138 kVK_ANSI_Keypad5 = 0x57,
139 kVK_ANSI_Keypad6 = 0x58,
140 kVK_ANSI_Keypad7 = 0x59,
141 kVK_ANSI_Keypad8 = 0x5B,
142 kVK_ANSI_Keypad9 = 0x5C
145 /* keycodes for keys that are independent of keyboard layout*/
157 kVK_RightShift = 0x3C,
158 kVK_RightOption = 0x3D,
159 kVK_RightControl = 0x3E,
163 kVK_VolumeDown = 0x49,
184 kVK_ForwardDelete = 0x75,
190 kVK_LeftArrow = 0x7B,
191 kVK_RightArrow = 0x7C,
192 kVK_DownArrow = 0x7D,
196 /* ISO keyboards only*/
198 kVK_ISO_Section = 0x0A
201 /* JIS keyboards only*/
204 kVK_JIS_Underscore = 0x5E,
205 kVK_JIS_KeypadComma = 0x5F,
211 static GHOST_TButtonMask convertButton(int button)
215 return GHOST_kButtonMaskLeft;
217 return GHOST_kButtonMaskRight;
219 return GHOST_kButtonMaskMiddle;
221 return GHOST_kButtonMaskButton4;
223 return GHOST_kButtonMaskButton5;
225 return GHOST_kButtonMaskLeft;
230 * Converts Mac rawkey codes (same for Cocoa & Carbon)
231 * into GHOST key codes
232 * @param rawCode The raw physical key code
233 * @param recvChar the character ignoring modifiers (except for shift)
234 * @return Ghost key code
236 static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction)
239 //printf("\nrecvchar %c 0x%x",recvChar,recvChar);
241 /*Physical keycodes not used due to map changes in int'l keyboards
242 case kVK_ANSI_A: return GHOST_kKeyA;
243 case kVK_ANSI_B: return GHOST_kKeyB;
244 case kVK_ANSI_C: return GHOST_kKeyC;
245 case kVK_ANSI_D: return GHOST_kKeyD;
246 case kVK_ANSI_E: return GHOST_kKeyE;
247 case kVK_ANSI_F: return GHOST_kKeyF;
248 case kVK_ANSI_G: return GHOST_kKeyG;
249 case kVK_ANSI_H: return GHOST_kKeyH;
250 case kVK_ANSI_I: return GHOST_kKeyI;
251 case kVK_ANSI_J: return GHOST_kKeyJ;
252 case kVK_ANSI_K: return GHOST_kKeyK;
253 case kVK_ANSI_L: return GHOST_kKeyL;
254 case kVK_ANSI_M: return GHOST_kKeyM;
255 case kVK_ANSI_N: return GHOST_kKeyN;
256 case kVK_ANSI_O: return GHOST_kKeyO;
257 case kVK_ANSI_P: return GHOST_kKeyP;
258 case kVK_ANSI_Q: return GHOST_kKeyQ;
259 case kVK_ANSI_R: return GHOST_kKeyR;
260 case kVK_ANSI_S: return GHOST_kKeyS;
261 case kVK_ANSI_T: return GHOST_kKeyT;
262 case kVK_ANSI_U: return GHOST_kKeyU;
263 case kVK_ANSI_V: return GHOST_kKeyV;
264 case kVK_ANSI_W: return GHOST_kKeyW;
265 case kVK_ANSI_X: return GHOST_kKeyX;
266 case kVK_ANSI_Y: return GHOST_kKeyY;
267 case kVK_ANSI_Z: return GHOST_kKeyZ;*/
269 /* Numbers keys mapped to handle some int'l keyboard (e.g. French)*/
270 case kVK_ISO_Section: return GHOST_kKeyUnknown;
271 case kVK_ANSI_1: return GHOST_kKey1;
272 case kVK_ANSI_2: return GHOST_kKey2;
273 case kVK_ANSI_3: return GHOST_kKey3;
274 case kVK_ANSI_4: return GHOST_kKey4;
275 case kVK_ANSI_5: return GHOST_kKey5;
276 case kVK_ANSI_6: return GHOST_kKey6;
277 case kVK_ANSI_7: return GHOST_kKey7;
278 case kVK_ANSI_8: return GHOST_kKey8;
279 case kVK_ANSI_9: return GHOST_kKey9;
280 case kVK_ANSI_0: return GHOST_kKey0;
282 case kVK_ANSI_Keypad0: return GHOST_kKeyNumpad0;
283 case kVK_ANSI_Keypad1: return GHOST_kKeyNumpad1;
284 case kVK_ANSI_Keypad2: return GHOST_kKeyNumpad2;
285 case kVK_ANSI_Keypad3: return GHOST_kKeyNumpad3;
286 case kVK_ANSI_Keypad4: return GHOST_kKeyNumpad4;
287 case kVK_ANSI_Keypad5: return GHOST_kKeyNumpad5;
288 case kVK_ANSI_Keypad6: return GHOST_kKeyNumpad6;
289 case kVK_ANSI_Keypad7: return GHOST_kKeyNumpad7;
290 case kVK_ANSI_Keypad8: return GHOST_kKeyNumpad8;
291 case kVK_ANSI_Keypad9: return GHOST_kKeyNumpad9;
292 case kVK_ANSI_KeypadDecimal: return GHOST_kKeyNumpadPeriod;
293 case kVK_ANSI_KeypadEnter: return GHOST_kKeyNumpadEnter;
294 case kVK_ANSI_KeypadPlus: return GHOST_kKeyNumpadPlus;
295 case kVK_ANSI_KeypadMinus: return GHOST_kKeyNumpadMinus;
296 case kVK_ANSI_KeypadMultiply: return GHOST_kKeyNumpadAsterisk;
297 case kVK_ANSI_KeypadDivide: return GHOST_kKeyNumpadSlash;
298 case kVK_ANSI_KeypadClear: return GHOST_kKeyUnknown;
300 case kVK_F1: return GHOST_kKeyF1;
301 case kVK_F2: return GHOST_kKeyF2;
302 case kVK_F3: return GHOST_kKeyF3;
303 case kVK_F4: return GHOST_kKeyF4;
304 case kVK_F5: return GHOST_kKeyF5;
305 case kVK_F6: return GHOST_kKeyF6;
306 case kVK_F7: return GHOST_kKeyF7;
307 case kVK_F8: return GHOST_kKeyF8;
308 case kVK_F9: return GHOST_kKeyF9;
309 case kVK_F10: return GHOST_kKeyF10;
310 case kVK_F11: return GHOST_kKeyF11;
311 case kVK_F12: return GHOST_kKeyF12;
312 case kVK_F13: return GHOST_kKeyF13;
313 case kVK_F14: return GHOST_kKeyF14;
314 case kVK_F15: return GHOST_kKeyF15;
315 case kVK_F16: return GHOST_kKeyF16;
316 case kVK_F17: return GHOST_kKeyF17;
317 case kVK_F18: return GHOST_kKeyF18;
318 case kVK_F19: return GHOST_kKeyF19;
319 case kVK_F20: return GHOST_kKeyF20;
321 case kVK_UpArrow: return GHOST_kKeyUpArrow;
322 case kVK_DownArrow: return GHOST_kKeyDownArrow;
323 case kVK_LeftArrow: return GHOST_kKeyLeftArrow;
324 case kVK_RightArrow: return GHOST_kKeyRightArrow;
326 case kVK_Return: return GHOST_kKeyEnter;
327 case kVK_Delete: return GHOST_kKeyBackSpace;
328 case kVK_ForwardDelete: return GHOST_kKeyDelete;
329 case kVK_Escape: return GHOST_kKeyEsc;
330 case kVK_Tab: return GHOST_kKeyTab;
331 case kVK_Space: return GHOST_kKeySpace;
333 case kVK_Home: return GHOST_kKeyHome;
334 case kVK_End: return GHOST_kKeyEnd;
335 case kVK_PageUp: return GHOST_kKeyUpPage;
336 case kVK_PageDown: return GHOST_kKeyDownPage;
338 /*case kVK_ANSI_Minus: return GHOST_kKeyMinus;
339 case kVK_ANSI_Equal: return GHOST_kKeyEqual;
340 case kVK_ANSI_Comma: return GHOST_kKeyComma;
341 case kVK_ANSI_Period: return GHOST_kKeyPeriod;
342 case kVK_ANSI_Slash: return GHOST_kKeySlash;
343 case kVK_ANSI_Semicolon: return GHOST_kKeySemicolon;
344 case kVK_ANSI_Quote: return GHOST_kKeyQuote;
345 case kVK_ANSI_Backslash: return GHOST_kKeyBackslash;
346 case kVK_ANSI_LeftBracket: return GHOST_kKeyLeftBracket;
347 case kVK_ANSI_RightBracket: return GHOST_kKeyRightBracket;
348 case kVK_ANSI_Grave: return GHOST_kKeyAccentGrave;*/
353 return GHOST_kKeyUnknown;
356 /* alphanumerical or punctuation key that is remappable in int'l keyboards */
357 if ((recvChar >= 'A') && (recvChar <= 'Z')) {
358 return (GHOST_TKey) (recvChar - 'A' + GHOST_kKeyA);
359 } else if ((recvChar >= 'a') && (recvChar <= 'z')) {
360 return (GHOST_TKey) (recvChar - 'a' + GHOST_kKeyA);
362 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
363 KeyboardLayoutRef keyLayout;
364 UCKeyboardLayout *uchrData;
366 KLGetCurrentKeyboardLayout(&keyLayout);
367 KLGetKeyboardLayoutProperty(keyLayout, kKLuchrData, (const void **)
369 /*get actual character value of the "remappable" keys in int'l keyboards,
370 if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
371 then fallback on using the received charactersIgnoringModifiers */
374 UInt32 deadKeyState=0;
375 UniCharCount actualStrLength=0;
377 UCKeyTranslate(uchrData, rawCode, keyAction, 0,
378 LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
382 /* Leopard and Snow Leopard 64bit compatible API*/
383 CFDataRef uchrHandle; /*the keyboard layout*/
384 TISInputSourceRef kbdTISHandle;
386 kbdTISHandle = TISCopyCurrentKeyboardLayoutInputSource();
387 uchrHandle = (CFDataRef)TISGetInputSourceProperty(kbdTISHandle,kTISPropertyUnicodeKeyLayoutData);
388 CFRelease(kbdTISHandle);
390 /*get actual character value of the "remappable" keys in int'l keyboards,
391 if keyboard layout is not correctly reported (e.g. some non Apple keyboards in Tiger),
392 then fallback on using the received charactersIgnoringModifiers */
395 UInt32 deadKeyState=0;
396 UniCharCount actualStrLength=0;
398 UCKeyTranslate((UCKeyboardLayout*)CFDataGetBytePtr(uchrHandle), rawCode, keyAction, 0,
399 LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &actualStrLength, &recvChar);
404 case '-': return GHOST_kKeyMinus;
405 case '=': return GHOST_kKeyEqual;
406 case ',': return GHOST_kKeyComma;
407 case '.': return GHOST_kKeyPeriod;
408 case '/': return GHOST_kKeySlash;
409 case ';': return GHOST_kKeySemicolon;
410 case '\'': return GHOST_kKeyQuote;
411 case '\\': return GHOST_kKeyBackslash;
412 case '[': return GHOST_kKeyLeftBracket;
413 case ']': return GHOST_kKeyRightBracket;
414 case '`': return GHOST_kKeyAccentGrave;
416 return GHOST_kKeyUnknown;
420 return GHOST_kKeyUnknown;
424 #pragma mark defines for 10.6 api not documented in 10.5
425 #ifndef MAC_OS_X_VERSION_10_6
427 /* The following event types are available on some hardware on 10.5.2 and later */
428 NSEventTypeGesture = 29,
429 NSEventTypeMagnify = 30,
430 NSEventTypeSwipe = 31,
431 NSEventTypeRotate = 18,
432 NSEventTypeBeginGesture = 19,
433 NSEventTypeEndGesture = 20
436 @interface NSEvent(GestureEvents)
437 /* This message is valid for events of type NSEventTypeMagnify, on 10.5.2 or later */
438 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
439 - (float)magnification; // change in magnification.
441 - (CGFloat)magnification; // change in magnification.
448 #pragma mark Utility functions
450 #define FIRSTFILEBUFLG 512
451 static bool g_hasFirstFile = false;
452 static char g_firstFileBuf[512];
454 //TODO:Need to investigate this. Function called too early in creator.c to have g_hasFirstFile == true
455 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) {
456 if (g_hasFirstFile) {
457 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1);
458 buf[FIRSTFILEBUFLG - 1] = '\0';
465 #if defined(WITH_QUICKTIME) && !defined(USE_QTKIT)
466 //Need to place this quicktime function in an ObjC file
467 //It is used to avoid memory leak when raising the quicktime "compression settings" standard dialog
471 extern int fromcocoa_request_qtcodec_settings(bContext *C, wmOperator *op);
474 int cocoa_request_qtcodec_settings(bContext *C, wmOperator *op)
477 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
479 result = fromcocoa_request_qtcodec_settings(C, op);
488 #pragma mark Cocoa objects
492 * ObjC object to capture applicationShouldTerminate, and send quit event
494 @interface CocoaAppDelegate : NSObject {
495 GHOST_SystemCocoa *systemCocoa;
497 - (void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa;
498 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
499 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
500 - (void)applicationWillTerminate:(NSNotification *)aNotification;
501 - (void)applicationWillBecomeActive:(NSNotification *)aNotification;
504 @implementation CocoaAppDelegate : NSObject
505 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
507 systemCocoa = sysCocoa;
510 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
512 return systemCocoa->handleOpenDocumentRequest(filename);
515 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
517 //TODO: implement graceful termination through Cocoa mechanism to avoid session log off to be cancelled
518 //Note that Cmd+Q is already handled by keyhandler
519 if (systemCocoa->handleQuitRequest() == GHOST_kExitNow)
520 return NSTerminateCancel;//NSTerminateNow;
522 return NSTerminateCancel;
525 // To avoid cancelling a log off process, we must use Cocoa termination process
526 // And this function is the only chance to perform clean up
527 // So WM_exit needs to be called directly, as the event loop will never run before termination
528 - (void)applicationWillTerminate:(NSNotification *)aNotification
530 /*G.afbreek = 0; //Let Cocoa perform the termination at the end
534 - (void)applicationWillBecomeActive:(NSNotification *)aNotification
536 systemCocoa->handleApplicationBecomeActiveEvent();
542 #pragma mark initialization/finalization
545 GHOST_SystemCocoa::GHOST_SystemCocoa()
548 struct timeval boottime;
550 char *rstring = NULL;
553 m_pressedMouseButtons =0;
554 m_isGestureInProgress = false;
557 m_outsideLoopEventProcessed = false;
558 m_needDelayedApplicationBecomeActiveEventProcessing = false;
559 m_displayManager = new GHOST_DisplayManagerCocoa ();
560 GHOST_ASSERT(m_displayManager, "GHOST_SystemCocoa::GHOST_SystemCocoa(): m_displayManager==0\n");
561 m_displayManager->initialize();
563 //NSEvent timeStamp is given in system uptime, state start date is boot time
565 mib[1] = KERN_BOOTTIME;
566 len = sizeof(struct timeval);
568 sysctl(mib, 2, &boottime, &len, NULL, 0);
569 m_start_time = ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
571 //Detect multitouch trackpad
574 sysctl( mib, 2, NULL, &len, NULL, 0 );
575 rstring = (char*)malloc( len );
576 sysctl( mib, 2, rstring, &len, NULL, 0 );
578 //Hack on MacBook revision, as multitouch avail. function missing
579 if (strstr(rstring,"MacBookAir") ||
580 (strstr(rstring,"MacBook") && (rstring[strlen(rstring)-3]>='5') && (rstring[strlen(rstring)-3]<='9')))
581 m_hasMultiTouchTrackpad = true;
582 else m_hasMultiTouchTrackpad = false;
587 m_ignoreWindowSizedMessages = false;
590 GHOST_SystemCocoa::~GHOST_SystemCocoa()
595 GHOST_TSuccess GHOST_SystemCocoa::init()
598 GHOST_TSuccess success = GHOST_System::init();
601 #ifdef WITH_INPUT_NDOF
602 m_ndofManager = new GHOST_NDOFManagerCocoa(*this);
605 //ProcessSerialNumber psn;
607 //Carbon stuff to move window & menu to foreground
608 /*if (!GetCurrentProcess(&psn)) {
609 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
610 SetFrontProcess(&psn);
613 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
615 [NSApplication sharedApplication];
617 if ([NSApp mainMenu] == nil) {
618 NSMenu *mainMenubar = [[NSMenu alloc] init];
619 NSMenuItem *menuItem;
623 //Create the application menu
624 appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
626 [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
627 [appMenu addItem:[NSMenuItem separatorItem]];
629 menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
630 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
632 menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
633 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
635 [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
637 menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
638 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
640 menuItem = [[NSMenuItem alloc] init];
641 [menuItem setSubmenu:appMenu];
643 [mainMenubar addItem:menuItem];
645 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu]; //Needed for 10.5
648 //Create the window menu
649 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
651 menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
652 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
654 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
656 menuItem = [windowMenu addItemWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"];
657 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
659 menuItem = [[NSMenuItem alloc] init];
660 [menuItem setSubmenu:windowMenu];
662 [mainMenubar addItem:menuItem];
665 [NSApp setMainMenu:mainMenubar];
666 [NSApp setWindowsMenu:windowMenu];
667 [windowMenu release];
670 if ([NSApp delegate] == nil) {
671 CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
672 [appDelegate setSystemCocoa:this];
673 [NSApp setDelegate:appDelegate];
676 [NSApp finishLaunching];
684 #pragma mark window management
686 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
688 //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
689 struct timeval currentTime;
691 gettimeofday(¤tTime, NULL);
693 //Return timestamp of system uptime
695 return ((currentTime.tv_sec*1000)+(currentTime.tv_usec/1000)-m_start_time);
699 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
701 //Note that OS X supports monitor hot plug
702 // We do not support multiple monitors at the moment
703 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
705 GHOST_TUns8 count = [[NSScreen screens] count];
712 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
714 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
715 //Get visible frame, that is frame excluding dock and top menu bar
716 NSRect frame = [[NSScreen mainScreen] visibleFrame];
718 //Returns max window contents (excluding title bar...)
719 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
720 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
722 width = contentRect.size.width;
723 height = contentRect.size.height;
729 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
730 const STR_String& title,
735 GHOST_TWindowState state,
736 GHOST_TDrawingContextType type,
738 const GHOST_TUns16 numOfAASamples,
739 const GHOST_TEmbedderWindowID parentWindow
742 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
743 GHOST_IWindow* window = 0;
745 //Get the available rect for including window contents
746 NSRect frame = [[NSScreen mainScreen] visibleFrame];
747 NSRect contentRect = [NSWindow contentRectForFrameRect:frame
748 styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)];
750 GHOST_TInt32 bottom = (contentRect.size.height - 1) - height - top;
752 //Ensures window top left is inside this available rect
753 left = left > contentRect.origin.x ? left : contentRect.origin.x;
754 bottom = bottom > contentRect.origin.y ? bottom : contentRect.origin.y;
756 window = new GHOST_WindowCocoa (this, title, left, bottom, width, height, state, type, stereoVisual, numOfAASamples);
759 if (window->getValid()) {
760 // Store the pointer to the window
761 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
762 m_windowManager->addWindow(window);
763 m_windowManager->setActiveWindow(window);
764 //Need to tell window manager the new window is the active one (Cocoa does not send the event activate upon window creation)
765 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window));
766 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
770 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
776 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): could not create window\n");
783 * @note : returns coordinates in Cocoa screen coordinates
785 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
787 NSPoint mouseLoc = [NSEvent mouseLocation];
789 // Returns the mouse location in screen coordinates
790 x = (GHOST_TInt32)mouseLoc.x;
791 y = (GHOST_TInt32)mouseLoc.y;
792 return GHOST_kSuccess;
796 * @note : expect Cocoa screen coordinates
798 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
800 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
801 if (!window) return GHOST_kFailure;
803 //Cursor and mouse dissociation placed here not to interfere with continuous grab
804 // (in cont. grab setMouseCursorPosition is directly called)
805 CGAssociateMouseAndMouseCursorPosition(false);
806 setMouseCursorPosition(x, y);
807 CGAssociateMouseAndMouseCursorPosition(true);
809 //Force mouse move event (not pushed by Cocoa)
810 pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, x, y));
811 m_outsideLoopEventProcessed = true;
813 return GHOST_kSuccess;
816 GHOST_TSuccess GHOST_SystemCocoa::setMouseCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
818 float xf=(float)x, yf=(float)y;
819 GHOST_WindowCocoa* window = (GHOST_WindowCocoa*)m_windowManager->getActiveWindow();
820 if (!window) return GHOST_kFailure;
822 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
823 NSScreen *windowScreen = window->getScreen();
824 NSRect screenRect = [windowScreen frame];
826 //Set position relative to current screen
827 xf -= screenRect.origin.x;
828 yf -= screenRect.origin.y;
830 //Quartz Display Services uses the old coordinates (top left origin)
831 yf = screenRect.size.height -yf;
833 CGDisplayMoveCursorToPoint((CGDirectDisplayID)[[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue], CGPointMake(xf, yf));
836 return GHOST_kSuccess;
840 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
842 keys.set(GHOST_kModifierKeyOS, (m_modifierMask & NSCommandKeyMask) ? true : false);
843 keys.set(GHOST_kModifierKeyLeftAlt, (m_modifierMask & NSAlternateKeyMask) ? true : false);
844 keys.set(GHOST_kModifierKeyLeftShift, (m_modifierMask & NSShiftKeyMask) ? true : false);
845 keys.set(GHOST_kModifierKeyLeftControl, (m_modifierMask & NSControlKeyMask) ? true : false);
847 return GHOST_kSuccess;
850 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
853 buttons.set(GHOST_kButtonMaskLeft, m_pressedMouseButtons & GHOST_kButtonMaskLeft);
854 buttons.set(GHOST_kButtonMaskRight, m_pressedMouseButtons & GHOST_kButtonMaskRight);
855 buttons.set(GHOST_kButtonMaskMiddle, m_pressedMouseButtons & GHOST_kButtonMaskMiddle);
856 buttons.set(GHOST_kButtonMaskButton4, m_pressedMouseButtons & GHOST_kButtonMaskButton4);
857 buttons.set(GHOST_kButtonMaskButton5, m_pressedMouseButtons & GHOST_kButtonMaskButton5);
858 return GHOST_kSuccess;
863 #pragma mark Event handlers
866 * The event queue polling function
868 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
870 bool anyProcessed = false;
873 // SetMouseCoalescingEnabled(false, NULL);
874 //TODO : implement timer ??
877 GHOST_TimerManager* timerMgr = getTimerManager();
880 GHOST_TUns64 next = timerMgr->nextFireTime();
883 if (next == GHOST_kFireTimeNever) {
884 timeOut = kEventDurationForever;
886 timeOut = (double)(next - getMilliSeconds())/1000.0;
891 ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
894 if (timerMgr->fireTimers(getMilliSeconds())) {
899 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
900 event = [NSApp nextEventMatchingMask:NSAnyEventMask
901 untilDate:[NSDate distantPast]
902 inMode:NSDefaultRunLoopMode
911 switch ([event type]) {
915 handleKeyEvent(event);
917 /* Support system-wide keyboard shortcuts, like Exposé, ...) =>included in always NSApp sendEvent */
918 /* if (([event modifierFlags] & NSCommandKeyMask) || [event type] == NSFlagsChanged) {
919 [NSApp sendEvent:event];
923 case NSLeftMouseDown:
925 case NSRightMouseDown:
928 case NSLeftMouseDragged:
929 case NSRightMouseDragged:
931 case NSOtherMouseDown:
933 case NSOtherMouseDragged:
934 case NSEventTypeMagnify:
935 case NSEventTypeRotate:
936 case NSEventTypeBeginGesture:
937 case NSEventTypeEndGesture:
938 handleMouseEvent(event);
942 case NSTabletProximity:
943 handleTabletEvent(event,[event type]);
946 /* Trackpad features, fired only from OS X 10.5.2
947 case NSEventTypeGesture:
948 case NSEventTypeSwipe:
954 NSAppKitDefined = 13,
955 NSSystemDefined = 14,
956 NSApplicationDefined = 15,
958 NSCursorUpdate = 17,*/
963 //Resend event to NSApp to ensure Mac wide events are handled
964 [NSApp sendEvent:event];
966 } while (event!= nil);
967 //} while (waitForEvent && !anyProcessed); Needed only for timer implementation
969 if (m_needDelayedApplicationBecomeActiveEventProcessing) handleApplicationBecomeActiveEvent();
971 if (m_outsideLoopEventProcessed) {
972 m_outsideLoopEventProcessed = false;
976 m_ignoreWindowSizedMessages = false;
981 //Note: called from NSApplication delegate
982 GHOST_TSuccess GHOST_SystemCocoa::handleApplicationBecomeActiveEvent()
984 //Update the modifiers key mask, as its status may have changed when the application was not active
985 //(that is when update events are sent to another application)
986 unsigned int modifiers;
987 GHOST_IWindow* window = m_windowManager->getActiveWindow();
990 m_needDelayedApplicationBecomeActiveEventProcessing = true;
991 return GHOST_kFailure;
993 else m_needDelayedApplicationBecomeActiveEventProcessing = false;
995 modifiers = [[[NSApplication sharedApplication] currentEvent] modifierFlags];
997 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
998 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1000 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1001 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1003 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1004 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1006 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1007 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1010 m_modifierMask = modifiers;
1012 m_outsideLoopEventProcessed = true;
1013 return GHOST_kSuccess;
1016 void GHOST_SystemCocoa::notifyExternalEventProcessed()
1018 m_outsideLoopEventProcessed = true;
1021 //Note: called from NSWindow delegate
1022 GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa* window)
1024 if (!validWindow(window)) {
1025 return GHOST_kFailure;
1029 case GHOST_kEventWindowClose:
1030 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
1032 case GHOST_kEventWindowActivate:
1033 m_windowManager->setActiveWindow(window);
1034 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
1035 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
1037 case GHOST_kEventWindowDeactivate:
1038 m_windowManager->setWindowInactive(window);
1039 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
1041 case GHOST_kEventWindowUpdate:
1042 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
1044 case GHOST_kEventWindowMove:
1045 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, window) );
1047 case GHOST_kEventWindowSize:
1048 if (!m_ignoreWindowSizedMessages)
1050 //Enforce only one resize message per event loop (coalescing all the live resize messages)
1051 window->updateDrawingContext();
1052 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
1053 //Mouse up event is trapped by the resizing event loop, so send it anyway to the window manager
1054 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonUp, window, convertButton(0)));
1055 m_ignoreWindowSizedMessages = true;
1059 return GHOST_kFailure;
1063 m_outsideLoopEventProcessed = true;
1064 return GHOST_kSuccess;
1067 //Note: called from NSWindow subclass
1068 GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,
1069 GHOST_WindowCocoa* window, int mouseX, int mouseY, void* data)
1071 if (!validWindow(window)) {
1072 return GHOST_kFailure;
1076 case GHOST_kEventDraggingEntered:
1077 case GHOST_kEventDraggingUpdated:
1078 case GHOST_kEventDraggingExited:
1079 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,NULL));
1082 case GHOST_kEventDraggingDropDone:
1084 GHOST_TUns8 * temp_buff;
1085 GHOST_TStringArray *strArray;
1086 NSArray *droppedArray;
1087 size_t pastedTextSize;
1088 NSString *droppedStr;
1089 GHOST_TEventDataPtr eventData;
1092 if (!data) return GHOST_kFailure;
1094 switch (draggedObjectType) {
1095 case GHOST_kDragnDropTypeFilenames:
1096 droppedArray = (NSArray*)data;
1098 strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray));
1099 if (!strArray) return GHOST_kFailure;
1101 strArray->count = [droppedArray count];
1102 if (strArray->count == 0) return GHOST_kFailure;
1104 strArray->strings = (GHOST_TUns8**) malloc(strArray->count*sizeof(GHOST_TUns8*));
1106 for (i=0;i<strArray->count;i++)
1108 droppedStr = [droppedArray objectAtIndex:i];
1110 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1111 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1114 strArray->count = i;
1118 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1119 temp_buff[pastedTextSize] = '\0';
1121 strArray->strings[i] = temp_buff;
1124 eventData = (GHOST_TEventDataPtr) strArray;
1127 case GHOST_kDragnDropTypeString:
1128 droppedStr = (NSString*)data;
1129 pastedTextSize = [droppedStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1131 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1133 if (temp_buff == NULL) {
1134 return GHOST_kFailure;
1137 strncpy((char*)temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize);
1139 temp_buff[pastedTextSize] = '\0';
1141 eventData = (GHOST_TEventDataPtr) temp_buff;
1144 case GHOST_kDragnDropTypeBitmap:
1146 NSImage *droppedImg = (NSImage*)data;
1147 NSSize imgSize = [droppedImg size];
1149 GHOST_TUns8 *rasterRGB = NULL;
1150 GHOST_TUns8 *rasterRGBA = NULL;
1151 GHOST_TUns8 *toIBuf = NULL;
1152 int x, y, to_i, from_i;
1153 NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA,*bitmapImage=nil;
1154 NSEnumerator *enumerator;
1155 NSImageRep *representation;
1157 ibuf = IMB_allocImBuf (imgSize.width , imgSize.height, 32, IB_rect);
1159 [droppedImg release];
1160 return GHOST_kFailure;
1163 /*Get the bitmap of the image*/
1164 enumerator = [[droppedImg representations] objectEnumerator];
1165 while ((representation = [enumerator nextObject])) {
1166 if ([representation isKindOfClass:[NSBitmapImageRep class]]) {
1167 bitmapImage = (NSBitmapImageRep *)representation;
1171 if (bitmapImage == nil) return GHOST_kFailure;
1173 if (([bitmapImage bitsPerPixel] == 32) && (([bitmapImage bitmapFormat] & 0x5) == 0)
1174 && ![bitmapImage isPlanar]) {
1175 /* Try a fast copy if the image is a meshed RGBA 32bit bitmap*/
1176 toIBuf = (GHOST_TUns8*)ibuf->rect;
1177 rasterRGB = (GHOST_TUns8*)[bitmapImage bitmapData];
1178 for (y = 0; y < imgSize.height; y++) {
1179 to_i = (imgSize.height-y-1)*imgSize.width;
1180 from_i = y*imgSize.width;
1181 memcpy(toIBuf+4*to_i, rasterRGB+4*from_i, 4*imgSize.width);
1185 /* Tell cocoa image resolution is same as current system one */
1186 [bitmapImage setSize:imgSize];
1188 /* Convert the image in a RGBA 32bit format */
1189 /* As Core Graphics does not support contextes with non premutliplied alpha,
1190 we need to get alpha key values in a separate batch */
1192 /* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
1193 blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1194 pixelsWide:imgSize.width
1195 pixelsHigh:imgSize.height
1196 bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
1197 colorSpaceName:NSDeviceRGBColorSpace
1198 bitmapFormat:(NSBitmapFormat)0
1199 bytesPerRow:4*imgSize.width
1200 bitsPerPixel:32/*RGB format padded to 32bits*/];
1202 [NSGraphicsContext saveGraphicsState];
1203 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
1205 [NSGraphicsContext restoreGraphicsState];
1207 rasterRGB = (GHOST_TUns8*)[blBitmapFormatImageRGB bitmapData];
1208 if (rasterRGB == NULL) {
1209 [bitmapImage release];
1210 [blBitmapFormatImageRGB release];
1211 [droppedImg release];
1212 return GHOST_kFailure;
1215 /* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
1216 blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
1217 pixelsWide:imgSize.width
1218 pixelsHigh:imgSize.height
1219 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
1220 colorSpaceName:NSDeviceRGBColorSpace
1221 bitmapFormat:(NSBitmapFormat)0
1222 bytesPerRow:4*imgSize.width
1223 bitsPerPixel:32/* RGBA */];
1225 [NSGraphicsContext saveGraphicsState];
1226 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
1228 [NSGraphicsContext restoreGraphicsState];
1230 rasterRGBA = (GHOST_TUns8*)[blBitmapFormatImageRGBA bitmapData];
1231 if (rasterRGBA == NULL) {
1232 [bitmapImage release];
1233 [blBitmapFormatImageRGB release];
1234 [blBitmapFormatImageRGBA release];
1235 [droppedImg release];
1236 return GHOST_kFailure;
1239 /*Copy the image to ibuf, flipping it vertically*/
1240 toIBuf = (GHOST_TUns8*)ibuf->rect;
1241 for (y = 0; y < imgSize.height; y++) {
1242 for (x = 0; x < imgSize.width; x++) {
1243 to_i = (imgSize.height-y-1)*imgSize.width + x;
1244 from_i = y*imgSize.width + x;
1246 toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
1247 toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
1248 toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
1249 toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
1253 [blBitmapFormatImageRGB release];
1254 [blBitmapFormatImageRGBA release];
1255 [droppedImg release];
1258 eventData = (GHOST_TEventDataPtr) ibuf;
1263 return GHOST_kFailure;
1266 pushEvent(new GHOST_EventDragnDrop(getMilliSeconds(),eventType,draggedObjectType,window,mouseX,mouseY,eventData));
1270 return GHOST_kFailure;
1272 m_outsideLoopEventProcessed = true;
1273 return GHOST_kSuccess;
1277 GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest()
1279 GHOST_Window* window = (GHOST_Window*)m_windowManager->getActiveWindow();
1281 //Discard quit event if we are in cursor grab sequence
1282 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1283 return GHOST_kExitCancel;
1285 //Check open windows if some changes are not saved
1286 if (m_windowManager->getAnyModifiedState())
1288 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved.\nDo you really want to quit ?",
1289 @"Cancel", @"Quit Anyway", nil);
1290 if (shouldQuit == NSAlertAlternateReturn)
1292 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1293 return GHOST_kExitNow;
1295 //Give back focus to the blender window if user selected cancel quit
1296 NSArray *windowsList = [NSApp orderedWindows];
1297 if ([windowsList count]) {
1298 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1299 //Handle the modifiers keyes changed state issue
1300 //as recovering from the quit dialog is like application
1301 //gaining focus back.
1302 //Main issue fixed is Cmd modifier not being cleared
1303 handleApplicationBecomeActiveEvent();
1309 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) );
1310 m_outsideLoopEventProcessed = true;
1311 return GHOST_kExitNow;
1314 return GHOST_kExitCancel;
1317 bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr)
1319 NSString *filepath = (NSString*)filepathStr;
1320 int confirmOpen = NSAlertAlternateReturn;
1321 NSArray *windowsList;
1323 size_t filenameTextSize;
1324 GHOST_Window* window= (GHOST_Window*)m_windowManager->getActiveWindow();
1330 //Discard event if we are in cursor grab sequence, it'll lead to "stuck cursor" situation if the alert panel is raised
1331 if (window && (window->getCursorGrabMode() != GHOST_kGrabDisable) && (window->getCursorGrabMode() != GHOST_kGrabNormal))
1332 return GHOST_kExitCancel;
1334 //Check open windows if some changes are not saved
1335 if (m_windowManager->getAnyModifiedState())
1337 confirmOpen = NSRunAlertPanel([NSString stringWithFormat:@"Opening %@",[filepath lastPathComponent]],
1338 @"Current document has not been saved.\nDo you really want to proceed?",
1339 @"Cancel", @"Open", nil);
1342 //Give back focus to the blender window
1343 windowsList = [NSApp orderedWindows];
1344 if ([windowsList count]) {
1345 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
1348 if (confirmOpen == NSAlertAlternateReturn)
1350 filenameTextSize = [filepath lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1352 temp_buff = (char*) malloc(filenameTextSize+1);
1354 if (temp_buff == NULL) {
1355 return GHOST_kFailure;
1358 strncpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize);
1360 temp_buff[filenameTextSize] = '\0';
1362 pushEvent(new GHOST_EventString(getMilliSeconds(),GHOST_kEventOpenMainFile,window,(GHOST_TEventDataPtr) temp_buff));
1369 GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr, short eventType)
1371 NSEvent *event = (NSEvent *)eventPtr;
1372 GHOST_IWindow* window;
1374 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1376 //printf("\nW failure for event 0x%x",[event type]);
1377 return GHOST_kFailure;
1380 GHOST_TabletData& ct=((GHOST_WindowCocoa*)window)->GetCocoaTabletData();
1382 switch (eventType) {
1384 ct.Pressure = [event pressure];
1385 ct.Xtilt = [event tilt].x;
1386 ct.Ytilt = [event tilt].y;
1389 case NSTabletProximity:
1393 if ([event isEnteringProximity])
1395 //pointer is entering tablet area proximity
1396 switch ([event pointingDeviceType]) {
1397 case NSPenPointingDevice:
1398 ct.Active = GHOST_kTabletModeStylus;
1400 case NSEraserPointingDevice:
1401 ct.Active = GHOST_kTabletModeEraser;
1403 case NSCursorPointingDevice:
1404 case NSUnknownPointingDevice:
1406 ct.Active = GHOST_kTabletModeNone;
1410 // pointer is leaving - return to mouse
1411 ct.Active = GHOST_kTabletModeNone;
1416 GHOST_ASSERT(FALSE,"GHOST_SystemCocoa::handleTabletEvent : unknown event received");
1417 return GHOST_kFailure;
1420 return GHOST_kSuccess;
1424 GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
1426 NSEvent *event = (NSEvent *)eventPtr;
1427 GHOST_WindowCocoa* window;
1429 window = (GHOST_WindowCocoa*)m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1431 //printf("\nW failure for event 0x%x",[event type]);
1432 return GHOST_kFailure;
1435 switch ([event type])
1437 case NSLeftMouseDown:
1438 case NSRightMouseDown:
1439 case NSOtherMouseDown:
1440 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
1441 //Handle tablet events combined with mouse events
1442 switch ([event subtype]) {
1443 case NX_SUBTYPE_TABLET_POINT:
1444 handleTabletEvent(eventPtr, NSTabletPoint);
1446 case NX_SUBTYPE_TABLET_PROXIMITY:
1447 handleTabletEvent(eventPtr, NSTabletProximity);
1450 //No tablet event included : do nothing
1456 case NSRightMouseUp:
1457 case NSOtherMouseUp:
1458 pushEvent(new GHOST_EventButton([event timestamp]*1000, GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
1459 //Handle tablet events combined with mouse events
1460 switch ([event subtype]) {
1461 case NX_SUBTYPE_TABLET_POINT:
1462 handleTabletEvent(eventPtr, NSTabletPoint);
1464 case NX_SUBTYPE_TABLET_PROXIMITY:
1465 handleTabletEvent(eventPtr, NSTabletProximity);
1468 //No tablet event included : do nothing
1473 case NSLeftMouseDragged:
1474 case NSRightMouseDragged:
1475 case NSOtherMouseDragged:
1476 //Handle tablet events combined with mouse events
1477 switch ([event subtype]) {
1478 case NX_SUBTYPE_TABLET_POINT:
1479 handleTabletEvent(eventPtr, NSTabletPoint);
1481 case NX_SUBTYPE_TABLET_PROXIMITY:
1482 handleTabletEvent(eventPtr, NSTabletProximity);
1485 //No tablet event included : do nothing
1490 switch (window->getCursorGrabMode()) {
1491 case GHOST_kGrabHide: //Cursor hidden grab operation : no cursor move
1493 GHOST_TInt32 x_warp, y_warp, x_accum, y_accum, x, y;
1495 window->getCursorGrabInitPos(x_warp, y_warp);
1497 window->getCursorGrabAccum(x_accum, y_accum);
1498 x_accum += [event deltaX];
1499 y_accum += -[event deltaY]; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1500 window->setCursorGrabAccum(x_accum, y_accum);
1502 window->clientToScreenIntern(x_warp+x_accum, y_warp+y_accum, x, y);
1503 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1506 case GHOST_kGrabWrap: //Wrap cursor at area/window boundaries
1508 NSPoint mousePos = [event locationInWindow];
1509 GHOST_TInt32 x_mouse= mousePos.x;
1510 GHOST_TInt32 y_mouse= mousePos.y;
1511 GHOST_TInt32 x_accum, y_accum, x_cur, y_cur, x, y;
1512 GHOST_Rect bounds, windowBounds, correctedBounds;
1514 /* fallback to window bounds */
1515 if(window->getCursorGrabBounds(bounds)==GHOST_kFailure)
1516 window->getClientBounds(bounds);
1518 //Switch back to Cocoa coordinates orientation (y=0 at botton,the same as blender internal btw!), and to client coordinates
1519 window->getClientBounds(windowBounds);
1520 window->screenToClient(bounds.m_l, bounds.m_b, correctedBounds.m_l, correctedBounds.m_t);
1521 window->screenToClient(bounds.m_r, bounds.m_t, correctedBounds.m_r, correctedBounds.m_b);
1522 correctedBounds.m_b = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_b;
1523 correctedBounds.m_t = (windowBounds.m_b - windowBounds.m_t) - correctedBounds.m_t;
1525 //Update accumulation counts
1526 window->getCursorGrabAccum(x_accum, y_accum);
1527 x_accum += [event deltaX]-m_cursorDelta_x;
1528 y_accum += -[event deltaY]-m_cursorDelta_y; //Strange Apple implementation (inverted coordinates for the deltaY) ...
1529 window->setCursorGrabAccum(x_accum, y_accum);
1532 //Warp mouse cursor if needed
1533 x_mouse += [event deltaX]-m_cursorDelta_x;
1534 y_mouse += -[event deltaY]-m_cursorDelta_y;
1535 correctedBounds.wrapPoint(x_mouse, y_mouse, 2);
1537 //Compensate for mouse moved event taking cursor position set into account
1538 m_cursorDelta_x = x_mouse-mousePos.x;
1539 m_cursorDelta_y = y_mouse-mousePos.y;
1541 //Set new cursor position
1542 window->clientToScreenIntern(x_mouse, y_mouse, x_cur, y_cur);
1543 setMouseCursorPosition(x_cur, y_cur); /* wrap */
1546 window->getCursorGrabInitPos(x_cur, y_cur);
1547 window->clientToScreenIntern(x_cur + x_accum, y_cur + y_accum, x, y);
1548 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1553 //Normal cursor operation: send mouse position in window
1554 NSPoint mousePos = [event locationInWindow];
1557 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1558 pushEvent(new GHOST_EventCursor([event timestamp]*1000, GHOST_kEventCursorMove, window, x, y));
1561 m_cursorDelta_y=0; //Mouse motion occurred between two cursor warps, so we can reset the delta counter
1569 /* Send trackpad event if inside a trackpad gesture, send wheel event otherwise */
1570 if (!m_hasMultiTouchTrackpad || !m_isGestureInProgress) {
1573 double deltaF = [event deltaY];
1575 if (deltaF == 0.0) deltaF = [event deltaX]; // make blender decide if it's horizontal scroll
1576 if (deltaF == 0.0) break; //discard trackpad delta=0 events
1578 delta = deltaF > 0.0 ? 1 : -1;
1579 pushEvent(new GHOST_EventWheel([event timestamp]*1000, window, delta));
1582 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 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1603 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventScroll, x, y, dx, dy));
1608 case NSEventTypeMagnify:
1610 NSPoint mousePos = [event locationInWindow];
1612 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1613 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventMagnify, x, y,
1614 [event magnification]*250.0 + 0.1, 0));
1618 case NSEventTypeRotate:
1620 NSPoint mousePos = [event locationInWindow];
1622 window->clientToScreenIntern(mousePos.x, mousePos.y, x, y);
1623 pushEvent(new GHOST_EventTrackpad([event timestamp]*1000, window, GHOST_kTrackpadEventRotate, x, y,
1624 -[event rotation] * 5.0, 0));
1626 case NSEventTypeBeginGesture:
1627 m_isGestureInProgress = true;
1629 case NSEventTypeEndGesture:
1630 m_isGestureInProgress = false;
1633 return GHOST_kFailure;
1637 return GHOST_kSuccess;
1641 GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1643 NSEvent *event = (NSEvent *)eventPtr;
1644 GHOST_IWindow* window;
1645 unsigned int modifiers;
1646 NSString *characters;
1647 NSData *convertedCharacters;
1649 unsigned char ascii;
1650 NSString* charsIgnoringModifiers;
1652 window = m_windowManager->getWindowAssociatedWithOSWindow((void*)[event window]);
1654 //printf("\nW failure for event 0x%x",[event type]);
1655 return GHOST_kFailure;
1658 /* unicode input - not entirely supported yet
1659 * but we are getting the right byte, Blender is not drawing it though
1660 * also some languages may need special treatment:
1661 - Japanese: romanji is used as input, and every 2 letters OSX converts the text
1662 to Hiragana/Katakana.
1663 - Korean: one add one letter at a time, and then the OSX join them in the equivalent
1666 char utf8_buf[6]= {'\0'};
1668 switch ([event type]) {
1672 charsIgnoringModifiers = [event charactersIgnoringModifiers];
1673 if ([charsIgnoringModifiers length]>0)
1674 keyCode = convertKey([event keyCode],
1675 [charsIgnoringModifiers characterAtIndex:0],
1676 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1678 keyCode = convertKey([event keyCode],0,
1679 [event type] == NSKeyDown?kUCKeyActionDown:kUCKeyActionUp);
1682 characters = [event characters];
1683 if ([characters length]>0) { //Check for dead keys
1684 //Convert characters to iso latin 1 encoding
1685 convertedCharacters = [characters dataUsingEncoding:NSISOLatin1StringEncoding];
1686 if ([convertedCharacters length]>0)
1687 ascii =((char*)[convertedCharacters bytes])[0];
1689 ascii = 0; //Character not available in iso latin 1 encoding
1695 if ([characters length]>0) {
1696 convertedCharacters = [characters dataUsingEncoding:NSUTF8StringEncoding];
1697 if ([convertedCharacters length]>0) {
1698 utf8_buf[0] = ((char*)[convertedCharacters bytes])[0];
1699 utf8_buf[1] = ((char*)[convertedCharacters bytes])[1];
1700 utf8_buf[2] = ((char*)[convertedCharacters bytes])[2];
1701 utf8_buf[3] = ((char*)[convertedCharacters bytes])[3];
1702 utf8_buf[4] = ((char*)[convertedCharacters bytes])[4];
1703 utf8_buf[5] = ((char*)[convertedCharacters bytes])[5];
1710 /* XXX the above code gives us the right utf8, however if we pass it along Font Object doesn't work.
1711 let's leave utf8 disabled for OSX before we fix that */
1712 utf8_buf[0] = '\0';//to be removed once things are working
1714 if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1715 break; //Cmd-Q is directly handled by Cocoa
1717 if ([event type] == NSKeyDown) {
1718 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyDown, window, keyCode, ascii, utf8_buf) );
1719 //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);
1721 pushEvent( new GHOST_EventKey([event timestamp]*1000, GHOST_kEventKeyUp, window, keyCode, ascii, utf8_buf) );
1722 //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);
1726 case NSFlagsChanged:
1727 modifiers = [event modifierFlags];
1729 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1730 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1732 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1733 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1735 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1736 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1738 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1739 pushEvent( new GHOST_EventKey([event timestamp]*1000, (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
1742 m_modifierMask = modifiers;
1746 return GHOST_kFailure;
1750 return GHOST_kSuccess;
1755 #pragma mark Clipboard get/set
1757 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1759 GHOST_TUns8 * temp_buff;
1760 size_t pastedTextSize;
1762 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1764 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1766 if (pasteBoard == nil) {
1771 NSArray *supportedTypes =
1772 [NSArray arrayWithObjects: NSStringPboardType, nil];
1774 NSString *bestType = [[NSPasteboard generalPasteboard]
1775 availableTypeFromArray:supportedTypes];
1777 if (bestType == nil) {
1782 NSString * textPasted = [pasteBoard stringForType:NSStringPboardType];
1784 if (textPasted == nil) {
1789 pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
1791 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1793 if (temp_buff == NULL) {
1798 strncpy((char*)temp_buff, [textPasted cStringUsingEncoding:NSISOLatin1StringEncoding], pastedTextSize);
1800 temp_buff[pastedTextSize] = '\0';
1811 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1813 NSString *textToCopy;
1815 if(selection) {return;} // for copying the selection, used on X11
1817 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1819 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1821 if (pasteBoard == nil) {
1826 NSArray *supportedTypes = [NSArray arrayWithObject:NSStringPboardType];
1828 [pasteBoard declareTypes:supportedTypes owner:nil];
1830 textToCopy = [NSString stringWithCString:buffer encoding:NSISOLatin1StringEncoding];
1832 [pasteBoard setString:textToCopy forType:NSStringPboardType];