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