e02e569cc6e758421ac51a0c17823db456bf8f15
[blender.git] / intern / ghost / intern / GHOST_SystemCarbon.cpp
1 /*
2  * $Id$
3  * ***** BEGIN GPL LICENSE BLOCK *****
4  *
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.
9  *
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.
14  *
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.
18  *
19  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
20  * All rights reserved.
21  *
22  * The Original Code is: all of this file.
23  *
24  * Contributor(s): none yet.
25  *
26  * ***** END GPL LICENSE BLOCK *****
27  */
28
29 /** \file ghost/intern/GHOST_SystemCarbon.cpp
30  *  \ingroup GHOST
31  */
32
33
34 /**
35
36  * $Id$
37  * Copyright (C) 2001 NaN Technologies B.V.
38  * @author      Maarten Gribnau
39  * @date        May 7, 2001
40  */
41
42 #include <Carbon/Carbon.h>
43 #include <ApplicationServices/ApplicationServices.h>
44 #include "GHOST_SystemCarbon.h"
45
46 #include "GHOST_DisplayManagerCarbon.h"
47 #include "GHOST_EventKey.h"
48 #include "GHOST_EventButton.h"
49 #include "GHOST_EventCursor.h"
50 #include "GHOST_EventWheel.h"
51 #ifdef WITH_INPUT_NDOF
52 #include "GHOST_EventNDOF.h"
53 #endif
54
55 #include "GHOST_TimerManager.h"
56 #include "GHOST_TimerTask.h"
57 #include "GHOST_WindowManager.h"
58 #include "GHOST_WindowCarbon.h"
59 #include "GHOST_NDOFManager.h"
60 #include "AssertMacros.h"
61
62 #define GHOST_KEY_SWITCH(mac, ghost) { case (mac): ghostKey = (ghost); break; }
63
64 /* blender class and types events */
65 enum {
66   kEventClassBlender              = 'blnd'
67 };
68
69 enum {
70         kEventBlenderNdofAxis                   = 1,
71         kEventBlenderNdofButtons                = 2
72 };
73
74 const EventTypeSpec     kEvents[] =
75 {
76         { kEventClassAppleEvent, kEventAppleEvent },
77 /*
78         { kEventClassApplication, kEventAppActivated },
79         { kEventClassApplication, kEventAppDeactivated },
80 */      
81         { kEventClassKeyboard, kEventRawKeyDown },
82         { kEventClassKeyboard, kEventRawKeyRepeat },
83         { kEventClassKeyboard, kEventRawKeyUp },
84         { kEventClassKeyboard, kEventRawKeyModifiersChanged },
85         
86         { kEventClassMouse, kEventMouseDown },
87         { kEventClassMouse, kEventMouseUp },
88         { kEventClassMouse, kEventMouseMoved },
89         { kEventClassMouse, kEventMouseDragged },
90         { kEventClassMouse, kEventMouseWheelMoved },
91         
92         { kEventClassWindow, kEventWindowClickZoomRgn } ,  /* for new zoom behaviour */ 
93         { kEventClassWindow, kEventWindowZoom },  /* for new zoom behaviour */ 
94         { kEventClassWindow, kEventWindowExpand } ,  /* for new zoom behaviour */ 
95         { kEventClassWindow, kEventWindowExpandAll },  /* for new zoom behaviour */ 
96
97         { kEventClassWindow, kEventWindowClose },
98         { kEventClassWindow, kEventWindowActivated },
99         { kEventClassWindow, kEventWindowDeactivated },
100         { kEventClassWindow, kEventWindowUpdate },
101         { kEventClassWindow, kEventWindowBoundsChanged },
102         
103         { kEventClassBlender, kEventBlenderNdofAxis },
104         { kEventClassBlender, kEventBlenderNdofButtons }
105         
106         
107         
108 };
109
110
111
112 static GHOST_TButtonMask convertButton(EventMouseButton button)
113 {
114         switch (button) {
115         case kEventMouseButtonPrimary:
116                 return GHOST_kButtonMaskLeft;
117         case kEventMouseButtonSecondary:
118                 return GHOST_kButtonMaskRight;
119         case kEventMouseButtonTertiary:
120         default:
121                 return GHOST_kButtonMaskMiddle;
122         }
123 }
124
125 static GHOST_TKey convertKey(int rawCode) 
126 {       
127                 /* This bit of magic converts the rawCode into a virtual
128                  * Mac key based on the current keyboard mapping, but
129                  * without regard to the modifiers (so we don't get 'a' 
130                  * and 'A' for example.
131                  */
132         static UInt32 dummy= 0;
133         Handle transData = (Handle) GetScriptManagerVariable(smKCHRCache);
134         unsigned char vk = KeyTranslate(transData, rawCode, &dummy);    
135                 /* Map numpad based on rawcodes first, otherwise they
136                  * look like non-numpad events.
137                  * Added too: mapping the number keys, for french keyboards etc (ton)
138                  */
139         // printf("GHOST: vk: %d %c raw: %d\n", vk, vk, rawCode);
140                  
141         switch (rawCode) {
142         case 18:        return GHOST_kKey1;
143         case 19:        return GHOST_kKey2;
144         case 20:        return GHOST_kKey3;
145         case 21:        return GHOST_kKey4;
146         case 23:        return GHOST_kKey5;
147         case 22:        return GHOST_kKey6;
148         case 26:        return GHOST_kKey7;
149         case 28:        return GHOST_kKey8;
150         case 25:        return GHOST_kKey9;
151         case 29:        return GHOST_kKey0;
152         
153         case 82:        return GHOST_kKeyNumpad0;
154         case 83:        return GHOST_kKeyNumpad1;
155         case 84:        return GHOST_kKeyNumpad2;
156         case 85:        return GHOST_kKeyNumpad3;
157         case 86:        return GHOST_kKeyNumpad4;
158         case 87:        return GHOST_kKeyNumpad5;
159         case 88:        return GHOST_kKeyNumpad6;
160         case 89:        return GHOST_kKeyNumpad7;
161         case 91:        return GHOST_kKeyNumpad8;
162         case 92:        return GHOST_kKeyNumpad9;
163         case 65:        return GHOST_kKeyNumpadPeriod;
164         case 76:        return GHOST_kKeyNumpadEnter;
165         case 69:        return GHOST_kKeyNumpadPlus;
166         case 78:        return GHOST_kKeyNumpadMinus;
167         case 67:        return GHOST_kKeyNumpadAsterisk;
168         case 75:        return GHOST_kKeyNumpadSlash;
169         }
170         
171         if ((vk >= 'a') && (vk <= 'z')) {
172                 return (GHOST_TKey) (vk - 'a' + GHOST_kKeyA);
173         } else if ((vk >= '0') && (vk <= '9')) {
174                 return (GHOST_TKey) (vk - '0' + GHOST_kKey0);
175         } else if (vk==16) {
176                 switch (rawCode) {
177                 case 122:       return GHOST_kKeyF1;
178                 case 120:       return GHOST_kKeyF2;
179                 case 99:        return GHOST_kKeyF3;
180                 case 118:       return GHOST_kKeyF4;
181                 case 96:        return GHOST_kKeyF5;
182                 case 97:        return GHOST_kKeyF6;
183                 case 98:        return GHOST_kKeyF7;
184                 case 100:       return GHOST_kKeyF8;
185                 case 101:       return GHOST_kKeyF9;
186                 case 109:       return GHOST_kKeyF10;
187                 case 103:       return GHOST_kKeyF11;
188                 case 111:       return GHOST_kKeyF12;  // Never get, is used for ejecting the CD! 
189                 }
190         } else {
191                 switch (vk) {
192                 case kUpArrowCharCode:          return GHOST_kKeyUpArrow;
193                 case kDownArrowCharCode:        return GHOST_kKeyDownArrow;
194                 case kLeftArrowCharCode:        return GHOST_kKeyLeftArrow;
195                 case kRightArrowCharCode:       return GHOST_kKeyRightArrow;
196
197                 case kReturnCharCode:           return GHOST_kKeyEnter;
198                 case kBackspaceCharCode:        return GHOST_kKeyBackSpace;
199                 case kDeleteCharCode:           return GHOST_kKeyDelete;
200                 case kEscapeCharCode:           return GHOST_kKeyEsc;
201                 case kTabCharCode:                      return GHOST_kKeyTab;
202                 case kSpaceCharCode:            return GHOST_kKeySpace;
203
204                 case kHomeCharCode:             return GHOST_kKeyHome;
205                 case kEndCharCode:                      return GHOST_kKeyEnd;
206                 case kPageUpCharCode:           return GHOST_kKeyUpPage;
207                 case kPageDownCharCode:         return GHOST_kKeyDownPage;
208
209                 case '-':       return GHOST_kKeyMinus;
210                 case '=':       return GHOST_kKeyEqual;
211                 case ',':       return GHOST_kKeyComma;
212                 case '.':       return GHOST_kKeyPeriod;
213                 case '/':       return GHOST_kKeySlash;
214                 case ';':       return GHOST_kKeySemicolon;
215                 case '\'':      return GHOST_kKeyQuote;
216                 case '\\':      return GHOST_kKeyBackslash;
217                 case '[':       return GHOST_kKeyLeftBracket;
218                 case ']':       return GHOST_kKeyRightBracket;
219                 case '`':       return GHOST_kKeyAccentGrave;
220                 }
221         }
222         
223         // printf("GHOST: unknown key: %d %d\n", vk, rawCode);
224         
225         return GHOST_kKeyUnknown;
226 }
227
228 /* MacOSX returns a Roman charset with kEventParamKeyMacCharCodes
229  * as defined here: http://developer.apple.com/documentation/mac/Text/Text-516.html
230  * I am not sure how international this works...
231  * For cross-platform convention, we'll use the Latin ascii set instead.
232  * As defined at: http://www.ramsch.org/martin/uni/fmi-hp/iso8859-1.html
233  * 
234  */
235 static unsigned char convertRomanToLatin(unsigned char ascii)
236 {
237
238         if(ascii<128) return ascii;
239         
240         switch(ascii) {
241         case 128:       return 142;
242         case 129:       return 143;
243         case 130:       return 128;
244         case 131:       return 201;
245         case 132:       return 209;
246         case 133:       return 214;
247         case 134:       return 220;
248         case 135:       return 225;
249         case 136:       return 224;
250         case 137:       return 226;
251         case 138:       return 228;
252         case 139:       return 227;
253         case 140:       return 229;
254         case 141:       return 231;
255         case 142:       return 233;
256         case 143:       return 232;
257         case 144:       return 234;
258         case 145:       return 235;
259         case 146:       return 237;
260         case 147:       return 236;
261         case 148:       return 238;
262         case 149:       return 239;
263         case 150:       return 241;
264         case 151:       return 243;
265         case 152:       return 242;
266         case 153:       return 244;
267         case 154:       return 246;
268         case 155:       return 245;
269         case 156:       return 250;
270         case 157:       return 249;
271         case 158:       return 251;
272         case 159:       return 252;
273         case 160:       return 0;
274         case 161:       return 176;
275         case 162:       return 162;
276         case 163:       return 163;
277         case 164:       return 167;
278         case 165:       return 183;
279         case 166:       return 182;
280         case 167:       return 223;
281         case 168:       return 174;
282         case 169:       return 169;
283         case 170:       return 174;
284         case 171:       return 180;
285         case 172:       return 168;
286         case 173:       return 0;
287         case 174:       return 198;
288         case 175:       return 216;
289         case 176:       return 0;
290         case 177:       return 177;
291         case 178:       return 0;
292         case 179:       return 0;
293         case 180:       return 165;
294         case 181:       return 181;
295         case 182:       return 0;
296         case 183:       return 0;
297         case 184:       return 215;
298         case 185:       return 0;
299         case 186:       return 0;
300         case 187:       return 170;
301         case 188:       return 186;
302         case 189:       return 0;
303         case 190:       return 230;
304         case 191:       return 248;
305         case 192:       return 191;
306         case 193:       return 161;
307         case 194:       return 172;
308         case 195:       return 0;
309         case 196:       return 0;
310         case 197:       return 0;
311         case 198:       return 0;
312         case 199:       return 171;
313         case 200:       return 187;
314         case 201:       return 201;
315         case 202:       return 0;
316         case 203:       return 192;
317         case 204:       return 195;
318         case 205:       return 213;
319         case 206:       return 0;
320         case 207:       return 0;
321         case 208:       return 0;
322         case 209:       return 0;
323         case 210:       return 0;
324         
325         case 214:       return 247;
326
327         case 229:       return 194;
328         case 230:       return 202;
329         case 231:       return 193;
330         case 232:       return 203;
331         case 233:       return 200;
332         case 234:       return 205;
333         case 235:       return 206;
334         case 236:       return 207;
335         case 237:       return 204;
336         case 238:       return 211;
337         case 239:       return 212;
338         case 240:       return 0;
339         case 241:       return 210;
340         case 242:       return 218;
341         case 243:       return 219;
342         case 244:       return 217;
343         case 245:       return 0;
344         case 246:       return 0;
345         case 247:       return 0;
346         case 248:       return 0;
347         case 249:       return 0;
348         case 250:       return 0;
349
350         
351                 default: return 0;
352         }
353
354 }
355
356
357 /***/
358
359 GHOST_SystemCarbon::GHOST_SystemCarbon() :
360         m_modifierMask(0)
361 {
362         m_displayManager = new GHOST_DisplayManagerCarbon ();
363         GHOST_ASSERT(m_displayManager, "GHOST_SystemCarbon::GHOST_SystemCarbon(): m_displayManager==0\n");
364         m_displayManager->initialize();
365
366         UnsignedWide micros;
367         ::Microseconds(&micros);
368         m_start_time = UnsignedWideToUInt64(micros)/1000;
369         m_ignoreWindowSizedMessages = false;
370 }
371
372 GHOST_SystemCarbon::~GHOST_SystemCarbon()
373 {
374 }
375
376
377 GHOST_TUns64 GHOST_SystemCarbon::getMilliSeconds() const
378 {
379         UnsignedWide micros;
380         ::Microseconds(&micros);
381         UInt64 millis;
382         millis = UnsignedWideToUInt64(micros);
383         return (millis / 1000) - m_start_time;
384 }
385
386
387 GHOST_TUns8 GHOST_SystemCarbon::getNumDisplays() const
388 {
389         // We do not support multiple monitors at the moment
390         return 1;
391 }
392
393
394 void GHOST_SystemCarbon::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const
395 {
396         BitMap screenBits;
397     Rect bnds = GetQDGlobalsScreenBits(&screenBits)->bounds;
398         width = bnds.right - bnds.left;
399         height = bnds.bottom - bnds.top;
400 }
401
402
403 GHOST_IWindow* GHOST_SystemCarbon::createWindow(
404         const STR_String& title, 
405         GHOST_TInt32 left,
406         GHOST_TInt32 top,
407         GHOST_TUns32 width,
408         GHOST_TUns32 height,
409         GHOST_TWindowState state,
410         GHOST_TDrawingContextType type,
411         bool stereoVisual,
412         const GHOST_TUns16 numOfAASamples,
413         const GHOST_TEmbedderWindowID parentWindow
414 )
415 {
416     GHOST_IWindow* window = 0;
417
418         window = new GHOST_WindowCarbon (title, left, top, width, height, state, type);
419
420     if (window) {
421         if (window->getValid()) {
422             // Store the pointer to the window 
423             GHOST_ASSERT(m_windowManager, "m_windowManager not initialized");
424             m_windowManager->addWindow(window);
425             m_windowManager->setActiveWindow(window);
426             pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
427         }
428         else {
429                         GHOST_PRINT("GHOST_SystemCarbon::createWindow(): window invalid\n");
430             delete window;
431             window = 0;
432         }
433     }
434         else {
435                 GHOST_PRINT("GHOST_SystemCarbon::createWindow(): could not create window\n");
436         }
437     return window;
438 }
439
440 GHOST_TSuccess GHOST_SystemCarbon::beginFullScreen(const GHOST_DisplaySetting& setting, GHOST_IWindow** window, const bool stereoVisual)
441 {       
442         GHOST_TSuccess success = GHOST_kFailure;
443
444         // need yo make this Carbon all on 10.5 for fullscreen to work correctly
445         CGCaptureAllDisplays();
446         
447         success = GHOST_System::beginFullScreen( setting, window, stereoVisual);
448         
449         if( success != GHOST_kSuccess ) {
450                         // fullscreen failed for other reasons, release
451                         CGReleaseAllDisplays(); 
452         }
453
454         return success;
455 }
456
457 GHOST_TSuccess GHOST_SystemCarbon::endFullScreen(void)
458 {       
459         CGReleaseAllDisplays();
460         return GHOST_System::endFullScreen();
461 }
462
463 /* this is an old style low level event queue.
464   As we want to handle our own timers, this is ok.
465   the full screen hack should be removed */
466 bool GHOST_SystemCarbon::processEvents(bool waitForEvent)
467 {
468         bool anyProcessed = false;
469         EventRef event;
470         
471 //      SetMouseCoalescingEnabled(false, NULL);
472
473         do {
474                 GHOST_TimerManager* timerMgr = getTimerManager();
475                 
476                 if (waitForEvent) {
477                         GHOST_TUns64 next = timerMgr->nextFireTime();
478                         double timeOut;
479                         
480                         if (next == GHOST_kFireTimeNever) {
481                                 timeOut = kEventDurationForever;
482                         } else {
483                                 timeOut = (double)(next - getMilliSeconds())/1000.0;
484                                 if (timeOut < 0.0)
485                                         timeOut = 0.0;
486                         }
487                         
488                         ::ReceiveNextEvent(0, NULL, timeOut, false, &event);
489                 }
490                 
491                 if (timerMgr->fireTimers(getMilliSeconds())) {
492                         anyProcessed = true;
493                 }
494
495                 if (getFullScreen()) {
496                         // Check if the full-screen window is dirty
497                         GHOST_IWindow* window = m_windowManager->getFullScreenWindow();
498                         if (((GHOST_WindowCarbon*)window)->getFullScreenDirty()) {
499                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
500                                 anyProcessed = true;
501                         }
502                 }
503
504                 /* end loop when no more events available */
505                 while (::ReceiveNextEvent(0, NULL, 0, true, &event)==noErr) {
506                         OSStatus status= ::SendEventToEventTarget(event, ::GetEventDispatcherTarget());
507                         if (status==noErr) {
508                                 anyProcessed = true;
509                         } else {
510                                 UInt32 i= ::GetEventClass(event);
511                                 
512                                         /* Ignore 'cgs ' class, no documentation on what they
513                                          * are, but we get a lot of them
514                                          */
515                                 if (i!='cgs ') {
516                                         if (i!='tblt') {  // tablet event. we use the one packaged in the mouse event
517                                                 ; //printf("Missed - Class: '%.4s', Kind: %d\n", &i, ::GetEventKind(event));
518                                         }
519                                 }
520                         }
521                         ::ReleaseEvent(event);
522                 }
523         } while (waitForEvent && !anyProcessed);
524         
525     return anyProcessed;
526 }
527         
528
529 GHOST_TSuccess GHOST_SystemCarbon::getCursorPosition(GHOST_TInt32& x, GHOST_TInt32& y) const
530 {
531     Point mouseLoc;
532     // Get the position of the mouse in the active port
533     ::GetGlobalMouse(&mouseLoc);
534     // Convert the coordinates to screen coordinates
535     x = (GHOST_TInt32)mouseLoc.h;
536     y = (GHOST_TInt32)mouseLoc.v;
537     return GHOST_kSuccess;
538 }
539
540
541 GHOST_TSuccess GHOST_SystemCarbon::setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y)
542 {
543         float xf=(float)x, yf=(float)y;
544
545         CGAssociateMouseAndMouseCursorPosition(false);
546         CGSetLocalEventsSuppressionInterval(0);
547         CGWarpMouseCursorPosition(CGPointMake(xf, yf));
548         CGAssociateMouseAndMouseCursorPosition(true);
549
550 //this doesn't work properly, see game engine mouse-look scripts
551 //      CGWarpMouseCursorPosition(CGPointMake(xf, yf));
552         // this call below sends event, but empties other events (like shift)
553         // CGPostMouseEvent(CGPointMake(xf, yf), TRUE, 1, FALSE, 0);
554
555     return GHOST_kSuccess;
556 }
557
558
559 GHOST_TSuccess GHOST_SystemCarbon::getModifierKeys(GHOST_ModifierKeys& keys) const
560 {
561     UInt32 modifiers = ::GetCurrentKeyModifiers();
562
563     keys.set(GHOST_kModifierKeyOS, (modifiers & cmdKey) ? true : false);
564     keys.set(GHOST_kModifierKeyLeftAlt, (modifiers & optionKey) ? true : false);
565     keys.set(GHOST_kModifierKeyLeftShift, (modifiers & shiftKey) ? true : false);
566     keys.set(GHOST_kModifierKeyLeftControl, (modifiers & controlKey) ? true : false);
567         
568     return GHOST_kSuccess;
569 }
570
571         /* XXX, incorrect for multibutton mice */
572 GHOST_TSuccess GHOST_SystemCarbon::getButtons(GHOST_Buttons& buttons) const
573 {
574     Boolean theOnlyButtonIsDown = ::Button();
575     buttons.clear();
576     buttons.set(GHOST_kButtonMaskLeft, theOnlyButtonIsDown);
577     return GHOST_kSuccess;
578 }
579
580 #define FIRSTFILEBUFLG 512
581 static bool g_hasFirstFile = false;
582 static char g_firstFileBuf[512];
583
584 extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) { 
585         if (g_hasFirstFile) {
586                 strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1);
587                 buf[FIRSTFILEBUFLG - 1] = '\0';
588                 return 1;
589         } else {
590                 return 0; 
591         }
592 }
593
594 OSErr GHOST_SystemCarbon::sAEHandlerLaunch(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
595 {
596         //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
597         
598         return noErr;
599 }
600
601 OSErr GHOST_SystemCarbon::sAEHandlerOpenDocs(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
602 {
603         //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
604         AEDescList docs;
605         SInt32 ndocs;
606         OSErr err;
607
608         err = AEGetParamDesc(event, keyDirectObject, typeAEList, &docs);
609         if (err != noErr)  return err;
610
611         err = AECountItems(&docs, &ndocs);
612         if (err==noErr) {
613                 int i;
614         
615                 for (i=0; i<ndocs; i++) {
616                         FSSpec fss;
617                         AEKeyword kwd;
618                         DescType actType;
619                         Size actSize;
620                 
621                         err = AEGetNthPtr(&docs, i+1, typeFSS, &kwd, &actType, &fss, sizeof(fss), &actSize);
622                         if (err!=noErr)
623                                 break;
624                 
625                         if (i==0) {
626                                 FSRef fsref;
627                                 
628                                 if (FSpMakeFSRef(&fss, &fsref)!=noErr)
629                                         break;
630                                 if (FSRefMakePath(&fsref, (UInt8*) g_firstFileBuf, sizeof(g_firstFileBuf))!=noErr)
631                                         break;
632
633                                 g_hasFirstFile = true;
634                         }
635                 }
636         }
637         
638         AEDisposeDesc(&docs);
639         
640         return err;
641 }
642
643 OSErr GHOST_SystemCarbon::sAEHandlerPrintDocs(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
644 {
645         //GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
646         
647         return noErr;
648 }
649
650 OSErr GHOST_SystemCarbon::sAEHandlerQuit(const AppleEvent *event, AppleEvent *reply, SInt32 refCon)
651 {
652         GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) refCon;
653         
654         sys->pushEvent( new GHOST_Event(sys->getMilliSeconds(), GHOST_kEventQuit, NULL) );
655         
656         return noErr;
657 }
658
659
660 GHOST_TSuccess GHOST_SystemCarbon::init()
661 {
662  
663     GHOST_TSuccess success = GHOST_System::init();
664     if (success) {
665                 /*
666          * Initialize the cursor to the standard arrow shape (so that we can change it later on).
667          * This initializes the cursor's visibility counter to 0.
668          */
669         ::InitCursor();
670
671                 MenuRef windMenu;
672                 ::CreateStandardWindowMenu(0, &windMenu);
673                 ::InsertMenu(windMenu, 0);
674                 ::DrawMenuBar();
675
676         ::InstallApplicationEventHandler(sEventHandlerProc, GetEventTypeCount(kEvents), kEvents, this, &m_handler);
677                 
678                 ::AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, sAEHandlerLaunch, (SInt32) this, false);
679                 ::AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, sAEHandlerOpenDocs, (SInt32) this, false);
680                 ::AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, sAEHandlerPrintDocs, (SInt32) this, false);
681                 ::AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, sAEHandlerQuit, (SInt32) this, false);
682                 
683     }
684     return success;
685 }
686
687
688 GHOST_TSuccess GHOST_SystemCarbon::exit()
689 {
690     return GHOST_System::exit();
691 }
692
693
694 OSStatus GHOST_SystemCarbon::handleWindowEvent(EventRef event)
695 {
696         WindowRef windowRef;
697         GHOST_WindowCarbon *window;
698         OSStatus err = eventNotHandledErr;
699         
700         // Check if the event was send to a GHOST window
701         ::GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &windowRef);
702         window = (GHOST_WindowCarbon*) ::GetWRefCon(windowRef);
703         if (!validWindow(window)) {
704                 return err;
705         }
706
707         //if (!getFullScreen()) {
708                 err = noErr;
709                 switch(::GetEventKind(event)) 
710                 {
711                         case kEventWindowClose:
712                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, window) );
713                                 break;
714                         case kEventWindowActivated:
715                                 m_windowManager->setActiveWindow(window);
716                                 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
717                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowActivate, window) );
718                                 break;
719                         case kEventWindowDeactivated:
720                                 m_windowManager->setWindowInactive(window);
721                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowDeactivate, window) );
722                                 break;
723                         case kEventWindowUpdate:
724                                 //if (getFullScreen()) GHOST_PRINT("GHOST_SystemCarbon::handleWindowEvent(): full-screen update event\n");
725                                 pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowUpdate, window) );
726                                 break;
727                         case kEventWindowBoundsChanged:
728                                 if (!m_ignoreWindowSizedMessages)
729                                 {
730                                         window->updateDrawingContext();
731                                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window) );
732                                 }
733                                 break;
734                         default:
735                                 err = eventNotHandledErr;
736                                 break;
737                 }
738 //      }
739         //else {
740                 //window = (GHOST_WindowCarbon*) m_windowManager->getFullScreenWindow();
741                 //GHOST_PRINT("GHOST_SystemCarbon::handleWindowEvent(): full-screen window event, " << window << "\n");
742                 //::RemoveEventFromQueue(::GetMainEventQueue(), event);
743         //}
744         
745         return err;
746 }
747
748 OSStatus GHOST_SystemCarbon::handleTabletEvent(EventRef event)
749 {
750         GHOST_IWindow* window = m_windowManager->getActiveWindow();
751         TabletPointRec tabletPointRecord;
752         TabletProximityRec      tabletProximityRecord;
753         UInt32 anInt32;
754         GHOST_TabletData& ct=((GHOST_WindowCarbon*)window)->GetCarbonTabletData();
755         OSStatus err = eventNotHandledErr;
756         
757         ct.Pressure = 0;
758         ct.Xtilt = 0;
759         ct.Ytilt = 0;
760         
761         // is there an embedded tablet event inside this mouse event? 
762         if(noErr == GetEventParameter(event, kEventParamTabletEventType, typeUInt32, NULL, sizeof(UInt32), NULL, (void *)&anInt32))
763         {
764                 // yes there is one!
765                 // Embedded tablet events can either be a proximity or pointer event.
766                 if(anInt32 == kEventTabletPoint)
767                 {
768                         //GHOST_PRINT("Embedded pointer event!\n");
769                         
770                         // Extract the tablet Pointer Event. If there is no Tablet Pointer data
771                         // in this event, then this call will return an error. Just ignore the
772                         // error and go on. This can occur when a proximity event is embedded in
773                         // a mouse event and you did not check the mouse event to see which type
774                         // of tablet event was embedded.
775                         if(noErr == GetEventParameter(event, kEventParamTabletPointRec,
776                                                                                   typeTabletPointRec, NULL,
777                                                                                   sizeof(TabletPointRec),
778                                                                                   NULL, (void *)&tabletPointRecord))
779                         {
780                                 ct.Pressure = tabletPointRecord.pressure / 65535.0f;
781                                 ct.Xtilt = tabletPointRecord.tiltX / 32767.0f; /* can be positive or negative */
782                                 ct.Ytilt = tabletPointRecord.tiltY / 32767.0f; /* can be positive or negative */
783                         }
784                 } else {
785                         //GHOST_PRINT("Embedded proximity event\n");
786                         
787                         // Extract the Tablet Proximity record from the event.
788                         if(noErr == GetEventParameter(event, kEventParamTabletProximityRec,
789                                                                                   typeTabletProximityRec, NULL,
790                                                                                   sizeof(TabletProximityRec),
791                                                                                   NULL, (void *)&tabletProximityRecord))
792                         {
793                                 if (tabletProximityRecord.enterProximity) {
794                                         //pointer is entering tablet area proximity
795                                         
796                                         switch(tabletProximityRecord.pointerType)
797                                         {
798                                                 case 1: /* stylus */
799                                                         ct.Active = GHOST_kTabletModeStylus;
800                                                         break;
801                                                 case 2: /* puck, not supported so far */
802                                                         ct.Active = GHOST_kTabletModeNone;
803                                                         break;
804                                                 case 3: /* eraser */
805                                                         ct.Active = GHOST_kTabletModeEraser;
806                                                         break;
807                                                 default:
808                                                         ct.Active = GHOST_kTabletModeNone;
809                                                         break;
810                                         }
811                                 } else {
812                                         // pointer is leaving - return to mouse
813                                         ct.Active = GHOST_kTabletModeNone;
814                                 }
815                         }
816                 }
817         err = noErr;
818         }
819         return err;
820 }
821
822 OSStatus GHOST_SystemCarbon::handleMouseEvent(EventRef event)
823 {
824     OSStatus err = eventNotHandledErr;
825         GHOST_IWindow* window = m_windowManager->getActiveWindow();
826         UInt32 kind = ::GetEventKind(event);
827                         
828         switch (kind)
829     {
830                 case kEventMouseDown:
831                 case kEventMouseUp:
832                         // Handle Mac application responsibilities
833                         if ((kind == kEventMouseDown) && handleMouseDown(event)) {
834                                 err = noErr;
835                         }
836                         else {
837                                 GHOST_TEventType type = (kind == kEventMouseDown) ? GHOST_kEventButtonDown : GHOST_kEventButtonUp;
838                                 EventMouseButton button;
839                                 
840                                 /* Window still gets mouse up after command-H */
841                                 if (m_windowManager->getActiveWindow()) {
842                                         // handle any tablet events that may have come with the mouse event (optional)
843                                         handleTabletEvent(event);
844                                         
845                                         ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
846                                         pushEvent(new GHOST_EventButton(getMilliSeconds(), type, window, convertButton(button)));
847                                         err = noErr;
848                                 }
849                         }
850             break;
851                         
852                 case kEventMouseMoved:
853                 case kEventMouseDragged: {
854                         Point mousePos;
855
856                         if (window) {
857                                 //handle any tablet events that may have come with the mouse event (optional)
858                                 handleTabletEvent(event);
859
860                                 ::GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &mousePos);
861                                 pushEvent(new GHOST_EventCursor(getMilliSeconds(), GHOST_kEventCursorMove, window, mousePos.h, mousePos.v));
862                                 err = noErr;
863                         }
864                         break;
865                 }
866                 case kEventMouseWheelMoved:
867                         {
868                                 OSStatus status;
869                                 //UInt32 modifiers;
870                                 EventMouseWheelAxis axis;
871                                 SInt32 delta;
872                                 //status = ::GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers);
873                                 //GHOST_ASSERT(status == noErr, "GHOST_SystemCarbon::handleMouseEvent(): GetEventParameter() failed");
874                                 status = ::GetEventParameter(event, kEventParamMouseWheelAxis, typeMouseWheelAxis, NULL, sizeof(axis), NULL, &axis);
875                                 GHOST_ASSERT(status == noErr, "GHOST_SystemCarbon::handleMouseEvent(): GetEventParameter() failed");
876                                 if (axis == kEventMouseWheelAxisY)
877                                 {
878                                         status = ::GetEventParameter(event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(delta), NULL, &delta);
879                                         GHOST_ASSERT(status == noErr, "GHOST_SystemCarbon::handleMouseEvent(): GetEventParameter() failed");
880                                         /*
881                                          * Limit mouse wheel delta to plus and minus one.
882                                          */
883                                         delta = delta > 0 ? 1 : -1;
884                                         pushEvent(new GHOST_EventWheel(getMilliSeconds(), window, delta));
885                                         err = noErr;
886                                 }
887                         }
888                         break;
889                 }
890         
891         return err;
892 }
893
894
895 OSStatus GHOST_SystemCarbon::handleKeyEvent(EventRef event)
896 {
897     OSStatus err = eventNotHandledErr;
898         GHOST_IWindow* window = m_windowManager->getActiveWindow();
899         UInt32 kind = ::GetEventKind(event);
900         UInt32 modifiers;
901         UInt32 rawCode;
902         GHOST_TKey key;
903         unsigned char ascii;
904
905         /* Can happen, very rarely - seems to only be when command-H makes
906          * the window go away and we still get an HKey up. 
907          */
908         if (!window) {
909                 //::GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &rawCode);
910                 //key = convertKey(rawCode);
911                 return err;
912         }
913         
914         err = noErr;
915         switch (kind) {
916                 case kEventRawKeyDown: 
917                 case kEventRawKeyRepeat: 
918                 case kEventRawKeyUp: 
919                         ::GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &rawCode);
920                         ::GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(char), NULL, &ascii);
921         
922                         key = convertKey(rawCode);
923                         ascii= convertRomanToLatin(ascii);
924                         
925         //              if (key!=GHOST_kKeyUnknown) {
926                                 GHOST_TEventType type;
927                                 if (kind == kEventRawKeyDown) {
928                                         type = GHOST_kEventKeyDown;
929                                 } else if (kind == kEventRawKeyRepeat) { 
930                                         type = GHOST_kEventKeyDown;  /* XXX, fixme */
931                                 } else {
932                                         type = GHOST_kEventKeyUp;
933                                 }
934                                 pushEvent( new GHOST_EventKey( getMilliSeconds(), type, window, key, ascii) );
935 //                      }
936                         break;
937         
938                 case kEventRawKeyModifiersChanged: 
939                                 /* ugh */
940                         ::GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers);
941                         if ((modifiers & shiftKey) != (m_modifierMask & shiftKey)) {
942                                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & shiftKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftShift) );
943                         }
944                         if ((modifiers & controlKey) != (m_modifierMask & controlKey)) {
945                                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & controlKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftControl) );
946                         }
947                         if ((modifiers & optionKey) != (m_modifierMask & optionKey)) {
948                                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & optionKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyLeftAlt) );
949                         }
950                         if ((modifiers & cmdKey) != (m_modifierMask & cmdKey)) {
951                                 pushEvent( new GHOST_EventKey(getMilliSeconds(), (modifiers & cmdKey)?GHOST_kEventKeyDown:GHOST_kEventKeyUp, window, GHOST_kKeyOS) );
952                         }
953                         
954                         m_modifierMask = modifiers;
955                         break;
956                         
957                 default:
958                         err = eventNotHandledErr;
959                         break;
960         }
961         
962         return err;
963 }
964
965
966 bool GHOST_SystemCarbon::handleMouseDown(EventRef event)
967 {
968         WindowPtr                       window;
969         short                           part;
970         BitMap                          screenBits;
971     bool                                handled = true;
972     GHOST_WindowCarbon* ghostWindow;
973     Point                               mousePos = {0 , 0};
974         
975         ::GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &mousePos);
976         
977         part = ::FindWindow(mousePos, &window);
978         ghostWindow = (GHOST_WindowCarbon*) ::GetWRefCon(window);
979         
980         switch (part) {
981                 case inMenuBar:
982                         handleMenuCommand(::MenuSelect(mousePos));
983                         break;
984                         
985                 case inDrag:
986                         /*
987                          * The DragWindow() routine creates a lot of kEventWindowBoundsChanged
988                          * events. By setting m_ignoreWindowSizedMessages these are suppressed.
989                          * @see GHOST_SystemCarbon::handleWindowEvent(EventRef event)
990                          */
991                         /* even worse: scale window also generates a load of events, and nothing 
992                            is handled (read: client's event proc called) until you release mouse (ton) */
993                         
994                         GHOST_ASSERT(validWindow(ghostWindow), "GHOST_SystemCarbon::handleMouseDown: invalid window");
995                         m_ignoreWindowSizedMessages = true;
996                         ::DragWindow(window, mousePos, &GetQDGlobalsScreenBits(&screenBits)->bounds);
997                         m_ignoreWindowSizedMessages = false;
998                         
999                         pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowMove, ghostWindow) );
1000
1001                         break;
1002                 
1003                 case inContent:
1004                         if (window != ::FrontWindow()) {
1005                                 ::SelectWindow(window);
1006                                 /*
1007                                  * We add a mouse down event on the newly actived window
1008                                  */             
1009                                 //GHOST_PRINT("GHOST_SystemCarbon::handleMouseDown(): adding mouse down event, " << ghostWindow << "\n");
1010                                 EventMouseButton button;
1011                                 ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button);
1012                                 pushEvent(new GHOST_EventButton(getMilliSeconds(), GHOST_kEventButtonDown, ghostWindow, convertButton(button)));
1013                         } else {
1014                                 handled = false;
1015                         }
1016                         break;
1017                         
1018                 case inGoAway:
1019                         GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0");
1020                         if (::TrackGoAway(window, mousePos))
1021                         {
1022                                 // todo: add option-close, because itÿs in the HIG
1023                                 // if (event.modifiers & optionKey) {
1024                                         // Close the clean documents, others will be confirmed one by one.
1025                                 //}
1026                                 // else {
1027                                 pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowClose, ghostWindow));
1028                                 //}
1029                         }
1030                         break;
1031                         
1032                 case inGrow:
1033                         GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0");
1034                         ::ResizeWindow(window, mousePos, NULL, NULL);
1035                         break;
1036                         
1037                 case inZoomIn:
1038                 case inZoomOut:
1039                         GHOST_ASSERT(ghostWindow, "GHOST_SystemCarbon::handleMouseEvent: ghostWindow==0");
1040                         if (::TrackBox(window, mousePos, part)) {
1041                                 int macState;
1042                                 
1043                                 macState = ghostWindow->getMac_windowState();
1044                                 if ( macState== 0)
1045                                         ::ZoomWindow(window, part, true);
1046                                 else 
1047                                         if (macState == 2) { // always ok
1048                                                         ::ZoomWindow(window, part, true);
1049                                                         ghostWindow->setMac_windowState(1);
1050                                         } else { // need to force size again
1051                                         //      GHOST_TUns32 scr_x,scr_y; /*unused*/
1052                                                 Rect outAvailableRect;
1053                                                 
1054                                                 ghostWindow->setMac_windowState(2);
1055                                                 ::GetAvailableWindowPositioningBounds ( GetMainDevice(), &outAvailableRect);
1056                                                 
1057                                                 //this->getMainDisplayDimensions(scr_x,scr_y);
1058                                                 ::SizeWindow (window, outAvailableRect.right-outAvailableRect.left,outAvailableRect.bottom-outAvailableRect.top-1,false);
1059                                                 ::MoveWindow (window, outAvailableRect.left, outAvailableRect.top,true);
1060                                         }
1061                                 
1062                         }
1063                         break;
1064
1065                 default:
1066                         handled = false;
1067                         break;
1068         }
1069         
1070         return handled;
1071 }
1072
1073
1074 bool GHOST_SystemCarbon::handleMenuCommand(GHOST_TInt32 menuResult)
1075 {
1076         short           menuID;
1077         short           menuItem;
1078         UInt32          command;
1079         bool            handled;
1080         OSErr           err;
1081         
1082         menuID = HiWord(menuResult);
1083         menuItem = LoWord(menuResult);
1084
1085         err = ::GetMenuItemCommandID(::GetMenuHandle(menuID), menuItem, &command);
1086
1087         handled = false;
1088         
1089         if (err || command == 0) {
1090         }
1091         else {
1092                 switch(command) {
1093                 }
1094         }
1095
1096         ::HiliteMenu(0);
1097     return handled;
1098 }
1099
1100
1101 OSStatus GHOST_SystemCarbon::sEventHandlerProc(EventHandlerCallRef handler, EventRef event, void* userData)
1102 {
1103         GHOST_SystemCarbon* sys = (GHOST_SystemCarbon*) userData;
1104     OSStatus err = eventNotHandledErr;
1105         GHOST_IWindow* window;
1106 #ifdef WITH_INPUT_NDOF
1107         GHOST_TEventNDOFData data;
1108 #endif
1109         UInt32 kind;
1110         
1111     switch (::GetEventClass(event))
1112     {
1113                 case kEventClassAppleEvent:
1114                         EventRecord eventrec;
1115                         if (ConvertEventRefToEventRecord(event, &eventrec)) {
1116                                 err = AEProcessAppleEvent(&eventrec);
1117                         }
1118                         break;
1119         case kEventClassMouse:
1120             err = sys->handleMouseEvent(event);
1121             break;
1122                 case kEventClassWindow:
1123                         err = sys->handleWindowEvent(event);
1124                         break;
1125                 case kEventClassKeyboard:
1126                         err = sys->handleKeyEvent(event);
1127                         break;
1128                 case kEventClassBlender :
1129 #ifdef WITH_INPUT_NDOF
1130                         window = sys->m_windowManager->getActiveWindow();
1131                         sys->m_ndofManager->GHOST_NDOFGetDatas(data);
1132                         kind = ::GetEventKind(event);
1133                         
1134                         switch (kind)
1135                         {
1136                                 case 1:
1137                                         sys->m_eventManager->pushEvent(new GHOST_EventNDOF(sys->getMilliSeconds(), GHOST_kEventNDOFMotion, window, data));
1138         //                              printf("motion\n");
1139                                         break;
1140                                 case 2:
1141                                         sys->m_eventManager->pushEvent(new GHOST_EventNDOF(sys->getMilliSeconds(), GHOST_kEventNDOFButton, window, data));
1142 //                                      printf("button\n");
1143                                         break;
1144                         }
1145 #endif
1146                         err = noErr;
1147                         break;
1148                 default : 
1149                         ;
1150                         break;
1151    }
1152
1153     return err;
1154 }
1155
1156 GHOST_TUns8* GHOST_SystemCarbon::getClipboard(bool selection) const
1157 {
1158         PasteboardRef inPasteboard;
1159         PasteboardItemID itemID;
1160         CFDataRef flavorData;
1161         OSStatus err = noErr;
1162         GHOST_TUns8 * temp_buff;
1163         CFRange range;
1164         OSStatus syncFlags;
1165         
1166         err = PasteboardCreate(kPasteboardClipboard, &inPasteboard);
1167         if(err != noErr) { return NULL;}
1168
1169         syncFlags = PasteboardSynchronize( inPasteboard );
1170                 /* as we always get in a new string, we can safely ignore sync flags if not an error*/
1171         if(syncFlags <0) { return NULL;}
1172
1173
1174         err = PasteboardGetItemIdentifier( inPasteboard, 1, &itemID );
1175         if(err != noErr) { return NULL;}
1176
1177         err = PasteboardCopyItemFlavorData( inPasteboard, itemID, CFSTR("public.utf8-plain-text"), &flavorData);
1178         if(err != noErr) { return NULL;}
1179
1180         range = CFRangeMake(0, CFDataGetLength(flavorData));
1181         
1182         temp_buff = (GHOST_TUns8*) malloc(range.length+1); 
1183
1184         CFDataGetBytes(flavorData, range, (UInt8*)temp_buff);
1185         
1186         temp_buff[range.length] = '\0';
1187         
1188         if(temp_buff) {
1189                 return temp_buff;
1190         } else {
1191                 return NULL;
1192         }
1193 }
1194
1195 void GHOST_SystemCarbon::putClipboard(GHOST_TInt8 *buffer, bool selection) const
1196 {
1197         if(selection) {return;} // for copying the selection, used on X11
1198
1199         PasteboardRef inPasteboard;
1200         CFDataRef textData = NULL;
1201         OSStatus err = noErr; /*For error checking*/
1202         OSStatus syncFlags;
1203         
1204         err = PasteboardCreate(kPasteboardClipboard, &inPasteboard);
1205         if(err != noErr) { return;}
1206         
1207         syncFlags = PasteboardSynchronize( inPasteboard ); 
1208         /* as we always put in a new string, we can safely ignore sync flags */
1209         if(syncFlags <0) { return;}
1210         
1211         err = PasteboardClear( inPasteboard );
1212         if(err != noErr) { return;}
1213         
1214         textData = CFDataCreate(kCFAllocatorDefault, (UInt8*)buffer, strlen(buffer));
1215         
1216         if (textData) {
1217                 err = PasteboardPutItemFlavor( inPasteboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), textData, 0);
1218                         if(err != noErr) { 
1219                                 if(textData) { CFRelease(textData);}
1220                                 return;
1221                         }
1222         }
1223         
1224         if(textData) {
1225                 CFRelease(textData);
1226         }
1227 }