Blender V2.61 - r43446
|
00001 /* 00002 * ***** BEGIN GPL LICENSE BLOCK ***** 00003 * 00004 * This program is free software; you can redistribute it and/or 00005 * modify it under the terms of the GNU General Public License 00006 * as published by the Free Software Foundation; either version 2 00007 * of the License, or (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License 00015 * along with this program; if not, write to the Free Software Foundation, 00016 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 * 00018 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. 00019 * All rights reserved. 00020 * 00021 * The Original Code is: all of this file. 00022 * 00023 * Contributor(s): none yet. 00024 * 00025 * ***** END GPL LICENSE BLOCK ***** 00026 */ 00027 00040 #include <Carbon/Carbon.h> 00041 #include <ApplicationServices/ApplicationServices.h> 00042 #include "GHOST_SystemCarbon.h" 00043 00044 #include "GHOST_DisplayManagerCarbon.h" 00045 #include "GHOST_EventKey.h" 00046 #include "GHOST_EventButton.h" 00047 #include "GHOST_EventCursor.h" 00048 #include "GHOST_EventWheel.h" 00049 #ifdef WITH_INPUT_NDOF 00050 #include "GHOST_EventNDOF.h" 00051 #endif 00052 00053 #include "GHOST_TimerManager.h" 00054 #include "GHOST_TimerTask.h" 00055 #include "GHOST_WindowManager.h" 00056 #include "GHOST_WindowCarbon.h" 00057 #include "GHOST_NDOFManager.h" 00058 #include "AssertMacros.h" 00059 00060 #define GHOST_KEY_SWITCH(mac, ghost) { case (mac): ghostKey = (ghost); break; } 00061 00062 /* blender class and types events */ 00063 enum { 00064 kEventClassBlender = 'blnd' 00065 }; 00066 00067 enum { 00068 kEventBlenderNdofAxis = 1, 00069 kEventBlenderNdofButtons = 2 00070 }; 00071 00072 const EventTypeSpec kEvents[] = 00073 { 00074 { kEventClassAppleEvent, kEventAppleEvent }, 00075 /* 00076 { kEventClassApplication, kEventAppActivated }, 00077 { kEventClassApplication, kEventAppDeactivated }, 00078 */ 00079 { kEventClassKeyboard, kEventRawKeyDown }, 00080 { kEventClassKeyboard, kEventRawKeyRepeat }, 00081 { kEventClassKeyboard, kEventRawKeyUp }, 00082 { kEventClassKeyboard, kEventRawKeyModifiersChanged }, 00083 00084 { kEventClassMouse, kEventMouseDown }, 00085 { kEventClassMouse, kEventMouseUp }, 00086 { kEventClassMouse, kEventMouseMoved }, 00087 { kEventClassMouse, kEventMouseDragged }, 00088 { kEventClassMouse, kEventMouseWheelMoved }, 00089 00090 { kEventClassWindow, kEventWindowClickZoomRgn } , /* for new zoom behaviour */ 00091 { kEventClassWindow, kEventWindowZoom }, /* for new zoom behaviour */ 00092 { kEventClassWindow, kEventWindowExpand } , /* for new zoom behaviour */ 00093 { kEventClassWindow, kEventWindowExpandAll }, /* for new zoom behaviour */ 00094 00095 { kEventClassWindow, kEventWindowClose }, 00096 { kEventClassWindow, kEventWindowActivated }, 00097 { kEventClassWindow, kEventWindowDeactivated }, 00098 { kEventClassWindow, kEventWindowUpdate }, 00099 { kEventClassWindow, kEventWindowBoundsChanged }, 00100 00101 { kEventClassBlender, kEventBlenderNdofAxis }, 00102 { kEventClassBlender, kEventBlenderNdofButtons } 00103 00104 00105 00106 }; 00107 00108 00109 00110 static GHOST_TButtonMask convertButton(EventMouseButton button) 00111 { 00112 switch (button) { 00113 case kEventMouseButtonPrimary: 00114 return GHOST_kButtonMaskLeft; 00115 case kEventMouseButtonSecondary: 00116 return GHOST_kButtonMaskRight; 00117 case kEventMouseButtonTertiary: 00118 default: 00119 return GHOST_kButtonMaskMiddle; 00120 } 00121 } 00122 00123 static GHOST_TKey convertKey(int rawCode) 00124 { 00125 /* This bit of magic converts the rawCode into a virtual 00126 * Mac key based on the current keyboard mapping, but 00127 * without regard to the modifiers (so we don't get 'a' 00128 * and 'A' for example. 00129 */ 00130 static UInt32 dummy= 0; 00131 Handle transData = (Handle) GetScriptManagerVariable(smKCHRCache); 00132 unsigned char vk = KeyTranslate(transData, rawCode, &dummy); 00133 /* Map numpad based on rawcodes first, otherwise they 00134 * look like non-numpad events. 00135 * Added too: mapping the number keys, for french keyboards etc (ton) 00136 */ 00137 // printf("GHOST: vk: %d %c raw: %d\n", vk, vk, rawCode); 00138 00139 switch (rawCode) { 00140 case 18: return GHOST_kKey1; 00141 case 19: return GHOST_kKey2; 00142 case 20: return GHOST_kKey3; 00143 case 21: return GHOST_kKey4; 00144 case 23: return GHOST_kKey5; 00145 case 22: return GHOST_kKey6; 00146 case 26: return GHOST_kKey7; 00147 case 28: return GHOST_kKey8; 00148 case 25: return GHOST_kKey9; 00149 case 29: return GHOST_kKey0; 00150 00151 case 82: return GHOST_kKeyNumpad0; 00152 case 83: return GHOST_kKeyNumpad1; 00153 case 84: return GHOST_kKeyNumpad2; 00154 case 85: return GHOST_kKeyNumpad3; 00155 case 86: return GHOST_kKeyNumpad4; 00156 case 87: return GHOST_kKeyNumpad5; 00157 case 88: return GHOST_kKeyNumpad6; 00158 case 89: return GHOST_kKeyNumpad7; 00159 case 91: return GHOST_kKeyNumpad8; 00160 case 92: return GHOST_kKeyNumpad9; 00161 case 65: return GHOST_kKeyNumpadPeriod; 00162 case 76: return GHOST_kKeyNumpadEnter; 00163 case 69: return GHOST_kKeyNumpadPlus; 00164 case 78: return GHOST_kKeyNumpadMinus; 00165 case 67: return GHOST_kKeyNumpadAsterisk; 00166 case 75: return GHOST_kKeyNumpadSlash; 00167 } 00168 00169 if ((vk >= 'a') && (vk <= 'z')) { 00170 return (GHOST_TKey) (vk - 'a' + GHOST_kKeyA); 00171 } else if ((vk >= '0') && (vk <= '9')) { 00172 return (GHOST_TKey) (vk - '0' + GHOST_kKey0); 00173 } else if (vk==16) { 00174 switch (rawCode) { 00175 case 122: return GHOST_kKeyF1; 00176 case 120: return GHOST_kKeyF2; 00177 case 99: return GHOST_kKeyF3; 00178 case 118: return GHOST_kKeyF4; 00179 case 96: return GHOST_kKeyF5; 00180 case 97: return GHOST_kKeyF6; 00181 case 98: return GHOST_kKeyF7; 00182 case 100: return GHOST_kKeyF8; 00183 case 101: return GHOST_kKeyF9; 00184 case 109: return GHOST_kKeyF10; 00185 case 103: return GHOST_kKeyF11; 00186 case 111: return GHOST_kKeyF12; // Never get, is used for ejecting the CD! 00187 } 00188 } else { 00189 switch (vk) { 00190 case kUpArrowCharCode: return GHOST_kKeyUpArrow; 00191 case kDownArrowCharCode: return GHOST_kKeyDownArrow; 00192 case kLeftArrowCharCode: return GHOST_kKeyLeftArrow; 00193 case kRightArrowCharCode: return GHOST_kKeyRightArrow; 00194 00195 case kReturnCharCode: return GHOST_kKeyEnter; 00196 case kBackspaceCharCode: return GHOST_kKeyBackSpace; 00197 case kDeleteCharCode: return GHOST_kKeyDelete; 00198 case kEscapeCharCode: return GHOST_kKeyEsc; 00199 case kTabCharCode: return GHOST_kKeyTab; 00200 case kSpaceCharCode: return GHOST_kKeySpace; 00201 00202 case kHomeCharCode: return GHOST_kKeyHome; 00203 case kEndCharCode: return GHOST_kKeyEnd; 00204 case kPageUpCharCode: return GHOST_kKeyUpPage; 00205 case kPageDownCharCode: return GHOST_kKeyDownPage; 00206 00207 case '-': return GHOST_kKeyMinus; 00208 case '=': return GHOST_kKeyEqual; 00209 case ',': return GHOST_kKeyComma; 00210 case '.': return GHOST_kKeyPeriod; 00211 case '/': return GHOST_kKeySlash; 00212 case ';': return GHOST_kKeySemicolon; 00213 case '\'': return GHOST_kKeyQuote; 00214 case '\\': return GHOST_kKeyBackslash; 00215 case '[': return GHOST_kKeyLeftBracket; 00216 case ']': return GHOST_kKeyRightBracket; 00217 case '`': return GHOST_kKeyAccentGrave; 00218 } 00219 } 00220 00221 // printf("GHOST: unknown key: %d %d\n", vk, rawCode); 00222 00223 return GHOST_kKeyUnknown; 00224 } 00225 00226 /* MacOSX returns a Roman charset with kEventParamKeyMacCharCodes 00227 * as defined here: http://developer.apple.com/documentation/mac/Text/Text-516.html 00228 * I am not sure how international this works... 00229 * For cross-platform convention, we'll use the Latin ascii set instead. 00230 * As defined at: http://www.ramsch.org/martin/uni/fmi-hp/iso8859-1.html 00231 * 00232 */ 00233 static unsigned char convertRomanToLatin(unsigned char ascii) 00234 { 00235 00236 if(ascii<128) return ascii; 00237 00238 switch(ascii) { 00239 case 128: return 142; 00240 case 129: return 143; 00241 case 130: return 128; 00242 case 131: return 201; 00243 case 132: return 209; 00244 case 133: return 214; 00245 case 134: return 220; 00246 case 135: return 225; 00247 case 136: return 224; 00248 case 137: return 226; 00249 case 138: return 228; 00250 case 139: return 227; 00251 case 140: return 229; 00252 case 141: return 231; 00253 case 142: return 233; 00254 case 143: return 232; 00255 case 144: return 234; 00256 case 145: return 235; 00257 case 146: return 237; 00258 case 147: return 236; 00259 case 148: return 238; 00260 case 149: return 239; 00261 case 150: return 241; 00262 case 151: return 243; 00263 case 152: return 242; 00264 case 153: return 244; 00265 case 154: return 246; 00266 case 155: return 245; 00267 case 156: return 250; 00268 case 157: return 249; 00269 case 158: return 251; 00270 case 159: return 252; 00271 case 160: return 0; 00272 case 161: return 176; 00273 case 162: return 162; 00274 case 163: return 163; 00275 case 164: return 167; 00276 case 165: return 183; 00277 case 166: return 182; 00278 case 167: return 223; 00279 case 168: return 174; 00280 case 169: return 169; 00281 case 170: return 174; 00282 case 171: return 180; 00283 case 172: return 168; 00284 case 173: return 0; 00285 case 174: return 198; 00286 case 175: return 216; 00287 case 176: return 0; 00288 case 177: return 177; 00289 case 178: return 0; 00290 case 179: return 0; 00291 case 180: return 165; 00292 case 181: return 181; 00293 case 182: return 0; 00294 case 183: return 0; 00295 case 184: return 215; 00296 case 185: return 0; 00297 case 186: return 0; 00298 case 187: return 170; 00299 case 188: return 186; 00300 case 189: return 0; 00301 case 190: return 230; 00302 case 191: return 248; 00303 case 192: return 191; 00304 case 193: return 161; 00305 case 194: return 172; 00306 case 195: return 0; 00307 case 196: return 0; 00308 case 197: return 0; 00309 case 198: return 0; 00310 case 199: return 171; 00311 case 200: return 187; 00312 case 201: return 201; 00313 case 202: return 0; 00314 case 203: return 192; 00315 case 204: return 195; 00316 case 205: return 213; 00317 case 206: return 0; 00318 case 207: return 0; 00319 case 208: return 0; 00320 case 209: return 0; 00321 case 210: return 0; 00322 00323 case 214: return 247; 00324 00325 case 229: return 194; 00326 case 230: return 202; 00327 case 231: return 193; 00328 case 232: return 203; 00329 case 233: return 200; 00330 case 234: return 205; 00331 case 235: return 206; 00332 case 236: return 207; 00333 case 237: return 204; 00334 case 238: return 211; 00335 case 239: return 212; 00336 case 240: return 0; 00337 case 241: return 210; 00338 case 242: return 218; 00339 case 243: return 219; 00340 case 244: return 217; 00341 case 245: return 0; 00342 case 246: return 0; 00343 case 247: return 0; 00344 case 248: return 0; 00345 case 249: return 0; 00346 case 250: return 0; 00347 00348 00349 default: return 0; 00350 } 00351 00352 } 00353 00354 00355 /***/ 00356 00357 GHOST_SystemCarbon::GHOST_SystemCarbon() : 00358 m_modifierMask(0) 00359 { 00360 m_displayManager = new GHOST_DisplayManagerCarbon (); 00361 GHOST_ASSERT(m_displayManager, "GHOST_SystemCarbon::GHOST_SystemCarbon(): m_displayManager==0\n"); 00362 m_displayManager->initialize(); 00363 00364 UnsignedWide micros; 00365 ::Microseconds(µs); 00366 m_start_time = UnsignedWideToUInt64(micros)/1000; 00367 m_ignoreWindowSizedMessages = false; 00368 } 00369 00370 GHOST_SystemCarbon::~GHOST_SystemCarbon() 00371 { 00372 } 00373 00374 00375 GHOST_TUns64 GHOST_SystemCarbon::getMilliSeconds() const 00376 { 00377 UnsignedWide micros; 00378 ::Microseconds(µs); 00379 UInt64 millis; 00380 millis = UnsignedWideToUInt64(micros); 00381 return (millis / 1000) - m_start_time; 00382 } 00383 00384 00385 GHOST_TUns8 GHOST_SystemCarbon::getNumDisplays() const 00386 { 00387 // We do not support multiple monitors at the moment 00388 return 1; 00389 } 00390 00391 00392 void GHOST_SystemCarbon::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const 00393 { 00394 BitMap screenBits; 00395 Rect bnds = GetQDGlobalsScreenBits(&screenBits)->bounds; 00396 width = bnds.right - bnds.left; 00397 height = bnds.bottom - bnds.top; 00398 } 00399 00400 00401 GHOST_IWindow* GHOST_SystemCarbon::createWindow( 00402 const STR_String& title, 00403 GHOST_TInt32 left, 00404 GHOST_TInt32 top, 00405 GHOST_TUns32 width, 00406 GHOST_TUns32 height, 00407 GHOST_TWindowState state, 00408 GHOST_TDrawingContextType type, 00409 bool stereoVisual, 00410 const GHOST_TUns16 numOfAASamples, 00411 const GHOST_TEmbedderWindowID parentWindow 00412 ) 00413 { 00414 GHOST_IWindow* window = 0; 00415 00416 window = new GHOST_WindowCarbon (title, left, top, width, height, state, type); 00417 00418 if (window) { 00419 if (window->getValid()) { 00420 // Store the pointer to the window 00421 GHOST_ASSERT(m_windowManager, "m_windowManager not initialized"); 00422 m_windowManager->addWindow(window); 00423 m_windowManager->setActiveWindow(window); 00424 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window)); 00425 } 00426 else { 00427 GHOST_PRINT("GHOST_SystemCarbon::createWindow(): window invalid\n"); 00428 delete window; 00429 window = 0; 00430 } 00431 } 00432 else { 00433 GHOST_PRINT("GHOST_SystemCarbon::createWindow(): could not create window\n"); 00434 } 00435 return window; 00436 } 00437 00438 GHOST_TSuccess GHOST_SystemCarbon::beginFullScreen(const GHOST_DisplaySetting& setting, GHOST_IWindow** window, const bool stereoVisual) 00439 { 00440 GHOST_TSuccess success = GHOST_kFailure; 00441 00442 // need yo make this Carbon all on 10.5 for fullscreen to work correctly 00443 CGCaptureAllDisplays(); 00444 00445 success = GHOST_System::beginFullScreen( setting, window, stereoVisual); 00446 00447 if( success != GHOST_kSuccess ) { 00448 // fullscreen failed for other reasons, release 00449 CGReleaseAllDisplays(); 00450 } 00451 00452 return success; 00453 } 00454 00455 GHOST_TSuccess GHOST_SystemCarbon::endFullScreen(void) 00456 { 00457 CGReleaseAllDisplays(); 00458 return GHOST_System::endFullScreen(); 00459 } 00460 00461 /* this is an old style low level event queue. 00462 As we want to handle our own timers, this is ok. 00463 the full screen hack should be removed */ 00464 bool GHOST_SystemCarbon::processEvents(bool waitForEvent) 00465 { 00466 bool anyProcessed = false; 00467 EventRef event; 00468 00469 // SetMouseCoalescingEnabled(false, NULL); 00470 00471 do { 00472 GHOST_TimerManager* timerMgr = getTimerManager(); 00473 00474 if (waitForEvent) { 00475 GHOST_TUns64 next = timerMgr->nextFireTime(); 00476 double timeOut; 00477 00478 if (next == GHOST_kFireTimeNever) { 00479 timeOut = kEventDurationForever; 00480 } else { 00481 timeOut = (double)(next - getMilliSeconds())/1000.0; 00482 if (timeOut < 0.0) 00483 timeOut = 0.0; 00484 } 00485 00486 ::ReceiveNextEvent(0, NULL, timeOut, false, &event); 00487 } 00488 00489 if (timerMgr->fireTimers(getMilliSeconds())) { 00490 anyProcessed = true; 00491 } 00492 00493 if (getFullScreen()) { 00494 // Check if the full-screen window is dirty 00495 GHOST_IWindow* window = m_windowManager->getFullScreenWindow(); 00496 if (((GHOST_WindowCarbon*)window)->getFullScreenDirty()) { 00497 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) ); 00498 anyProcessed = true; 00499 } 00500 } 00501 00502 /* end loop when no more events available */ 00503 while (::ReceiveNextEvent(0, NULL, 0, true, &event)==noErr) { 00504 OSStatus status= ::SendEventToEventTarget(event, ::GetEventDispatcherTarget()); 00505 if (status==noErr) { 00506 anyProcessed = true; 00507 } else { 00508 UInt32 i= ::GetEventClass(event); 00509 00510 /* Ignore 'cgs ' class, no documentation on what they 00511 * are, but we get a lot of them 00512 */ 00513 if (i!='cgs ') { 00514 if (i!='tblt') { // tablet event. we use the one packaged in the mouse event 00515 ; //printf("Missed - Class: '%.4s', Kind: %d\n", &i, ::GetEventKind(event)); 00516 } 00517 } 00518 } 00519 ::ReleaseEvent(event); 00520 } 00521 } while (waitForEvent && !anyProcessed); 00522 00523 return anyProcessed; 00524 } 00525 00526 00527 GHOST_TSuccess GHOST_SystemCarbon::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const 00528 { 00529 Point mouseLoc; 00530 // Get the position of the mouse in the active port 00531 ::GetGlobalMouse(&mouseLoc); 00532 // Convert the coordinates to screen coordinates 00533 x = (GHOST_TInt32)mouseLoc.h; 00534 y = (GHOST_TInt32)mouseLoc.v; 00535 return GHOST_kSuccess; 00536 } 00537 00538 00539 GHOST_TSuccess GHOST_SystemCarbon::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y) 00540 { 00541 float xf=(float)x, yf=(float)y; 00542 00543 CGAssociateMouseAndMouseCursorPosition(false); 00544 CGSetLocalEventsSuppressionInterval(0); 00545 CGWarpMouseCursorPosition(CGPointMake(xf, yf)); 00546 CGAssociateMouseAndMouseCursorPosition(true); 00547 00548 //this doesn't work properly, see game engine mouse-look scripts 00549 // CGWarpMouseCursorPosition(CGPointMake(xf, yf)); 00550 // this call below sends event, but empties other events (like shift) 00551 // CGPostMouseEvent(CGPointMake(xf, yf), TRUE, 1, FALSE, 0); 00552 00553 return GHOST_kSuccess; 00554 } 00555 00556 00557 GHOST_TSuccess GHOST_SystemCarbon::getModifierKeys(GHOST_ModifierKeys& keys) const 00558 { 00559 UInt32 modifiers = ::GetCurrentKeyModifiers(); 00560 00561 keys.set(GHOST_kModifierKeyOS, (modifiers & cmdKey) ? true : false); 00562 keys.set(GHOST_kModifierKeyLeftAlt, (modifiers & optionKey) ? true : false); 00563 keys.set(GHOST_kModifierKeyLeftShift, (modifiers & shiftKey) ? true : false); 00564 keys.set(GHOST_kModifierKeyLeftControl, (modifiers & controlKey) ? true : false); 00565 00566 return GHOST_kSuccess; 00567 } 00568 00569 /* XXX, incorrect for multibutton mice */ 00570 GHOST_TSuccess GHOST_SystemCarbon::getButtons(GHOST_Buttons& buttons) const 00571 { 00572 Boolean theOnlyButtonIsDown = ::Button(); 00573 buttons.clear(); 00574 buttons.set(GHOST_kButtonMaskLeft, theOnlyButtonIsDown); 00575 return GHOST_kSuccess; 00576 } 00577 00578 #define FIRSTFILEBUFLG 512 00579 static bool g_hasFirstFile = false; 00580 static char g_firstFileBuf[512]; 00581 00582 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) 00583 { 00584 if (g_hasFirstFile) { 00585 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1); 00586 buf[FIRSTFILEBUFLG - 1] = '\0'; 00587 return 1; 00588 } else { 00589 return 0; 00590 } 00591 } 00592 00593 OSErr GHOST_SystemCarbon::sAEHandlerLaunch(const AppleEvent *event, AppleEvent *reply, SInt32 refCon) 00594 { 00595 //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon; 00596 00597 return noErr; 00598 } 00599 00600 OSErr GHOST_SystemCarbon::sAEHandlerOpenDocs(const AppleEvent *event, AppleEvent *reply, SInt32 refCon) 00601 { 00602 //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon; 00603 AEDescList docs; 00604 SInt32 ndocs; 00605 OSErr err; 00606 00607 err = AEGetParamDesc(event, keyDirectObject, typeAEList, &docs); 00608 if (err != noErr) return err; 00609 00610 err = AECountItems(&docs, &ndocs); 00611 if (err==noErr) { 00612 int i; 00613 00614 for (i=0; i<ndocs; i++) { 00615 FSSpec fss; 00616 AEKeyword kwd; 00617 DescType actType; 00618 Size actSize; 00619 00620 err = AEGetNthPtr(&docs, i+1, typeFSS, &kwd, &actType, &fss, sizeof(fss), &actSize); 00621 if (err!=noErr) 00622 break; 00623 00624 if (i==0) { 00625 FSRef fsref; 00626 00627 if (FSpMakeFSRef(&fss, &fsref)!=noErr) 00628 break; 00629 if (FSRefMakePath(&fsref, (UInt8*) g_firstFileBuf, sizeof(g_firstFileBuf))!=noErr) 00630 break; 00631 00632 g_hasFirstFile = true; 00633 } 00634 } 00635 } 00636 00637 AEDisposeDesc(&docs); 00638 00639 return err; 00640 } 00641 00642 OSErr GHOST_SystemCarbon::sAEHandlerPrintDocs(const AppleEvent *event, AppleEvent *reply, SInt32 refCon) 00643 { 00644 //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon; 00645 00646 return noErr; 00647 } 00648 00649 OSErr GHOST_SystemCarbon::sAEHandlerQuit(const AppleEvent *event, AppleEvent *reply, SInt32 refCon) 00650 { 00651 GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon; 00652 00653 sys->pushEvent( new GHOST_Event(sys->getMilliSeconds(), GHOST_kEventQuit, NULL) ); 00654 00655 return noErr; 00656 } 00657 00658 00659 GHOST_TSuccess GHOST_SystemCarbon::init() 00660 { 00661 00662 GHOST_TSuccess success = GHOST_System::init(); 00663 if (success) { 00664 /* 00665 * Initialize the cursor to the standard arrow shape (so that we can change it later on). 00666 * This initializes the cursor's visibility counter to 0. 00667 */ 00668 ::InitCursor(); 00669 00670 MenuRef windMenu; 00671 ::CreateStandardWindowMenu(0, &windMenu); 00672 ::InsertMenu(windMenu, 0); 00673 ::DrawMenuBar(); 00674 00675 ::InstallApplicationEventHandler(sEventHandlerProc, GetEventTypeCount(kEvents), kEvents, this, &m_handler); 00676 00677 ::AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, sAEHandlerLaunch, (SInt32) this, false); 00678 ::AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, sAEHandlerOpenDocs, (SInt32) this, false); 00679 ::AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, sAEHandlerPrintDocs, (SInt32) this, false); 00680 ::AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, sAEHandlerQuit, (SInt32) this, false); 00681 00682 } 00683 return success; 00684 } 00685 00686 00687 GHOST_TSuccess GHOST_SystemCarbon::exit() 00688 { 00689 return GHOST_System::exit(); 00690 } 00691 00692 00693 OSStatus GHOST_SystemCarbon::handleWindowEvent(EventRef event) 00694 { 00695 WindowRef windowRef; 00696 GHOST_WindowCarbon *window; 00697 OSStatus err = eventNotHandledErr; 00698 00699 // Check if the event was send to a GHOST window 00700 ::GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &windowRef); 00701 window = (GHOST_WindowCarbon*) ::GetWRefCon(windowRef); 00702 if (!validWindow(window)) { 00703 return err; 00704 } 00705 00706 //if (!getFullScreen()) { 00707 err = noErr; 00708 switch(::GetEventKind(event)) 00709 { 00710 case kEventWindowClose: 00711 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) ); 00712 break; 00713 case kEventWindowActivated: 00714 m_windowManager->setActiveWindow(window); 00715 window->loadCursor(window->getCursorVisibility(), window->getCursorShape()); 00716 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) ); 00717 break; 00718 case kEventWindowDeactivated: 00719 m_windowManager->setWindowInactive(window); 00720 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) ); 00721 break; 00722 case kEventWindowUpdate: 00723 //if (getFullScreen()) GHOST_PRINT("GHOST_SystemCarbon::handleWindowEvent(): full-screen update event\n"); 00724 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) ); 00725 break; 00726 case kEventWindowBoundsChanged: 00727 if (!m_ignoreWindowSizedMessages) 00728 { 00729 window->updateDrawingContext(); 00730 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) ); 00731 } 00732 break; 00733 default: 00734 err = eventNotHandledErr; 00735 break; 00736 } 00737 // } 00738 //else { 00739 //window = (GHOST_WindowCarbon*) m_windowManager->getFullScreenWindow(); 00740 //GHOST_PRINT("GHOST_SystemCarbon::handleWindowEvent(): full-screen window event, " << window << "\n"); 00741 //::RemoveEventFromQueue(::GetMainEventQueue(), event); 00742 //} 00743 00744 return err; 00745 } 00746 00747 OSStatus GHOST_SystemCarbon::handleTabletEvent(EventRef event) 00748 { 00749 GHOST_IWindow* window = m_windowManager->getActiveWindow(); 00750 TabletPointRec tabletPointRecord; 00751 TabletProximityRec tabletProximityRecord; 00752 UInt32 anInt32; 00753 GHOST_TabletData& ct=((GHOST_WindowCarbon*)window)->GetCarbonTabletData(); 00754 OSStatus err = eventNotHandledErr; 00755 00756 ct.Pressure = 0; 00757 ct.Xtilt = 0; 00758 ct.Ytilt = 0; 00759 00760 // is there an embedded tablet event inside this mouse event? 00761 if(noErr == GetEventParameter(event, kEventParamTabletEventType, typeUInt32, NULL, sizeof(UInt32), NULL, (void *)&anInt32)) 00762 { 00763 // yes there is one! 00764 // Embedded tablet events can either be a proximity or pointer event. 00765 if(anInt32 == kEventTabletPoint) 00766 { 00767 //GHOST_PRINT("Embedded pointer event!\n"); 00768 00769 // Extract the tablet Pointer Event. If there is no Tablet Pointer data 00770 // in this event, then this call will return an error. Just ignore the 00771 // error and go on. This can occur when a proximity event is embedded in 00772 // a mouse event and you did not check the mouse event to see which type 00773 // of tablet event was embedded. 00774 if(noErr == GetEventParameter(event, kEventParamTabletPointRec, 00775 typeTabletPointRec, NULL, 00776 sizeof(TabletPointRec), 00777 NULL, (void *)&tabletPointRecord)) 00778 { 00779 ct.Pressure = tabletPointRecord.pressure / 65535.0f; 00780 ct.Xtilt = tabletPointRecord.tiltX / 32767.0f; /* can be positive or negative */ 00781 ct.Ytilt = tabletPointRecord.tiltY / 32767.0f; /* can be positive or negative */ 00782 } 00783 } else { 00784 //GHOST_PRINT("Embedded proximity event\n"); 00785 00786 // Extract the Tablet Proximity record from the event. 00787 if(noErr == GetEventParameter(event, kEventParamTabletProximityRec, 00788 typeTabletProximityRec, NULL, 00789 sizeof(TabletProximityRec), 00790 NULL, (void *)&tabletProximityRecord)) 00791 { 00792 if (tabletProximityRecord.enterProximity) { 00793 //pointer is entering tablet area proximity 00794 00795 switch(tabletProximityRecord.pointerType) 00796 { 00797 case 1: /* stylus */ 00798 ct.Active = GHOST_kTabletModeStylus; 00799 break; 00800 case 2: /* puck, not supported so far */ 00801 ct.Active = GHOST_kTabletModeNone; 00802 break; 00803 case 3: /* eraser */ 00804 ct.Active = GHOST_kTabletModeEraser; 00805 break; 00806 default: 00807 ct.Active = GHOST_kTabletModeNone; 00808 break; 00809 } 00810 } else { 00811 // pointer is leaving - return to mouse 00812 ct.Active = GHOST_kTabletModeNone; 00813 } 00814 } 00815 } 00816 err = noErr; 00817 } 00818 return err; 00819 } 00820 00821 OSStatus GHOST_SystemCarbon::handleMouseEvent(EventRef event) 00822 { 00823 OSStatus err = eventNotHandledErr; 00824 GHOST_IWindow* window = m_windowManager->getActiveWindow(); 00825 UInt32 kind = ::GetEventKind(event); 00826 00827 switch (kind) 00828 { 00829 case kEventMouseDown: 00830 case kEventMouseUp: 00831 // Handle Mac application responsibilities 00832 if ((kind == kEventMouseDown) && handleMouseDown(event)) { 00833 err = noErr; 00834 } 00835 else { 00836 GHOST_TEventType type = (kind == kEventMouseDown) ? GHOST_kEventButtonDown : GHOST_kEventButtonUp; 00837 EventMouseButton button; 00838 00839 /* Window still gets mouse up after command-H */ 00840 if (m_windowManager->getActiveWindow()) { 00841 // handle any tablet events that may have come with the mouse event (optional) 00842 handleTabletEvent(event); 00843 00844 ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button); 00845 pushEvent(new GHOST_EventButton(getMilliSeconds(), type, window, convertButton(button))); 00846 err = noErr; 00847 } 00848 } 00849 break; 00850 00851 case kEventMouseMoved: 00852 case kEventMouseDragged: { 00853 Point mousePos; 00854 00855 if (window) { 00856 //handle any tablet events that may have come with the mouse event (optional) 00857 handleTabletEvent(event); 00858 00859 ::GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &mousePos); 00860 pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, mousePos.h, mousePos.v)); 00861 err = noErr; 00862 } 00863 break; 00864 } 00865 case kEventMouseWheelMoved: 00866 { 00867 OSStatus status; 00868 //UInt32 modifiers; 00869 EventMouseWheelAxis axis; 00870 SInt32 delta; 00871 //status = ::GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers); 00872 //GHOST_ASSERT(status == noErr, "GHOST_SystemCarbon::handleMouseEvent(): GetEventParameter() failed"); 00873 status = ::GetEventParameter(event, kEventParamMouseWheelAxis, typeMouseWheelAxis, NULL, sizeof(axis), NULL, &axis); 00874 GHOST_ASSERT(status == noErr, "GHOST_SystemCarbon::handleMouseEvent(): GetEventParameter() failed"); 00875 if (axis == kEventMouseWheelAxisY) 00876 { 00877 status = ::GetEventParameter(event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(delta), NULL, &delta); 00878 GHOST_ASSERT(status == noErr, "GHOST_SystemCarbon::handleMouseEvent(): GetEventParameter() failed"); 00879 /* 00880 * Limit mouse wheel delta to plus and minus one. 00881 */ 00882 delta = delta > 0 ? 1 : -1; 00883 pushEvent(new GHOST_EventWheel(getMilliSeconds(), window, delta)); 00884 err = noErr; 00885 } 00886 } 00887 break; 00888 } 00889 00890 return err; 00891 } 00892 00893 00894 OSStatus GHOST_SystemCarbon::handleKeyEvent(EventRef event) 00895 { 00896 OSStatus err = eventNotHandledErr; 00897 GHOST_IWindow* window = m_windowManager->getActiveWindow(); 00898 UInt32 kind = ::GetEventKind(event); 00899 UInt32 modifiers; 00900 UInt32 rawCode; 00901 GHOST_TKey key; 00902 unsigned char ascii; 00903 00904 /* Can happen, very rarely - seems to only be when command-H makes 00905 * the window go away and we still get an HKey up. 00906 */ 00907 if (!window) { 00908 //::GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &rawCode); 00909 //key = convertKey(rawCode); 00910 return err; 00911 } 00912 00913 err = noErr; 00914 switch (kind) { 00915 case kEventRawKeyDown: 00916 case kEventRawKeyRepeat: 00917 case kEventRawKeyUp: 00918 ::GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &rawCode); 00919 ::GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(char), NULL, &ascii); 00920 00921 key = convertKey(rawCode); 00922 ascii= convertRomanToLatin(ascii); 00923 00924 // if (key!=GHOST_kKeyUnknown) { 00925 GHOST_TEventType type; 00926 if (kind == kEventRawKeyDown) { 00927 type = GHOST_kEventKeyDown; 00928 } else if (kind == kEventRawKeyRepeat) { 00929 type = GHOST_kEventKeyDown; /* XXX, fixme */ 00930 } else { 00931 type = GHOST_kEventKeyUp; 00932 } 00933 pushEvent( new GHOST_EventKey( getMilliSeconds(), type, window, key, ascii, NULL) ); 00934 // } 00935 break; 00936 00937 case kEventRawKeyModifiersChanged: 00938 /* ugh */ 00939 ::GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); 00940 if ((modifiers & shiftKey) != (m_modifierMask & shiftKey)) { 00941 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & shiftKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) ); 00942 } 00943 if ((modifiers & controlKey) != (m_modifierMask & controlKey)) { 00944 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & controlKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) ); 00945 } 00946 if ((modifiers & optionKey) != (m_modifierMask & optionKey)) { 00947 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & optionKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) ); 00948 } 00949 if ((modifiers & cmdKey) != (m_modifierMask & cmdKey)) { 00950 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & cmdKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) ); 00951 } 00952 00953 m_modifierMask = modifiers; 00954 break; 00955 00956 default: 00957 err = eventNotHandledErr; 00958 break; 00959 } 00960 00961 return err; 00962 } 00963 00964 00965 bool GHOST_SystemCarbon::handleMouseDown(EventRef event) 00966 { 00967 WindowPtr window; 00968 short part; 00969 BitMap screenBits; 00970 bool handled = true; 00971 GHOST_WindowCarbon* ghostWindow; 00972 Point mousePos = {0 , 0}; 00973 00974 ::GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &mousePos); 00975 00976 part = ::FindWindow(mousePos, &window); 00977 ghostWindow = (GHOST_WindowCarbon*) ::GetWRefCon(window); 00978 00979 switch (part) { 00980 case inMenuBar: 00981 handleMenuCommand(::MenuSelect(mousePos)); 00982 break; 00983 00984 case inDrag: 00985 /* 00986 * The DragWindow() routine creates a lot of kEventWindowBoundsChanged 00987 * events. By setting m_ignoreWindowSizedMessages these are suppressed. 00988 * @see GHOST_SystemCarbon::handleWindowEvent(EventRef event) 00989 */ 00990 /* even worse: scale window also generates a load of events, and nothing 00991 is handled (read: client's event proc called) until you release mouse (ton) */ 00992 00993 GHOST_ASSERT(validWindow(ghostWindow), "GHOST_SystemCarbon::handleMouseDown: invalid window"); 00994 m_ignoreWindowSizedMessages = true; 00995 ::DragWindow(window, mousePos, &GetQDGlobalsScreenBits(&screenBits)->bounds); 00996 m_ignoreWindowSizedMessages = false; 00997 00998 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, ghostWindow) ); 00999 01000 break; 01001 01002 case inContent: 01003 if (window != ::FrontWindow()) { 01004 ::SelectWindow(window); 01005 /* 01006 * We add a mouse down event on the newly actived window 01007 */ 01008 //GHOST_PRINT("GHOST_SystemCarbon::handleMouseDown(): adding mouse down event, " << ghostWindow << "\n"); 01009 EventMouseButton button; 01010 ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button); 01011 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonDown, ghostWindow, convertButton(button))); 01012 } else { 01013 handled = false; 01014 } 01015 break; 01016 01017 case inGoAway: 01018 GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0"); 01019 if (::TrackGoAway(window, mousePos)) 01020 { 01021 // todo: add option-close, because itÿs in the HIG 01022 // if (event.modifiers & optionKey) { 01023 // Close the clean documents, others will be confirmed one by one. 01024 //} 01025 // else { 01026 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, ghostWindow)); 01027 //} 01028 } 01029 break; 01030 01031 case inGrow: 01032 GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0"); 01033 ::ResizeWindow(window, mousePos, NULL, NULL); 01034 break; 01035 01036 case inZoomIn: 01037 case inZoomOut: 01038 GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0"); 01039 if (::TrackBox(window, mousePos, part)) { 01040 int macState; 01041 01042 macState = ghostWindow->getMac_windowState(); 01043 if ( macState== 0) 01044 ::ZoomWindow(window, part, true); 01045 else 01046 if (macState == 2) { // always ok 01047 ::ZoomWindow(window, part, true); 01048 ghostWindow->setMac_windowState(1); 01049 } else { // need to force size again 01050 // GHOST_TUns32 scr_x,scr_y; /*unused*/ 01051 Rect outAvailableRect; 01052 01053 ghostWindow->setMac_windowState(2); 01054 ::GetAvailableWindowPositioningBounds ( GetMainDevice(), &outAvailableRect); 01055 01056 //this->getMainDisplayDimensions(scr_x,scr_y); 01057 ::SizeWindow (window, outAvailableRect.right-outAvailableRect.left,outAvailableRect.bottom-outAvailableRect.top-1,false); 01058 ::MoveWindow (window, outAvailableRect.left, outAvailableRect.top,true); 01059 } 01060 01061 } 01062 break; 01063 01064 default: 01065 handled = false; 01066 break; 01067 } 01068 01069 return handled; 01070 } 01071 01072 01073 bool GHOST_SystemCarbon::handleMenuCommand(GHOST_TInt32 menuResult) 01074 { 01075 short menuID; 01076 short menuItem; 01077 UInt32 command; 01078 bool handled; 01079 OSErr err; 01080 01081 menuID = HiWord(menuResult); 01082 menuItem = LoWord(menuResult); 01083 01084 err = ::GetMenuItemCommandID(::GetMenuHandle(menuID), menuItem, &command); 01085 01086 handled = false; 01087 01088 if (err || command == 0) { 01089 } 01090 else { 01091 switch(command) { 01092 } 01093 } 01094 01095 ::HiliteMenu(0); 01096 return handled; 01097 } 01098 01099 01100 OSStatus GHOST_SystemCarbon::sEventHandlerProc(EventHandlerCallRef handler, EventRef event, void* userData) 01101 { 01102 GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) userData; 01103 OSStatus err = eventNotHandledErr; 01104 GHOST_IWindow* window; 01105 #ifdef WITH_INPUT_NDOF 01106 GHOST_TEventNDOFData data; 01107 #endif 01108 UInt32 kind; 01109 01110 switch (::GetEventClass(event)) 01111 { 01112 case kEventClassAppleEvent: 01113 EventRecord eventrec; 01114 if (ConvertEventRefToEventRecord(event, &eventrec)) { 01115 err = AEProcessAppleEvent(&eventrec); 01116 } 01117 break; 01118 case kEventClassMouse: 01119 err = sys->handleMouseEvent(event); 01120 break; 01121 case kEventClassWindow: 01122 err = sys->handleWindowEvent(event); 01123 break; 01124 case kEventClassKeyboard: 01125 err = sys->handleKeyEvent(event); 01126 break; 01127 case kEventClassBlender : 01128 #ifdef WITH_INPUT_NDOF 01129 window = sys->m_windowManager->getActiveWindow(); 01130 sys->m_ndofManager->GHOST_NDOFGetDatas(data); 01131 kind = ::GetEventKind(event); 01132 01133 switch (kind) 01134 { 01135 case 1: 01136 sys->m_eventManager->pushEvent(new GHOST_EventNDOF(sys->getMilliSeconds(), GHOST_kEventNDOFMotion, window, data)); 01137 // printf("motion\n"); 01138 break; 01139 case 2: 01140 sys->m_eventManager->pushEvent(new GHOST_EventNDOF(sys->getMilliSeconds(), GHOST_kEventNDOFButton, window, data)); 01141 // printf("button\n"); 01142 break; 01143 } 01144 #endif 01145 err = noErr; 01146 break; 01147 default : 01148 ; 01149 break; 01150 } 01151 01152 return err; 01153 } 01154 01155 GHOST_TUns8* GHOST_SystemCarbon::getClipboard(bool selection) const 01156 { 01157 PasteboardRef inPasteboard; 01158 PasteboardItemID itemID; 01159 CFDataRef flavorData; 01160 OSStatus err = noErr; 01161 GHOST_TUns8 * temp_buff; 01162 CFRange range; 01163 OSStatus syncFlags; 01164 01165 err = PasteboardCreate(kPasteboardClipboard, &inPasteboard); 01166 if(err != noErr) { return NULL;} 01167 01168 syncFlags = PasteboardSynchronize( inPasteboard ); 01169 /* as we always get in a new string, we can safely ignore sync flags if not an error*/ 01170 if(syncFlags <0) { return NULL;} 01171 01172 01173 err = PasteboardGetItemIdentifier( inPasteboard, 1, &itemID ); 01174 if(err != noErr) { return NULL;} 01175 01176 err = PasteboardCopyItemFlavorData( inPasteboard, itemID, CFSTR("public.utf8-plain-text"), &flavorData); 01177 if(err != noErr) { return NULL;} 01178 01179 range = CFRangeMake(0, CFDataGetLength(flavorData)); 01180 01181 temp_buff = (GHOST_TUns8*) malloc(range.length+1); 01182 01183 CFDataGetBytes(flavorData, range, (UInt8*)temp_buff); 01184 01185 temp_buff[range.length] = '\0'; 01186 01187 if(temp_buff) { 01188 return temp_buff; 01189 } else { 01190 return NULL; 01191 } 01192 } 01193 01194 void GHOST_SystemCarbon::putClipboard(GHOST_TInt8 *buffer, bool selection) const 01195 { 01196 if(selection) {return;} // for copying the selection, used on X11 01197 01198 PasteboardRef inPasteboard; 01199 CFDataRef textData = NULL; 01200 OSStatus err = noErr; /*For error checking*/ 01201 OSStatus syncFlags; 01202 01203 err = PasteboardCreate(kPasteboardClipboard, &inPasteboard); 01204 if(err != noErr) { return;} 01205 01206 syncFlags = PasteboardSynchronize( inPasteboard ); 01207 /* as we always put in a new string, we can safely ignore sync flags */ 01208 if(syncFlags <0) { return;} 01209 01210 err = PasteboardClear( inPasteboard ); 01211 if(err != noErr) { return;} 01212 01213 textData = CFDataCreate(kCFAllocatorDefault, (UInt8*)buffer, strlen(buffer)); 01214 01215 if (textData) { 01216 err = PasteboardPutItemFlavor( inPasteboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), textData, 0); 01217 if(err != noErr) { 01218 if(textData) { CFRelease(textData);} 01219 return; 01220 } 01221 } 01222 01223 if(textData) { 01224 CFRelease(textData); 01225 } 01226 }