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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
20 * All rights reserved.
22 * The Original Code is: all of this file.
24 * Contributor(s): Maarten Gribnau 05/2001
25 * Damien Plisson 09/2009
27 * ***** END GPL LICENSE BLOCK *****
30 #import <Cocoa/Cocoa.h>
33 #include <sys/types.h>
34 #include <sys/sysctl.h>
36 #include "GHOST_SystemCocoa.h"
38 #include "GHOST_DisplayManagerCocoa.h"
39 #include "GHOST_EventKey.h"
40 #include "GHOST_EventButton.h"
41 #include "GHOST_EventCursor.h"
42 #include "GHOST_EventWheel.h"
43 #include "GHOST_EventNDOF.h"
45 #include "GHOST_TimerManager.h"
46 #include "GHOST_TimerTask.h"
47 #include "GHOST_WindowManager.h"
48 #include "GHOST_WindowCocoa.h"
49 #include "GHOST_NDOFManager.h"
50 #include "AssertMacros.h"
52 #pragma mark KeyMap, mouse converters
54 //TODO: remove (kept as reminder to implement window events)
56 const EventTypeSpec kEvents[] =
58 { kEventClassAppleEvent, kEventAppleEvent },
60 // { kEventClassApplication, kEventAppActivated },
61 // { kEventClassApplication, kEventAppDeactivated },
63 { kEventClassKeyboard, kEventRawKeyDown },
64 { kEventClassKeyboard, kEventRawKeyRepeat },
65 { kEventClassKeyboard, kEventRawKeyUp },
66 { kEventClassKeyboard, kEventRawKeyModifiersChanged },
68 { kEventClassMouse, kEventMouseDown },
69 { kEventClassMouse, kEventMouseUp },
70 { kEventClassMouse, kEventMouseMoved },
71 { kEventClassMouse, kEventMouseDragged },
72 { kEventClassMouse, kEventMouseWheelMoved },
74 { kEventClassWindow, kEventWindowClickZoomRgn } , // for new zoom behaviour
75 { kEventClassWindow, kEventWindowZoom }, // for new zoom behaviour
76 { kEventClassWindow, kEventWindowExpand } , // for new zoom behaviour
77 { kEventClassWindow, kEventWindowExpandAll }, // for new zoom behaviour
79 { kEventClassWindow, kEventWindowClose },
80 { kEventClassWindow, kEventWindowActivated },
81 { kEventClassWindow, kEventWindowDeactivated },
82 { kEventClassWindow, kEventWindowUpdate },
83 { kEventClassWindow, kEventWindowBoundsChanged },
85 { kEventClassBlender, kEventBlenderNdofAxis },
86 { kEventClassBlender, kEventBlenderNdofButtons }
92 static GHOST_TButtonMask convertButton(EventMouseButton button)
96 return GHOST_kButtonMaskLeft;
98 return GHOST_kButtonMaskRight;
100 return GHOST_kButtonMaskMiddle;
102 return GHOST_kButtonMaskButton4;
104 return GHOST_kButtonMaskButton5;
106 return GHOST_kButtonMaskLeft;
111 * Converts Mac rawkey codes (same for Cocoa & Carbon)
112 * into GHOST key codes
113 * @param rawCode The raw physical key code
114 * @param recvChar the character ignoring modifiers (except for shift)
115 * @return Ghost key code
117 static GHOST_TKey convertKey(int rawCode, unichar recvChar)
120 //printf("\nrecvchar %c 0x%x",recvChar,recvChar);
122 /*Physical keycodes not used due to map changes in int'l keyboards
123 case kVK_ANSI_A: return GHOST_kKeyA;
124 case kVK_ANSI_B: return GHOST_kKeyB;
125 case kVK_ANSI_C: return GHOST_kKeyC;
126 case kVK_ANSI_D: return GHOST_kKeyD;
127 case kVK_ANSI_E: return GHOST_kKeyE;
128 case kVK_ANSI_F: return GHOST_kKeyF;
129 case kVK_ANSI_G: return GHOST_kKeyG;
130 case kVK_ANSI_H: return GHOST_kKeyH;
131 case kVK_ANSI_I: return GHOST_kKeyI;
132 case kVK_ANSI_J: return GHOST_kKeyJ;
133 case kVK_ANSI_K: return GHOST_kKeyK;
134 case kVK_ANSI_L: return GHOST_kKeyL;
135 case kVK_ANSI_M: return GHOST_kKeyM;
136 case kVK_ANSI_N: return GHOST_kKeyN;
137 case kVK_ANSI_O: return GHOST_kKeyO;
138 case kVK_ANSI_P: return GHOST_kKeyP;
139 case kVK_ANSI_Q: return GHOST_kKeyQ;
140 case kVK_ANSI_R: return GHOST_kKeyR;
141 case kVK_ANSI_S: return GHOST_kKeyS;
142 case kVK_ANSI_T: return GHOST_kKeyT;
143 case kVK_ANSI_U: return GHOST_kKeyU;
144 case kVK_ANSI_V: return GHOST_kKeyV;
145 case kVK_ANSI_W: return GHOST_kKeyW;
146 case kVK_ANSI_X: return GHOST_kKeyX;
147 case kVK_ANSI_Y: return GHOST_kKeyY;
148 case kVK_ANSI_Z: return GHOST_kKeyZ;*/
150 /* Numbers keys mapped to handle some int'l keyboard (e.g. French)*/
151 case kVK_ISO_Section: return GHOST_kKeyUnknown;
152 case kVK_ANSI_1: return GHOST_kKey1;
153 case kVK_ANSI_2: return GHOST_kKey2;
154 case kVK_ANSI_3: return GHOST_kKey3;
155 case kVK_ANSI_4: return GHOST_kKey4;
156 case kVK_ANSI_5: return GHOST_kKey5;
157 case kVK_ANSI_6: return GHOST_kKey6;
158 case kVK_ANSI_7: return GHOST_kKey7;
159 case kVK_ANSI_8: return GHOST_kKey8;
160 case kVK_ANSI_9: return GHOST_kKey9;
161 case kVK_ANSI_0: return GHOST_kKey0;
163 case kVK_ANSI_Keypad0: return GHOST_kKeyNumpad0;
164 case kVK_ANSI_Keypad1: return GHOST_kKeyNumpad1;
165 case kVK_ANSI_Keypad2: return GHOST_kKeyNumpad2;
166 case kVK_ANSI_Keypad3: return GHOST_kKeyNumpad3;
167 case kVK_ANSI_Keypad4: return GHOST_kKeyNumpad4;
168 case kVK_ANSI_Keypad5: return GHOST_kKeyNumpad5;
169 case kVK_ANSI_Keypad6: return GHOST_kKeyNumpad6;
170 case kVK_ANSI_Keypad7: return GHOST_kKeyNumpad7;
171 case kVK_ANSI_Keypad8: return GHOST_kKeyNumpad8;
172 case kVK_ANSI_Keypad9: return GHOST_kKeyNumpad9;
173 case kVK_ANSI_KeypadDecimal: return GHOST_kKeyNumpadPeriod;
174 case kVK_ANSI_KeypadEnter: return GHOST_kKeyNumpadEnter;
175 case kVK_ANSI_KeypadPlus: return GHOST_kKeyNumpadPlus;
176 case kVK_ANSI_KeypadMinus: return GHOST_kKeyNumpadMinus;
177 case kVK_ANSI_KeypadMultiply: return GHOST_kKeyNumpadAsterisk;
178 case kVK_ANSI_KeypadDivide: return GHOST_kKeyNumpadSlash;
179 case kVK_ANSI_KeypadClear: return GHOST_kKeyUnknown;
181 case kVK_F1: return GHOST_kKeyF1;
182 case kVK_F2: return GHOST_kKeyF2;
183 case kVK_F3: return GHOST_kKeyF3;
184 case kVK_F4: return GHOST_kKeyF4;
185 case kVK_F5: return GHOST_kKeyF5;
186 case kVK_F6: return GHOST_kKeyF6;
187 case kVK_F7: return GHOST_kKeyF7;
188 case kVK_F8: return GHOST_kKeyF8;
189 case kVK_F9: return GHOST_kKeyF9;
190 case kVK_F10: return GHOST_kKeyF10;
191 case kVK_F11: return GHOST_kKeyF11;
192 case kVK_F12: return GHOST_kKeyF12;
193 case kVK_F13: return GHOST_kKeyF13;
194 case kVK_F14: return GHOST_kKeyF14;
195 case kVK_F15: return GHOST_kKeyF15;
196 case kVK_F16: return GHOST_kKeyF16;
197 case kVK_F17: return GHOST_kKeyF17;
198 case kVK_F18: return GHOST_kKeyF18;
199 case kVK_F19: return GHOST_kKeyF19;
200 case kVK_F20: return GHOST_kKeyF20;
202 case kVK_UpArrow: return GHOST_kKeyUpArrow;
203 case kVK_DownArrow: return GHOST_kKeyDownArrow;
204 case kVK_LeftArrow: return GHOST_kKeyLeftArrow;
205 case kVK_RightArrow: return GHOST_kKeyRightArrow;
207 case kVK_Return: return GHOST_kKeyEnter;
208 case kVK_Delete: return GHOST_kKeyBackSpace;
209 case kVK_ForwardDelete: return GHOST_kKeyDelete;
210 case kVK_Escape: return GHOST_kKeyEsc;
211 case kVK_Tab: return GHOST_kKeyTab;
212 case kVK_Space: return GHOST_kKeySpace;
214 case kVK_Home: return GHOST_kKeyHome;
215 case kVK_End: return GHOST_kKeyEnd;
216 case kVK_PageUp: return GHOST_kKeyUpPage;
217 case kVK_PageDown: return GHOST_kKeyDownPage;
219 /*case kVK_ANSI_Minus: return GHOST_kKeyMinus;
220 case kVK_ANSI_Equal: return GHOST_kKeyEqual;
221 case kVK_ANSI_Comma: return GHOST_kKeyComma;
222 case kVK_ANSI_Period: return GHOST_kKeyPeriod;
223 case kVK_ANSI_Slash: return GHOST_kKeySlash;
224 case kVK_ANSI_Semicolon: return GHOST_kKeySemicolon;
225 case kVK_ANSI_Quote: return GHOST_kKeyQuote;
226 case kVK_ANSI_Backslash: return GHOST_kKeyBackslash;
227 case kVK_ANSI_LeftBracket: return GHOST_kKeyLeftBracket;
228 case kVK_ANSI_RightBracket: return GHOST_kKeyRightBracket;
229 case kVK_ANSI_Grave: return GHOST_kKeyAccentGrave;*/
234 return GHOST_kKeyUnknown;
237 /*Then detect on character value for "remappable" keys in int'l keyboards*/
238 if ((recvChar >= 'A') && (recvChar <= 'Z')) {
239 return (GHOST_TKey) (recvChar - 'A' + GHOST_kKeyA);
240 } else if ((recvChar >= 'a') && (recvChar <= 'z')) {
241 return (GHOST_TKey) (recvChar - 'a' + GHOST_kKeyA);
244 case '-': return GHOST_kKeyMinus;
245 case '=': return GHOST_kKeyEqual;
246 case ',': return GHOST_kKeyComma;
247 case '.': return GHOST_kKeyPeriod;
248 case '/': return GHOST_kKeySlash;
249 case ';': return GHOST_kKeySemicolon;
250 case '\'': return GHOST_kKeyQuote;
251 case '\\': return GHOST_kKeyBackslash;
252 case '[': return GHOST_kKeyLeftBracket;
253 case ']': return GHOST_kKeyRightBracket;
254 case '`': return GHOST_kKeyAccentGrave;
256 return GHOST_kKeyUnknown;
259 return GHOST_kKeyUnknown;
262 /* MacOSX returns a Roman charset with kEventParamKeyMacCharCodes
263 * as defined here: http://developer.apple.com/documentation/mac/Text/Text-516.html
264 * I am not sure how international this works...
265 * For cross-platform convention, we'll use the Latin ascii set instead.
266 * As defined at: http://www.ramsch.org/martin/uni/fmi-hp/iso8859-1.html
269 static unsigned char convertRomanToLatin(unsigned char ascii)
272 if(ascii<128) return ascii;
275 case 128: return 142;
276 case 129: return 143;
277 case 130: return 128;
278 case 131: return 201;
279 case 132: return 209;
280 case 133: return 214;
281 case 134: return 220;
282 case 135: return 225;
283 case 136: return 224;
284 case 137: return 226;
285 case 138: return 228;
286 case 139: return 227;
287 case 140: return 229;
288 case 141: return 231;
289 case 142: return 233;
290 case 143: return 232;
291 case 144: return 234;
292 case 145: return 235;
293 case 146: return 237;
294 case 147: return 236;
295 case 148: return 238;
296 case 149: return 239;
297 case 150: return 241;
298 case 151: return 243;
299 case 152: return 242;
300 case 153: return 244;
301 case 154: return 246;
302 case 155: return 245;
303 case 156: return 250;
304 case 157: return 249;
305 case 158: return 251;
306 case 159: return 252;
308 case 161: return 176;
309 case 162: return 162;
310 case 163: return 163;
311 case 164: return 167;
312 case 165: return 183;
313 case 166: return 182;
314 case 167: return 223;
315 case 168: return 174;
316 case 169: return 169;
317 case 170: return 174;
318 case 171: return 180;
319 case 172: return 168;
321 case 174: return 198;
322 case 175: return 216;
324 case 177: return 177;
327 case 180: return 165;
328 case 181: return 181;
331 case 184: return 215;
334 case 187: return 170;
335 case 188: return 186;
337 case 190: return 230;
338 case 191: return 248;
339 case 192: return 191;
340 case 193: return 161;
341 case 194: return 172;
346 case 199: return 171;
347 case 200: return 187;
348 case 201: return 201;
350 case 203: return 192;
351 case 204: return 195;
352 case 205: return 213;
359 case 214: return 247;
361 case 229: return 194;
362 case 230: return 202;
363 case 231: return 193;
364 case 232: return 203;
365 case 233: return 200;
366 case 234: return 205;
367 case 235: return 206;
368 case 236: return 207;
369 case 237: return 204;
370 case 238: return 211;
371 case 239: return 212;
373 case 241: return 210;
374 case 242: return 218;
375 case 243: return 219;
376 case 244: return 217;
390 #define FIRSTFILEBUFLG 512
391 static bool g_hasFirstFile = false;
392 static char g_firstFileBuf[512];
394 //TODO:Need to investigate this. Function called too early in creator.c to have g_hasFirstFile == true
395 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) {
396 if (g_hasFirstFile) {
397 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1);
398 buf[FIRSTFILEBUFLG - 1] = '\0';
406 #pragma mark Cocoa objects
410 * ObjC object to capture applicationShouldTerminate, and send quit event
412 @interface CocoaAppDelegate : NSObject {
413 GHOST_SystemCocoa *systemCocoa;
415 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa;
416 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
419 @implementation CocoaAppDelegate : NSObject
420 -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
422 systemCocoa = sysCocoa;
425 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
427 //Note that Cmd+Q is already handled by keyhandler
428 //FIXME: Need an event "QuitRequest"
429 int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes may not have been saved. Do you really want to quit ?",
432 if (shouldQuit == NSAlertAlternateReturn)
433 systemCocoa->pushEvent( new GHOST_Event(systemCocoa->getMilliSeconds(), GHOST_kEventQuit, NULL) );
435 return NSTerminateCancel;
441 #pragma mark initialization/finalization
445 GHOST_SystemCocoa::GHOST_SystemCocoa()
448 m_pressedMouseButtons =0;
449 m_displayManager = new GHOST_DisplayManagerCocoa ();
450 GHOST_ASSERT(m_displayManager, "GHOST_SystemCocoa::GHOST_SystemCocoa(): m_displayManager==0\n");
451 m_displayManager->initialize();
453 //NSEvent timeStamp is given in system uptime, state start date is boot time
454 //FIXME : replace by Cocoa equivalent
456 struct timeval boottime;
460 mib[1] = KERN_BOOTTIME;
461 len = sizeof(struct timeval);
463 sysctl(mib, 2, &boottime, &len, NULL, 0);
464 m_start_time = ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
466 m_ignoreWindowSizedMessages = false;
469 GHOST_SystemCocoa::~GHOST_SystemCocoa()
474 GHOST_TSuccess GHOST_SystemCocoa::init()
477 GHOST_TSuccess success = GHOST_System::init();
479 //ProcessSerialNumber psn;
481 //FIXME: Carbon stuff to move window & menu to foreground
482 /*if (!GetCurrentProcess(&psn)) {
483 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
484 SetFrontProcess(&psn);
487 m_autoReleasePool = [[NSAutoreleasePool alloc] init];
489 [NSApplication sharedApplication];
491 if ([NSApp mainMenu] == nil) {
492 NSMenu *mainMenubar = [[NSMenu alloc] init];
493 NSMenuItem *menuItem;
495 //Create the application menu
496 NSMenu *appMenu = [[NSMenu alloc] initWithTitle:@"Blender"];
498 [appMenu addItemWithTitle:@"About Blender" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
499 [appMenu addItem:[NSMenuItem separatorItem]];
501 menuItem = [appMenu addItemWithTitle:@"Hide Blender" action:@selector(hide:) keyEquivalent:@"h"];
502 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
504 menuItem = [appMenu addItemWithTitle:@"Hide others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
505 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
507 [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
509 menuItem = [appMenu addItemWithTitle:@"Quit Blender" action:@selector(terminate:) keyEquivalent:@"q"];
510 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
512 menuItem = [[NSMenuItem alloc] init];
513 [menuItem setSubmenu:appMenu];
515 [mainMenubar addItem:menuItem];
519 //Create the window menu
520 NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
522 menuItem = [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
523 [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
525 [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
527 menuItem = [[NSMenuItem alloc] init];
528 [menuItem setSubmenu:windowMenu];
530 [mainMenubar addItem:menuItem];
533 [NSApp setMainMenu:mainMenubar];
534 [NSApp setWindowsMenu:windowMenu];
535 [windowMenu release];
537 [NSApp finishLaunching];
539 if ([NSApp delegate] == nil) {
540 CocoaAppDelegate *appDelegate = [[CocoaAppDelegate alloc] init];
541 [appDelegate setSystemCocoa:this];
542 [NSApp setDelegate:appDelegate];
547 * Initialize the cursor to the standard arrow shape (so that we can change it later on).
548 * This initializes the cursor's visibility counter to 0.
553 ::CreateStandardWindowMenu(0, &windMenu);
554 ::InsertMenu(windMenu, 0);
557 ::InstallApplicationEventHandler(sEventHandlerProc, GetEventTypeCount(kEvents), kEvents, this, &m_handler);
559 ::AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, sAEHandlerLaunch, (SInt32) this, false);
560 ::AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, sAEHandlerOpenDocs, (SInt32) this, false);
561 ::AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, sAEHandlerPrintDocs, (SInt32) this, false);
562 ::AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, sAEHandlerQuit, (SInt32) this, false);
569 GHOST_TSuccess GHOST_SystemCocoa::exit()
571 NSAutoreleasePool* pool = (NSAutoreleasePool *)m_autoReleasePool;
573 return GHOST_System::exit();
576 #pragma mark window management
578 GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const
580 //Cocoa equivalent exists in 10.6 ([[NSProcessInfo processInfo] systemUptime])
582 struct timeval boottime;
586 mib[1] = KERN_BOOTTIME;
587 len = sizeof(struct timeval);
589 sysctl(mib, 2, &boottime, &len, NULL, 0);
591 return ((boottime.tv_sec*1000)+(boottime.tv_usec/1000));
595 GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const
597 //Note that OS X supports monitor hot plug
598 // We do not support multiple monitors at the moment
599 return [[NSScreen screens] count];
603 void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
605 //TODO: Provide visible frame or total frame, check for consistency with rest of code
606 NSRect frame = [[NSScreen mainScreen] visibleFrame];
608 width = frame.size.width;
609 height = frame.size.height;
613 GHOST_IWindow* GHOST_SystemCocoa::createWindow(
614 const STR_String& title,
619 GHOST_TWindowState state,
620 GHOST_TDrawingContextType type,
622 const GHOST_TEmbedderWindowID parentWindow
625 GHOST_IWindow* window = 0;
627 window = new GHOST_WindowCocoa (title, left, top, width, height, state, type);
630 if (window->getValid()) {
631 // Store the pointer to the window
632 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
633 m_windowManager->addWindow(window);
634 m_windowManager->setActiveWindow(window);
635 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
638 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): window invalid\n");
644 GHOST_PRINT("GHOST_SystemCocoa::createWindow(): could not create window\n");
649 GHOST_TSuccess GHOST_SystemCocoa::beginFullScreen(const GHOST_DisplaySetting& setting, GHOST_IWindow** window, const bool stereoVisual)
651 GHOST_TSuccess success = GHOST_kFailure;
653 //TODO: update this method
654 // need yo make this Carbon all on 10.5 for fullscreen to work correctly
655 CGCaptureAllDisplays();
657 success = GHOST_System::beginFullScreen( setting, window, stereoVisual);
659 if( success != GHOST_kSuccess ) {
660 // fullscreen failed for other reasons, release
661 CGReleaseAllDisplays();
667 GHOST_TSuccess GHOST_SystemCocoa::endFullScreen(void)
669 //TODO: update this method
670 CGReleaseAllDisplays();
671 return GHOST_System::endFullScreen();
677 GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
679 NSPoint mouseLoc = [NSEvent mouseLocation];
681 // Convert the coordinates to screen coordinates
682 x = (GHOST_TInt32)mouseLoc.x;
683 y = (GHOST_TInt32)mouseLoc.y;
684 return GHOST_kSuccess;
688 GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y) const
690 float xf=(float)x, yf=(float)y;
692 CGAssociateMouseAndMouseCursorPosition(false);
693 CGWarpMouseCursorPosition(CGPointMake(xf, yf));
694 CGAssociateMouseAndMouseCursorPosition(true);
696 return GHOST_kSuccess;
700 GHOST_TSuccess GHOST_SystemCocoa::getModifierKeys(GHOST_ModifierKeys& keys) const
702 NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
703 //Direct query to modifierFlags can be used in 10.6
705 keys.set(GHOST_kModifierKeyCommand, (modifiers & NSCommandKeyMask) ? true : false);
706 keys.set(GHOST_kModifierKeyLeftAlt, (modifiers & NSAlternateKeyMask) ? true : false);
707 keys.set(GHOST_kModifierKeyLeftShift, (modifiers & NSShiftKeyMask) ? true : false);
708 keys.set(GHOST_kModifierKeyLeftControl, (modifiers & NSControlKeyMask) ? true : false);
710 return GHOST_kSuccess;
713 GHOST_TSuccess GHOST_SystemCocoa::getButtons(GHOST_Buttons& buttons) const
716 buttons.set(GHOST_kButtonMaskLeft, m_pressedMouseButtons & GHOST_kButtonMaskLeft);
717 buttons.set(GHOST_kButtonMaskRight, m_pressedMouseButtons & GHOST_kButtonMaskRight);
718 buttons.set(GHOST_kButtonMaskMiddle, m_pressedMouseButtons & GHOST_kButtonMaskMiddle);
719 buttons.set(GHOST_kButtonMaskButton4, m_pressedMouseButtons & GHOST_kButtonMaskButton4);
720 buttons.set(GHOST_kButtonMaskButton5, m_pressedMouseButtons & GHOST_kButtonMaskButton5);
721 return GHOST_kSuccess;
726 #pragma mark Event handlers
729 * The event queue polling function
731 bool GHOST_SystemCocoa::processEvents(bool waitForEvent)
733 NSAutoreleasePool* pool = (NSAutoreleasePool*)m_autoReleasePool;
734 //bool anyProcessed = false;
737 //Reinit the AutoReleasePool
738 //This is not done the typical Cocoa way (init at beginning of loop, and drain at the end)
739 //to allow pool to work with other function calls outside this loop (but in same thread)
741 m_autoReleasePool = [[NSAutoreleasePool alloc] init];
743 // SetMouseCoalescingEnabled(false, NULL);
744 //TODO : implement timer ??
747 GHOST_TimerManager* timerMgr = getTimerManager();
750 GHOST_TUns64 next = timerMgr->nextFireTime();
753 if (next == GHOST_kFireTimeNever) {
754 timeOut = kEventDurationForever;
756 timeOut = (double)(next - getMilliSeconds())/1000.0;
761 ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
764 if (timerMgr->fireTimers(getMilliSeconds())) {
768 //TODO: check fullscreen redrawing issues
769 if (getFullScreen()) {
770 // Check if the full-screen window is dirty
771 GHOST_IWindow* window = m_windowManager->getFullScreenWindow();
772 if (((GHOST_WindowCarbon*)window)->getFullScreenDirty()) {
773 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
779 event = [NSApp nextEventMatchingMask:NSAnyEventMask
780 untilDate:[NSDate distantPast]
781 inMode:NSDefaultRunLoopMode
786 //anyProcessed = true;
788 switch ([event type]) {
792 handleKeyEvent(event);
794 /* Support system-wide keyboard shortcuts, like Exposé, ...) =>included in always NSApp sendEvent */
795 /* if (([event modifierFlags] & NSCommandKeyMask) || [event type] == NSFlagsChanged) {
796 [NSApp sendEvent:event];
800 case NSLeftMouseDown:
802 case NSRightMouseDown:
805 case NSLeftMouseDragged:
806 case NSRightMouseDragged:
808 case NSOtherMouseDown:
810 case NSOtherMouseDragged:
811 handleMouseEvent(event);
815 case NSTabletProximity:
816 handleTabletEvent(event);
819 /* Trackpad features, will need OS X 10.6 for implementation
820 case NSEventTypeGesture:
821 case NSEventTypeMagnify:
822 case NSEventTypeSwipe:
823 case NSEventTypeRotate:
824 case NSEventTypeBeginGesture:
825 case NSEventTypeEndGesture:
831 NSAppKitDefined = 13,
832 NSSystemDefined = 14,
833 NSApplicationDefined = 15,
835 NSCursorUpdate = 17,*/
840 //Resend event to NSApp to ensure Mac wide events are handled
841 [NSApp sendEvent:event];
842 } while (event!= nil);
843 //} while (waitForEvent && !anyProcessed); Needed only for timer implementation
846 return true; //anyProcessed;
849 //TODO: To be called from NSWindow delegate
850 int GHOST_SystemCocoa::handleWindowEvent(void *eventPtr)
852 /*WindowRef windowRef;
853 GHOST_WindowCocoa *window;
855 // Check if the event was send to a GHOST window
856 ::GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &windowRef);
857 window = (GHOST_WindowCarbon*) ::GetWRefCon(windowRef);
858 if (!validWindow(window)) {
862 //if (!getFullScreen()) {
866 case kEventWindowClose:
867 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
869 case kEventWindowActivated:
870 m_windowManager->setActiveWindow(window);
871 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
872 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
874 case kEventWindowDeactivated:
875 m_windowManager->setWindowInactive(window);
876 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
878 case kEventWindowUpdate:
879 //if (getFullScreen()) GHOST_PRINT("GHOST_SystemCarbon::handleWindowEvent(): full-screen update event\n");
880 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
882 case kEventWindowBoundsChanged:
883 if (!m_ignoreWindowSizedMessages)
885 window->updateDrawingContext();
886 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
890 err = eventNotHandledErr;
895 //window = (GHOST_WindowCarbon*) m_windowManager->getFullScreenWindow();
896 //GHOST_PRINT("GHOST_SystemCarbon::handleWindowEvent(): full-screen window event, " << window << "\n");
897 //::RemoveEventFromQueue(::GetMainEventQueue(), event);
900 return GHOST_kSuccess;
903 int GHOST_SystemCocoa::handleTabletEvent(void *eventPtr)
905 NSEvent *event = (NSEvent *)eventPtr;
906 GHOST_IWindow* window = m_windowManager->getActiveWindow();
907 GHOST_TabletData& ct=((GHOST_WindowCocoa*)window)->GetCocoaTabletData();
908 NSUInteger tabletEvent;
914 //Handle tablet events combined with mouse events
915 switch ([event subtype]) {
916 case NX_SUBTYPE_TABLET_POINT:
917 tabletEvent = NSTabletPoint;
919 case NX_SUBTYPE_TABLET_PROXIMITY:
920 tabletEvent = NSTabletProximity;
924 tabletEvent = [event type];
928 switch (tabletEvent) {
930 ct.Pressure = [event tangentialPressure];
931 ct.Xtilt = [event tilt].x;
932 ct.Ytilt = [event tilt].y;
935 case NSTabletProximity:
936 if ([event isEnteringProximity])
938 //pointer is entering tablet area proximity
939 switch ([event pointingDeviceType]) {
940 case NSPenPointingDevice:
941 ct.Active = GHOST_kTabletModeStylus;
943 case NSEraserPointingDevice:
944 ct.Active = GHOST_kTabletModeEraser;
946 case NSCursorPointingDevice:
947 case NSUnknownPointingDevice:
949 ct.Active = GHOST_kTabletModeNone;
953 // pointer is leaving - return to mouse
954 ct.Active = GHOST_kTabletModeNone;
959 GHOST_ASSERT(FALSE,"GHOST_SystemCocoa::handleTabletEvent : unknown event received");
960 return GHOST_kFailure;
963 return GHOST_kSuccess;
967 int GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
969 NSEvent *event = (NSEvent *)eventPtr;
970 GHOST_IWindow* window = m_windowManager->getActiveWindow();
972 switch ([event type])
974 case NSLeftMouseDown:
975 case NSRightMouseDown:
976 case NSOtherMouseDown:
977 if (m_windowManager->getActiveWindow()) {
978 pushEvent(new GHOST_EventButton([event timestamp], GHOST_kEventButtonDown, window, convertButton([event buttonNumber])));
980 handleTabletEvent(eventPtr);
986 if (m_windowManager->getActiveWindow()) {
987 pushEvent(new GHOST_EventButton([event timestamp], GHOST_kEventButtonUp, window, convertButton([event buttonNumber])));
989 handleTabletEvent(eventPtr);
992 case NSLeftMouseDragged:
993 case NSRightMouseDragged:
994 case NSOtherMouseDragged:
995 handleTabletEvent(eventPtr);
998 NSPoint mousePos = [event locationInWindow];
999 pushEvent(new GHOST_EventCursor([event timestamp], GHOST_kEventCursorMove, window, mousePos.x, mousePos.y));
1006 delta = [event deltaY] > 0 ? 1 : -1;
1007 pushEvent(new GHOST_EventWheel(getMilliSeconds(), window, delta));
1013 return GHOST_kFailure;
1017 return GHOST_kSuccess;
1021 int GHOST_SystemCocoa::handleKeyEvent(void *eventPtr)
1023 NSEvent *event = (NSEvent *)eventPtr;
1024 GHOST_IWindow* window = m_windowManager->getActiveWindow();
1025 NSUInteger modifiers;
1027 unsigned char ascii;
1029 /* Can happen, very rarely - seems to only be when command-H makes
1030 * the window go away and we still get an HKey up.
1033 return GHOST_kFailure;
1036 switch ([event type]) {
1039 keyCode = convertKey([event keyCode],
1040 [[event charactersIgnoringModifiers] characterAtIndex:0]);
1041 ascii= convertRomanToLatin((char)[[event characters] characterAtIndex:0]);
1043 if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask))
1044 break; //Cmd-Q is directly handled by Cocoa
1046 if ([event type] == NSKeyDown) {
1047 pushEvent( new GHOST_EventKey([event timestamp], GHOST_kEventKeyDown, window, keyCode, ascii) );
1048 //printf("\nKey pressed keyCode=%u ascii=%i %c",keyCode,ascii,ascii);
1050 pushEvent( new GHOST_EventKey([event timestamp], GHOST_kEventKeyUp, window, keyCode, ascii) );
1054 case NSFlagsChanged:
1055 modifiers = [event modifierFlags];
1056 if ((modifiers & NSShiftKeyMask) != (m_modifierMask & NSShiftKeyMask)) {
1057 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSShiftKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
1059 if ((modifiers & NSControlKeyMask) != (m_modifierMask & NSControlKeyMask)) {
1060 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSControlKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
1062 if ((modifiers & NSAlternateKeyMask) != (m_modifierMask & NSAlternateKeyMask)) {
1063 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSAlternateKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
1065 if ((modifiers & NSCommandKeyMask) != (m_modifierMask & NSCommandKeyMask)) {
1066 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & NSCommandKeyMask)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyCommand) );
1069 m_modifierMask = modifiers;
1073 return GHOST_kFailure;
1077 return GHOST_kSuccess;
1081 /* System wide mouse clicks are handled directly through systematic event forwarding to Cocoa
1082 bool GHOST_SystemCarbon::handleMouseDown(void *eventPtr)
1084 NSEvent *event = (NSEvent *)eventPtr;
1088 bool handled = true;
1089 GHOST_WindowCarbon* ghostWindow;
1090 Point mousePos = {0 , 0};
1092 ::GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &mousePos);
1094 part = ::FindWindow(mousePos, &window);
1095 ghostWindow = (GHOST_WindowCarbon*) ::GetWRefCon(window);
1099 handleMenuCommand(::MenuSelect(mousePos));
1104 // * The DragWindow() routine creates a lot of kEventWindowBoundsChanged
1105 // * events. By setting m_ignoreWindowSizedMessages these are suppressed.
1106 // * @see GHOST_SystemCarbon::handleWindowEvent(EventRef event)
1108 // even worse: scale window also generates a load of events, and nothing
1109 // is handled (read: client's event proc called) until you release mouse (ton)
1111 GHOST_ASSERT(validWindow(ghostWindow), "GHOST_SystemCarbon::handleMouseDown: invalid window");
1112 m_ignoreWindowSizedMessages = true;
1113 ::DragWindow(window, mousePos, &GetQDGlobalsScreenBits(&screenBits)->bounds);
1114 m_ignoreWindowSizedMessages = false;
1116 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, ghostWindow) );
1121 if (window != ::FrontWindow()) {
1122 ::SelectWindow(window);
1124 // * We add a mouse down event on the newly actived window
1126 //GHOST_PRINT("GHOST_SystemCarbon::handleMouseDown(): adding mouse down event, " << ghostWindow << "\n");
1127 EventMouseButton button;
1128 ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
1129 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonDown, ghostWindow, convertButton(button)));
1136 GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0");
1137 if (::TrackGoAway(window, mousePos))
1139 // todo: add option-close, because itÿs in the HIG
1140 // if (event.modifiers & optionKey) {
1141 // Close the clean documents, others will be confirmed one by one.
1144 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, ghostWindow));
1150 GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0");
1151 ::ResizeWindow(window, mousePos, NULL, NULL);
1156 GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0");
1157 if (::TrackBox(window, mousePos, part)) {
1160 macState = ghostWindow->getMac_windowState();
1162 ::ZoomWindow(window, part, true);
1164 if (macState == 2) { // always ok
1165 ::ZoomWindow(window, part, true);
1166 ghostWindow->setMac_windowState(1);
1167 } else { // need to force size again
1168 // GHOST_TUns32 scr_x,scr_y; //unused
1169 Rect outAvailableRect;
1171 ghostWindow->setMac_windowState(2);
1172 ::GetAvailableWindowPositioningBounds ( GetMainDevice(), &outAvailableRect);
1174 //this->getMainDisplayDimensions(scr_x,scr_y);
1175 ::SizeWindow (window, outAvailableRect.right-outAvailableRect.left,outAvailableRect.bottom-outAvailableRect.top-1,false);
1176 ::MoveWindow (window, outAvailableRect.left, outAvailableRect.top,true);
1191 bool GHOST_SystemCarbon::handleMenuCommand(GHOST_TInt32 menuResult)
1199 menuID = HiWord(menuResult);
1200 menuItem = LoWord(menuResult);
1202 err = ::GetMenuItemCommandID(::GetMenuHandle(menuID), menuItem, &command);
1206 if (err || command == 0) {
1219 #pragma mark Clipboard get/set
1221 GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const
1223 GHOST_TUns8 * temp_buff;
1224 size_t pastedTextSize;
1226 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1228 if (pasteBoard = nil) {
1232 NSArray *supportedTypes =
1233 [NSArray arrayWithObjects: @"public.utf8-plain-text", nil];
1235 NSString *bestType = [[NSPasteboard generalPasteboard]
1236 availableTypeFromArray:supportedTypes];
1238 if (bestType == nil) { return NULL; }
1240 NSString * textPasted = [pasteBoard stringForType:@"public.utf8-plain-text"];
1242 pastedTextSize = [textPasted lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1244 temp_buff = (GHOST_TUns8*) malloc(pastedTextSize+1);
1246 if (temp_buff == NULL) return NULL;
1248 strncpy((char*)temp_buff, [textPasted UTF8String], pastedTextSize);
1250 temp_buff[pastedTextSize] = '\0';
1259 void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1261 NSString *textToCopy;
1263 if(selection) {return;} // for copying the selection, used on X11
1266 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
1268 if (pasteBoard = nil) {
1272 NSArray *supportedTypes = [NSArray arrayWithObjects: @"public.utf8-plain-text",nil];
1274 [pasteBoard declareTypes:supportedTypes owner:nil];
1276 textToCopy = [NSString stringWithUTF8String:buffer];
1278 [pasteBoard setString:textToCopy forType:@"public.utf8-plain-text"];
1284 #pragma mark Carbon stuff to remove
1289 OSErr GHOST_SystemCarbon::sAEHandlerLaunch(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
1291 //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
1296 OSErr GHOST_SystemCarbon::sAEHandlerOpenDocs(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
1298 //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
1303 err = AEGetParamDesc(event, keyDirectObject, typeAEList, &docs);
1304 if (err != noErr) return err;
1306 err = AECountItems(&docs, &ndocs);
1310 for (i=0; i<ndocs; i++) {
1316 err = AEGetNthPtr(&docs, i+1, typeFSS, &kwd, &actType, &fss, sizeof(fss), &actSize);
1323 if (FSpMakeFSRef(&fss, &fsref)!=noErr)
1325 if (FSRefMakePath(&fsref, (UInt8*) g_firstFileBuf, sizeof(g_firstFileBuf))!=noErr)
1328 g_hasFirstFile = true;
1333 AEDisposeDesc(&docs);
1338 OSErr GHOST_SystemCarbon::sAEHandlerPrintDocs(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
1340 //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
1345 OSErr GHOST_SystemCarbon::sAEHandlerQuit(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
1347 GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
1349 sys->pushEvent( new GHOST_Event(sys->getMilliSeconds(), GHOST_kEventQuit, NULL) );