Adds Cocos libraries.
authordsc <david.schoonover@gmail.com>
Wed, 8 Jun 2011 23:08:20 +0000 (16:08 -0700)
committerdsc <david.schoonover@gmail.com>
Wed, 8 Jun 2011 23:08:20 +0000 (16:08 -0700)
198 files changed:
libs/CocosDenshion/CDAudioManager.h [new file with mode: 0644]
libs/CocosDenshion/CDAudioManager.m [new file with mode: 0644]
libs/CocosDenshion/CDConfig.h [new file with mode: 0644]
libs/CocosDenshion/CDOpenALSupport.h [new file with mode: 0644]
libs/CocosDenshion/CDOpenALSupport.m [new file with mode: 0644]
libs/CocosDenshion/CocosDenshion.h [new file with mode: 0644]
libs/CocosDenshion/CocosDenshion.m [new file with mode: 0644]
libs/CocosDenshion/SimpleAudioEngine.h [new file with mode: 0644]
libs/CocosDenshion/SimpleAudioEngine.m [new file with mode: 0644]
libs/FontLabel/FontLabel.h [new file with mode: 0644]
libs/FontLabel/FontLabel.m [new file with mode: 0644]
libs/FontLabel/FontLabelStringDrawing.h [new file with mode: 0644]
libs/FontLabel/FontLabelStringDrawing.m [new file with mode: 0644]
libs/FontLabel/FontManager.h [new file with mode: 0644]
libs/FontLabel/FontManager.m [new file with mode: 0644]
libs/FontLabel/ZAttributedString.h [new file with mode: 0644]
libs/FontLabel/ZAttributedString.m [new file with mode: 0644]
libs/FontLabel/ZAttributedStringPrivate.h [new file with mode: 0644]
libs/FontLabel/ZFont.h [new file with mode: 0644]
libs/FontLabel/ZFont.m [new file with mode: 0644]
libs/TouchJSON/CDataScanner.h [new file with mode: 0644]
libs/TouchJSON/CDataScanner.m [new file with mode: 0644]
libs/TouchJSON/Extensions/CDataScanner_Extensions.h [new file with mode: 0644]
libs/TouchJSON/Extensions/CDataScanner_Extensions.m [new file with mode: 0644]
libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.h [new file with mode: 0644]
libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.m [new file with mode: 0644]
libs/TouchJSON/JSON/CJSONDeserializer.h [new file with mode: 0644]
libs/TouchJSON/JSON/CJSONDeserializer.m [new file with mode: 0644]
libs/TouchJSON/JSON/CJSONScanner.h [new file with mode: 0644]
libs/TouchJSON/JSON/CJSONScanner.m [new file with mode: 0644]
libs/TouchJSON/JSON/CJSONSerializer.h [new file with mode: 0644]
libs/TouchJSON/JSON/CJSONSerializer.m [new file with mode: 0644]
libs/TouchJSON/JSON/JSONRepresentation.h [new file with mode: 0644]
libs/cocos2d/CCAction.h [new file with mode: 0644]
libs/cocos2d/CCAction.m [new file with mode: 0644]
libs/cocos2d/CCActionCamera.h [new file with mode: 0644]
libs/cocos2d/CCActionCamera.m [new file with mode: 0644]
libs/cocos2d/CCActionEase.h [new file with mode: 0644]
libs/cocos2d/CCActionEase.m [new file with mode: 0644]
libs/cocos2d/CCActionGrid.h [new file with mode: 0644]
libs/cocos2d/CCActionGrid.m [new file with mode: 0644]
libs/cocos2d/CCActionGrid3D.h [new file with mode: 0644]
libs/cocos2d/CCActionGrid3D.m [new file with mode: 0644]
libs/cocos2d/CCActionInstant.h [new file with mode: 0644]
libs/cocos2d/CCActionInstant.m [new file with mode: 0644]
libs/cocos2d/CCActionInterval.h [new file with mode: 0644]
libs/cocos2d/CCActionInterval.m [new file with mode: 0644]
libs/cocos2d/CCActionManager.h [new file with mode: 0644]
libs/cocos2d/CCActionManager.m [new file with mode: 0644]
libs/cocos2d/CCActionPageTurn3D.h [new file with mode: 0644]
libs/cocos2d/CCActionPageTurn3D.m [new file with mode: 0644]
libs/cocos2d/CCActionProgressTimer.h [new file with mode: 0644]
libs/cocos2d/CCActionProgressTimer.m [new file with mode: 0644]
libs/cocos2d/CCActionTiledGrid.h [new file with mode: 0644]
libs/cocos2d/CCActionTiledGrid.m [new file with mode: 0644]
libs/cocos2d/CCActionTween.h [new file with mode: 0644]
libs/cocos2d/CCActionTween.m [new file with mode: 0644]
libs/cocos2d/CCAnimation.h [new file with mode: 0644]
libs/cocos2d/CCAnimation.m [new file with mode: 0644]
libs/cocos2d/CCAnimationCache.h [new file with mode: 0644]
libs/cocos2d/CCAnimationCache.m [new file with mode: 0644]
libs/cocos2d/CCAtlasNode.h [new file with mode: 0644]
libs/cocos2d/CCAtlasNode.m [new file with mode: 0644]
libs/cocos2d/CCBlockSupport.h [new file with mode: 0644]
libs/cocos2d/CCBlockSupport.m [new file with mode: 0644]
libs/cocos2d/CCCamera.h [new file with mode: 0644]
libs/cocos2d/CCCamera.m [new file with mode: 0644]
libs/cocos2d/CCConfiguration.h [new file with mode: 0644]
libs/cocos2d/CCConfiguration.m [new file with mode: 0644]
libs/cocos2d/CCDirector.h [new file with mode: 0644]
libs/cocos2d/CCDirector.m [new file with mode: 0644]
libs/cocos2d/CCDrawingPrimitives.h [new file with mode: 0644]
libs/cocos2d/CCDrawingPrimitives.m [new file with mode: 0644]
libs/cocos2d/CCGrabber.h [new file with mode: 0644]
libs/cocos2d/CCGrabber.m [new file with mode: 0644]
libs/cocos2d/CCGrid.h [new file with mode: 0644]
libs/cocos2d/CCGrid.m [new file with mode: 0644]
libs/cocos2d/CCLabelAtlas.h [new file with mode: 0644]
libs/cocos2d/CCLabelAtlas.m [new file with mode: 0644]
libs/cocos2d/CCLabelBMFont.h [new file with mode: 0644]
libs/cocos2d/CCLabelBMFont.m [new file with mode: 0644]
libs/cocos2d/CCLabelTTF.h [new file with mode: 0644]
libs/cocos2d/CCLabelTTF.m [new file with mode: 0644]
libs/cocos2d/CCLayer.h [new file with mode: 0644]
libs/cocos2d/CCLayer.m [new file with mode: 0644]
libs/cocos2d/CCMenu.h [new file with mode: 0644]
libs/cocos2d/CCMenu.m [new file with mode: 0644]
libs/cocos2d/CCMenuItem.h [new file with mode: 0644]
libs/cocos2d/CCMenuItem.m [new file with mode: 0644]
libs/cocos2d/CCMotionStreak.h [new file with mode: 0644]
libs/cocos2d/CCMotionStreak.m [new file with mode: 0644]
libs/cocos2d/CCNode.h [new file with mode: 0644]
libs/cocos2d/CCNode.m [new file with mode: 0644]
libs/cocos2d/CCParallaxNode.h [new file with mode: 0644]
libs/cocos2d/CCParallaxNode.m [new file with mode: 0644]
libs/cocos2d/CCParticleExamples.h [new file with mode: 0644]
libs/cocos2d/CCParticleExamples.m [new file with mode: 0644]
libs/cocos2d/CCParticleSystem.h [new file with mode: 0644]
libs/cocos2d/CCParticleSystem.m [new file with mode: 0644]
libs/cocos2d/CCParticleSystemPoint.h [new file with mode: 0644]
libs/cocos2d/CCParticleSystemPoint.m [new file with mode: 0644]
libs/cocos2d/CCParticleSystemQuad.h [new file with mode: 0644]
libs/cocos2d/CCParticleSystemQuad.m [new file with mode: 0644]
libs/cocos2d/CCProgressTimer.h [new file with mode: 0644]
libs/cocos2d/CCProgressTimer.m [new file with mode: 0644]
libs/cocos2d/CCProtocols.h [new file with mode: 0644]
libs/cocos2d/CCRenderTexture.h [new file with mode: 0644]
libs/cocos2d/CCRenderTexture.m [new file with mode: 0644]
libs/cocos2d/CCRibbon.h [new file with mode: 0644]
libs/cocos2d/CCRibbon.m [new file with mode: 0644]
libs/cocos2d/CCScene.h [new file with mode: 0644]
libs/cocos2d/CCScene.m [new file with mode: 0644]
libs/cocos2d/CCScheduler.h [new file with mode: 0644]
libs/cocos2d/CCScheduler.m [new file with mode: 0644]
libs/cocos2d/CCSprite.h [new file with mode: 0644]
libs/cocos2d/CCSprite.m [new file with mode: 0644]
libs/cocos2d/CCSpriteBatchNode.h [new file with mode: 0644]
libs/cocos2d/CCSpriteBatchNode.m [new file with mode: 0644]
libs/cocos2d/CCSpriteFrame.h [new file with mode: 0644]
libs/cocos2d/CCSpriteFrame.m [new file with mode: 0644]
libs/cocos2d/CCSpriteFrameCache.h [new file with mode: 0644]
libs/cocos2d/CCSpriteFrameCache.m [new file with mode: 0644]
libs/cocos2d/CCTMXLayer.h [new file with mode: 0644]
libs/cocos2d/CCTMXLayer.m [new file with mode: 0644]
libs/cocos2d/CCTMXObjectGroup.h [new file with mode: 0644]
libs/cocos2d/CCTMXObjectGroup.m [new file with mode: 0644]
libs/cocos2d/CCTMXTiledMap.h [new file with mode: 0644]
libs/cocos2d/CCTMXTiledMap.m [new file with mode: 0644]
libs/cocos2d/CCTMXXMLParser.h [new file with mode: 0644]
libs/cocos2d/CCTMXXMLParser.m [new file with mode: 0644]
libs/cocos2d/CCTexture2D.h [new file with mode: 0644]
libs/cocos2d/CCTexture2D.m [new file with mode: 0644]
libs/cocos2d/CCTextureAtlas.h [new file with mode: 0644]
libs/cocos2d/CCTextureAtlas.m [new file with mode: 0644]
libs/cocos2d/CCTextureCache.h [new file with mode: 0644]
libs/cocos2d/CCTextureCache.m [new file with mode: 0644]
libs/cocos2d/CCTexturePVR.h [new file with mode: 0644]
libs/cocos2d/CCTexturePVR.m [new file with mode: 0644]
libs/cocos2d/CCTileMapAtlas.h [new file with mode: 0644]
libs/cocos2d/CCTileMapAtlas.m [new file with mode: 0644]
libs/cocos2d/CCTransition.h [new file with mode: 0644]
libs/cocos2d/CCTransition.m [new file with mode: 0644]
libs/cocos2d/CCTransitionPageTurn.h [new file with mode: 0644]
libs/cocos2d/CCTransitionPageTurn.m [new file with mode: 0644]
libs/cocos2d/CCTransitionRadial.h [new file with mode: 0644]
libs/cocos2d/CCTransitionRadial.m [new file with mode: 0644]
libs/cocos2d/Platforms/CCGL.h [new file with mode: 0644]
libs/cocos2d/Platforms/CCNS.h [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/CCDirectorMac.h [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/CCDirectorMac.m [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/CCEventDispatcher.h [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/CCEventDispatcher.m [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/MacGLView.h [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/MacGLView.m [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/MacWindow.h [new file with mode: 0644]
libs/cocos2d/Platforms/Mac/MacWindow.m [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCDirectorIOS.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCDirectorIOS.m [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCTouchDelegateProtocol.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCTouchDispatcher.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCTouchDispatcher.m [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCTouchHandler.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/CCTouchHandler.m [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/EAGLView.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/EAGLView.m [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/ES1Renderer.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/ES1Renderer.m [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/ESRenderer.h [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/glu.c [new file with mode: 0644]
libs/cocos2d/Platforms/iOS/glu.h [new file with mode: 0644]
libs/cocos2d/Support/CCArray.h [new file with mode: 0644]
libs/cocos2d/Support/CCArray.m [new file with mode: 0644]
libs/cocos2d/Support/CCFileUtils.h [new file with mode: 0644]
libs/cocos2d/Support/CCFileUtils.m [new file with mode: 0644]
libs/cocos2d/Support/CCProfiling.h [new file with mode: 0644]
libs/cocos2d/Support/CCProfiling.m [new file with mode: 0644]
libs/cocos2d/Support/CGPointExtension.h [new file with mode: 0644]
libs/cocos2d/Support/CGPointExtension.m [new file with mode: 0644]
libs/cocos2d/Support/OpenGL_Internal.h [new file with mode: 0644]
libs/cocos2d/Support/TGAlib.h [new file with mode: 0644]
libs/cocos2d/Support/TGAlib.m [new file with mode: 0644]
libs/cocos2d/Support/TransformUtils.h [new file with mode: 0644]
libs/cocos2d/Support/TransformUtils.m [new file with mode: 0644]
libs/cocos2d/Support/ZipUtils.h [new file with mode: 0644]
libs/cocos2d/Support/ZipUtils.m [new file with mode: 0644]
libs/cocos2d/Support/base64.c [new file with mode: 0644]
libs/cocos2d/Support/base64.h [new file with mode: 0644]
libs/cocos2d/Support/ccCArray.h [new file with mode: 0644]
libs/cocos2d/Support/ccUtils.c [new file with mode: 0644]
libs/cocos2d/Support/ccUtils.h [new file with mode: 0644]
libs/cocos2d/Support/uthash.h [new file with mode: 0644]
libs/cocos2d/Support/utlist.h [new file with mode: 0644]
libs/cocos2d/ccConfig.h [new file with mode: 0644]
libs/cocos2d/ccMacros.h [new file with mode: 0644]
libs/cocos2d/ccTypes.h [new file with mode: 0644]
libs/cocos2d/cocos2d.h [new file with mode: 0644]
libs/cocos2d/cocos2d.m [new file with mode: 0644]
src/game/QQGame.mm

diff --git a/libs/CocosDenshion/CDAudioManager.h b/libs/CocosDenshion/CDAudioManager.h
new file mode 100644 (file)
index 0000000..2475929
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+
+#import "CocosDenshion.h"
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000
+    #import <AVFoundation/AVFoundation.h>
+#else
+    #import "CDXMacOSXSupport.h"
+#endif
+
+/** Different modes of the engine */
+typedef enum {
+       kAMM_FxOnly,                                    //!Other apps will be able to play audio
+       kAMM_FxPlusMusic,                               //!Only this app will play audio
+       kAMM_FxPlusMusicIfNoOtherAudio, //!If another app is playing audio at start up then allow it to continue and don't play music
+       kAMM_MediaPlayback,                             //!This app takes over audio e.g music player app
+       kAMM_PlayAndRecord                              //!App takes over audio and has input and output
+} tAudioManagerMode;
+
+/** Possible states of the engine */
+typedef enum {
+       kAMStateUninitialised, //!Audio manager has not been initialised - do not use
+       kAMStateInitialising,  //!Audio manager is in the process of initialising - do not use
+       kAMStateInitialised        //!Audio manager is initialised - safe to use
+} tAudioManagerState;
+
+typedef enum {
+       kAMRBDoNothing,                     //Audio manager will not do anything on resign or becoming active
+       kAMRBStopPlay,                          //Background music is stopped on resign and resumed on become active
+       kAMRBStop                                       //Background music is stopped on resign but not resumed - maybe because you want to do this from within your game
+} tAudioManagerResignBehavior;
+
+/** Notifications */
+extern NSString * const kCDN_AudioManagerInitialised;
+
+@interface CDAsynchInitialiser : NSOperation {}        
+@end
+
+/** CDAudioManager supports two long audio source channels called left and right*/
+typedef enum {
+       kASC_Left = 0,
+       kASC_Right = 1
+} tAudioSourceChannel; 
+
+typedef enum {
+       kLAS_Init,
+       kLAS_Loaded,
+       kLAS_Playing,
+       kLAS_Paused,
+       kLAS_Stopped,
+} tLongAudioSourceState;
+
+@class CDLongAudioSource;
+@protocol CDLongAudioSourceDelegate <NSObject>
+@optional
+/** The audio source completed playing */
+- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource;
+/** The file used to load the audio source has changed */
+- (void) cdAudioSourceFileDidChange:(CDLongAudioSource *) audioSource;
+@end
+
+/**
+ CDLongAudioSource represents an audio source that has a long duration which makes
+ it costly to load into memory for playback as an effect using CDSoundEngine. Examples
+ include background music and narration tracks. The audio file may or may not be compressed.
+ Bear in mind that current iDevices can only use hardware to decode a single compressed
+ audio file at a time and playing multiple compressed files will result in a performance drop
+ as software decompression will take place.
+ @since v0.99
+ */
+@interface CDLongAudioSource : NSObject <AVAudioPlayerDelegate, CDAudioInterruptProtocol>{
+       AVAudioPlayer   *audioSourcePlayer;
+       NSString                *audioSourceFilePath;
+       NSInteger               numberOfLoops;
+       float                   volume;
+       id<CDLongAudioSourceDelegate> delegate; 
+       BOOL                    mute;
+       BOOL                    enabled_;
+       BOOL                    backgroundMusic;
+@public        
+       BOOL                    systemPaused;//Used for auto resign handling
+       NSTimeInterval  systemPauseLocation;//Used for auto resign handling
+@protected
+       tLongAudioSourceState state;
+}      
+@property (readonly) AVAudioPlayer *audioSourcePlayer;
+@property (readonly) NSString *audioSourceFilePath;
+@property (readwrite, nonatomic) NSInteger numberOfLoops;
+@property (readwrite, nonatomic) float volume;
+@property (assign) id<CDLongAudioSourceDelegate> delegate;
+/* This long audio source functions as background music */
+@property (readwrite, nonatomic) BOOL backgroundMusic;
+
+/** Loads the file into the audio source */
+-(void) load:(NSString*) filePath;
+/** Plays the audio source */
+-(void) play;
+/** Stops playing the audio soruce */
+-(void) stop;
+/** Pauses the audio source */
+-(void) pause;
+/** Rewinds the audio source */
+-(void) rewind;
+/** Resumes playing the audio source if it was paused */
+-(void) resume;
+/** Returns whether or not the audio source is playing */
+-(BOOL) isPlaying;
+
+@end
+
+/** 
+ CDAudioManager manages audio requirements for a game.  It provides access to a CDSoundEngine object
+ for playing sound effects.  It provides access to two CDLongAudioSource object (left and right channel)
+ for playing long duration audio such as background music and narration tracks.  Additionally it manages
+ the audio session to take care of things like audio session interruption and interacting with the audio
+ of other apps that are running on the device.
+ Requirements:
+ - Firmware: OS 2.2 or greater 
+ - Files: CDAudioManager.*, CocosDenshion.*
+ - Frameworks: OpenAL, AudioToolbox, AVFoundation
+ @since v0.8
+ */
+@interface CDAudioManager : NSObject <CDLongAudioSourceDelegate, CDAudioInterruptProtocol, AVAudioSessionDelegate> {
+       CDSoundEngine           *soundEngine;
+       CDLongAudioSource       *backgroundMusic;
+       NSMutableArray          *audioSourceChannels;
+       NSString*                       _audioSessionCategory;
+       BOOL                            _audioWasPlayingAtStartup;
+       tAudioManagerMode       _mode;
+       SEL backgroundMusicCompletionSelector;
+       id backgroundMusicCompletionListener;
+       BOOL willPlayBackgroundMusic;
+       BOOL _mute;
+       BOOL _resigned;
+       BOOL _interrupted;
+       BOOL _audioSessionActive;
+       BOOL enabled_;
+       
+       //For handling resign/become active
+       BOOL _isObservingAppEvents;
+       tAudioManagerResignBehavior _resignBehavior;
+}
+
+@property (readonly) CDSoundEngine *soundEngine;
+@property (readonly) CDLongAudioSource *backgroundMusic;
+@property (readonly) BOOL willPlayBackgroundMusic;
+
+/** Returns the shared singleton */
++ (CDAudioManager *) sharedManager;
++ (tAudioManagerState) sharedManagerState;
+/** Configures the shared singleton with a mode*/
++ (void) configure: (tAudioManagerMode) mode;
+/** Initializes the engine asynchronously with a mode */
++ (void) initAsynchronously: (tAudioManagerMode) mode;
+/** Initializes the engine synchronously with a mode, channel definition and a total number of channels */
+- (id) init: (tAudioManagerMode) mode;
+-(void) audioSessionInterrupted;
+-(void) audioSessionResumed;
+-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle;
+/** Returns true is audio is muted at a hardware level e.g user has ringer switch set to off */
+-(BOOL) isDeviceMuted;
+/** Returns true if another app is playing audio such as the iPod music player */
+-(BOOL) isOtherAudioPlaying;
+/** Sets the way the audio manager interacts with the operating system such as whether it shares output with other apps or obeys the mute switch */
+-(void) setMode:(tAudioManagerMode) mode;
+/** Shuts down the shared audio manager instance so that it can be reinitialised */
++(void) end;
+
+/** Call if you want to use built in resign behavior but need to do some additional audio processing on resign active. */
+- (void) applicationWillResignActive;
+/** Call if you want to use built in resign behavior but need to do some additional audio processing on become active. */
+- (void) applicationDidBecomeActive;
+
+//New AVAudioPlayer API
+/** Loads the data from the specified file path to the channel's audio source */
+-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel;
+/** Retrieves the audio source for the specified channel */
+-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel;
+
+//Legacy AVAudioPlayer API
+/** Plays music in background. The music can be looped or not
+ It is recommended to use .aac files as background music since they are decoded by the device (hardware).
+ */
+-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop;
+/** Preloads a background music */
+-(void) preloadBackgroundMusic:(NSString*) filePath;
+/** Stops playing the background music */
+-(void) stopBackgroundMusic;
+/** Pauses the background music */
+-(void) pauseBackgroundMusic;
+/** Rewinds the background music */
+-(void) rewindBackgroundMusic;
+/** Resumes playing the background music */
+-(void) resumeBackgroundMusic;
+/** Returns whether or not the background music is playing */
+-(BOOL) isBackgroundMusicPlaying;
+
+-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector;
+
+@end
+
+/** Fader for long audio source objects */
+@interface CDLongAudioSourceFader : CDPropertyModifier{}
+@end
+
+static const int kCDNoBuffer = -1;
+
+/** Allows buffers to be associated with file names */
+@interface CDBufferManager:NSObject{
+       NSMutableDictionary* loadedBuffers;
+       NSMutableArray  *freedBuffers;
+       CDSoundEngine *soundEngine;
+       int nextBufferId;
+}
+
+-(id) initWithEngine:(CDSoundEngine *) theSoundEngine;
+-(int) bufferForFile:(NSString*) filePath create:(BOOL) create;
+-(void) releaseBufferForFile:(NSString *) filePath;
+
+@end
+
diff --git a/libs/CocosDenshion/CDAudioManager.m b/libs/CocosDenshion/CDAudioManager.m
new file mode 100644 (file)
index 0000000..0929f3c
--- /dev/null
@@ -0,0 +1,887 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+
+
+#import "CDAudioManager.h"
+
+NSString * const kCDN_AudioManagerInitialised = @"kCDN_AudioManagerInitialised";
+
+//NSOperation object used to asynchronously initialise 
+@implementation CDAsynchInitialiser
+
+-(void) main {
+       [super main];
+       [CDAudioManager sharedManager];
+}      
+
+@end
+
+@implementation CDLongAudioSource
+
+@synthesize audioSourcePlayer, audioSourceFilePath, delegate, backgroundMusic;
+
+-(id) init {
+       if ((self = [super init])) {
+               state = kLAS_Init;
+               volume = 1.0f;
+               mute = NO;
+               enabled_ = YES;
+       }
+       return self;
+}
+
+-(void) dealloc {
+       CDLOGINFO(@"Denshion::CDLongAudioSource - deallocating %@", self);
+       [audioSourcePlayer release];
+       [audioSourceFilePath release];
+       [super dealloc];
+}      
+
+-(void) load:(NSString*) filePath {
+       //We have alread loaded a file previously,  check if we are being asked to load the same file
+       if (state == kLAS_Init || ![filePath isEqualToString:audioSourceFilePath]) {
+               CDLOGINFO(@"Denshion::CDLongAudioSource - Loading new audio source %@",filePath);
+               //New file
+               if (state != kLAS_Init) {
+                       [audioSourceFilePath release];//Release old file path
+                       [audioSourcePlayer release];//Release old AVAudioPlayer, they can't be reused
+               }
+               audioSourceFilePath = [filePath copy];
+               NSError *error = nil;
+               NSString *path = [CDUtilities fullPathFromRelativePath:audioSourceFilePath];
+               audioSourcePlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
+               if (error == nil) {
+                       [audioSourcePlayer prepareToPlay];
+                       audioSourcePlayer.delegate = self;
+                       if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceFileDidChange:)]) {
+                               //Tell our delegate the file has changed
+                               [delegate cdAudioSourceFileDidChange:self];
+                       }       
+               } else {
+                       CDLOG(@"Denshion::CDLongAudioSource - Error initialising audio player: %@",error);
+               }       
+       } else {
+               //Same file - just return it to a consistent state
+               [self pause];
+               [self rewind];
+       }
+       audioSourcePlayer.volume = volume;
+       audioSourcePlayer.numberOfLoops = numberOfLoops;
+       state = kLAS_Loaded;
+}      
+
+-(void) play {
+       if (enabled_) {
+               self->systemPaused = NO;
+               [audioSourcePlayer play];
+       } else {
+               CDLOGINFO(@"Denshion::CDLongAudioSource long audio source didn't play because it is disabled");
+       }       
+}      
+
+-(void) stop {
+       [audioSourcePlayer stop];
+}      
+
+-(void) pause {
+       [audioSourcePlayer pause];
+}      
+
+-(void) rewind {
+       [audioSourcePlayer setCurrentTime:0];
+}
+
+-(void) resume {
+       [audioSourcePlayer play];
+}      
+
+-(BOOL) isPlaying {
+       if (state != kLAS_Init) {
+               return [audioSourcePlayer isPlaying];
+       } else {
+               return NO;
+       }
+}
+
+-(void) setVolume:(float) newVolume
+{
+       volume = newVolume;
+       if (state != kLAS_Init && !mute) {
+               audioSourcePlayer.volume = newVolume;
+       }       
+}
+
+-(float) volume 
+{
+       return volume;
+}
+
+#pragma mark Audio Interrupt Protocol
+-(BOOL) mute
+{
+       return mute;
+}      
+
+-(void) setMute:(BOOL) muteValue 
+{
+       if (mute != muteValue) {
+               if (mute) {
+                       //Turn sound back on
+                       audioSourcePlayer.volume = volume;
+               } else {
+                       audioSourcePlayer.volume = 0.0f;
+               }
+               mute = muteValue;
+       }       
+}      
+
+-(BOOL) enabled 
+{
+       return enabled_;
+}      
+
+-(void) setEnabled:(BOOL)enabledValue 
+{
+       if (enabledValue != enabled_) {
+               enabled_ = enabledValue;
+               if (!enabled_) {
+                       //"Stop" the sounds
+                       [self pause];
+                       [self rewind];
+               }       
+       }       
+}      
+
+-(NSInteger) numberOfLoops {
+       return numberOfLoops;
+}      
+
+-(void) setNumberOfLoops:(NSInteger) loopCount
+{
+       audioSourcePlayer.numberOfLoops = loopCount;
+       numberOfLoops = loopCount;
+}      
+
+- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
+       CDLOGINFO(@"Denshion::CDLongAudioSource - audio player finished");
+#if TARGET_IPHONE_SIMULATOR    
+       CDLOGINFO(@"Denshion::CDLongAudioSource - workaround for OpenAL clobbered audio issue");
+       //This is a workaround for an issue in all simulators (tested to 3.1.2).  Problem is 
+       //that OpenAL audio playback is clobbered when an AVAudioPlayer stops.  Workaround
+       //is to keep the player playing on an endless loop with 0 volume and then when
+       //it is played again reset the volume and set loop count appropriately.
+       //NB: this workaround is not foolproof but it is good enough for most situations.
+       player.numberOfLoops = -1;
+       player.volume = 0;
+       [player play];
+#endif 
+       if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceDidFinishPlaying:)]) {
+               [delegate cdAudioSourceDidFinishPlaying:self];
+       }       
+}      
+
+-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
+       CDLOGINFO(@"Denshion::CDLongAudioSource - audio player interrupted");
+}
+
+-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
+       CDLOGINFO(@"Denshion::CDLongAudioSource - audio player resumed");
+       if (self.backgroundMusic) {
+               //Check if background music can play as rules may have changed during 
+               //the interruption. This is to address a specific issue in 4.x when
+               //fast task switching
+               if([CDAudioManager sharedManager].willPlayBackgroundMusic) {
+                       [player play];
+               }       
+       } else {
+               [player play];
+       }       
+}      
+
+@end
+
+
+@interface CDAudioManager (PrivateMethods)
+-(BOOL) audioSessionSetActive:(BOOL) active;
+-(BOOL) audioSessionSetCategory:(NSString*) category;
+-(void) badAlContextHandler;
+@end
+
+
+@implementation CDAudioManager
+#define BACKGROUND_MUSIC_CHANNEL kASC_Left
+
+@synthesize soundEngine, willPlayBackgroundMusic;
+static CDAudioManager *sharedManager;
+static tAudioManagerState _sharedManagerState = kAMStateUninitialised;
+static tAudioManagerMode configuredMode;
+static BOOL configured = FALSE;
+
+-(BOOL) audioSessionSetActive:(BOOL) active {
+       NSError *activationError = nil;
+       if ([[AVAudioSession sharedInstance] setActive:active error:&activationError]) {
+               _audioSessionActive = active;
+               CDLOGINFO(@"Denshion::CDAudioManager - Audio session set active %i succeeded", active); 
+               return YES;
+       } else {
+               //Failed
+               CDLOG(@"Denshion::CDAudioManager - Audio session set active %i failed with error %@", active, activationError); 
+               return NO;
+       }       
+}      
+
+-(BOOL) audioSessionSetCategory:(NSString*) category {
+       NSError *categoryError = nil;
+       if ([[AVAudioSession sharedInstance] setCategory:category error:&categoryError]) {
+               CDLOGINFO(@"Denshion::CDAudioManager - Audio session set category %@ succeeded", category); 
+               return YES;
+       } else {
+               //Failed
+               CDLOG(@"Denshion::CDAudioManager - Audio session set category %@ failed with error %@", category, categoryError); 
+               return NO;
+       }       
+}      
+
+// Init
++ (CDAudioManager *) sharedManager
+{
+       @synchronized(self)     {
+               if (!sharedManager) {
+                       if (!configured) {
+                               //Set defaults here
+                               configuredMode = kAMM_FxPlusMusicIfNoOtherAudio;
+                       }
+                       sharedManager = [[CDAudioManager alloc] init:configuredMode];
+                       _sharedManagerState = kAMStateInitialised;//This is only really relevant when using asynchronous initialisation
+                       [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AudioManagerInitialised object:nil];
+               }       
+       }
+       return sharedManager;
+}
+
++ (tAudioManagerState) sharedManagerState {
+       return _sharedManagerState;
+}      
+
+/**
+ * Call this to set up audio manager asynchronously.  Initialisation is finished when sharedManagerState == kAMStateInitialised
+ */
++ (void) initAsynchronously: (tAudioManagerMode) mode {
+       @synchronized(self) {
+               if (_sharedManagerState == kAMStateUninitialised) {
+                       _sharedManagerState = kAMStateInitialising;
+                       [CDAudioManager configure:mode];
+                       CDAsynchInitialiser *initOp = [[[CDAsynchInitialiser alloc] init] autorelease];
+                       NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease];
+                       [opQ addOperation:initOp];
+               }       
+       }
+}      
+
++ (id) alloc
+{
+       @synchronized(self)     {
+               NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton.");
+               return [super alloc];
+       }
+       return nil;
+}
+
+/*
+ * Call this method before accessing the shared manager in order to configure the shared audio manager
+ */
++ (void) configure: (tAudioManagerMode) mode {
+       configuredMode = mode;
+       configured = TRUE;
+}      
+
+-(BOOL) isOtherAudioPlaying {
+       UInt32 isPlaying = 0;
+       UInt32 varSize = sizeof(isPlaying);
+       AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &varSize, &isPlaying);
+       return (isPlaying != 0);
+}
+
+-(void) setMode:(tAudioManagerMode) mode {
+
+       _mode = mode;
+       switch (_mode) {
+                       
+               case kAMM_FxOnly:
+                       //Share audio with other app
+                       CDLOGINFO(@"Denshion::CDAudioManager - Audio will be shared");
+                       //_audioSessionCategory = kAudioSessionCategory_AmbientSound;
+                       _audioSessionCategory = AVAudioSessionCategoryAmbient;
+                       willPlayBackgroundMusic = NO;
+                       break;
+                       
+               case kAMM_FxPlusMusic:
+                       //Use audio exclusively - if other audio is playing it will be stopped
+                       CDLOGINFO(@"Denshion::CDAudioManager -  Audio will be exclusive");
+                       //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound;
+                       _audioSessionCategory = AVAudioSessionCategorySoloAmbient;
+                       willPlayBackgroundMusic = YES;
+                       break;
+                       
+               case kAMM_MediaPlayback:
+                       //Use audio exclusively, ignore mute switch and sleep
+                       CDLOGINFO(@"Denshion::CDAudioManager -  Media playback mode, audio will be exclusive");
+                       //_audioSessionCategory = kAudioSessionCategory_MediaPlayback;
+                       _audioSessionCategory = AVAudioSessionCategoryPlayback;
+                       willPlayBackgroundMusic = YES;
+                       break;
+                       
+               case kAMM_PlayAndRecord:
+                       //Use audio exclusively, ignore mute switch and sleep, has inputs and outputs
+                       CDLOGINFO(@"Denshion::CDAudioManager -  Play and record mode, audio will be exclusive");
+                       //_audioSessionCategory = kAudioSessionCategory_PlayAndRecord;
+                       _audioSessionCategory = AVAudioSessionCategoryPlayAndRecord;
+                       willPlayBackgroundMusic = YES;
+                       break;
+                       
+               default:
+                       //kAudioManagerFxPlusMusicIfNoOtherAudio
+                       if ([self isOtherAudioPlaying]) {
+                               CDLOGINFO(@"Denshion::CDAudioManager - Other audio is playing audio will be shared");
+                               //_audioSessionCategory = kAudioSessionCategory_AmbientSound;
+                               _audioSessionCategory = AVAudioSessionCategoryAmbient;
+                               willPlayBackgroundMusic = NO;
+                       } else {
+                               CDLOGINFO(@"Denshion::CDAudioManager - Other audio is not playing audio will be exclusive");
+                               //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound;
+                               _audioSessionCategory = AVAudioSessionCategorySoloAmbient;
+                               willPlayBackgroundMusic = YES;
+                       }       
+                       
+                       break;
+       }
+        
+       [self audioSessionSetCategory:_audioSessionCategory];
+       
+}      
+
+/**
+ * This method is used to work around various bugs introduced in 4.x OS versions. In some circumstances the 
+ * audio session is interrupted but never resumed, this results in the loss of OpenAL audio when following 
+ * standard practices. If we detect this situation then we will attempt to resume the audio session ourselves.
+ * Known triggers: lock the device then unlock it (iOS 4.2 gm), playback a song using MPMediaPlayer (iOS 4.0)
+ */
+- (void) badAlContextHandler {
+       if (_interrupted && alcGetCurrentContext() == NULL) {
+               CDLOG(@"Denshion::CDAudioManager - bad OpenAL context detected, attempting to resume audio session");
+               [self audioSessionResumed];
+       }       
+}      
+
+- (id) init: (tAudioManagerMode) mode {
+       if ((self = [super init])) {
+               
+               //Initialise the audio session 
+               AVAudioSession* session = [AVAudioSession sharedInstance];
+               session.delegate = self;
+       
+               _mode = mode;
+               backgroundMusicCompletionSelector = nil;
+               _isObservingAppEvents = FALSE;
+               _mute = NO;
+               _resigned = NO;
+               _interrupted = NO;
+               enabled_ = YES;
+               _audioSessionActive = NO;
+               [self setMode:mode];
+               soundEngine = [[CDSoundEngine alloc] init];
+               
+               //Set up audioSource channels
+               audioSourceChannels = [[NSMutableArray alloc] init];
+               CDLongAudioSource *leftChannel = [[CDLongAudioSource alloc] init];
+               leftChannel.backgroundMusic = YES;
+               CDLongAudioSource *rightChannel = [[CDLongAudioSource alloc] init];
+               rightChannel.backgroundMusic = NO;
+               [audioSourceChannels insertObject:leftChannel atIndex:kASC_Left];       
+               [audioSourceChannels insertObject:rightChannel atIndex:kASC_Right];
+               [leftChannel release];
+               [rightChannel release];
+               //Used to support legacy APIs
+               backgroundMusic = [self audioSourceForChannel:BACKGROUND_MUSIC_CHANNEL];
+               backgroundMusic.delegate = self;
+               
+               //Add handler for bad al context messages, these are posted by the sound engine.
+               [[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(badAlContextHandler) name:kCDN_BadAlContext object:nil];
+
+       }       
+       return self;            
+}      
+
+-(void) dealloc {
+       CDLOGINFO(@"Denshion::CDAudioManager - deallocating");
+       [self stopBackgroundMusic];
+       [soundEngine release];
+       [[NSNotificationCenter defaultCenter] removeObserver:self];
+       [self audioSessionSetActive:NO];
+       [audioSourceChannels release];
+       [super dealloc];
+}      
+
+/** Retrieves the audio source for the specified channel */
+-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel 
+{
+       return (CDLongAudioSource*)[audioSourceChannels objectAtIndex:channel];
+}      
+
+/** Loads the data from the specified file path to the channel's audio source */
+-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel
+{
+       CDLongAudioSource *audioSource = [self audioSourceForChannel:channel];
+       if (audioSource) {
+               [audioSource load:filePath];
+       }
+       return audioSource;
+}      
+
+-(BOOL) isBackgroundMusicPlaying {
+       return [self.backgroundMusic isPlaying];
+}      
+
+//NB: originally I tried using a route change listener and intended to store the current route,
+//however, on a 3gs running 3.1.2 no route change is generated when the user switches the 
+//ringer mute switch to off (i.e. enables sound) therefore polling is the only reliable way to
+//determine ringer switch state
+-(BOOL) isDeviceMuted {
+
+#if TARGET_IPHONE_SIMULATOR
+       //Calling audio route stuff on the simulator causes problems
+       return NO;
+#else  
+       CFStringRef newAudioRoute;
+       UInt32 propertySize = sizeof (CFStringRef);
+       
+       AudioSessionGetProperty (
+                                                        kAudioSessionProperty_AudioRoute,
+                                                        &propertySize,
+                                                        &newAudioRoute
+                                                        );
+       
+       if (newAudioRoute == NULL) {
+               //Don't expect this to happen but playing safe otherwise a null in the CFStringCompare will cause a crash
+               return YES;
+       } else {        
+               CFComparisonResult newDeviceIsMuted =   CFStringCompare (
+                                                                                                                                newAudioRoute,
+                                                                                                                                (CFStringRef) @"",
+                                                                                                                                0
+                                                                                                                                );
+               
+               return (newDeviceIsMuted == kCFCompareEqualTo);
+       }       
+#endif
+}      
+
+#pragma mark Audio Interrupt Protocol
+
+-(BOOL) mute {
+       return _mute;
+}      
+
+-(void) setMute:(BOOL) muteValue {
+       if (muteValue != _mute) {
+               _mute = muteValue;
+               [soundEngine setMute:muteValue];
+               for( CDLongAudioSource *audioSource in audioSourceChannels) {
+                       audioSource.mute = muteValue;
+               }       
+       }       
+}
+
+-(BOOL) enabled {
+       return enabled_;
+}      
+
+-(void) setEnabled:(BOOL) enabledValue {
+       if (enabledValue != enabled_) {
+               enabled_ = enabledValue;
+               [soundEngine setEnabled:enabled_];
+               for( CDLongAudioSource *audioSource in audioSourceChannels) {
+                       audioSource.enabled = enabled_;
+               }       
+       }       
+}
+
+-(CDLongAudioSource*) backgroundMusic
+{
+       return backgroundMusic;
+}      
+
+//Load background music ready for playing
+-(void) preloadBackgroundMusic:(NSString*) filePath
+{
+       [self.backgroundMusic load:filePath];   
+}      
+
+-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
+{
+       [self.backgroundMusic load:filePath];
+
+       if (!willPlayBackgroundMusic || _mute) {
+               CDLOGINFO(@"Denshion::CDAudioManager - play bgm aborted because audio is not exclusive or sound is muted");
+               return;
+       }
+               
+       if (loop) {
+               [self.backgroundMusic setNumberOfLoops:-1];
+       } else {
+               [self.backgroundMusic setNumberOfLoops:0];
+       }       
+       [self.backgroundMusic play];
+}
+
+-(void) stopBackgroundMusic
+{
+       [self.backgroundMusic stop];
+}
+
+-(void) pauseBackgroundMusic
+{
+       [self.backgroundMusic pause];
+}      
+
+-(void) resumeBackgroundMusic
+{
+       if (!willPlayBackgroundMusic || _mute) {
+               CDLOGINFO(@"Denshion::CDAudioManager - resume bgm aborted because audio is not exclusive or sound is muted");
+               return;
+       }
+       
+       [self.backgroundMusic resume];
+}      
+
+-(void) rewindBackgroundMusic
+{
+       [self.backgroundMusic rewind];
+}      
+
+-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector {
+       backgroundMusicCompletionListener = listener;
+       backgroundMusicCompletionSelector = selector;
+}      
+
+/*
+ * Call this method to have the audio manager automatically handle application resign and
+ * become active.  Pass a tAudioManagerResignBehavior to indicate the desired behavior
+ * for resigning and becoming active again.
+ *
+ * If autohandle is YES then the applicationWillResignActive and applicationDidBecomActive 
+ * methods are automatically called, otherwise you must call them yourself at the appropriate time.
+ *
+ * Based on idea of Dominique Bongard
+ */
+-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle { 
+
+       if (!_isObservingAppEvents && autoHandle) {
+               [[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(applicationWillResignActive:) name:@"UIApplicationWillResignActiveNotification" object:nil];
+               [[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(applicationDidBecomeActive:) name:@"UIApplicationDidBecomeActiveNotification" object:nil];
+               [[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(applicationWillTerminate:) name:@"UIApplicationWillTerminateNotification" object:nil];
+               _isObservingAppEvents = TRUE;
+       }
+       _resignBehavior = resignBehavior;
+}      
+
+- (void) applicationWillResignActive {
+       self->_resigned = YES;
+       
+       //Set the audio sesssion to one that allows sharing so that other audio won't be clobbered on resume
+       [self audioSessionSetCategory:AVAudioSessionCategoryAmbient];
+       
+       switch (_resignBehavior) {
+                       
+               case kAMRBStopPlay:
+                       
+                       for( CDLongAudioSource *audioSource in audioSourceChannels) {
+                               if (audioSource.isPlaying) {
+                                       audioSource->systemPaused = YES;
+                                       audioSource->systemPauseLocation = audioSource.audioSourcePlayer.currentTime;
+                                       [audioSource stop];
+                               } else {
+                                       //Music is either paused or stopped, if it is paused it will be restarted
+                                       //by OS so we will stop it.
+                                       audioSource->systemPaused = NO;
+                                       [audioSource stop];
+                               }
+                       }
+                       break;
+                       
+               case kAMRBStop:
+                       //Stop music regardless of whether it is playing or not because if it was paused
+                       //then the OS would resume it
+                       for( CDLongAudioSource *audioSource in audioSourceChannels) {
+                               [audioSource stop];
+                       }       
+                       
+               default:
+                       break;
+                       
+       }                       
+       CDLOGINFO(@"Denshion::CDAudioManager - handled resign active");
+}
+
+//Called when application resigns active only if setResignBehavior has been called
+- (void) applicationWillResignActive:(NSNotification *) notification
+{
+       [self applicationWillResignActive];
+}      
+
+- (void) applicationDidBecomeActive {
+       
+       if (self->_resigned) {
+               _resigned = NO;
+               //Reset the mode incase something changed with audio while we were inactive
+               [self setMode:_mode];
+               switch (_resignBehavior) {
+                               
+                       case kAMRBStopPlay:
+                               
+                               //Music had been stopped but stop maintains current time
+                               //so playing again will continue from where music was before resign active.
+                               //We check if music can be played because while we were inactive the user might have
+                               //done something that should force music to not play such as starting a track in the iPod
+                               if (self.willPlayBackgroundMusic) {
+                                       for( CDLongAudioSource *audioSource in audioSourceChannels) {
+                                               if (audioSource->systemPaused) {
+                                                       [audioSource resume];
+                                                       audioSource->systemPaused = NO;
+                                               }
+                                       }
+                               }
+                               break;
+                               
+                       default:
+                               break;
+                               
+               }
+               CDLOGINFO(@"Denshion::CDAudioManager - audio manager handled become active");
+       }
+}
+
+//Called when application becomes active only if setResignBehavior has been called
+- (void) applicationDidBecomeActive:(NSNotification *) notification
+{
+       [self applicationDidBecomeActive];
+}
+
+//Called when application terminates only if setResignBehavior has been called 
+- (void) applicationWillTerminate:(NSNotification *) notification
+{
+       CDLOGINFO(@"Denshion::CDAudioManager - audio manager handling terminate");
+       [self stopBackgroundMusic];
+}
+
+/** The audio source completed playing */
+- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource {
+       CDLOGINFO(@"Denshion::CDAudioManager - audio manager got told background music finished");
+       if (backgroundMusicCompletionSelector != nil) {
+               [backgroundMusicCompletionListener performSelector:backgroundMusicCompletionSelector];
+       }       
+}      
+
+-(void) beginInterruption {
+       CDLOGINFO(@"Denshion::CDAudioManager - begin interruption");
+       [self audioSessionInterrupted];
+}
+
+-(void) endInterruption {
+       CDLOGINFO(@"Denshion::CDAudioManager - end interruption");
+       [self audioSessionResumed];
+}
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
+-(void) endInterruptionWithFlags:(NSUInteger)flags {
+       CDLOGINFO(@"Denshion::CDAudioManager - interruption ended with flags %i",flags);
+       if (flags == AVAudioSessionInterruptionFlags_ShouldResume) {
+               [self audioSessionResumed];
+       }       
+}
+#endif
+
+-(void)audioSessionInterrupted 
+{ 
+    if (!_interrupted) {
+               CDLOGINFO(@"Denshion::CDAudioManager - Audio session interrupted"); 
+               _interrupted = YES;
+
+               // Deactivate the current audio session 
+           [self audioSessionSetActive:NO];
+               
+               if (alcGetCurrentContext() != NULL) {
+                       CDLOGINFO(@"Denshion::CDAudioManager - Setting OpenAL context to NULL"); 
+
+                       ALenum  error = AL_NO_ERROR;
+
+                       // set the current context to NULL will 'shutdown' openAL 
+                       alcMakeContextCurrent(NULL); 
+               
+                       if((error = alGetError()) != AL_NO_ERROR) {
+                               CDLOG(@"Denshion::CDAudioManager - Error making context current %x\n", error);
+                       } 
+                       #pragma unused(error)
+               }
+       }       
+} 
+
+-(void)audioSessionResumed 
+{ 
+       if (_interrupted) {
+               CDLOGINFO(@"Denshion::CDAudioManager - Audio session resumed"); 
+               _interrupted = NO;
+               
+               BOOL activationResult = NO;
+               // Reactivate the current audio session
+               activationResult = [self audioSessionSetActive:YES]; 
+               
+               //This code is to handle a problem with iOS 4.0 and 4.01 where reactivating the session can fail if
+               //task switching is performed too rapidly. A test case that reliably reproduces the issue is to call the
+               //iPhone and then hang up after two rings (timing may vary ;))
+               //Basically we keep waiting and trying to let the OS catch up with itself but the number of tries is
+               //limited.
+               if (!activationResult) {
+                       CDLOG(@"Denshion::CDAudioManager - Failure reactivating audio session, will try wait-try cycle"); 
+                       int activateCount = 0;
+                       while (!activationResult && activateCount < 10) {
+                               [NSThread sleepForTimeInterval:0.5];
+                               activationResult = [self audioSessionSetActive:YES]; 
+                               activateCount++;
+                               CDLOGINFO(@"Denshion::CDAudioManager - Reactivation attempt %i status = %i",activateCount,activationResult); 
+                       }       
+               }
+               
+               if (alcGetCurrentContext() == NULL) {
+                       CDLOGINFO(@"Denshion::CDAudioManager - Restoring OpenAL context"); 
+                       ALenum  error = AL_NO_ERROR;
+                       // Restore open al context 
+                       alcMakeContextCurrent([soundEngine openALContext]); 
+                       if((error = alGetError()) != AL_NO_ERROR) {
+                               CDLOG(@"Denshion::CDAudioManager - Error making context current%x\n", error);
+                       } 
+                       #pragma unused(error)
+               }       
+       }       
+}
+
++(void) end {
+       [sharedManager release];
+       sharedManager = nil;
+}      
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+@implementation CDLongAudioSourceFader
+
+-(void) _setTargetProperty:(float) newVal {
+       ((CDLongAudioSource*)target).volume = newVal;
+}      
+
+-(float) _getTargetProperty {
+       return ((CDLongAudioSource*)target).volume;
+}
+
+-(void) _stopTarget {
+       //Pause instead of stop as stop releases resources and causes problems in the simulator
+       [((CDLongAudioSource*)target) pause];
+}
+
+-(Class) _allowableType {
+       return [CDLongAudioSource class];
+}      
+
+@end
+///////////////////////////////////////////////////////////////////////////////////////
+@implementation CDBufferManager
+
+-(id) initWithEngine:(CDSoundEngine *) theSoundEngine {
+       if ((self = [super init])) {
+               soundEngine = theSoundEngine;
+               loadedBuffers = [[NSMutableDictionary alloc] initWithCapacity:CD_BUFFERS_START];
+               freedBuffers = [[NSMutableArray alloc] init];
+               nextBufferId = 0;
+       }       
+       return self;
+}      
+
+-(void) dealloc {
+       [loadedBuffers release];
+       [freedBuffers release];
+       [super dealloc];
+}      
+
+-(int) bufferForFile:(NSString*) filePath create:(BOOL) create {
+       
+       NSNumber* soundId = (NSNumber*)[loadedBuffers objectForKey:filePath];
+       if(soundId == nil)
+       {
+               if (create) {
+                       NSNumber* bufferId = nil;
+                       //First try to get a buffer from the free buffers
+                       if ([freedBuffers count] > 0) {
+                               bufferId = [[[freedBuffers lastObject] retain] autorelease];
+                               [freedBuffers removeLastObject]; 
+                               CDLOGINFO(@"Denshion::CDBufferManager reusing buffer id %i",[bufferId intValue]);
+                       } else {
+                               bufferId = [[NSNumber alloc] initWithInt:nextBufferId];
+                               [bufferId autorelease];
+                               CDLOGINFO(@"Denshion::CDBufferManager generating new buffer id %i",[bufferId intValue]);
+                               nextBufferId++;
+                       }
+                       
+                       if ([soundEngine loadBuffer:[bufferId intValue] filePath:filePath]) {
+                               //File successfully loaded
+                               CDLOGINFO(@"Denshion::CDBufferManager buffer loaded %@ %@",bufferId,filePath);
+                               [loadedBuffers setObject:bufferId forKey:filePath];
+                               return [bufferId intValue];
+                       } else {
+                               //File didn't load, put buffer id on free list
+                               [freedBuffers addObject:bufferId];
+                               return kCDNoBuffer;
+                       }       
+               } else {
+                       //No matching buffer was found
+                       return kCDNoBuffer;
+               }       
+       } else {
+               return [soundId intValue];
+       }       
+}      
+
+-(void) releaseBufferForFile:(NSString *) filePath {
+       int bufferId = [self bufferForFile:filePath create:NO];
+       if (bufferId != kCDNoBuffer) {
+               [soundEngine unloadBuffer:bufferId];
+               [loadedBuffers removeObjectForKey:filePath];
+               NSNumber *freedBufferId = [[NSNumber alloc] initWithInt:bufferId];
+               [freedBufferId autorelease];
+               [freedBuffers addObject:freedBufferId];
+       }       
+}      
+@end
+
+
+
diff --git a/libs/CocosDenshion/CDConfig.h b/libs/CocosDenshion/CDConfig.h
new file mode 100644 (file)
index 0000000..2bd8f76
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+#define COCOSDENSHION_VERSION "Aphex.rc"
+
+
+/**
+ If enabled code useful for debugging such as parameter check assertions will be performed.
+ If you experience any problems you should enable this and test your code with a debug build.
+ */
+//#define CD_DEBUG 1
+
+/**
+ The total number of sounds/buffers that can be loaded assuming memory is sufficient
+ */
+//Number of buffers slots that will be initially created
+#define CD_BUFFERS_START 64
+//Number of buffers that will be added 
+#define CD_BUFFERS_INCREMENT 16
+
+/**
+ If enabled, OpenAL code will use static buffers. When static buffers are used the audio
+ data is managed outside of OpenAL, this eliminates a memcpy operation which leads to 
+ higher performance when loading sounds.
+ However, the downside is that when the audio data is freed you must
+ be certain that it is no longer being accessed otherwise your app will crash. Testing on OS 2.2.1
+ and 3.1.2 has shown that this may occur if a buffer is being used by a source with state = AL_PLAYING
+ when the buffer is deleted. If the data is freed too quickly after the source is stopped then
+ a crash will occur. The implemented workaround is that when static buffers are used the unloadBuffer code will wait for
+ any playing sources to finish playing before the associated buffer and data are deleted, however, this delay may negate any 
+ performance gains that are achieved during loading.
+ Performance tests on a 1st gen iPod running OS 2.2.1 loading the CocosDenshionDemo sounds were ~0.14 seconds without
+ static buffers and ~0.12 seconds when using static buffers.
+
+ */
+//#define CD_USE_STATIC_BUFFERS 1
+
+
diff --git a/libs/CocosDenshion/CDOpenALSupport.h b/libs/CocosDenshion/CDOpenALSupport.h
new file mode 100644 (file)
index 0000000..661c69e
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ Disclaimer: IMPORTANT:  This Apple software is supplied to you by
+ Apple Inc. ("Apple") in consideration of your agreement to the
+ following terms, and your use, installation, modification or
+ redistribution of this Apple software constitutes acceptance of these
+ terms.  If you do not agree with these terms, please do not use,
+ install, modify or redistribute this Apple software.
+ In consideration of your agreement to abide by the following terms, and
+ subject to these terms, Apple grants you a personal, non-exclusive
+ license, under Apple's copyrights in this original Apple software (the
+ "Apple Software"), to use, reproduce, modify and redistribute the Apple
+ Software, with or without modifications, in source and/or binary forms;
+ provided that if you redistribute the Apple Software in its entirety and
+ without modifications, you must retain this notice and the following
+ text and disclaimers in all such redistributions of the Apple Software.
+ Neither the name, trademarks, service marks or logos of Apple Inc.
+ may be used to endorse or promote products derived from the Apple
+ Software without specific prior written permission from Apple.  Except
+ as expressly stated in this notice, no other rights or licenses, express
+ or implied, are granted by Apple herein, including but not limited to
+ any patent rights that may be infringed by your derivative works or by
+ other works in which the Apple Software may be incorporated.
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
+ MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
+ THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
+ OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
+ MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
+ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
+ STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ $Id$
+ */
+
+/*
+ This file contains code from version 1.1 and 1.4 of MyOpenALSupport.h taken from Apple's oalTouch version.
+ The 1.4 version code is used for loading IMA4 files, however, this code causes very noticeable clicking
+ when used to load wave files that are looped so the 1.1 version code is used specifically for loading
+ wav files.
+ */
+
+#ifndef __CD_OPENAL_H
+#define __CD_OPENAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif 
+       
+
+#import <OpenAL/al.h>
+#import <OpenAL/alc.h>
+#import <CoreFoundation/CFURL.h>
+
+
+//Taken from oalTouch MyOpenALSupport 1.1
+void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*    outSampleRate);
+void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate);
+void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate);
+       
+#ifdef __cplusplus
+}
+#endif
+
+#endif 
+
+
diff --git a/libs/CocosDenshion/CDOpenALSupport.m b/libs/CocosDenshion/CDOpenALSupport.m
new file mode 100644 (file)
index 0000000..ab0df8e
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ Disclaimer: IMPORTANT:  This Apple software is supplied to you by
+ Apple Inc. ("Apple") in consideration of your agreement to the
+ following terms, and your use, installation, modification or
+ redistribution of this Apple software constitutes acceptance of these
+ terms.  If you do not agree with these terms, please do not use,
+ install, modify or redistribute this Apple software.
+ In consideration of your agreement to abide by the following terms, and
+ subject to these terms, Apple grants you a personal, non-exclusive
+ license, under Apple's copyrights in this original Apple software (the
+ "Apple Software"), to use, reproduce, modify and redistribute the Apple
+ Software, with or without modifications, in source and/or binary forms;
+ provided that if you redistribute the Apple Software in its entirety and
+ without modifications, you must retain this notice and the following
+ text and disclaimers in all such redistributions of the Apple Software.
+ Neither the name, trademarks, service marks or logos of Apple Inc.
+ may be used to endorse or promote products derived from the Apple
+ Software without specific prior written permission from Apple.  Except
+ as expressly stated in this notice, no other rights or licenses, express
+ or implied, are granted by Apple herein, including but not limited to
+ any patent rights that may be infringed by your derivative works or by
+ other works in which the Apple Software may be incorporated.
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
+ MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
+ THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
+ OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
+ MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
+ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
+ STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ $Id: CDOpenALSupport.h 16 2010-03-11 06:22:10Z steveoldmeadow $
+ */
+
+#import "CDOpenALSupport.h"
+#import "CocosDenshion.h"
+#import <AudioToolbox/AudioToolbox.h>
+#import <AudioToolbox/ExtendedAudioFile.h>
+
+//Taken from oalTouch MyOpenALSupport 1.1
+void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*    outSampleRate)
+{
+       OSStatus                                                err = noErr;    
+       UInt64                                                  fileDataSize = 0;
+       AudioStreamBasicDescription             theFileFormat;
+       UInt32                                                  thePropertySize = sizeof(theFileFormat);
+       AudioFileID                                             afid = 0;
+       void*                                                   theData = NULL;
+       
+       // Open a file with ExtAudioFileOpen()
+       err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, &afid);
+       if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileOpenURL FAILED, Error = %ld\n", err); goto Exit; }
+       
+       // Get the audio data format
+       err = AudioFileGetProperty(afid, kAudioFilePropertyDataFormat, &thePropertySize, &theFileFormat);
+       if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFileProperty_DataFormat) FAILED, Error = %ld\n", err); goto Exit; }
+       
+       if (theFileFormat.mChannelsPerFrame > 2)  { 
+               CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit;
+       }
+       
+       if ((theFileFormat.mFormatID != kAudioFormatLinearPCM) || (!TestAudioFormatNativeEndian(theFileFormat))) { 
+               CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be little-endian PCM\n"); goto Exit;
+       }
+       
+       if ((theFileFormat.mBitsPerChannel != 8) && (theFileFormat.mBitsPerChannel != 16)) { 
+               CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be 8 or 16 bit PCM\n"); goto Exit;
+       }
+       
+       
+       thePropertySize = sizeof(fileDataSize);
+       err = AudioFileGetProperty(afid, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);
+       if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFilePropertyAudioDataByteCount) FAILED, Error = %ld\n", err); goto Exit; }
+       
+       // Read all the data into memory
+       UInt32          dataSize = (UInt32)fileDataSize;
+       theData = malloc(dataSize);
+       if (theData)
+       {
+               AudioFileReadBytes(afid, false, 0, &dataSize, theData);
+               if(err == noErr)
+               {
+                       // success
+                       *outDataSize = (ALsizei)dataSize;
+                       //This fix was added by me, however, 8 bit sounds have a clipping sound at the end so aren't really usable (SO)
+                       if (theFileFormat.mBitsPerChannel == 16) { 
+                               *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
+                       } else {
+                               *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8;   
+                       }       
+                       *outSampleRate = (ALsizei)theFileFormat.mSampleRate;
+               }
+               else 
+               { 
+                       // failure
+                       free (theData);
+                       theData = NULL; // make sure to return NULL
+                       CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", err); goto Exit;
+               }       
+       }
+       
+Exit:
+       // Dispose the ExtAudioFileRef, it is no longer needed
+       if (afid) AudioFileClose(afid);
+       return theData;
+}
+
+//Taken from oalTouch MyOpenALSupport 1.4
+void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate)
+{
+       OSStatus                                                status = noErr;
+       BOOL                                                    abort = NO;
+       SInt64                                                  theFileLengthInFrames = 0;
+       AudioStreamBasicDescription             theFileFormat;
+       UInt32                                                  thePropertySize = sizeof(theFileFormat);
+       ExtAudioFileRef                                 extRef = NULL;
+       void*                                                   theData = NULL;
+       AudioStreamBasicDescription             theOutputFormat;
+       UInt32                                                  dataSize = 0;
+       
+       // Open a file with ExtAudioFileOpen()
+       status = ExtAudioFileOpenURL(inFileURL, &extRef);
+       if (status != noErr)
+       {
+               CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status);
+               abort = YES;
+       }
+       if (abort)
+               goto Exit;
+       
+       // Get the audio data format
+       status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat);
+       if (status != noErr)
+       {
+               CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", status);
+               abort = YES;
+       }
+       if (abort)
+               goto Exit;
+       
+       if (theFileFormat.mChannelsPerFrame > 2)
+       {
+               CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n");
+               abort = YES;
+       }
+       if (abort)
+               goto Exit;
+       
+       // Set the client format to 16 bit signed integer (native-endian) data
+       // Maintain the channel count and sample rate of the original source format
+       theOutputFormat.mSampleRate = theFileFormat.mSampleRate;
+       theOutputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame;
+       
+       theOutputFormat.mFormatID = kAudioFormatLinearPCM;
+       theOutputFormat.mBytesPerPacket = 2 * theOutputFormat.mChannelsPerFrame;
+       theOutputFormat.mFramesPerPacket = 1;
+       theOutputFormat.mBytesPerFrame = 2 * theOutputFormat.mChannelsPerFrame;
+       theOutputFormat.mBitsPerChannel = 16;
+       theOutputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
+       
+       // Set the desired client (output) data format
+       status = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(theOutputFormat), &theOutputFormat);
+       if (status != noErr)
+       {
+               CDLOG(@"MyGetOpenALAudioData: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) FAILED, Error = %ld\n", status);
+               abort = YES;
+       }
+       if (abort)
+               goto Exit;
+       
+       // Get the total frame count
+       thePropertySize = sizeof(theFileLengthInFrames);
+       status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames);
+       if (status != noErr)
+       {
+               CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", status);
+               abort = YES;
+       }
+       if (abort)
+               goto Exit;
+       
+       // Read all the data into memory
+       dataSize = (UInt32) theFileLengthInFrames * theOutputFormat.mBytesPerFrame;
+       theData = malloc(dataSize);
+       if (theData)
+       {
+               AudioBufferList         theDataBuffer;
+               theDataBuffer.mNumberBuffers = 1;
+               theDataBuffer.mBuffers[0].mDataByteSize = dataSize;
+               theDataBuffer.mBuffers[0].mNumberChannels = theOutputFormat.mChannelsPerFrame;
+               theDataBuffer.mBuffers[0].mData = theData;
+               
+               // Read the data into an AudioBufferList
+               status = ExtAudioFileRead(extRef, (UInt32*)&theFileLengthInFrames, &theDataBuffer);
+               if(status == noErr)
+               {
+                       // success
+                       *outDataSize = (ALsizei)dataSize;
+                       *outDataFormat = (theOutputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
+                       *outSampleRate = (ALsizei)theOutputFormat.mSampleRate;
+               }
+               else
+               {
+                       // failure
+                       free (theData);
+                       theData = NULL; // make sure to return NULL
+                       CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", status);
+                       abort = YES;
+               }
+       }
+       if (abort)
+               goto Exit;
+       
+Exit:
+       // Dispose the ExtAudioFileRef, it is no longer needed
+       if (extRef) ExtAudioFileDispose(extRef);
+       return theData;
+}
+
+void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*   outSampleRate) {
+       
+       CFStringRef extension = CFURLCopyPathExtension(inFileURL);
+       CFComparisonResult isWavFile = 0;
+       if (extension != NULL) {
+               isWavFile = CFStringCompare (extension,(CFStringRef)@"wav", kCFCompareCaseInsensitive);
+               CFRelease(extension);
+       }       
+       
+       if (isWavFile == kCFCompareEqualTo) {
+               return CDloadWaveAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate);       
+       } else {
+               return CDloadCafAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate);                
+       }
+}
+
diff --git a/libs/CocosDenshion/CocosDenshion.h b/libs/CocosDenshion/CocosDenshion.h
new file mode 100644 (file)
index 0000000..638d852
--- /dev/null
@@ -0,0 +1,440 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+
+
+
+/** 
+@file
+@b IMPORTANT
+There are 3 different ways of using CocosDenshion. Depending on which you choose you 
+will need to include different files and frameworks.
+
+@par SimpleAudioEngine
+This is recommended for basic audio requirements. If you just want to play some sound fx
+and some background music and have no interest in learning the lower level workings then
+this is the interface to use.
+
+Requirements:
+ - Firmware: OS 2.2 or greater 
+ - Files: SimpleAudioEngine.*, CocosDenshion.*
+ - Frameworks: OpenAL, AudioToolbox, AVFoundation
+@par CDAudioManager
+CDAudioManager is basically a thin wrapper around an AVAudioPlayer object used for playing
+background music and a CDSoundEngine object used for playing sound effects. It manages the
+audio session for you deals with audio session interruption. It is fairly low level and it
+is expected you have some understanding of the underlying technologies. For example, for 
+many use cases regarding background music it is expected you will work directly with the
+backgroundMusic AVAudioPlayer which is exposed as a property.
+Requirements:
+  - Firmware: OS 2.2 or greater 
+  - Files: CDAudioManager.*, CocosDenshion.*
+  - Frameworks: OpenAL, AudioToolbox, AVFoundation
+
+@par CDSoundEngine
+CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch 
+example. It can playback up to 32 sounds simultaneously with control over pitch, pan
+and gain.  It can be set up to handle audio session interruption automatically.  You 
+may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine
+because you require OS 2.0 compatibility.
+Requirements:
+  - Firmware: OS 2.0 or greater 
+  - Files: CocosDenshion.*
+  - Frameworks: OpenAL, AudioToolbox
+*/ 
+
+#import <OpenAL/al.h>
+#import <OpenAL/alc.h>
+#import <AudioToolbox/AudioToolbox.h>
+#import <Foundation/Foundation.h>
+#import "CDConfig.h"
+
+
+#if !defined(CD_DEBUG) || CD_DEBUG == 0
+#define CDLOG(...) do {} while (0)
+#define CDLOGINFO(...) do {} while (0)
+
+#elif CD_DEBUG == 1
+#define CDLOG(...) NSLog(__VA_ARGS__)
+#define CDLOGINFO(...) do {} while (0)
+
+#elif CD_DEBUG > 1
+#define CDLOG(...) NSLog(__VA_ARGS__)
+#define CDLOGINFO(...) NSLog(__VA_ARGS__)
+#endif // CD_DEBUG
+
+
+#import "CDOpenALSupport.h"
+
+//Tested source limit on 2.2.1 and 3.1.2 with up to 128 sources and appears to work. Older OS versions e.g 2.2 may support only 32
+#define CD_SOURCE_LIMIT 32 //Total number of sources we will ever want, may actually get less
+#define CD_NO_SOURCE 0xFEEDFAC //Return value indicating playback failed i.e. no source
+#define CD_IGNORE_AUDIO_SESSION 0xBEEFBEE //Used internally to indicate audio session will not be handled
+#define CD_MUTE      0xFEEDBAB //Return value indicating sound engine is muted or non functioning
+#define CD_NO_SOUND = -1;
+
+#define CD_SAMPLE_RATE_HIGH 44100
+#define CD_SAMPLE_RATE_MID  22050
+#define CD_SAMPLE_RATE_LOW  16000
+#define CD_SAMPLE_RATE_BASIC 8000
+#define CD_SAMPLE_RATE_DEFAULT 44100
+
+extern NSString * const kCDN_BadAlContext;
+extern NSString * const kCDN_AsynchLoadComplete;
+
+extern float const kCD_PitchDefault;
+extern float const kCD_PitchLowerOneOctave;
+extern float const kCD_PitchHigherOneOctave;
+extern float const kCD_PanDefault;
+extern float const kCD_PanFullLeft;
+extern float const kCD_PanFullRight;
+extern float const kCD_GainDefault;
+
+enum bufferState {
+       CD_BS_EMPTY = 0,
+       CD_BS_LOADED = 1,
+       CD_BS_FAILED = 2
+};
+
+typedef struct _sourceGroup {
+       int startIndex;
+       int currentIndex;
+       int totalSources;
+       bool enabled;
+       bool nonInterruptible;
+       int *sourceStatuses;//pointer into array of source status information
+} sourceGroup;
+
+typedef struct _bufferInfo {
+       ALuint bufferId;
+       int bufferState;
+       void* bufferData;
+       ALenum format;
+       ALsizei sizeInBytes;
+       ALsizei frequencyInHertz;
+} bufferInfo;  
+
+typedef struct _sourceInfo {
+       bool usable;
+       ALuint sourceId;
+       ALuint attachedBufferId;
+} sourceInfo;  
+
+#pragma mark CDAudioTransportProtocol
+
+@protocol CDAudioTransportProtocol <NSObject>
+/** Play the audio */
+-(BOOL) play;
+/** Pause the audio, retain resources */
+-(BOOL) pause;
+/** Stop the audio, release resources */
+-(BOOL) stop;
+/** Return playback to beginning */
+-(BOOL) rewind;
+@end
+
+#pragma mark CDAudioInterruptProtocol
+
+@protocol CDAudioInterruptProtocol <NSObject>
+/** Is audio mute */
+-(BOOL) mute;
+/** If YES then audio is silenced but not stopped, calls to start new audio will proceed but silently */
+-(void) setMute:(BOOL) muteValue;
+/** Is audio enabled */
+-(BOOL) enabled;
+/** If NO then all audio is stopped and any calls to start new audio will be ignored */
+-(void) setEnabled:(BOOL) enabledValue;
+@end
+
+#pragma mark CDUtilities
+/**
+ Collection of utilities required by CocosDenshion
+ */
+@interface CDUtilities : NSObject
+{
+}      
+
+/** Fundamentally the same as the corresponding method is CCFileUtils but added to break binding to cocos2d */
++(NSString*) fullPathFromRelativePath:(NSString*) relPath;
+
+@end
+
+
+#pragma mark CDSoundEngine
+
+/** CDSoundEngine is built upon OpenAL and works with SDK 2.0.
+ CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch 
+ example. It can playback up to 32 sounds simultaneously with control over pitch, pan
+ and gain.  It can be set up to handle audio session interruption automatically.  You 
+ may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine
+ because you require OS 2.0 compatibility.
+ Requirements:
+ - Firmware: OS 2.0 or greater 
+ - Files: CocosDenshion.*
+ - Frameworks: OpenAL, AudioToolbox
+ @since v0.8
+ */
+@class CDSoundSource;
+@interface CDSoundEngine : NSObject <CDAudioInterruptProtocol> {
+       
+       bufferInfo              *_buffers;
+       sourceInfo              *_sources;
+       sourceGroup         *_sourceGroups;
+       ALCcontext              *context;
+       NSUInteger              _sourceGroupTotal;
+       UInt32                  _audioSessionCategory;
+       BOOL                    _handleAudioSession;
+       ALfloat                 _preMuteGain;
+       NSObject        *_mutexBufferLoad;
+       BOOL                    mute_;
+       BOOL                    enabled_;
+
+       ALenum                  lastErrorCode_;
+       BOOL                    functioning_;
+       float                   asynchLoadProgress_;
+       BOOL                    getGainWorks_;
+       
+       //For managing dynamic allocation of sources and buffers
+       int sourceTotal_;
+       int bufferTotal;
+        
+}
+
+@property (readwrite, nonatomic) ALfloat masterGain;
+@property (readonly)  ALenum lastErrorCode;//Last OpenAL error code that was generated
+@property (readonly)  BOOL functioning;//Is the sound engine functioning
+@property (readwrite) float asynchLoadProgress;
+@property (readonly)  BOOL getGainWorks;//Does getting the gain for a source work
+/** Total number of sources available */
+@property (readonly) int sourceTotal;
+/** Total number of source groups that have been defined */
+@property (readonly) NSUInteger sourceGroupTotal;
+
+/** Sets the sample rate for the audio mixer. For best performance this should match the sample rate of your audio content */
++(void) setMixerSampleRate:(Float32) sampleRate;
+
+/** Initializes the engine with a group definition and a total number of groups */
+-(id)init;
+
+/** Plays a sound in a channel group with a pitch, pan and gain. The sound could played looped or not */
+-(ALuint) playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop;
+
+/** Creates and returns a sound source object for the specified sound within the specified source group.
+ */
+-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId;
+
+/** Stops playing a sound */
+- (void) stopSound:(ALuint) sourceId;
+/** Stops playing a source group */
+- (void) stopSourceGroup:(int) sourceGroupId;
+/** Stops all playing sounds */
+-(void) stopAllSounds;
+-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions;
+-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total;
+-(void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible;
+-(void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled;
+-(BOOL) sourceGroupEnabled:(int) sourceGroupId;
+-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq;
+-(BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath;
+-(void) loadBuffersAsynchronously:(NSArray *) loadRequests;
+-(BOOL) unloadBuffer:(int) soundId;
+-(ALCcontext *) openALContext;
+
+/** Returns the duration of the buffer in seconds or a negative value if the buffer id is invalid */
+-(float) bufferDurationInSeconds:(int) soundId;
+/** Returns the size of the buffer in bytes or a negative value if the buffer id is invalid */
+-(ALsizei) bufferSizeInBytes:(int) soundId;
+/** Returns the sampling frequency of the buffer in hertz or a negative value if the buffer id is invalid */
+-(ALsizei) bufferFrequencyInHertz:(int) soundId;
+
+/** Used internally, never call unless you know what you are doing */
+-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource;
+
+@end
+
+#pragma mark CDSoundSource
+/** CDSoundSource is a wrapper around an OpenAL sound source.
+ It allows you to manipulate properties such as pitch, gain, pan and looping while the 
+ sound is playing. CDSoundSource is based on the old CDSourceWrapper class but with much
+ added functionality.
+ @since v1.0
+ */
+@interface CDSoundSource : NSObject <CDAudioTransportProtocol, CDAudioInterruptProtocol> {
+       ALenum lastError;
+@public
+       ALuint _sourceId;
+       ALuint _sourceIndex;
+       CDSoundEngine* _engine;
+       int _soundId;
+       float _preMuteGain;
+       BOOL enabled_;
+       BOOL mute_;
+}
+@property (readwrite, nonatomic) float pitch;
+@property (readwrite, nonatomic) float gain;
+@property (readwrite, nonatomic) float pan;
+@property (readwrite, nonatomic) BOOL looping;
+@property (readonly)  BOOL isPlaying;
+@property (readwrite, nonatomic) int soundId;
+/** Returns the duration of the attached buffer in seconds or a negative value if the buffer is invalid */
+@property (readonly) float durationInSeconds;
+
+/** Stores the last error code that occurred. Check against AL_NO_ERROR */
+@property (readonly) ALenum lastError;
+/** Do not init yourself, get an instance from the sourceForSound factory method on CDSoundEngine */
+-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine;
+
+@end
+
+#pragma mark CDAudioInterruptTargetGroup
+
+/** Container for objects that implement audio interrupt protocol i.e. they can be muted and enabled.
+ Setting mute and enabled for the group propagates to all children. 
+ Designed to be used with your CDSoundSource objects to get them to comply with global enabled and mute settings
+ if that is what you want to do.*/
+@interface CDAudioInterruptTargetGroup : NSObject <CDAudioInterruptProtocol> {
+       BOOL mute_;
+       BOOL enabled_;
+       NSMutableArray *children_;
+}
+-(void) addAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget;
+@end
+
+#pragma mark CDAsynchBufferLoader
+
+/** CDAsynchBufferLoader
+ TODO
+ */
+@interface CDAsynchBufferLoader : NSOperation {
+       NSArray *_loadRequests;
+       CDSoundEngine *_soundEngine;
+}      
+
+-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine;
+
+@end
+
+#pragma mark CDBufferLoadRequest
+
+/** CDBufferLoadRequest */
+@interface CDBufferLoadRequest: NSObject
+{
+       NSString *filePath;
+       int              soundId;
+       //id       loader;
+}
+
+@property (readonly) NSString *filePath;
+@property (readonly) int soundId;
+
+- (id)init:(int) theSoundId filePath:(const NSString *) theFilePath;
+@end
+
+/** Interpolation type */
+typedef enum {
+       kIT_Linear,                     //!Straight linear interpolation fade
+       kIT_SCurve,                     //!S curved interpolation
+       kIT_Exponential         //!Exponential interpolation
+} tCDInterpolationType;
+
+#pragma mark CDFloatInterpolator
+@interface CDFloatInterpolator: NSObject
+{
+       float start;
+       float end;
+       float lastValue;
+       tCDInterpolationType interpolationType;
+}
+@property (readwrite, nonatomic) float start;
+@property (readwrite, nonatomic) float end;
+@property (readwrite, nonatomic) tCDInterpolationType interpolationType;
+
+/** Return a value between min and max based on t which represents fractional progress where 0 is the start
+ and 1 is the end */
+-(float) interpolate:(float) t;
+-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal;
+
+@end
+
+#pragma mark CDPropertyModifier
+
+/** Base class for classes that modify properties such as pitch, pan and gain */
+@interface CDPropertyModifier: NSObject
+{
+       CDFloatInterpolator *interpolator;
+       float startValue;
+       float endValue;
+       id target;
+       BOOL stopTargetWhenComplete;
+       
+}
+@property (readwrite, nonatomic) BOOL stopTargetWhenComplete;
+@property (readwrite, nonatomic) float startValue;
+@property (readwrite, nonatomic) float endValue;
+@property (readwrite, nonatomic) tCDInterpolationType interpolationType;
+
+-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal;
+/** Set to a fractional value between 0 and 1 where 0 equals the start and 1 equals the end*/
+-(void) modify:(float) t;
+
+-(void) _setTargetProperty:(float) newVal;
+-(float) _getTargetProperty;
+-(void) _stopTarget;
+-(Class) _allowableType;
+
+@end
+
+#pragma mark CDSoundSourceFader
+
+/** Fader for CDSoundSource objects */
+@interface CDSoundSourceFader : CDPropertyModifier{}
+@end
+
+#pragma mark CDSoundSourcePanner
+
+/** Panner for CDSoundSource objects */
+@interface CDSoundSourcePanner : CDPropertyModifier{}
+@end
+
+#pragma mark CDSoundSourcePitchBender
+
+/** Pitch bender for CDSoundSource objects */
+@interface CDSoundSourcePitchBender : CDPropertyModifier{}
+@end
+
+#pragma mark CDSoundEngineFader
+
+/** Fader for CDSoundEngine objects */
+@interface CDSoundEngineFader : CDPropertyModifier{}
+@end
+
+
+
+
diff --git a/libs/CocosDenshion/CocosDenshion.m b/libs/CocosDenshion/CocosDenshion.m
new file mode 100644 (file)
index 0000000..8d94116
--- /dev/null
@@ -0,0 +1,1598 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+
+#import "CocosDenshion.h"
+
+typedef ALvoid AL_APIENTRY     (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
+ALvoid  alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
+{
+       static  alBufferDataStaticProcPtr       proc = NULL;
+    
+    if (proc == NULL) {
+        proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
+    }
+    
+    if (proc)
+        proc(bid, format, data, size, freq);
+       
+    return;
+}
+
+typedef ALvoid AL_APIENTRY     (*alcMacOSXMixerOutputRateProcPtr) (const ALdouble value);
+ALvoid  alcMacOSXMixerOutputRateProc(const ALdouble value)
+{
+       static  alcMacOSXMixerOutputRateProcPtr proc = NULL;
+    
+    if (proc == NULL) {
+        proc = (alcMacOSXMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXMixerOutputRate");
+    }
+    
+    if (proc)
+        proc(value);
+       
+    return;
+}
+
+NSString * const kCDN_BadAlContext = @"kCDN_BadAlContext";
+NSString * const kCDN_AsynchLoadComplete = @"kCDN_AsynchLoadComplete";
+float const kCD_PitchDefault = 1.0f;
+float const kCD_PitchLowerOneOctave = 0.5f;
+float const kCD_PitchHigherOneOctave = 2.0f;
+float const kCD_PanDefault = 0.0f;
+float const kCD_PanFullLeft = -1.0f;
+float const kCD_PanFullRight = 1.0f;
+float const kCD_GainDefault = 1.0f;
+
+@interface CDSoundEngine (PrivateMethods)
+-(BOOL) _initOpenAL;
+-(void) _testGetGain;
+-(void) _dumpSourceGroupsInfo;
+-(void) _getSourceIndexForSourceGroup;
+-(void) _freeSourceGroups;
+-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total; 
+@end
+
+#pragma mark -
+#pragma mark CDUtilities
+
+@implementation CDUtilities
+
++(NSString*) fullPathFromRelativePath:(NSString*) relPath
+{
+       // do not convert an absolute path (starting with '/')
+       if(([relPath length] > 0) && ([relPath characterAtIndex:0] == '/'))
+       {
+               return relPath;
+       }
+       
+       NSMutableArray *imagePathComponents = [NSMutableArray arrayWithArray:[relPath pathComponents]];
+       NSString *file = [imagePathComponents lastObject];
+       
+       [imagePathComponents removeLastObject];
+       NSString *imageDirectory = [NSString pathWithComponents:imagePathComponents];
+       
+       NSString *fullpath = [[NSBundle mainBundle] pathForResource:file ofType:nil inDirectory:imageDirectory];
+       if (fullpath == nil)
+               fullpath = relPath;
+       
+       return fullpath;        
+}
+
+@end
+
+#pragma mark -
+#pragma mark CDSoundEngine
+
+@implementation CDSoundEngine
+
+static Float32 _mixerSampleRate;
+static BOOL _mixerRateSet = NO;
+
+@synthesize lastErrorCode = lastErrorCode_;
+@synthesize functioning = functioning_;
+@synthesize asynchLoadProgress = asynchLoadProgress_;
+@synthesize getGainWorks = getGainWorks_;
+@synthesize sourceTotal = sourceTotal_;
+
++ (void) setMixerSampleRate:(Float32) sampleRate {
+       _mixerRateSet = YES;
+       _mixerSampleRate = sampleRate;
+}      
+
+- (void) _testGetGain {
+       float testValue = 0.7f;
+       ALuint testSourceId = _sources[0].sourceId;
+       alSourcef(testSourceId, AL_GAIN, 0.0f);//Start from know value
+       alSourcef(testSourceId, AL_GAIN, testValue);
+       ALfloat gainVal;
+       alGetSourcef(testSourceId, AL_GAIN, &gainVal);
+       getGainWorks_ = (gainVal == testValue);
+}
+
+//Generate sources one at a time until we fail
+-(void) _generateSources {
+       
+       _sources = (sourceInfo*)malloc( sizeof(_sources[0]) * CD_SOURCE_LIMIT);
+       BOOL hasFailed = NO;
+       sourceTotal_ = 0;
+       alGetError();//Clear error
+       while (!hasFailed && sourceTotal_ < CD_SOURCE_LIMIT) {
+               alGenSources(1, &(_sources[sourceTotal_].sourceId));
+               if (alGetError() == AL_NO_ERROR) {
+                       //Now try attaching source to null buffer
+                       alSourcei(_sources[sourceTotal_].sourceId, AL_BUFFER, 0);
+                       if (alGetError() == AL_NO_ERROR) {
+                               _sources[sourceTotal_].usable = true;   
+                               sourceTotal_++;
+                       } else {
+                               hasFailed = YES;
+                       }       
+               } else {
+                       _sources[sourceTotal_].usable = false;
+                       hasFailed = YES;
+               }       
+       }
+       //Mark the rest of the sources as not usable
+       for (int i=sourceTotal_; i < CD_SOURCE_LIMIT; i++) {
+               _sources[i].usable = false;
+       }       
+}      
+
+-(void) _generateBuffers:(int) startIndex endIndex:(int) endIndex {
+       if (_buffers) {
+               alGetError();
+               for (int i=startIndex; i <= endIndex; i++) {
+                       alGenBuffers(1, &_buffers[i].bufferId);
+                       _buffers[i].bufferData = NULL;
+                       if (alGetError() == AL_NO_ERROR) {
+                               _buffers[i].bufferState = CD_BS_EMPTY;
+                       } else {
+                               _buffers[i].bufferState = CD_BS_FAILED;
+                               CDLOG(@"Denshion::CDSoundEngine - buffer creation failed %i",i);
+                       }       
+               }
+       }       
+}
+
+/**
+ * Internal method called during init
+ */
+- (BOOL) _initOpenAL
+{
+       //ALenum                        error;
+       context = NULL;
+       ALCdevice               *newDevice = NULL;
+
+       //Set the mixer rate for the audio mixer
+       if (!_mixerRateSet) {
+               _mixerSampleRate = CD_SAMPLE_RATE_DEFAULT;
+       }
+       alcMacOSXMixerOutputRateProc(_mixerSampleRate);
+       CDLOGINFO(@"Denshion::CDSoundEngine - mixer output rate set to %0.2f",_mixerSampleRate);
+       
+       // Create a new OpenAL Device
+       // Pass NULL to specify the system's default output device
+       newDevice = alcOpenDevice(NULL);
+       if (newDevice != NULL)
+       {
+               // Create a new OpenAL Context
+               // The new context will render to the OpenAL Device just created 
+               context = alcCreateContext(newDevice, 0);
+               if (context != NULL)
+               {
+                       // Make the new context the Current OpenAL Context
+                       alcMakeContextCurrent(context);
+                       
+                       // Create some OpenAL Buffer Objects
+                       [self _generateBuffers:0 endIndex:bufferTotal-1];
+                       
+                       // Create some OpenAL Source Objects
+                       [self _generateSources];
+                       
+               }
+       } else {
+               return FALSE;//No device
+       }       
+       alGetError();//Clear error
+       return TRUE;
+}
+
+- (void) dealloc {
+       
+       ALCcontext      *currentContext = NULL;
+    ALCdevice  *device = NULL;
+       
+       [self stopAllSounds];
+
+       CDLOGINFO(@"Denshion::CDSoundEngine - Deallocing sound engine.");
+       [self _freeSourceGroups];
+       
+       // Delete the Sources
+       CDLOGINFO(@"Denshion::CDSoundEngine - deleting sources.");
+       for (int i=0; i < sourceTotal_; i++) {
+               alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Detach from current buffer
+           alDeleteSources(1, &(_sources[i].sourceId));
+               if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
+                       CDLOG(@"Denshion::CDSoundEngine - Error deleting source! %x\n", lastErrorCode_);
+               } 
+       }       
+
+       // Delete the Buffers
+       CDLOGINFO(@"Denshion::CDSoundEngine - deleting buffers.");
+       for (int i=0; i < bufferTotal; i++) {
+               alDeleteBuffers(1, &_buffers[i].bufferId);
+#ifdef CD_USE_STATIC_BUFFERS
+               if (_buffers[i].bufferData) {
+                       free(_buffers[i].bufferData);
+               }       
+#endif         
+       }       
+       CDLOGINFO(@"Denshion::CDSoundEngine - free buffers.");
+       free(_buffers);
+    currentContext = alcGetCurrentContext();
+    //Get device for active context
+    device = alcGetContextsDevice(currentContext);
+    //Release context
+       CDLOGINFO(@"Denshion::CDSoundEngine - destroy context.");
+    alcDestroyContext(currentContext);
+    //Close device
+       CDLOGINFO(@"Denshion::CDSoundEngine - close device.");
+    alcCloseDevice(device);
+       CDLOGINFO(@"Denshion::CDSoundEngine - free sources.");
+       free(_sources);
+       
+       //Release mutexes
+       [_mutexBufferLoad release];
+       
+       [super dealloc];
+}      
+
+-(NSUInteger) sourceGroupTotal {
+       return _sourceGroupTotal;
+}      
+
+-(void) _freeSourceGroups 
+{
+       CDLOGINFO(@"Denshion::CDSoundEngine freeing source groups");
+       if(_sourceGroups) {
+               for (int i=0; i < _sourceGroupTotal; i++) {
+                       if (_sourceGroups[i].sourceStatuses) {
+                               free(_sourceGroups[i].sourceStatuses);
+                               CDLOGINFO(@"Denshion::CDSoundEngine freed source statuses %i",i);
+                       }       
+               }
+               free(_sourceGroups);
+       }       
+}      
+
+-(BOOL) _redefineSourceGroups:(int[]) definitions total:(NSUInteger) total
+{
+       if (_sourceGroups) {
+               //Stop all sounds
+               [self stopAllSounds];
+               //Need to free source groups
+               [self _freeSourceGroups];
+       }
+       return [self _setUpSourceGroups:definitions total:total];
+}      
+
+-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total 
+{
+       _sourceGroups = (sourceGroup *)malloc( sizeof(_sourceGroups[0]) * total);
+       if(!_sourceGroups) {
+               CDLOG(@"Denshion::CDSoundEngine - source groups memory allocation failed");
+               return NO;
+       }
+       
+       _sourceGroupTotal = total;
+       int sourceCount = 0;
+       for (int i=0; i < _sourceGroupTotal; i++) {
+               
+               _sourceGroups[i].startIndex = 0;
+               _sourceGroups[i].currentIndex = _sourceGroups[i].startIndex;
+               _sourceGroups[i].enabled = false;
+               _sourceGroups[i].nonInterruptible = false;
+               _sourceGroups[i].totalSources = definitions[i];
+               _sourceGroups[i].sourceStatuses = malloc(sizeof(_sourceGroups[i].sourceStatuses[0]) * _sourceGroups[i].totalSources);
+               if (_sourceGroups[i].sourceStatuses) {
+                       for (int j=0; j < _sourceGroups[i].totalSources; j++) {
+                               //First bit is used to indicate whether source is locked, index is shifted back 1 bit
+                               _sourceGroups[i].sourceStatuses[j] = (sourceCount + j) << 1;    
+                       }       
+               }       
+               sourceCount += definitions[i];
+       }
+       return YES;
+}
+
+-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total {
+       [self _redefineSourceGroups:sourceGroupDefinitions total:total];
+}
+
+-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions {
+       CDLOGINFO(@"Denshion::CDSoundEngine - source groups defined by NSArray.");
+       NSUInteger totalDefs = [sourceGroupDefinitions count];
+       int* defs = (int *)malloc( sizeof(int) * totalDefs);
+       int currentIndex = 0;
+       for (id currentDef in sourceGroupDefinitions) {
+               if ([currentDef isKindOfClass:[NSNumber class]]) {
+                       defs[currentIndex] = (int)[(NSNumber*)currentDef integerValue];
+                       CDLOGINFO(@"Denshion::CDSoundEngine - found definition %i.",defs[currentIndex]);
+               } else {
+                       CDLOG(@"Denshion::CDSoundEngine - warning, did not understand source definition.");
+                       defs[currentIndex] = 0;
+               }       
+               currentIndex++;
+       }
+       [self _redefineSourceGroups:defs total:totalDefs];
+       free(defs);
+}      
+
+- (id)init
+{      
+       if ((self = [super init])) {
+               
+               //Create mutexes
+               _mutexBufferLoad = [[NSObject alloc] init];
+               
+               asynchLoadProgress_ = 0.0f;
+               
+               bufferTotal = CD_BUFFERS_START;
+               _buffers = (bufferInfo *)malloc( sizeof(_buffers[0]) * bufferTotal);
+       
+               // Initialize our OpenAL environment
+               if ([self _initOpenAL]) {
+                       //Set up the default source group - a single group that contains all the sources
+                       int sourceDefs[1];
+                       sourceDefs[0] = self.sourceTotal;
+                       [self _setUpSourceGroups:sourceDefs total:1];
+
+                       functioning_ = YES;
+                       //Synchronize premute gain
+                       _preMuteGain = self.masterGain;
+                       mute_ = NO;
+                       enabled_ = YES;
+                       //Test whether get gain works for sources
+                       [self _testGetGain];
+               } else {
+                       //Something went wrong with OpenAL
+                       functioning_ = NO;
+               }
+       }
+       
+       return self;
+}
+
+/**
+ * Delete the buffer identified by soundId
+ * @return true if buffer deleted successfully, otherwise false
+ */
+- (BOOL) unloadBuffer:(int) soundId 
+{
+       //Ensure soundId is within array bounds otherwise memory corruption will occur
+       if (soundId < 0 || soundId >= bufferTotal) {
+               CDLOG(@"Denshion::CDSoundEngine - soundId is outside array bounds, maybe you need to increase CD_MAX_BUFFERS");
+               return FALSE;
+       }       
+       
+       //Before a buffer can be deleted any sources that are attached to it must be stopped
+       for (int i=0; i < sourceTotal_; i++) {
+               //Note: tried getting the AL_BUFFER attribute of the source instead but doesn't
+               //appear to work on a device - just returned zero.
+               if (_buffers[soundId].bufferId == _sources[i].attachedBufferId) {
+                       
+                       CDLOG(@"Denshion::CDSoundEngine - Found attached source %i %i %i",i,_buffers[soundId].bufferId,_sources[i].sourceId);
+#ifdef CD_USE_STATIC_BUFFERS
+                       //When using static buffers a crash may occur if a source is playing with a buffer that is about
+                       //to be deleted even though we stop the source and successfully delete the buffer. Crash is confirmed
+                       //on 2.2.1 and 3.1.2, however, it will only occur if a source is used rapidly after having its prior
+                       //data deleted. To avoid any possibility of the crash we wait for the source to finish playing.
+                       ALint state;
+                       
+                       alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state);
+                       
+                       if (state == AL_PLAYING) {
+                               CDLOG(@"Denshion::CDSoundEngine - waiting for source to complete playing before removing buffer data"); 
+                               alSourcei(_sources[i].sourceId, AL_LOOPING, FALSE);//Turn off looping otherwise loops will never end
+                               while (state == AL_PLAYING) {
+                                       alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state);
+                                       usleep(10000);
+                               }
+                       }
+#endif                 
+                       //Stop source and detach
+                       alSourceStop(_sources[i].sourceId);     
+                       if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
+                               CDLOG(@"Denshion::CDSoundEngine - error stopping source: %x\n", lastErrorCode_);
+                       }       
+                       
+                       alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Attach to "NULL" buffer to detach
+                       if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
+                               CDLOG(@"Denshion::CDSoundEngine - error detaching buffer: %x\n", lastErrorCode_);
+                       } else {
+                               //Record that source is now attached to nothing
+                               _sources[i].attachedBufferId = 0;
+                       }       
+               }       
+       }       
+       
+       alDeleteBuffers(1, &_buffers[soundId].bufferId);
+       if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
+               CDLOG(@"Denshion::CDSoundEngine - error deleting buffer: %x\n", lastErrorCode_);
+               _buffers[soundId].bufferState = CD_BS_FAILED;
+               return FALSE;
+       } else {
+#ifdef CD_USE_STATIC_BUFFERS
+               //Free previous data, if alDeleteBuffer has returned without error then no 
+               if (_buffers[soundId].bufferData) {
+                       CDLOGINFO(@"Denshion::CDSoundEngine - freeing static data for soundId %i @ %i",soundId,_buffers[soundId].bufferData);
+                       free(_buffers[soundId].bufferData);//Free the old data
+                       _buffers[soundId].bufferData = NULL;
+               }
+#endif         
+       }       
+       
+       alGenBuffers(1, &_buffers[soundId].bufferId);
+       if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
+               CDLOG(@"Denshion::CDSoundEngine - error regenerating buffer: %x\n", lastErrorCode_);
+               _buffers[soundId].bufferState = CD_BS_FAILED;
+               return FALSE;
+       } else {
+               //We now have an empty buffer
+               _buffers[soundId].bufferState = CD_BS_EMPTY;
+               CDLOGINFO(@"Denshion::CDSoundEngine - buffer %i successfully unloaded\n",soundId);
+               return TRUE;
+       }       
+}      
+
+/**
+ * Load buffers asynchronously 
+ * Check asynchLoadProgress for progress. asynchLoadProgress represents fraction of completion. When it equals 1.0 loading
+ * is complete. NB: asynchLoadProgress is simply based on the number of load requests, it does not take into account
+ * file sizes.
+ * @param An array of CDBufferLoadRequest objects
+ */
+- (void) loadBuffersAsynchronously:(NSArray *) loadRequests {
+       @synchronized(self) {
+               asynchLoadProgress_ = 0.0f;
+               CDAsynchBufferLoader *loaderOp = [[[CDAsynchBufferLoader alloc] init:loadRequests soundEngine:self] autorelease];
+               NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease];
+               [opQ addOperation:loaderOp];
+       }
+}      
+
+-(BOOL) _resizeBuffers:(int) increment {
+       
+       void * tmpBufferInfos = realloc( _buffers, sizeof(_buffers[0]) * (bufferTotal + increment) );
+       
+       if(!tmpBufferInfos) {
+               free(tmpBufferInfos);
+               return NO;
+       } else {
+               _buffers = tmpBufferInfos;
+               int oldBufferTotal = bufferTotal;
+               bufferTotal = bufferTotal + increment;
+               [self _generateBuffers:oldBufferTotal endIndex:bufferTotal-1];
+               return YES;
+       }       
+}      
+
+-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq {
+
+       @synchronized(_mutexBufferLoad) {
+               
+               if (!functioning_) {
+                       //OpenAL initialisation has previously failed
+                       CDLOG(@"Denshion::CDSoundEngine - Loading buffer failed because sound engine state != functioning");
+                       return FALSE;
+               }
+               
+               //Ensure soundId is within array bounds otherwise memory corruption will occur
+               if (soundId < 0) {
+                       CDLOG(@"Denshion::CDSoundEngine - soundId is negative");
+                       return FALSE;
+               }
+               
+               if (soundId >= bufferTotal) {
+                       //Need to resize the buffers
+                       int requiredIncrement = CD_BUFFERS_INCREMENT;
+                       while (bufferTotal + requiredIncrement < soundId) {
+                               requiredIncrement += CD_BUFFERS_INCREMENT;
+                       }
+                       CDLOGINFO(@"Denshion::CDSoundEngine - attempting to resize buffers by %i for sound %i",requiredIncrement,soundId);
+                       if (![self _resizeBuffers:requiredIncrement]) {
+                               CDLOG(@"Denshion::CDSoundEngine - buffer resize failed");
+                               return FALSE;
+                       }       
+               }       
+               
+               if (soundData)
+               {
+                       if (_buffers[soundId].bufferState != CD_BS_EMPTY) {
+                               CDLOGINFO(@"Denshion::CDSoundEngine - non empty buffer, regenerating");
+                               if (![self unloadBuffer:soundId]) {
+                                       //Deletion of buffer failed, delete buffer routine has set buffer state and lastErrorCode
+                                       return NO;
+                               }       
+                       }       
+                       
+#ifdef CD_DEBUG
+                       //Check that sample rate matches mixer rate and warn if they do not
+                       if (freq != (int)_mixerSampleRate) {
+                               CDLOGINFO(@"Denshion::CDSoundEngine - WARNING sample rate does not match mixer sample rate performance may not be optimal.");
+                       }       
+#endif         
+                       
+#ifdef CD_USE_STATIC_BUFFERS
+                       alBufferDataStaticProc(_buffers[soundId].bufferId, format, soundData, size, freq);
+                       _buffers[soundId].bufferData = data;//Save the pointer to the new data
+#else          
+                       alBufferData(_buffers[soundId].bufferId, format, soundData, size, freq);
+#endif
+                       if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) {
+                               CDLOG(@"Denshion::CDSoundEngine -  error attaching audio to buffer: %x", lastErrorCode_);
+                               _buffers[soundId].bufferState = CD_BS_FAILED;
+                               return FALSE;
+                       } 
+               } else {
+                       CDLOG(@"Denshion::CDSoundEngine Buffer data is null!");
+                       _buffers[soundId].bufferState = CD_BS_FAILED;
+                       return FALSE;
+               }       
+               
+               _buffers[soundId].format = format;
+               _buffers[soundId].sizeInBytes = size;
+               _buffers[soundId].frequencyInHertz = freq;
+               _buffers[soundId].bufferState = CD_BS_LOADED;
+               CDLOGINFO(@"Denshion::CDSoundEngine Buffer %i loaded format:%i freq:%i size:%i",soundId,format,freq,size);
+               return TRUE;
+       }//end mutex
+}      
+
+/**
+ * Load sound data for later play back.
+ * @return TRUE if buffer loaded okay for play back otherwise false
+ */
+- (BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath
+{
+
+       ALvoid* data;
+       ALenum  format;
+       ALsizei size;
+       ALsizei freq;
+       
+       CDLOGINFO(@"Denshion::CDSoundEngine - Loading openAL buffer %i %@", soundId, filePath);
+       
+       CFURLRef fileURL = nil;
+       NSString *path = [CDUtilities fullPathFromRelativePath:filePath];
+       if (path) {
+               fileURL = (CFURLRef)[[NSURL fileURLWithPath:path] retain];
+       }
+
+       if (fileURL)
+       {
+               data = CDGetOpenALAudioData(fileURL, &size, &format, &freq);
+               CFRelease(fileURL);
+               BOOL result = [self loadBufferFromData:soundId soundData:data format:format size:size freq:freq];
+#ifndef CD_USE_STATIC_BUFFERS
+               free(data);//Data can be freed here because alBufferData performs a memcpy              
+#endif
+               return result;
+       } else {
+               CDLOG(@"Denshion::CDSoundEngine Could not find file!\n");
+               //Don't change buffer state here as it will be the same as before method was called     
+               return FALSE;
+       }       
+}
+
+-(BOOL) validateBufferId:(int) soundId {
+       if (soundId < 0 || soundId >= bufferTotal) {
+               CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId buffer outside range %i",soundId);
+               return NO;
+       } else if (_buffers[soundId].bufferState != CD_BS_LOADED) {
+               CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId invalide buffer state %i",soundId);
+               return NO;
+       } else {
+               return YES;
+       }       
+}      
+
+-(float) bufferDurationInSeconds:(int) soundId {
+       if ([self validateBufferId:soundId]) {
+               float factor = 0.0f;
+               switch (_buffers[soundId].format) {
+                       case AL_FORMAT_MONO8:
+                               factor = 1.0f;
+                               break;
+                       case AL_FORMAT_MONO16:
+                               factor = 0.5f;
+                               break;
+                       case AL_FORMAT_STEREO8:
+                               factor = 0.5f;
+                               break;
+                       case AL_FORMAT_STEREO16:
+                               factor = 0.25f;
+                               break;
+               }       
+               return (float)_buffers[soundId].sizeInBytes/(float)_buffers[soundId].frequencyInHertz * factor;
+       } else {
+               return -1.0f;
+       }       
+}      
+
+-(ALsizei) bufferSizeInBytes:(int) soundId {
+       if ([self validateBufferId:soundId]) {
+               return _buffers[soundId].sizeInBytes;
+       } else {
+               return -1.0f;
+       }       
+}      
+
+-(ALsizei) bufferFrequencyInHertz:(int) soundId {
+       if ([self validateBufferId:soundId]) {
+               return _buffers[soundId].frequencyInHertz;
+       } else {
+               return -1.0f;
+       }       
+}      
+
+- (ALfloat) masterGain {
+       if (mute_) {
+               //When mute the real gain will always be 0 therefore return the preMuteGain value
+               return _preMuteGain;
+       } else {        
+               ALfloat gain;
+               alGetListenerf(AL_GAIN, &gain);
+               return gain;
+       }       
+}      
+
+/**
+ * Overall gain setting multiplier. e.g 0.5 is half the gain.
+ */
+- (void) setMasterGain:(ALfloat) newGainValue {
+       if (mute_) {
+               _preMuteGain = newGainValue;
+       } else {        
+               alListenerf(AL_GAIN, newGainValue);
+       }       
+}
+
+#pragma mark CDSoundEngine AudioInterrupt protocol
+- (BOOL) mute {
+       return mute_;
+}      
+
+/**
+ * Setting mute silences all sounds but playing sounds continue to advance playback
+ */
+- (void) setMute:(BOOL) newMuteValue {
+       
+       if (newMuteValue == mute_) {
+               return;
+       }
+       
+       mute_ = newMuteValue;
+       if (mute_) {
+               //Remember what the gain was
+               _preMuteGain = self.masterGain;
+               //Set gain to 0 - do not use the property as this will adjust preMuteGain when muted
+               alListenerf(AL_GAIN, 0.0f);
+       } else {
+               //Restore gain to what it was before being muted
+               self.masterGain = _preMuteGain;
+       }       
+}
+
+- (BOOL) enabled {
+       return enabled_;
+}
+
+- (void) setEnabled:(BOOL)enabledValue
+{
+       if (enabled_ == enabledValue) {
+               return;
+       }       
+       enabled_ = enabledValue;
+       if (enabled_ == NO) {
+               [self stopAllSounds];
+       }       
+}      
+
+-(void) _lockSource:(int) sourceIndex lock:(BOOL) lock {
+       BOOL found = NO;
+       for (int i=0; i < _sourceGroupTotal && !found; i++) {
+               if (_sourceGroups[i].sourceStatuses) {
+                       for (int j=0; j < _sourceGroups[i].totalSources && !found; j++) {
+                               //First bit is used to indicate whether source is locked, index is shifted back 1 bit
+                               if((_sourceGroups[i].sourceStatuses[j] >> 1)==sourceIndex) {
+                                       if (lock) {
+                                               //Set first bit to lock this source
+                                               _sourceGroups[i].sourceStatuses[j] |= 1;
+                                       } else {
+                                               //Unset first bit to unlock this source
+                                               _sourceGroups[i].sourceStatuses[j] &= ~1; 
+                                       }       
+                                       found = YES;
+                               }       
+                       }       
+               }       
+       }
+}      
+
+-(int) _getSourceIndexForSourceGroup:(int)sourceGroupId 
+{
+       //Ensure source group id is valid to prevent memory corruption
+       if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
+               CDLOG(@"Denshion::CDSoundEngine invalid source group id %i",sourceGroupId);
+               return CD_NO_SOURCE;
+       }       
+
+       int sourceIndex = -1;//Using -1 to indicate no source found
+       BOOL complete = NO;
+       ALint sourceState = 0;
+       sourceGroup *thisSourceGroup = &_sourceGroups[sourceGroupId];
+       thisSourceGroup->currentIndex = thisSourceGroup->startIndex;
+       while (!complete) {
+               //Iterate over sources looking for one that is not locked, first bit indicates if source is locked
+               if ((thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] & 1) == 0) {
+                       //This source is not locked
+                       sourceIndex = thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] >> 1;//shift back to get the index
+                       if (thisSourceGroup->nonInterruptible) {
+                               //Check if this source is playing, if so it can't be interrupted
+                               alGetSourcei(_sources[sourceIndex].sourceId, AL_SOURCE_STATE, &sourceState);
+                               if (sourceState != AL_PLAYING) {
+                                       //complete = YES;
+                                       //Set start index so next search starts at the next position
+                                       thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1;
+                                       break;
+                               } else {
+                                       sourceIndex = -1;//The source index was no good because the source was playing
+                               }       
+                       } else {        
+                               //complete = YES;
+                               //Set start index so next search starts at the next position
+                               thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1;
+                               break;
+                       }       
+               }
+               thisSourceGroup->currentIndex++;
+               if (thisSourceGroup->currentIndex >= thisSourceGroup->totalSources) {
+                       //Reset to the beginning
+                       thisSourceGroup->currentIndex = 0;      
+               }       
+               if (thisSourceGroup->currentIndex == thisSourceGroup->startIndex) {
+                       //We have looped around and got back to the start
+                       complete = YES;
+               }       
+       }
+
+       //Reset start index to beginning if beyond bounds
+       if (thisSourceGroup->startIndex >= thisSourceGroup->totalSources) {
+               thisSourceGroup->startIndex = 0;        
+       }       
+       
+       if (sourceIndex >= 0) {
+               return sourceIndex;
+       } else {        
+               return CD_NO_SOURCE;
+       }       
+       
+}      
+
+/**
+ * Play a sound.
+ * @param soundId the id of the sound to play (buffer id).
+ * @param SourceGroupId the source group that will be used to play the sound.
+ * @param pitch pitch multiplier. e.g 1.0 is unaltered, 0.5 is 1 octave lower. 
+ * @param pan stereo position. -1 is fully left, 0 is centre and 1 is fully right.
+ * @param gain gain multiplier. e.g. 1.0 is unaltered, 0.5 is half the gain
+ * @param loop should the sound be looped or one shot.
+ * @return the id of the source being used to play the sound or CD_MUTE if the sound engine is muted or non functioning 
+ * or CD_NO_SOURCE if a problem occurs setting up the source
+ * 
+ */
+- (ALuint)playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop {
+
+#ifdef CD_DEBUG
+       //Sanity check parameters - only in DEBUG
+       NSAssert(soundId >= 0, @"soundId can not be negative");
+       NSAssert(soundId < bufferTotal, @"soundId exceeds limit");
+       NSAssert(sourceGroupId >= 0, @"sourceGroupId can not be negative");
+       NSAssert(sourceGroupId < _sourceGroupTotal, @"sourceGroupId exceeds limit");
+       NSAssert(pitch > 0, @"pitch must be greater than zero");
+       NSAssert(pan >= -1 && pan <= 1, @"pan must be between -1 and 1");
+       NSAssert(gain >= 0, @"gain can not be negative");
+#endif
+       //If mute or initialisation has failed or buffer is not loaded then do nothing
+       if (!enabled_ || !functioning_ || _buffers[soundId].bufferState != CD_BS_LOADED || _sourceGroups[sourceGroupId].enabled) {
+#ifdef CD_DEBUG
+               if (!functioning_) {
+                       CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because sound engine is not functioning");
+               } else if (_buffers[soundId].bufferState != CD_BS_LOADED) {
+                       CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because buffer %i is not loaded", soundId);
+               }       
+#endif         
+               return CD_MUTE;
+       }       
+
+       int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];//This method ensures sourceIndex is valid
+       
+       if (sourceIndex != CD_NO_SOURCE) {
+               ALint state;
+               ALuint source = _sources[sourceIndex].sourceId;
+               ALuint buffer = _buffers[soundId].bufferId;
+               alGetError();//Clear the error code     
+               alGetSourcei(source, AL_SOURCE_STATE, &state);
+               if (state == AL_PLAYING) {
+                       alSourceStop(source);
+               }       
+               alSourcei(source, AL_BUFFER, buffer);//Attach to sound
+               alSourcef(source, AL_PITCH, pitch);//Set pitch
+               alSourcei(source, AL_LOOPING, loop);//Set looping
+               alSourcef(source, AL_GAIN, gain);//Set gain/volume
+               float sourcePosAL[] = {pan, 0.0f, 0.0f};//Set position - just using left and right panning
+               alSourcefv(source, AL_POSITION, sourcePosAL);
+               alGetError();//Clear the error code
+               alSourcePlay(source);
+               if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) {
+                       //Everything was okay
+                       _sources[sourceIndex].attachedBufferId = buffer;
+                       return source;
+               } else {
+                       if (alcGetCurrentContext() == NULL) {
+                               CDLOGINFO(@"Denshion::CDSoundEngine - posting bad OpenAL context message");
+                               [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil];
+                       }                               
+                       return CD_NO_SOURCE;
+               }       
+       } else {        
+               return CD_NO_SOURCE;
+       }       
+}
+
+-(BOOL) _soundSourceAttachToBuffer:(CDSoundSource*) soundSource soundId:(int) soundId  {
+       //Attach the source to the buffer
+       ALint state;
+       ALuint source = soundSource->_sourceId;
+       ALuint buffer = _buffers[soundId].bufferId;
+       alGetSourcei(source, AL_SOURCE_STATE, &state);
+       if (state == AL_PLAYING) {
+               alSourceStop(source);
+       }
+       alGetError();//Clear the error code     
+       alSourcei(source, AL_BUFFER, buffer);//Attach to sound data
+       if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) {
+               _sources[soundSource->_sourceIndex].attachedBufferId = buffer;
+               //_sourceBufferAttachments[soundSource->_sourceIndex] = buffer;//Keep track of which
+               soundSource->_soundId = soundId;
+               return YES;
+       } else {
+               return NO;
+       }       
+}      
+
+/**
+ * Get a sound source for the specified sound in the specified source group
+ */
+-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId 
+{
+       if (!functioning_) {
+               return nil;
+       }       
+       //Check if a source is available
+       int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];
+       if (sourceIndex != CD_NO_SOURCE) { 
+               CDSoundSource *result = [[CDSoundSource alloc] init:_sources[sourceIndex].sourceId sourceIndex:sourceIndex soundEngine:self];
+               [self _lockSource:sourceIndex lock:YES];
+               //Try to attach to the buffer
+               if ([self _soundSourceAttachToBuffer:result soundId:soundId]) {
+                       //Set to a known state
+                       result.pitch = 1.0f;
+                       result.pan = 0.0f;
+                       result.gain = 1.0f;
+                       result.looping = NO;
+                       return [result autorelease];
+               } else {
+                       //Release the sound source we just created, this will also unlock the source
+                       [result release];
+                       return nil;
+               }       
+       } else {
+               //No available source within that source group
+               return nil;
+       }
+}      
+
+-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource {
+       CDLOGINFO(@"Denshion::CDSoundEngine _soundSourcePreRelease %i",soundSource->_sourceIndex);
+       //Unlock the sound source's source
+       [self _lockSource:soundSource->_sourceIndex lock:NO];
+}      
+
+/**
+ * Stop all sounds playing within a source group
+ */
+- (void) stopSourceGroup:(int) sourceGroupId {
+       
+       if (!functioning_ || sourceGroupId >= _sourceGroupTotal || sourceGroupId < 0) {
+               return;
+       }       
+       int sourceCount = _sourceGroups[sourceGroupId].totalSources;
+       for (int i=0; i < sourceCount; i++) {
+               int sourceIndex = _sourceGroups[sourceGroupId].sourceStatuses[i] >> 1;
+               alSourceStop(_sources[sourceIndex].sourceId);
+       }
+       alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
+}      
+
+/**
+ * Stop a sound playing.
+ * @param sourceId an OpenAL source identifier i.e. the return value of playSound
+ */
+- (void)stopSound:(ALuint) sourceId {
+       if (!functioning_) {
+               return;
+       }       
+       alSourceStop(sourceId);
+       alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
+}
+
+- (void) stopAllSounds {
+       for (int i=0; i < sourceTotal_; i++) {
+               alSourceStop(_sources[i].sourceId);
+       }       
+       alGetError();//Clear error in case we stopped any sounds that couldn't be stopped
+}      
+
+/**
+ * Set a source group as non interruptible.  Default is that source groups are interruptible.
+ * Non interruptible means that if a request to play a sound is made for a source group and there are
+ * no free sources available then the play request will be ignored and CD_NO_SOURCE will be returned.
+ */
+- (void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible {
+       //Ensure source group id is valid to prevent memory corruption
+       if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
+               CDLOG(@"Denshion::CDSoundEngine setSourceGroupNonInterruptible invalid source group id %i",sourceGroupId);
+               return;
+       }       
+       
+       if (isNonInterruptible) {
+               _sourceGroups[sourceGroupId].nonInterruptible = true;
+       } else {
+               _sourceGroups[sourceGroupId].nonInterruptible = false;
+       }       
+}
+
+/**
+ * Set the mute property for a source group. If mute is turned on any sounds in that source group
+ * will be stopped and further sounds in that source group will play. However, turning mute off
+ * will not restart any sounds that were playing when mute was turned on. Also the mute setting 
+ * for the sound engine must be taken into account. If the sound engine is mute no sounds will play
+ * no matter what the source group mute setting is.
+ */
+- (void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled {
+       //Ensure source group id is valid to prevent memory corruption
+       if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) {
+               CDLOG(@"Denshion::CDSoundEngine setSourceGroupEnabled invalid source group id %i",sourceGroupId);
+               return;
+       }       
+       
+       if (enabled) {
+               _sourceGroups[sourceGroupId].enabled = true;
+               [self stopSourceGroup:sourceGroupId];
+       } else {
+               _sourceGroups[sourceGroupId].enabled = false;   
+       }       
+}
+
+/**
+ * Return the mute property for the source group identified by sourceGroupId
+ */
+- (BOOL) sourceGroupEnabled:(int) sourceGroupId {
+       return _sourceGroups[sourceGroupId].enabled;
+}
+
+-(ALCcontext *) openALContext {
+       return context;
+}      
+
+- (void) _dumpSourceGroupsInfo {
+#ifdef CD_DEBUG        
+       CDLOGINFO(@"-------------- source Group Info --------------");
+       for (int i=0; i < _sourceGroupTotal; i++) {
+               CDLOGINFO(@"Group: %i start:%i total:%i",i,_sourceGroups[i].startIndex, _sourceGroups[i].totalSources);
+               CDLOGINFO(@"----- mute:%i nonInterruptible:%i",_sourceGroups[i].enabled, _sourceGroups[i].nonInterruptible);
+               CDLOGINFO(@"----- Source statuses ----");
+               for (int j=0; j < _sourceGroups[i].totalSources; j++) {
+                       CDLOGINFO(@"Source status:%i index=%i locked=%i",j,_sourceGroups[i].sourceStatuses[j] >> 1, _sourceGroups[i].sourceStatuses[j] & 1);
+               }       
+       }       
+#endif 
+}      
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+@implementation CDSoundSource
+
+@synthesize lastError;
+
+//Macro for handling the al error code
+#define CDSOUNDSOURCE_UPDATE_LAST_ERROR (lastError = alGetError())
+#define CDSOUNDSOURCE_ERROR_HANDLER ( CDSOUNDSOURCE_UPDATE_LAST_ERROR == AL_NO_ERROR)
+
+-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine {
+       if ((self = [super init])) {
+               _sourceId = theSourceId;
+               _engine = engine;
+               _sourceIndex = index;
+               enabled_ = YES;
+               mute_ = NO;
+               _preMuteGain = self.gain;
+       } 
+       return self;
+}
+
+-(void) dealloc
+{
+       CDLOGINFO(@"Denshion::CDSoundSource deallocated %i",self->_sourceIndex);
+
+       //Notify sound engine we are about to release
+       [_engine _soundSourcePreRelease:self];
+       [super dealloc];
+}      
+
+- (void) setPitch:(float) newPitchValue {
+       alSourcef(_sourceId, AL_PITCH, newPitchValue);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+}      
+
+- (void) setGain:(float) newGainValue {
+       if (!mute_) {
+               alSourcef(_sourceId, AL_GAIN, newGainValue);    
+       } else {
+               _preMuteGain = newGainValue;
+       }       
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+}
+
+- (void) setPan:(float) newPanValue {
+       float sourcePosAL[] = {newPanValue, 0.0f, 0.0f};//Set position - just using left and right panning
+       alSourcefv(_sourceId, AL_POSITION, sourcePosAL);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+
+}
+
+- (void) setLooping:(BOOL) newLoopingValue {
+       alSourcei(_sourceId, AL_LOOPING, newLoopingValue);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+
+}
+
+- (BOOL) isPlaying {
+       ALint state;
+       alGetSourcei(_sourceId, AL_SOURCE_STATE, &state);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+       return (state == AL_PLAYING);
+}      
+
+- (float) pitch {
+       ALfloat pitchVal;
+       alGetSourcef(_sourceId, AL_PITCH, &pitchVal);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+       return pitchVal;
+}
+
+- (float) pan {
+       ALfloat sourcePosAL[] = {0.0f,0.0f,0.0f};
+       alGetSourcefv(_sourceId, AL_POSITION, sourcePosAL);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+       return sourcePosAL[0];
+}
+
+- (float) gain {
+       if (!mute_) {
+               ALfloat val;
+               alGetSourcef(_sourceId, AL_GAIN, &val);
+               CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+               return val;
+       } else {
+               return _preMuteGain;
+       }       
+}      
+
+- (BOOL) looping {
+       ALfloat val;
+       alGetSourcef(_sourceId, AL_LOOPING, &val);
+       CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+       return val;
+}
+
+-(BOOL) stop {
+       alSourceStop(_sourceId);
+       return CDSOUNDSOURCE_ERROR_HANDLER;
+}      
+
+-(BOOL) play {
+       if (enabled_) {
+               alSourcePlay(_sourceId);
+               CDSOUNDSOURCE_UPDATE_LAST_ERROR;
+               if (lastError != AL_NO_ERROR) {
+                       if (alcGetCurrentContext() == NULL) {
+                               CDLOGINFO(@"Denshion::CDSoundSource - posting bad OpenAL context message");
+                               [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil];
+                       }       
+                       return NO;
+               } else {
+                       return YES;
+               }       
+       } else {
+               return NO;
+       }
+}      
+
+-(BOOL) pause {
+       alSourcePause(_sourceId);
+       return CDSOUNDSOURCE_ERROR_HANDLER;
+}
+
+-(BOOL) rewind {
+       alSourceRewind(_sourceId);
+       return CDSOUNDSOURCE_ERROR_HANDLER;
+}
+
+-(void) setSoundId:(int) soundId {
+       [_engine _soundSourceAttachToBuffer:self soundId:soundId];
+}
+
+-(int) soundId {
+       return _soundId;
+}      
+
+-(float) durationInSeconds {
+       return [_engine bufferDurationInSeconds:_soundId];
+}      
+
+#pragma mark CDSoundSource AudioInterrupt protocol
+- (BOOL) mute {
+       return mute_;
+}      
+
+/**
+ * Setting mute silences all sounds but playing sounds continue to advance playback
+ */
+- (void) setMute:(BOOL) newMuteValue {
+       
+       if (newMuteValue == mute_) {
+               return;
+       }
+       
+       if (newMuteValue) {
+               //Remember what the gain was
+               _preMuteGain = self.gain;
+               self.gain = 0.0f;
+               mute_ = newMuteValue;//Make sure this is done after setting the gain property as the setter behaves differently depending on mute value
+       } else {
+               //Restore gain to what it was before being muted
+               mute_ = newMuteValue;
+               self.gain = _preMuteGain;
+       }       
+}
+
+- (BOOL) enabled {
+       return enabled_;
+}
+
+- (void) setEnabled:(BOOL)enabledValue
+{
+       if (enabled_ == enabledValue) {
+               return;
+       }       
+       enabled_ = enabledValue;
+       if (enabled_ == NO) {
+               [self stop];
+       }       
+}      
+
+@end
+
+////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDAudioInterruptTargetGroup
+
+@implementation CDAudioInterruptTargetGroup
+
+-(id) init {
+       if ((self = [super init])) {
+               children_ = [[NSMutableArray alloc] initWithCapacity:32];
+               enabled_ = YES;
+               mute_ = NO;
+       }
+       return self;
+}      
+
+-(void) addAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget {
+       //Synchronize child with group settings;
+       [interruptibleTarget setMute:mute_];
+       [interruptibleTarget setEnabled:enabled_];
+       [children_ addObject:interruptibleTarget];
+}      
+
+-(void) removeAudioInterruptTarget:(NSObject<CDAudioInterruptProtocol>*) interruptibleTarget {
+       [children_ removeObjectIdenticalTo:interruptibleTarget];
+}      
+
+- (BOOL) mute {
+       return mute_;
+}      
+
+/**
+ * Setting mute silences all sounds but playing sounds continue to advance playback
+ */
+- (void) setMute:(BOOL) newMuteValue {
+       
+       if (newMuteValue == mute_) {
+               return;
+       }
+       
+       for (NSObject<CDAudioInterruptProtocol>* target in children_) {
+               [target setMute:newMuteValue];
+       }       
+}
+
+- (BOOL) enabled {
+       return enabled_;
+}
+
+- (void) setEnabled:(BOOL)enabledValue
+{
+       if (enabledValue == enabled_) {
+               return;
+       }
+       
+       for (NSObject<CDAudioInterruptProtocol>* target in children_) {
+               [target setEnabled:enabledValue];
+       }       
+}      
+
+@end
+
+
+
+////////////////////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark CDAsynchBufferLoader
+
+@implementation CDAsynchBufferLoader
+
+-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine {
+       if ((self = [super init])) {
+               _loadRequests = loadRequests;
+               [_loadRequests retain];
+               _soundEngine = theSoundEngine;
+               [_soundEngine retain];
+       } 
+       return self;
+}      
+
+-(void) main {
+       CDLOGINFO(@"Denshion::CDAsynchBufferLoader - loading buffers");
+       [super main];
+       _soundEngine.asynchLoadProgress = 0.0f;
+
+       if ([_loadRequests count] > 0) {
+               float increment = 1.0f / [_loadRequests count];
+               //Iterate over load request and load
+               for (CDBufferLoadRequest *loadRequest in _loadRequests) {
+                       [_soundEngine loadBuffer:loadRequest.soundId filePath:loadRequest.filePath];
+                       _soundEngine.asynchLoadProgress += increment;
+               }       
+       }       
+       
+       //Completed
+       _soundEngine.asynchLoadProgress = 1.0f;
+       [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AsynchLoadComplete object:nil];
+       
+}      
+
+-(void) dealloc {
+       [_loadRequests release];
+       [_soundEngine release];
+       [super dealloc];
+}      
+
+@end
+
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDBufferLoadRequest
+
+@implementation CDBufferLoadRequest
+
+@synthesize filePath, soundId;
+
+-(id) init:(int) theSoundId filePath:(const NSString *) theFilePath {
+       if ((self = [super init])) {
+               soundId = theSoundId;
+               filePath = [theFilePath copy];//TODO: is retain necessary or does copy set retain count
+               [filePath retain];
+       } 
+       return self;
+}
+
+-(void) dealloc {
+       [filePath release];
+       [super dealloc];
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDFloatInterpolator
+
+@implementation CDFloatInterpolator
+@synthesize start,end,interpolationType;
+
+-(float) interpolate:(float) t {
+       
+       if (t < 1.0f) {
+               switch (interpolationType) {
+                       case kIT_Linear:
+                               //Linear interpolation
+                               return ((end - start) * t) + start;
+                               
+                       case kIT_SCurve:
+                               //Cubic s curve t^2 * (3 - 2t)
+                               return ((float)(t * t * (3.0 - (2.0 * t))) * (end - start)) + start;
+                               
+                       case kIT_Exponential:   
+                               //Formulas taken from EaseAction
+                               if (end > start) {
+                                       //Fade in
+                                       float logDelta = (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f;
+                                       return ((end - start) * logDelta) + start;
+                               } else {
+                                       //Fade Out
+                                       float logDelta = (-powf(2, -10 * t/1) + 1);
+                                       return ((end - start) * logDelta) + start;
+                               }
+                       default:
+                               return 0.0f;
+               }
+       } else {
+               return end;
+       } 
+}
+
+-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal {
+       if ((self = [super init])) {
+               start = startVal;
+               end = endVal;
+               interpolationType = type;
+       } 
+       return self;
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDPropertyModifier
+
+@implementation CDPropertyModifier
+
+@synthesize stopTargetWhenComplete;
+
+-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal {
+       if ((self = [super init])) {
+               if (target) {
+                       //Release the previous target if there is one
+                       [target release];
+               }       
+               target = theTarget;
+#if CD_DEBUG
+               //Check target is of the required type
+               if (![theTarget isMemberOfClass:[self _allowableType]] ) {
+                       CDLOG(@"Denshion::CDPropertyModifier target is not of type %@",[self _allowableType]);
+                       NSAssert([theTarget isKindOfClass:[CDSoundEngine class]], @"CDPropertyModifier target not of required type");
+               }
+#endif         
+               [target retain];
+               startValue = startVal;
+               endValue = endVal;
+               if (interpolator) {
+                       //Release previous interpolator if there is one
+                       [interpolator release];
+               }       
+               interpolator = [[CDFloatInterpolator alloc] init:type startVal:startVal endVal:endVal];
+               stopTargetWhenComplete = NO;
+       }
+       return self;
+}      
+
+-(void) dealloc {
+       CDLOGINFO(@"Denshion::CDPropertyModifier deallocated %@",self);
+       [target release];
+       [interpolator release];
+       [super dealloc];
+}      
+
+-(void) modify:(float) t {
+       if (t < 1.0) {
+               [self _setTargetProperty:[interpolator interpolate:t]];
+       } else {
+               //At the end
+               [self _setTargetProperty:endValue];
+               if (stopTargetWhenComplete) {
+                       [self _stopTarget];
+               }       
+       }       
+}      
+
+-(float) startValue {
+       return startValue;
+}
+
+-(void) setStartValue:(float) startVal
+{
+       startValue = startVal;
+       interpolator.start = startVal;
+}      
+
+-(float) endValue {
+       return startValue;
+}
+
+-(void) setEndValue:(float) endVal
+{
+       endValue = endVal;
+       interpolator.end = endVal;
+}      
+
+-(tCDInterpolationType) interpolationType {
+       return interpolator.interpolationType;
+}
+
+-(void) setInterpolationType:(tCDInterpolationType) interpolationType {
+       interpolator.interpolationType = interpolationType;
+}      
+
+-(void) _setTargetProperty:(float) newVal {
+
+}      
+
+-(float) _getTargetProperty {
+       return 0.0f;
+}      
+
+-(void) _stopTarget {
+
+}      
+
+-(Class) _allowableType {
+       return [NSObject class];
+}      
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDSoundSourceFader
+
+@implementation CDSoundSourceFader
+
+-(void) _setTargetProperty:(float) newVal {
+       ((CDSoundSource*)target).gain = newVal;
+}      
+
+-(float) _getTargetProperty {
+       return ((CDSoundSource*)target).gain;
+}
+
+-(void) _stopTarget {
+       [((CDSoundSource*)target) stop];
+}
+
+-(Class) _allowableType {
+       return [CDSoundSource class];
+}      
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDSoundSourcePanner
+
+@implementation CDSoundSourcePanner
+
+-(void) _setTargetProperty:(float) newVal {
+       ((CDSoundSource*)target).pan = newVal;
+}      
+
+-(float) _getTargetProperty {
+       return ((CDSoundSource*)target).pan;
+}
+
+-(void) _stopTarget {
+       [((CDSoundSource*)target) stop];
+}
+
+-(Class) _allowableType {
+       return [CDSoundSource class];
+}      
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDSoundSourcePitchBender
+
+@implementation CDSoundSourcePitchBender
+
+-(void) _setTargetProperty:(float) newVal {
+       ((CDSoundSource*)target).pitch = newVal;
+}      
+
+-(float) _getTargetProperty {
+       return ((CDSoundSource*)target).pitch;
+}
+
+-(void) _stopTarget {
+       [((CDSoundSource*)target) stop];
+}
+
+-(Class) _allowableType {
+       return [CDSoundSource class];
+}      
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+#pragma mark CDSoundEngineFader
+
+@implementation CDSoundEngineFader
+
+-(void) _setTargetProperty:(float) newVal {
+       ((CDSoundEngine*)target).masterGain = newVal;
+}      
+
+-(float) _getTargetProperty {
+       return ((CDSoundEngine*)target).masterGain;
+}
+
+-(void) _stopTarget {
+       [((CDSoundEngine*)target) stopAllSounds];
+}
+
+-(Class) _allowableType {
+       return [CDSoundEngine class];
+}      
+
+@end
+
+
diff --git a/libs/CocosDenshion/SimpleAudioEngine.h b/libs/CocosDenshion/SimpleAudioEngine.h
new file mode 100644 (file)
index 0000000..35396c6
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+
+
+#import "CDAudioManager.h"
+
+/**
+ A wrapper to the CDAudioManager object.
+ This is recommended for basic audio requirements. If you just want to play some sound fx
+ and some background music and have no interest in learning the lower level workings then
+ this is the interface to use.
+ Requirements:
+ - Firmware: OS 2.2 or greater 
+ - Files: SimpleAudioEngine.*, CocosDenshion.*
+ - Frameworks: OpenAL, AudioToolbox, AVFoundation
+ @since v0.8
+ */
+@interface SimpleAudioEngine : NSObject <CDAudioInterruptProtocol> {
+       
+       BOOL    mute_;
+       BOOL    enabled_;
+}
+
+/** Background music volume. Range is 0.0f to 1.0f. This will only have an effect if willPlayBackgroundMusic returns YES */
+@property (readwrite) float backgroundMusicVolume;
+/** Effects volume. Range is 0.0f to 1.0f */
+@property (readwrite) float effectsVolume;
+/** If NO it indicates background music will not be played either because no background music is loaded or the audio session does not permit it.*/
+@property (readonly) BOOL willPlayBackgroundMusic;
+
+/** returns the shared instance of the SimpleAudioEngine object */
++ (SimpleAudioEngine*) sharedEngine;
+
+/** Preloads a music file so it will be ready to play as background music */
+-(void) preloadBackgroundMusic:(NSString*) filePath;
+
+/** plays background music in a loop*/
+-(void) playBackgroundMusic:(NSString*) filePath;
+/** plays background music, if loop is true the music will repeat otherwise it will be played once */
+-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop;
+/** stops playing background music */
+-(void) stopBackgroundMusic;
+/** pauses the background music */
+-(void) pauseBackgroundMusic;
+/** resume background music that has been paused */
+-(void) resumeBackgroundMusic;
+/** rewind the background music */
+-(void) rewindBackgroundMusic;
+/** returns whether or not the background music is playing */
+-(BOOL) isBackgroundMusicPlaying;
+
+/** plays an audio effect with a file path*/
+-(ALuint) playEffect:(NSString*) filePath;
+/** stop a sound that is playing, note you must pass in the soundId that is returned when you started playing the sound with playEffect */
+-(void) stopEffect:(ALuint) soundId;
+/** plays an audio effect with a file path, pitch, pan and gain */
+-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain;
+/** preloads an audio effect */
+-(void) preloadEffect:(NSString*) filePath;
+/** unloads an audio effect from memory */
+-(void) unloadEffect:(NSString*) filePath;
+/** Gets a CDSoundSource object set up to play the specified file. */
+-(CDSoundSource *) soundSourceForFile:(NSString*) filePath;
+
+/** Shuts down the shared audio engine instance so that it can be reinitialised */
++(void) end;
+
+@end
diff --git a/libs/CocosDenshion/SimpleAudioEngine.m b/libs/CocosDenshion/SimpleAudioEngine.m
new file mode 100644 (file)
index 0000000..cdff26c
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ Copyright (c) 2010 Steve Oldmeadow
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $Id$
+ */
+
+#import "SimpleAudioEngine.h"
+
+@implementation SimpleAudioEngine
+
+static SimpleAudioEngine *sharedEngine = nil;
+static CDSoundEngine* soundEngine = nil;
+static CDAudioManager *am = nil;
+static CDBufferManager *bufferManager = nil;
+
+// Init
++ (SimpleAudioEngine *) sharedEngine
+{
+       @synchronized(self)     {
+               if (!sharedEngine)
+                       sharedEngine = [[SimpleAudioEngine alloc] init];
+       }
+       return sharedEngine;
+}
+
++ (id) alloc
+{
+       @synchronized(self)     {
+               NSAssert(sharedEngine == nil, @"Attempted to allocate a second instance of a singleton.");
+               return [super alloc];
+       }
+       return nil;
+}
+
+-(id) init
+{
+       if((self=[super init])) {
+               am = [CDAudioManager sharedManager];
+               soundEngine = am.soundEngine;
+               bufferManager = [[CDBufferManager alloc] initWithEngine:soundEngine];
+               mute_ = NO;
+               enabled_ = YES;
+       }
+       return self;
+}
+
+// Memory
+- (void) dealloc
+{
+       am = nil;
+       soundEngine = nil;
+       bufferManager = nil;
+       [super dealloc];
+}
+
++(void) end 
+{
+       am = nil;
+       [CDAudioManager end];
+       [bufferManager release];
+       [sharedEngine release];
+       sharedEngine = nil;
+}      
+
+#pragma mark SimpleAudioEngine - background music
+
+-(void) preloadBackgroundMusic:(NSString*) filePath {
+       [am preloadBackgroundMusic:filePath];
+}
+
+-(void) playBackgroundMusic:(NSString*) filePath
+{
+       [am playBackgroundMusic:filePath loop:TRUE];
+}
+
+-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
+{
+       [am playBackgroundMusic:filePath loop:loop];
+}
+
+-(void) stopBackgroundMusic
+{
+       [am stopBackgroundMusic];
+}
+
+-(void) pauseBackgroundMusic {
+       [am pauseBackgroundMusic];
+}      
+
+-(void) resumeBackgroundMusic {
+       [am resumeBackgroundMusic];
+}      
+
+-(void) rewindBackgroundMusic {
+       [am rewindBackgroundMusic];
+}
+
+-(BOOL) isBackgroundMusicPlaying {
+       return [am isBackgroundMusicPlaying];
+}      
+
+-(BOOL) willPlayBackgroundMusic {
+       return [am willPlayBackgroundMusic];
+}
+
+#pragma mark SimpleAudioEngine - sound effects
+
+-(ALuint) playEffect:(NSString*) filePath
+{
+       return [self playEffect:filePath pitch:1.0f pan:0.0f gain:1.0f];
+}
+
+-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain
+{
+       int soundId = [bufferManager bufferForFile:filePath create:YES];
+       if (soundId != kCDNoBuffer) {
+               return [soundEngine playSound:soundId sourceGroupId:0 pitch:pitch pan:pan gain:gain loop:false];
+       } else {
+               return CD_MUTE;
+       }       
+}
+
+-(void) stopEffect:(ALuint) soundId {
+       [soundEngine stopSound:soundId];
+}      
+
+-(void) preloadEffect:(NSString*) filePath
+{
+       int soundId = [bufferManager bufferForFile:filePath create:YES];
+       if (soundId == kCDNoBuffer) {
+               CDLOG(@"Denshion::SimpleAudioEngine sound failed to preload %@",filePath);
+       }
+}
+
+-(void) unloadEffect:(NSString*) filePath
+{
+       CDLOGINFO(@"Denshion::SimpleAudioEngine unloadedEffect %@",filePath);
+       [bufferManager releaseBufferForFile:filePath];
+}
+
+#pragma mark Audio Interrupt Protocol
+-(BOOL) mute
+{
+       return mute_;
+}
+
+-(void) setMute:(BOOL) muteValue
+{
+       if (mute_ != muteValue) {
+               mute_ = muteValue;
+               am.mute = mute_;
+       }       
+}
+
+-(BOOL) enabled
+{
+       return enabled_;
+}
+
+-(void) setEnabled:(BOOL) enabledValue
+{
+       if (enabled_ != enabledValue) {
+               enabled_ = enabledValue;
+               am.enabled = enabled_;
+       }       
+}
+
+
+#pragma mark SimpleAudioEngine - BackgroundMusicVolume
+-(float) backgroundMusicVolume
+{
+       return am.backgroundMusic.volume;
+}      
+
+-(void) setBackgroundMusicVolume:(float) volume
+{
+       am.backgroundMusic.volume = volume;
+}      
+
+#pragma mark SimpleAudioEngine - EffectsVolume
+-(float) effectsVolume
+{
+       return am.soundEngine.masterGain;
+}      
+
+-(void) setEffectsVolume:(float) volume
+{
+       am.soundEngine.masterGain = volume;
+}      
+
+-(CDSoundSource *) soundSourceForFile:(NSString*) filePath {
+       int soundId = [bufferManager bufferForFile:filePath create:YES];
+       if (soundId != kCDNoBuffer) {
+               CDSoundSource *result = [soundEngine soundSourceForSound:soundId sourceGroupId:0];
+               CDLOGINFO(@"Denshion::SimpleAudioEngine sound source created for %@",filePath);
+               return result;
+       } else {
+               return nil;
+       }       
+}      
+
+@end 
diff --git a/libs/FontLabel/FontLabel.h b/libs/FontLabel/FontLabel.h
new file mode 100644 (file)
index 0000000..6de9c2c
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  FontLabel.h
+//  FontLabel
+//
+//  Created by Kevin Ballard on 5/8/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@class ZFont;
+@class ZAttributedString;
+
+@interface FontLabel : UILabel {
+       void *reserved; // works around a bug in UILabel
+       ZFont *zFont;
+       ZAttributedString *zAttributedText;
+}
+@property (nonatomic, setter=setCGFont:) CGFontRef cgFont __AVAILABILITY_INTERNAL_DEPRECATED;
+@property (nonatomic, assign) CGFloat pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
+@property (nonatomic, retain, setter=setZFont:) ZFont *zFont;
+// if attributedText is nil, fall back on using the inherited UILabel properties
+// if attributedText is non-nil, the font/text/textColor
+// in addition, adjustsFontSizeToFitWidth does not work with attributed text
+@property (nonatomic, copy) ZAttributedString *zAttributedText;
+// -initWithFrame:fontName:pointSize: uses FontManager to look up the font name
+- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize;
+- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font;
+- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
+@end
diff --git a/libs/FontLabel/FontLabel.m b/libs/FontLabel/FontLabel.m
new file mode 100644 (file)
index 0000000..58975b1
--- /dev/null
@@ -0,0 +1,195 @@
+//
+//  FontLabel.m
+//  FontLabel
+//
+//  Created by Kevin Ballard on 5/8/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "FontLabel.h"
+#import "FontManager.h"
+#import "FontLabelStringDrawing.h"
+#import "ZFont.h"
+
+@interface ZFont (ZFontPrivate)
+@property (nonatomic, readonly) CGFloat ratio;
+@end
+
+@implementation FontLabel
+@synthesize zFont;
+@synthesize zAttributedText;
+
+- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize {
+       return [self initWithFrame:frame zFont:[[FontManager sharedManager] zFontWithName:fontName pointSize:pointSize]];
+}
+
+- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font {
+       if ((self = [super initWithFrame:frame])) {
+               zFont = [font retain];
+       }
+       return self;
+}
+
+- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize {
+       return [self initWithFrame:frame zFont:[ZFont fontWithCGFont:font size:pointSize]];
+}
+
+- (CGFontRef)cgFont {
+       return self.zFont.cgFont;
+}
+
+- (void)setCGFont:(CGFontRef)font {
+       if (self.zFont.cgFont != font) {
+               self.zFont = [ZFont fontWithCGFont:font size:self.zFont.pointSize];
+       }
+}
+
+- (CGFloat)pointSize {
+       return self.zFont.pointSize;
+}
+
+- (void)setPointSize:(CGFloat)pointSize {
+       if (self.zFont.pointSize != pointSize) {
+               self.zFont = [ZFont fontWithCGFont:self.zFont.cgFont size:pointSize];
+       }
+}
+
+- (void)setZAttributedText:(ZAttributedString *)attStr {
+       if (zAttributedText != attStr) {
+               [zAttributedText release];
+               zAttributedText = [attStr copy];
+               [self setNeedsDisplay];
+       }
+}
+
+- (void)drawTextInRect:(CGRect)rect {
+       if (self.zFont == NULL && self.zAttributedText == nil) {
+               [super drawTextInRect:rect];
+               return;
+       }
+       
+       if (self.zAttributedText == nil) {
+               // this method is documented as setting the text color for us, but that doesn't appear to be the case
+               if (self.highlighted) {
+                       [(self.highlightedTextColor ?: [UIColor whiteColor]) setFill];
+               } else {
+                       [(self.textColor ?: [UIColor blackColor]) setFill];
+               }
+               
+               ZFont *actualFont = self.zFont;
+               CGSize origSize = rect.size;
+               if (self.numberOfLines == 1) {
+                       origSize.height = actualFont.leading;
+                       CGPoint point = CGPointMake(rect.origin.x,
+                                                                               rect.origin.y + roundf(((rect.size.height - actualFont.leading) / 2.0f)));
+                       CGSize size = [self.text sizeWithZFont:actualFont];
+                       if (self.adjustsFontSizeToFitWidth && self.minimumFontSize < actualFont.pointSize) {
+                               if (size.width > origSize.width) {
+                                       CGFloat desiredRatio = (origSize.width * actualFont.ratio) / size.width;
+                                       CGFloat desiredPointSize = desiredRatio * actualFont.pointSize / actualFont.ratio;
+                                       actualFont = [actualFont fontWithSize:MAX(MAX(desiredPointSize, self.minimumFontSize), 1.0f)];
+                                       size = [self.text sizeWithZFont:actualFont];
+                               }
+                               if (!CGSizeEqualToSize(origSize, size)) {
+                                       switch (self.baselineAdjustment) {
+                                               case UIBaselineAdjustmentAlignCenters:
+                                                       point.y += roundf((origSize.height - size.height) / 2.0f);
+                                                       break;
+                                               case UIBaselineAdjustmentAlignBaselines:
+                                                       point.y += (self.zFont.ascender - actualFont.ascender);
+                                                       break;
+                                               case UIBaselineAdjustmentNone:
+                                                       break;
+                                       }
+                               }
+                       }
+                       size.width = MIN(size.width, origSize.width);
+                       // adjust the point for alignment
+                       switch (self.textAlignment) {
+                               case UITextAlignmentLeft:
+                                       break;
+                               case UITextAlignmentCenter:
+                                       point.x += (origSize.width - size.width) / 2.0f;
+                                       break;
+                               case UITextAlignmentRight:
+                                       point.x += origSize.width - size.width;
+                                       break;
+                       }
+                       [self.text drawAtPoint:point forWidth:size.width withZFont:actualFont lineBreakMode:self.lineBreakMode];
+               } else {
+                       CGSize size = [self.text sizeWithZFont:actualFont constrainedToSize:origSize lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines];
+                       CGPoint point = rect.origin;
+                       point.y += roundf((rect.size.height - size.height) / 2.0f);
+                       rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)};
+                       [self.text drawInRect:rect withZFont:actualFont lineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines];
+               }
+       } else {
+               ZAttributedString *attStr = self.zAttributedText;
+               if (self.highlighted) {
+                       // modify the string to change the base color
+                       ZMutableAttributedString *mutStr = [[attStr mutableCopy] autorelease];
+                       NSRange activeRange = NSMakeRange(0, attStr.length);
+                       while (activeRange.length > 0) {
+                               NSRange effective;
+                               UIColor *color = [attStr attribute:ZForegroundColorAttributeName atIndex:activeRange.location
+                                                        longestEffectiveRange:&effective inRange:activeRange];
+                               if (color == nil) {
+                                       [mutStr addAttribute:ZForegroundColorAttributeName value:[UIColor whiteColor] range:effective];
+                               }
+                               activeRange.location += effective.length, activeRange.length -= effective.length;
+                       }
+                       attStr = mutStr;
+               }
+               CGSize size = [attStr sizeConstrainedToSize:rect.size lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines];
+               CGPoint point = rect.origin;
+               point.y += roundf((rect.size.height - size.height) / 2.0f);
+               rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)};
+               [attStr drawInRect:rect withLineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines];
+       }
+}
+
+- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {
+       if (self.zFont == NULL && self.zAttributedText == nil) {
+               return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
+       }
+       
+       if (numberOfLines == 1) {
+               // if numberOfLines == 1 we need to use the version that converts spaces
+               CGSize size;
+               if (self.zAttributedText == nil) {
+                       size = [self.text sizeWithZFont:self.zFont];
+               } else {
+                       size = [self.zAttributedText size];
+               }
+               bounds.size.width = MIN(bounds.size.width, size.width);
+               bounds.size.height = MIN(bounds.size.height, size.height);
+       } else {
+               if (numberOfLines > 0) bounds.size.height = MIN(bounds.size.height, self.zFont.leading * numberOfLines);
+               if (self.zAttributedText == nil) {
+                       bounds.size = [self.text sizeWithZFont:self.zFont constrainedToSize:bounds.size lineBreakMode:self.lineBreakMode];
+               } else {
+                       bounds.size = [self.zAttributedText sizeConstrainedToSize:bounds.size lineBreakMode:self.lineBreakMode];
+               }
+       }
+       return bounds;
+}
+
+- (void)dealloc {
+       [zFont release];
+       [zAttributedText release];
+       [super dealloc];
+}
+@end
diff --git a/libs/FontLabel/FontLabelStringDrawing.h b/libs/FontLabel/FontLabelStringDrawing.h
new file mode 100644 (file)
index 0000000..821da22
--- /dev/null
@@ -0,0 +1,69 @@
+//
+//  FontLabelStringDrawing.h
+//  FontLabel
+//
+//  Created by Kevin Ballard on 5/5/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <UIKit/UIKit.h>
+#import "ZAttributedString.h"
+
+@class ZFont;
+
+@interface NSString (FontLabelStringDrawing)
+// CGFontRef-based methods
+- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
+- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size __AVAILABILITY_INTERNAL_DEPRECATED;
+- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size
+                  lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED;
+- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
+- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
+- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
+          lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED;
+- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
+          lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment __AVAILABILITY_INTERNAL_DEPRECATED;
+
+// ZFont-based methods
+- (CGSize)sizeWithZFont:(ZFont *)font;
+- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size;
+- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode;
+- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
+                 numberOfLines:(NSUInteger)numberOfLines;
+- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font;
+- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font;
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
+                  alignment:(UITextAlignment)alignment;
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
+                  alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines;
+@end
+
+@interface ZAttributedString (ZAttributedStringDrawing)
+- (CGSize)size;
+- (CGSize)sizeConstrainedToSize:(CGSize)size;
+- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode;
+- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
+                                 numberOfLines:(NSUInteger)numberOfLines;
+- (CGSize)drawAtPoint:(CGPoint)point;
+- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode;
+- (CGSize)drawInRect:(CGRect)rect;
+- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode;
+- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment;
+- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment
+          numberOfLines:(NSUInteger)numberOfLines;
+@end
diff --git a/libs/FontLabel/FontLabelStringDrawing.m b/libs/FontLabel/FontLabelStringDrawing.m
new file mode 100644 (file)
index 0000000..2907372
--- /dev/null
@@ -0,0 +1,892 @@
+//
+//  FontLabelStringDrawing.m
+//  FontLabel
+//
+//  Created by Kevin Ballard on 5/5/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "FontLabelStringDrawing.h"
+#import "ZFont.h"
+#import "ZAttributedStringPrivate.h"
+
+@interface ZFont (ZFontPrivate)
+@property (nonatomic, readonly) CGFloat ratio;
+@end
+
+#define kUnicodeHighSurrogateStart 0xD800
+#define kUnicodeHighSurrogateEnd 0xDBFF
+#define kUnicodeHighSurrogateMask kUnicodeHighSurrogateStart
+#define kUnicodeLowSurrogateStart 0xDC00
+#define kUnicodeLowSurrogateEnd 0xDFFF
+#define kUnicodeLowSurrogateMask kUnicodeLowSurrogateStart
+#define kUnicodeSurrogateTypeMask 0xFC00
+#define UnicharIsHighSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeHighSurrogateMask)
+#define UnicharIsLowSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeLowSurrogateMask)
+#define ConvertSurrogatePairToUTF32(high, low) ((UInt32)((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000))
+
+typedef enum {
+       kFontTableFormat4 = 4,
+       kFontTableFormat12 = 12,
+} FontTableFormat;
+
+typedef struct fontTable {
+       NSUInteger retainCount;
+       CFDataRef cmapTable;
+       FontTableFormat format;
+       union {
+               struct {
+                       UInt16 segCountX2;
+                       UInt16 *endCodes;
+                       UInt16 *startCodes;
+                       UInt16 *idDeltas;
+                       UInt16 *idRangeOffsets;
+               } format4;
+               struct {
+                       UInt32 nGroups;
+                       struct {
+                               UInt32 startCharCode;
+                               UInt32 endCharCode;
+                               UInt32 startGlyphCode;
+                       } *groups;
+               } format12;
+       } cmap;
+} fontTable;
+
+static FontTableFormat supportedFormats[] = { kFontTableFormat4, kFontTableFormat12 };
+static size_t supportedFormatsCount = sizeof(supportedFormats) / sizeof(FontTableFormat);
+
+static fontTable *newFontTable(CFDataRef cmapTable, FontTableFormat format) {
+       fontTable *table = (struct fontTable *)malloc(sizeof(struct fontTable));
+       table->retainCount = 1;
+       table->cmapTable = CFRetain(cmapTable);
+       table->format = format;
+       return table;
+}
+
+static fontTable *retainFontTable(fontTable *table) {
+       if (table != NULL) {
+               table->retainCount++;
+       }
+       return table;
+}
+
+static void releaseFontTable(fontTable *table) {
+       if (table != NULL) {
+               if (table->retainCount <= 1) {
+                       CFRelease(table->cmapTable);
+                       free(table);
+               } else {
+                       table->retainCount--;
+               }
+       }
+}
+
+static const void *fontTableRetainCallback(CFAllocatorRef allocator, const void *value) {
+       return retainFontTable((fontTable *)value);
+}
+
+static void fontTableReleaseCallback(CFAllocatorRef allocator, const void *value) {
+       releaseFontTable((fontTable *)value);
+}
+
+static const CFDictionaryValueCallBacks kFontTableDictionaryValueCallBacks = {
+       .version = 0,
+       .retain = &fontTableRetainCallback,
+       .release = &fontTableReleaseCallback,
+       .copyDescription = NULL,
+       .equal = NULL
+};
+
+// read the cmap table from the font
+// we only know how to understand some of the table formats at the moment
+static fontTable *readFontTableFromCGFont(CGFontRef font) {
+       CFDataRef cmapTable = CGFontCopyTableForTag(font, 'cmap');
+       NSCAssert1(cmapTable != NULL, @"CGFontCopyTableForTag returned NULL for 'cmap' tag in font %@",
+                          (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)"));
+       const UInt8 * const bytes = CFDataGetBytePtr(cmapTable);
+       NSCAssert1(OSReadBigInt16(bytes, 0) == 0, @"cmap table for font %@ has bad version number",
+                          (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)"));
+       UInt16 numberOfSubtables = OSReadBigInt16(bytes, 2);
+       const UInt8 *unicodeSubtable = NULL;
+       //UInt16 unicodeSubtablePlatformID;
+       UInt16 unicodeSubtablePlatformSpecificID;
+       FontTableFormat unicodeSubtableFormat;
+       const UInt8 * const encodingSubtables = &bytes[4];
+       for (UInt16 i = 0; i < numberOfSubtables; i++) {
+               const UInt8 * const encodingSubtable = &encodingSubtables[8 * i];
+               UInt16 platformID = OSReadBigInt16(encodingSubtable, 0);
+               UInt16 platformSpecificID = OSReadBigInt16(encodingSubtable, 2);
+               // find the best subtable
+               // best is defined by a combination of encoding and format
+               // At the moment we only support format 4, so ignore all other format tables
+               // We prefer platformID == 0, but we will also accept Microsoft's unicode format
+               if (platformID == 0 || (platformID == 3 && platformSpecificID == 1)) {
+                       BOOL preferred = NO;
+                       if (unicodeSubtable == NULL) {
+                               preferred = YES;
+                       } else if (platformID == 0 && platformSpecificID > unicodeSubtablePlatformSpecificID) {
+                               preferred = YES;
+                       }
+                       if (preferred) {
+                               UInt32 offset = OSReadBigInt32(encodingSubtable, 4);
+                               const UInt8 *subtable = &bytes[offset];
+                               UInt16 format = OSReadBigInt16(subtable, 0);
+                               for (size_t i = 0; i < supportedFormatsCount; i++) {
+                                       if (format == supportedFormats[i]) {
+                                               if (format >= 8) {
+                                                       // the version is a fixed-point
+                                                       UInt16 formatFrac = OSReadBigInt16(subtable, 2);
+                                                       if (formatFrac != 0) {
+                                                               // all the current formats with a Fixed version are always *.0
+                                                               continue;
+                                                       }
+                                               }
+                                               unicodeSubtable = subtable;
+                                               //unicodeSubtablePlatformID = platformID;
+                                               unicodeSubtablePlatformSpecificID = platformSpecificID;
+                                               unicodeSubtableFormat = format;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+       }
+       fontTable *table = NULL;
+       if (unicodeSubtable != NULL) {
+               table = newFontTable(cmapTable, unicodeSubtableFormat);
+               switch (unicodeSubtableFormat) {
+                       case kFontTableFormat4:
+                               // subtable format 4
+                               //UInt16 length = OSReadBigInt16(unicodeSubtable, 2);
+                               //UInt16 language = OSReadBigInt16(unicodeSubtable, 4);
+                               table->cmap.format4.segCountX2 = OSReadBigInt16(unicodeSubtable, 6);
+                               //UInt16 searchRange = OSReadBigInt16(unicodeSubtable, 8);
+                               //UInt16 entrySelector = OSReadBigInt16(unicodeSubtable, 10);
+                               //UInt16 rangeShift = OSReadBigInt16(unicodeSubtable, 12);
+                               table->cmap.format4.endCodes = (UInt16*)&unicodeSubtable[14];
+                               table->cmap.format4.startCodes = (UInt16*)&((UInt8*)table->cmap.format4.endCodes)[table->cmap.format4.segCountX2+2];
+                               table->cmap.format4.idDeltas = (UInt16*)&((UInt8*)table->cmap.format4.startCodes)[table->cmap.format4.segCountX2];
+                               table->cmap.format4.idRangeOffsets = (UInt16*)&((UInt8*)table->cmap.format4.idDeltas)[table->cmap.format4.segCountX2];
+                               //UInt16 *glyphIndexArray = &idRangeOffsets[segCountX2];
+                               break;
+                       case kFontTableFormat12:
+                               table->cmap.format12.nGroups = OSReadBigInt32(unicodeSubtable, 12);
+                               table->cmap.format12.groups = (void *)&unicodeSubtable[16];
+                               break;
+                       default:
+                               releaseFontTable(table);
+                               table = NULL;
+               }
+       }
+       CFRelease(cmapTable);
+       return table;
+}
+
+// outGlyphs must be at least size n
+static void mapCharactersToGlyphsInFont(const fontTable *table, unichar characters[], size_t charLen, CGGlyph outGlyphs[], size_t *outGlyphLen) {
+       if (table != NULL) {
+               NSUInteger j = 0;
+               switch (table->format) {
+                       case kFontTableFormat4: {
+                               for (NSUInteger i = 0; i < charLen; i++, j++) {
+                                       unichar c = characters[i];
+                                       UInt16 segOffset;
+                                       BOOL foundSegment = NO;
+                                       for (segOffset = 0; segOffset < table->cmap.format4.segCountX2; segOffset += 2) {
+                                               UInt16 endCode = OSReadBigInt16(table->cmap.format4.endCodes, segOffset);
+                                               if (endCode >= c) {
+                                                       foundSegment = YES;
+                                                       break;
+                                               }
+                                       }
+                                       if (!foundSegment) {
+                                               // no segment
+                                               // this is an invalid font
+                                               outGlyphs[j] = 0;
+                                       } else {
+                                               UInt16 startCode = OSReadBigInt16(table->cmap.format4.startCodes, segOffset);
+                                               if (!(startCode <= c)) {
+                                                       // the code falls in a hole between segments
+                                                       outGlyphs[j] = 0;
+                                               } else {
+                                                       UInt16 idRangeOffset = OSReadBigInt16(table->cmap.format4.idRangeOffsets, segOffset);
+                                                       if (idRangeOffset == 0) {
+                                                               UInt16 idDelta = OSReadBigInt16(table->cmap.format4.idDeltas, segOffset);
+                                                               outGlyphs[j] = (c + idDelta) % 65536;
+                                                       } else {
+                                                               // use the glyphIndexArray
+                                                               UInt16 glyphOffset = idRangeOffset + 2 * (c - startCode);
+                                                               outGlyphs[j] = OSReadBigInt16(&((UInt8*)table->cmap.format4.idRangeOffsets)[segOffset], glyphOffset);
+                                                       }
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+                       case kFontTableFormat12: {
+                               UInt32 lastSegment = UINT32_MAX;
+                               for (NSUInteger i = 0; i < charLen; i++, j++) {
+                                       unichar c = characters[i];
+                                       UInt32 c32 = c;
+                                       if (UnicharIsHighSurrogate(c)) {
+                                               if (i+1 < charLen) { // do we have another character after this one?
+                                                       unichar cc = characters[i+1];
+                                                       if (UnicharIsLowSurrogate(cc)) {
+                                                               c32 = ConvertSurrogatePairToUTF32(c, cc);
+                                                               i++;
+                                                       }
+                                               }
+                                       }
+                                       // Start the heuristic search
+                                       // If this is an ASCII char, just do a linear search
+                                       // Otherwise do a hinted, modified binary search
+                                       // Start the first pivot at the last range found
+                                       // And when moving the pivot, limit the movement by increasing
+                                       // powers of two. This should help with locality
+                                       __typeof__(table->cmap.format12.groups[0]) *foundGroup = NULL;
+                                       if (c32 <= 0x7F) {
+                                               // ASCII
+                                               for (UInt32 idx = 0; idx < table->cmap.format12.nGroups; idx++) {
+                                                       __typeof__(table->cmap.format12.groups[idx]) *group = &table->cmap.format12.groups[idx];
+                                                       if (c32 < OSSwapBigToHostInt32(group->startCharCode)) {
+                                                               // we've fallen into a hole
+                                                               break;
+                                                       } else if (c32 <= OSSwapBigToHostInt32(group->endCharCode)) {
+                                                               // this is the range
+                                                               foundGroup = group;
+                                                               break;
+                                                       }
+                                               }
+                                       } else {
+                                               // heuristic search
+                                               UInt32 maxJump = (lastSegment == UINT32_MAX ? UINT32_MAX / 2 : 8);
+                                               UInt32 lowIdx = 0, highIdx = table->cmap.format12.nGroups; // highIdx is the first invalid idx
+                                               UInt32 pivot = (lastSegment == UINT32_MAX ? lowIdx + (highIdx - lowIdx) / 2 : lastSegment);
+                                               while (highIdx > lowIdx) {
+                                                       __typeof__(table->cmap.format12.groups[pivot]) *group = &table->cmap.format12.groups[pivot];
+                                                       if (c32 < OSSwapBigToHostInt32(group->startCharCode)) {
+                                                               highIdx = pivot;
+                                                       } else if (c32 > OSSwapBigToHostInt32(group->endCharCode)) {
+                                                               lowIdx = pivot + 1;
+                                                       } else {
+                                                               // we've hit the range
+                                                               foundGroup = group;
+                                                               break;
+                                                       }
+                                                       if (highIdx - lowIdx > maxJump * 2) {
+                                                               if (highIdx == pivot) {
+                                                                       pivot -= maxJump;
+                                                               } else {
+                                                                       pivot += maxJump;
+                                                               }
+                                                               maxJump *= 2;
+                                                       } else {
+                                                               pivot = lowIdx + (highIdx - lowIdx) / 2;
+                                                       }
+                                               }
+                                               if (foundGroup != NULL) lastSegment = pivot;
+                                       }
+                                       if (foundGroup == NULL) {
+                                               outGlyphs[j] = 0;
+                                       } else {
+                                               outGlyphs[j] = (CGGlyph)(OSSwapBigToHostInt32(foundGroup->startGlyphCode) +
+                                                                                                (c32 - OSSwapBigToHostInt32(foundGroup->startCharCode)));
+                                       }
+                               }
+                               break;
+                       }
+               }
+               if (outGlyphLen != NULL) *outGlyphLen = j;
+       } else {
+               // we have no table, so just null out the glyphs
+               bzero(outGlyphs, charLen*sizeof(CGGlyph));
+               if (outGlyphLen != NULL) *outGlyphLen = 0;
+       }
+}
+
+static BOOL mapGlyphsToAdvancesInFont(ZFont *font, size_t n, CGGlyph glyphs[], CGFloat outAdvances[]) {
+       int advances[n];
+       if (CGFontGetGlyphAdvances(font.cgFont, glyphs, n, advances)) {
+               CGFloat ratio = font.ratio;
+               
+               for (size_t i = 0; i < n; i++) {
+                       outAdvances[i] = advances[i]*ratio;
+               }
+               return YES;
+       } else {
+               bzero(outAdvances, n*sizeof(CGFloat));
+       }
+       return NO;
+}
+
+static id getValueOrDefaultForRun(ZAttributeRun *run, NSString *key) {
+       id value = [run.attributes objectForKey:key];
+       if (value == nil) {
+               static NSDictionary *defaultValues = nil;
+               if (defaultValues == nil) {
+                       defaultValues = [[NSDictionary alloc] initWithObjectsAndKeys:
+                                                        [ZFont fontWithUIFont:[UIFont systemFontOfSize:12]], ZFontAttributeName,
+                                                        [UIColor blackColor], ZForegroundColorAttributeName,
+                                                        [UIColor clearColor], ZBackgroundColorAttributeName,
+                                                        [NSNumber numberWithInt:ZUnderlineStyleNone], ZUnderlineStyleAttributeName,
+                                                        nil];
+               }
+               value = [defaultValues objectForKey:key];
+       }
+       return value;
+}
+
+static void readRunInformation(NSArray *attributes, NSUInteger len, CFMutableDictionaryRef fontTableMap,
+                                                          NSUInteger index, ZAttributeRun **currentRun, NSUInteger *nextRunStart,
+                                                          ZFont **currentFont, fontTable **currentTable) {
+       *currentRun = [attributes objectAtIndex:index];
+       *nextRunStart = ([attributes count] > index+1 ? [[attributes objectAtIndex:index+1] index] : len);
+       *currentFont = getValueOrDefaultForRun(*currentRun, ZFontAttributeName);
+       if (!CFDictionaryGetValueIfPresent(fontTableMap, (*currentFont).cgFont, (const void **)currentTable)) {
+               *currentTable = readFontTableFromCGFont((*currentFont).cgFont);
+               CFDictionarySetValue(fontTableMap, (*currentFont).cgFont, *currentTable);
+               releaseFontTable(*currentTable);
+       }
+}
+
+static CGSize drawOrSizeTextConstrainedToSize(BOOL performDraw, NSString *string, NSArray *attributes, CGSize constrainedSize, NSUInteger maxLines,
+                                                                                         UILineBreakMode lineBreakMode, UITextAlignment alignment, BOOL ignoreColor) {
+       NSUInteger len = [string length];
+       NSUInteger idx = 0;
+       CGPoint drawPoint = CGPointZero;
+       CGSize retValue = CGSizeZero;
+       CGContextRef ctx = (performDraw ? UIGraphicsGetCurrentContext() : NULL);
+       
+       BOOL convertNewlines = (maxLines == 1);
+       
+       // Extract the characters from the string
+       // Convert newlines to spaces if necessary
+       unichar *characters = (unichar *)malloc(sizeof(unichar) * len);
+       if (convertNewlines) {
+               NSCharacterSet *charset = [NSCharacterSet newlineCharacterSet];
+               NSRange range = NSMakeRange(0, len);
+               size_t cIdx = 0;
+               while (range.length > 0) {
+                       NSRange newlineRange = [string rangeOfCharacterFromSet:charset options:0 range:range];
+                       if (newlineRange.location == NSNotFound) {
+                               [string getCharacters:&characters[cIdx] range:range];
+                               cIdx += range.length;
+                               break;
+                       } else {
+                               NSUInteger delta = newlineRange.location - range.location;
+                               if (newlineRange.location > range.location) {
+                                       [string getCharacters:&characters[cIdx] range:NSMakeRange(range.location, delta)];
+                               }
+                               cIdx += delta;
+                               characters[cIdx] = (unichar)' ';
+                               cIdx++;
+                               delta += newlineRange.length;
+                               range.location += delta, range.length -= delta;
+                               if (newlineRange.length == 1 && range.length >= 1 &&
+                                       [string characterAtIndex:newlineRange.location] == (unichar)'\r' &&
+                                       [string characterAtIndex:range.location] == (unichar)'\n') {
+                                       // CRLF sequence, skip the LF
+                                       range.location += 1, range.length -= 1;
+                               }
+                       }
+               }
+               len = cIdx;
+       } else {
+               [string getCharacters:characters range:NSMakeRange(0, len)];
+       }
+       
+       // Create storage for glyphs and advances
+       CGGlyph *glyphs;
+       CGFloat *advances;
+       {
+               NSUInteger maxRunLength = 0;
+               ZAttributeRun *a = [attributes objectAtIndex:0];
+               for (NSUInteger i = 1; i < [attributes count]; i++) {
+                       ZAttributeRun *b = [attributes objectAtIndex:i];
+                       maxRunLength = MAX(maxRunLength, b.index - a.index);
+                       a = b;
+               }
+               maxRunLength = MAX(maxRunLength, len - a.index);
+               maxRunLength++; // for a potential ellipsis
+               glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * maxRunLength);
+               advances = (CGFloat *)malloc(sizeof(CGFloat) * maxRunLength);
+       }
+       
+       // Use this table to cache all fontTable objects
+       CFMutableDictionaryRef fontTableMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
+                                                                                                                                        &kFontTableDictionaryValueCallBacks);
+       
+       // Fetch initial style values
+       NSUInteger currentRunIdx = 0;
+       ZAttributeRun *currentRun;
+       NSUInteger nextRunStart;
+       ZFont *currentFont;
+       fontTable *currentTable;
+       
+#define READ_RUN() readRunInformation(attributes, len, fontTableMap, \
+                                                                         currentRunIdx, &currentRun, &nextRunStart, \
+                                                                         &currentFont, &currentTable)
+       
+       READ_RUN();
+       
+       // fetch the glyphs for the first run
+       size_t glyphCount;
+       NSUInteger glyphIdx;
+       
+#define READ_GLYPHS() do { \
+               mapCharactersToGlyphsInFont(currentTable, &characters[currentRun.index], (nextRunStart - currentRun.index), glyphs, &glyphCount); \
+               mapGlyphsToAdvancesInFont(currentFont, (nextRunStart - currentRun.index), glyphs, advances); \
+               glyphIdx = 0; \
+       } while (0)
+       
+       READ_GLYPHS();
+       
+       NSMutableCharacterSet *alphaCharset = [NSMutableCharacterSet alphanumericCharacterSet];
+       [alphaCharset addCharactersInString:@"([{'\"\u2019\u02BC"];
+       
+       // scan left-to-right looking for newlines or until we hit the width constraint
+       // When we hit a wrapping point, calculate truncation as follows:
+       // If we have room to draw at least one more character on the next line, no truncation
+       // Otherwise apply the truncation algorithm to the current line.
+       // After calculating any truncation, draw.
+       // Each time we hit the end of an attribute run, calculate the new font and make sure
+       // it fits (vertically) within the size constraint. If not, truncate this line.
+       // When we draw, iterate over the attribute runs for this line and draw each run separately
+       BOOL lastLine = NO; // used to indicate truncation and to stop the iterating
+       NSUInteger lineCount = 1;
+       while (idx < len && !lastLine) {
+               if (maxLines > 0 && lineCount == maxLines) {
+                       lastLine = YES;
+               }
+               // scan left-to-right
+               struct {
+                       NSUInteger index;
+                       NSUInteger glyphIndex;
+                       NSUInteger currentRunIdx;
+               } indexCache = { idx, glyphIdx, currentRunIdx };
+               CGSize lineSize = CGSizeMake(0, currentFont.leading);
+               CGFloat lineAscender = currentFont.ascender;
+               struct {
+                       NSUInteger index;
+                       NSUInteger glyphIndex;
+                       NSUInteger currentRunIdx;
+                       CGSize lineSize;
+               } lastWrapCache = {0, 0, 0, CGSizeZero};
+               BOOL inAlpha = NO; // used for calculating wrap points
+               
+               BOOL finishLine = NO;
+               for (;idx <= len && !finishLine;) {
+                       NSUInteger skipCount = 0;
+                       if (idx == len) {
+                               finishLine = YES;
+                               lastLine = YES;
+                       } else {
+                               if (idx >= nextRunStart) {
+                                       // cycle the font and table and grab the next set of glyphs
+                                       do {
+                                               currentRunIdx++;
+                                               READ_RUN();
+                                       } while (idx >= nextRunStart);
+                                       READ_GLYPHS();
+                                       // re-scan the characters to synchronize the glyph index
+                                       for (NSUInteger j = currentRun.index; j < idx; j++) {
+                                               if (UnicharIsHighSurrogate(characters[j]) && j+1<len && UnicharIsLowSurrogate(characters[j+1])) {
+                                                       j++;
+                                               }
+                                               glyphIdx++;
+                                       }
+                                       if (currentFont.leading > lineSize.height) {
+                                               lineSize.height = currentFont.leading;
+                                               if (retValue.height + currentFont.ascender > constrainedSize.height) {
+                                                       lastLine = YES;
+                                                       finishLine = YES;
+                                               }
+                                       }
+                                       lineAscender = MAX(lineAscender, currentFont.ascender);
+                               }
+                               unichar c = characters[idx];
+                               // Mark a wrap point before spaces and after any stretch of non-alpha characters
+                               BOOL markWrap = NO;
+                               if (c == (unichar)' ') {
+                                       markWrap = YES;
+                               } else if ([alphaCharset characterIsMember:c]) {
+                                       if (!inAlpha) {
+                                               markWrap = YES;
+                                               inAlpha = YES;
+                                       }
+                               } else {
+                                       inAlpha = NO;
+                               }
+                               if (markWrap) {
+                                       lastWrapCache = (__typeof__(lastWrapCache)){
+                                               .index = idx,
+                                               .glyphIndex = glyphIdx,
+                                               .currentRunIdx = currentRunIdx,
+                                               .lineSize = lineSize
+                                       };
+                               }
+                               // process the line
+                               if (c == (unichar)'\n' || c == 0x0085) { // U+0085 is the NEXT_LINE unicode character
+                                       finishLine = YES;
+                                       skipCount = 1;
+                               } else if (c == (unichar)'\r') {
+                                       finishLine = YES;
+                                       // check for CRLF
+                                       if (idx+1 < len && characters[idx+1] == (unichar)'\n') {
+                                               skipCount = 2;
+                                       } else {
+                                               skipCount = 1;
+                                       }
+                               } else if (lineSize.width + advances[glyphIdx] > constrainedSize.width) {
+                                       finishLine = YES;
+                                       if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) {
+                                               lastLine = YES;
+                                       }
+                                       // walk backwards if wrapping is necessary
+                                       if (lastWrapCache.index > indexCache.index && lineBreakMode != UILineBreakModeCharacterWrap &&
+                                               (!lastLine || lineBreakMode != UILineBreakModeClip)) {
+                                               // we're doing some sort of word wrapping
+                                               idx = lastWrapCache.index;
+                                               lineSize = lastWrapCache.lineSize;
+                                               if (!lastLine) {
+                                                       // re-check if this is the last line
+                                                       if (lastWrapCache.currentRunIdx != currentRunIdx) {
+                                                               currentRunIdx = lastWrapCache.currentRunIdx;
+                                                               READ_RUN();
+                                                               READ_GLYPHS();
+                                                       }
+                                                       if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) {
+                                                               lastLine = YES;
+                                                       }
+                                               }
+                                               glyphIdx = lastWrapCache.glyphIndex;
+                                               // skip any spaces
+                                               for (NSUInteger j = idx; j < len && characters[j] == (unichar)' '; j++) {
+                                                       skipCount++;
+                                               }
+                                       }
+                               }
+                       }
+                       if (finishLine) {
+                               // TODO: support head/middle truncation
+                               if (lastLine && idx < len && lineBreakMode == UILineBreakModeTailTruncation) {
+                                       // truncate
+                                       unichar ellipsis = 0x2026; // ellipsis (…)
+                                       CGGlyph ellipsisGlyph;
+                                       mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL);
+                                       CGFloat ellipsisWidth;
+                                       mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth);
+                                       while ((idx - indexCache.index) > 1 && lineSize.width + ellipsisWidth > constrainedSize.width) {
+                                               // we have more than 1 character and we're too wide, so back up
+                                               idx--;
+                                               if (UnicharIsHighSurrogate(characters[idx]) && UnicharIsLowSurrogate(characters[idx+1])) {
+                                                       idx--;
+                                               }
+                                               if (idx < currentRun.index) {
+                                                       ZFont *oldFont = currentFont;
+                                                       do {
+                                                               currentRunIdx--;
+                                                               READ_RUN();
+                                                       } while (idx < currentRun.index);
+                                                       READ_GLYPHS();
+                                                       glyphIdx = glyphCount-1;
+                                                       if (oldFont != currentFont) {
+                                                               mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL);
+                                                               mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth);
+                                                       }
+                                               } else {
+                                                       glyphIdx--;
+                                               }
+                                               lineSize.width -= advances[glyphIdx];
+                                       }
+                                       // skip any spaces before truncating
+                                       while ((idx - indexCache.index) > 1 && characters[idx-1] == (unichar)' ') {
+                                               idx--;
+                                               if (idx < currentRun.index) {
+                                                       currentRunIdx--;
+                                                       READ_RUN();
+                                                       READ_GLYPHS();
+                                                       glyphIdx = glyphCount-1;
+                                               } else {
+                                                       glyphIdx--;
+                                               }
+                                               lineSize.width -= advances[glyphIdx];
+                                       }
+                                       lineSize.width += ellipsisWidth;
+                                       glyphs[glyphIdx] = ellipsisGlyph;
+                                       idx++;
+                                       glyphIdx++;
+                               }
+                               retValue.width = MAX(retValue.width, lineSize.width);
+                               retValue.height += lineSize.height;
+                               
+                               // draw
+                               if (performDraw) {
+                                       switch (alignment) {
+                                               case UITextAlignmentLeft:
+                                                       drawPoint.x = 0;
+                                                       break;
+                                               case UITextAlignmentCenter:
+                                                       drawPoint.x = (constrainedSize.width - lineSize.width) / 2.0f;
+                                                       break;
+                                               case UITextAlignmentRight:
+                                                       drawPoint.x = constrainedSize.width - lineSize.width;
+                                                       break;
+                                       }
+                                       NSUInteger stopGlyphIdx = glyphIdx;
+                                       NSUInteger lastRunIdx = currentRunIdx;
+                                       NSUInteger stopCharIdx = idx;
+                                       idx = indexCache.index;
+                                       if (currentRunIdx != indexCache.currentRunIdx) {
+                                               currentRunIdx = indexCache.currentRunIdx;
+                                               READ_RUN();
+                                               READ_GLYPHS();
+                                       }
+                                       glyphIdx = indexCache.glyphIndex;
+                                       for (NSUInteger drawIdx = currentRunIdx; drawIdx <= lastRunIdx; drawIdx++) {
+                                               if (drawIdx != currentRunIdx) {
+                                                       currentRunIdx = drawIdx;
+                                                       READ_RUN();
+                                                       READ_GLYPHS();
+                                               }
+                                               NSUInteger numGlyphs;
+                                               if (drawIdx == lastRunIdx) {
+                                                       numGlyphs = stopGlyphIdx - glyphIdx;
+                                                       idx = stopCharIdx;
+                                               } else {
+                                                       numGlyphs = glyphCount - glyphIdx;
+                                                       idx = nextRunStart;
+                                               }
+                                               CGContextSetFont(ctx, currentFont.cgFont);
+                                               CGContextSetFontSize(ctx, currentFont.pointSize);
+                                               // calculate the fragment size
+                                               CGFloat fragmentWidth = 0;
+                                               for (NSUInteger g = 0; g < numGlyphs; g++) {
+                                                       fragmentWidth += advances[glyphIdx + g];
+                                               }
+                                               
+                                               if (!ignoreColor) {
+                                                       UIColor *foregroundColor = getValueOrDefaultForRun(currentRun, ZForegroundColorAttributeName);
+                                                       UIColor *backgroundColor = getValueOrDefaultForRun(currentRun, ZBackgroundColorAttributeName);
+                                                       if (backgroundColor != nil && ![backgroundColor isEqual:[UIColor clearColor]]) {
+                                                               [backgroundColor setFill];
+                                                               UIRectFillUsingBlendMode((CGRect){ drawPoint, { fragmentWidth, lineSize.height } }, kCGBlendModeNormal);
+                                                       }
+                                                       [foregroundColor setFill];
+                                               }
+                                               
+                                               CGContextShowGlyphsAtPoint(ctx, drawPoint.x, drawPoint.y + lineAscender, &glyphs[glyphIdx], numGlyphs);
+                                               NSNumber *underlineStyle = getValueOrDefaultForRun(currentRun, ZUnderlineStyleAttributeName);
+                                               if ([underlineStyle     integerValue] & ZUnderlineStyleMask) {
+                                                       // we only support single for the time being
+                                                       UIRectFill(CGRectMake(drawPoint.x, drawPoint.y + lineAscender, fragmentWidth, 1));
+                                               }
+                                               drawPoint.x += fragmentWidth;
+                                               glyphIdx += numGlyphs;
+                                       }
+                                       drawPoint.y += lineSize.height;
+                               }
+                               idx += skipCount;
+                               glyphIdx += skipCount;
+                               lineCount++;
+                       } else {
+                               lineSize.width += advances[glyphIdx];
+                               glyphIdx++;
+                               idx++;
+                               if (idx < len && UnicharIsHighSurrogate(characters[idx-1]) && UnicharIsLowSurrogate(characters[idx])) {
+                                       // skip the second half of the surrogate pair
+                                       idx++;
+                               }
+                       }
+               }
+       }
+       CFRelease(fontTableMap);
+       free(glyphs);
+       free(advances);
+       free(characters);
+       
+#undef READ_GLYPHS
+#undef READ_RUN
+       
+       return retValue;
+}
+
+static NSArray *attributeRunForFont(ZFont *font) {
+       return [NSArray arrayWithObject:[ZAttributeRun attributeRunWithIndex:0
+                                                                                                                         attributes:[NSDictionary dictionaryWithObject:font
+                                                                                                                                                                                                        forKey:ZFontAttributeName]]];
+}
+
+static CGSize drawTextInRect(CGRect rect, NSString *text, NSArray *attributes, UILineBreakMode lineBreakMode,
+                                                        UITextAlignment alignment, NSUInteger numberOfLines, BOOL ignoreColor) {
+       CGContextRef ctx = UIGraphicsGetCurrentContext();
+       
+       CGContextSaveGState(ctx);
+       
+       // flip it upside-down because our 0,0 is upper-left, whereas ttfs are for screens where 0,0 is lower-left
+       CGAffineTransform textTransform = CGAffineTransformMake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
+       CGContextSetTextMatrix(ctx, textTransform);
+       
+       CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y);
+       
+       CGContextSetTextDrawingMode(ctx, kCGTextFill);
+       CGSize size = drawOrSizeTextConstrainedToSize(YES, text, attributes, rect.size, numberOfLines, lineBreakMode, alignment, ignoreColor);
+       
+       CGContextRestoreGState(ctx);
+       
+       return size;
+}
+
+@implementation NSString (FontLabelStringDrawing)
+// CGFontRef-based methods
+- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
+       return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize]];
+}
+
+- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size {
+       return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size];
+}
+
+- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size
+                  lineBreakMode:(UILineBreakMode)lineBreakMode {
+       return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size lineBreakMode:lineBreakMode];
+}
+
+- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
+       return [self drawAtPoint:point withZFont:[ZFont fontWithCGFont:font size:pointSize]];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
+       return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize]];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize lineBreakMode:(UILineBreakMode)lineBreakMode {
+       return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
+          lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment {
+       return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode alignment:alignment];
+}
+
+// ZFont-based methods
+- (CGSize)sizeWithZFont:(ZFont *)font {
+       CGSize size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
+                                                                                                 UILineBreakModeClip, UITextAlignmentLeft, YES);
+       return CGSizeMake(ceilf(size.width), ceilf(size.height));
+}
+
+- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size {
+       return [self sizeWithZFont:font constrainedToSize:size lineBreakMode:UILineBreakModeWordWrap];
+}
+
+/*
+ According to experimentation with UIStringDrawing, this can actually return a CGSize whose height is greater
+ than the one passed in. The two cases are as follows:
+ 1. If the given size parameter's height is smaller than a single line, the returned value will
+ be the height of one line.
+ 2. If the given size parameter's height falls between multiples of a line height, and the wrapped string
+ actually extends past the size.height, and the difference between size.height and the previous multiple
+ of a line height is >= the font's ascender, then the returned size's height is extended to the next line.
+ To put it simply, if the baseline point of a given line falls in the given size, the entire line will
+ be present in the output size.
+ */
+- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode {
+       size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, 0, lineBreakMode, UITextAlignmentLeft, YES);
+       return CGSizeMake(ceilf(size.width), ceilf(size.height));
+}
+
+- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
+                 numberOfLines:(NSUInteger)numberOfLines {
+       size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, numberOfLines, lineBreakMode, UITextAlignmentLeft, YES);
+       return CGSizeMake(ceilf(size.width), ceilf(size.height));
+}
+
+- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font {
+       return [self drawAtPoint:point forWidth:CGFLOAT_MAX withZFont:font lineBreakMode:UILineBreakModeClip];
+}
+
+- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode {
+       return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self, attributeRunForFont(font), lineBreakMode, UITextAlignmentLeft, 1, YES);
+}
+
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font {
+       return [self drawInRect:rect withZFont:font lineBreakMode:UILineBreakModeWordWrap];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode {
+       return [self drawInRect:rect withZFont:font lineBreakMode:lineBreakMode alignment:UITextAlignmentLeft];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
+                  alignment:(UITextAlignment)alignment {
+       return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, 0, YES);
+}
+
+- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
+                  alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines {
+       return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, numberOfLines, YES);
+}
+@end
+
+@implementation ZAttributedString (ZAttributedStringDrawing)
+- (CGSize)size {
+       CGSize size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
+                                                                                                 UILineBreakModeClip, UITextAlignmentLeft, NO);
+       return CGSizeMake(ceilf(size.width), ceilf(size.height));
+}
+
+- (CGSize)sizeConstrainedToSize:(CGSize)size {
+       return [self sizeConstrainedToSize:size lineBreakMode:UILineBreakModeWordWrap];
+}
+
+- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode {
+       size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, 0, lineBreakMode, UITextAlignmentLeft, NO);
+       return CGSizeMake(ceilf(size.width), ceilf(size.height));
+}
+
+- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
+                                 numberOfLines:(NSUInteger)numberOfLines {
+       size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, numberOfLines, lineBreakMode, UITextAlignmentLeft, NO);
+       return CGSizeMake(ceilf(size.width), ceilf(size.height));
+}
+
+- (CGSize)drawAtPoint:(CGPoint)point {
+       return [self drawAtPoint:point forWidth:CGFLOAT_MAX lineBreakMode:UILineBreakModeClip];
+}
+
+- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode {
+       return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self.string, self.attributes, lineBreakMode, UITextAlignmentLeft, 1, NO);
+}
+
+- (CGSize)drawInRect:(CGRect)rect {
+       return [self drawInRect:rect withLineBreakMode:UILineBreakModeWordWrap];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode {
+       return [self drawInRect:rect withLineBreakMode:lineBreakMode alignment:UITextAlignmentLeft];
+}
+
+- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment {
+       return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, 0, NO);
+}
+
+- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment
+          numberOfLines:(NSUInteger)numberOfLines {
+       return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, numberOfLines, NO);
+}
+@end
diff --git a/libs/FontLabel/FontManager.h b/libs/FontLabel/FontManager.h
new file mode 100644 (file)
index 0000000..1592b8a
--- /dev/null
@@ -0,0 +1,85 @@
+//
+//  FontManager.h
+//  FontLabel
+//
+//  Created by Kevin Ballard on 5/5/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+
+@class ZFont;
+
+@interface FontManager : NSObject {
+       CFMutableDictionaryRef fonts;
+       NSMutableDictionary *urls;
+}
++ (FontManager *)sharedManager;
+/*!
+    @method     
+    @abstract   Loads a TTF font from the main bundle
+       @param filename The name of the font file to load (with or without extension).
+       @return YES if the font was loaded, NO if an error occurred
+    @discussion If the font has already been loaded, this method does nothing and returns YES.
+                               This method first attempts to load the font by appending .ttf to the filename.
+                               If that file does not exist, it tries the filename exactly as given.
+*/
+- (BOOL)loadFont:(NSString *)filename;
+/*!
+       @method
+       @abstract       Loads a font from the given file URL
+       @param url A file URL that points to a font file
+       @return YES if the font was loaded, NO if an error occurred
+       @discussion If the font has already been loaded, this method does nothing and returns YES.
+*/
+- (BOOL)loadFontURL:(NSURL *)url;
+/*!
+    @method     
+    @abstract   Returns the loaded font with the given filename
+       @param filename The name of the font file that was given to -loadFont:
+       @return A CGFontRef, or NULL if the specified font cannot be found
+    @discussion If the font has not been loaded yet, -loadFont: will be
+                called with the given name first.
+*/
+- (CGFontRef)fontWithName:(NSString *)filename __AVAILABILITY_INTERNAL_DEPRECATED;
+/*!
+       @method
+       @abstract       Returns a ZFont object corresponding to the loaded font with the given filename and point size
+       @param filename The name of the font file that was given to -loadFont:
+       @param pointSize The point size of the font
+       @return A ZFont, or NULL if the specified font cannot be found
+       @discussion If the font has not been loaded yet, -loadFont: will be
+                               called with the given name first.
+*/
+- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize;
+/*!
+       @method
+       @abstract       Returns a ZFont object corresponding to the loaded font with the given file URL and point size
+       @param url A file URL that points to a font file
+       @param pointSize The point size of the font
+       @return A ZFont, or NULL if the specified font cannot be loaded
+       @discussion If the font has not been loaded yet, -loadFontURL: will be called with the given URL first.
+*/
+- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize;
+/*!
+       @method
+       @abstract   Returns a CFArrayRef of all loaded CGFont objects
+       @return A CFArrayRef of all loaded CGFont objects
+       @description You are responsible for releasing the CFArrayRef
+*/
+- (CFArrayRef)copyAllFonts;
+@end
diff --git a/libs/FontLabel/FontManager.m b/libs/FontLabel/FontManager.m
new file mode 100644 (file)
index 0000000..12eac2d
--- /dev/null
@@ -0,0 +1,123 @@
+//
+//  FontManager.m
+//  FontLabel
+//
+//  Created by Kevin Ballard on 5/5/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "FontManager.h"
+#import "ZFont.h"
+
+static FontManager *sharedFontManager = nil;
+
+@implementation FontManager
++ (FontManager *)sharedManager {
+       @synchronized(self) {
+               if (sharedFontManager == nil) {
+                       sharedFontManager = [[self alloc] init];
+               }
+       }
+       return sharedFontManager;
+}
+
+- (id)init {
+       if ((self = [super init])) {
+               fonts = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+               urls = [[NSMutableDictionary alloc] init];
+       }
+       return self;
+}
+
+- (BOOL)loadFont:(NSString *)filename {
+       NSString *fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"ttf"];
+       if (fontPath == nil) {
+               fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
+       }
+       if (fontPath == nil) return NO;
+       
+       NSURL *url = [NSURL fileURLWithPath:fontPath];
+       if ([self loadFontURL:url]) {
+               [urls setObject:url forKey:filename];
+               return YES;
+       }
+       return NO;
+}
+
+- (BOOL)loadFontURL:(NSURL *)url {
+       CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((CFURLRef)url);
+       if (fontDataProvider == NULL) return NO;
+       CGFontRef newFont = CGFontCreateWithDataProvider(fontDataProvider); 
+       CGDataProviderRelease(fontDataProvider); 
+       if (newFont == NULL) return NO;
+       
+       CFDictionarySetValue(fonts, url, newFont);
+       CGFontRelease(newFont);
+       return YES;
+}
+
+- (CGFontRef)fontWithName:(NSString *)filename {
+       CGFontRef font = NULL;
+       NSURL *url = [urls objectForKey:filename];
+       if (url == nil && [self loadFont:filename]) {
+               url = [urls objectForKey:filename];
+       }
+       if (url != nil) {
+               font = (CGFontRef)CFDictionaryGetValue(fonts, url);
+       }
+       return font;
+}
+
+- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize {
+       NSURL *url = [urls objectForKey:filename];
+       if (url == nil && [self loadFont:filename]) {
+               url = [urls objectForKey:filename];
+       }
+       if (url != nil) {
+               CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
+               if (cgFont != NULL) {
+                       return [ZFont fontWithCGFont:cgFont size:pointSize];
+               }
+       }
+       return nil;
+}
+
+- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize {
+       CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
+       if (cgFont == NULL && [self loadFontURL:url]) {
+               cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
+       }
+       if (cgFont != NULL) {
+               return [ZFont fontWithCGFont:cgFont size:pointSize];
+       }
+       return nil;
+}
+
+- (CFArrayRef)copyAllFonts {
+       CFIndex count = CFDictionaryGetCount(fonts);
+       CGFontRef *values = (CGFontRef *)malloc(sizeof(CGFontRef) * count);
+       CFDictionaryGetKeysAndValues(fonts, NULL, (const void **)values);
+       CFArrayRef array = CFArrayCreate(NULL, (const void **)values, count, &kCFTypeArrayCallBacks);
+       free(values);
+       return array;
+}
+
+- (void)dealloc {
+       CFRelease(fonts);
+       [urls release];
+       [super dealloc];
+}
+@end
diff --git a/libs/FontLabel/ZAttributedString.h b/libs/FontLabel/ZAttributedString.h
new file mode 100644 (file)
index 0000000..e194c81
--- /dev/null
@@ -0,0 +1,77 @@
+//
+//  ZAttributedString.h
+//  FontLabel
+//
+//  Created by Kevin Ballard on 9/22/09.
+//  Copyright 2009 Zynga Game Networks. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#if NS_BLOCKS_AVAILABLE
+#define Z_BLOCKS 1
+#else
+// set this to 1 if you are using PLBlocks
+#define Z_BLOCKS 0
+#endif
+
+#if Z_BLOCKS
+enum {
+       ZAttributedStringEnumerationReverse = (1UL << 1),
+       ZAttributedStringEnumerationLongestEffectiveRangeNotRequired = (1UL << 20)
+};
+typedef NSUInteger ZAttributedStringEnumerationOptions;
+#endif
+
+@interface ZAttributedString : NSObject <NSCoding, NSCopying, NSMutableCopying> {
+       NSMutableString *_buffer;
+       NSMutableArray *_attributes;
+}
+@property (nonatomic, readonly) NSUInteger length;
+@property (nonatomic, readonly) NSString *string;
+- (id)initWithAttributedString:(ZAttributedString *)attr;
+- (id)initWithString:(NSString *)str;
+- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes;
+- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange;
+- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit;
+- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange;
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange;
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit;
+#if Z_BLOCKS
+- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
+                               usingBlock:(void (^)(id value, NSRange range, BOOL *stop))block;
+- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
+                                               usingBlock:(void (^)(NSDictionary *attrs, NSRange range, BOOL *stop))block;
+#endif
+- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString;
+@end
+
+@interface ZMutableAttributedString : ZAttributedString {
+}
+- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range;
+- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range;
+- (void)appendAttributedString:(ZAttributedString *)str;
+- (void)deleteCharactersInRange:(NSRange)range;
+- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx;
+- (void)removeAttribute:(NSString *)name range:(NSRange)range;
+- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str;
+- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
+- (void)setAttributedString:(ZAttributedString *)str;
+- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range;
+@end
+
+extern NSString * const ZFontAttributeName;
+extern NSString * const ZForegroundColorAttributeName;
+extern NSString * const ZBackgroundColorAttributeName;
+extern NSString * const ZUnderlineStyleAttributeName;
+
+enum {
+       ZUnderlineStyleNone = 0x00,
+       ZUnderlineStyleSingle = 0x01
+};
+#define ZUnderlineStyleMask 0x00FF
+
+enum {
+       ZUnderlinePatternSolid = 0x0000
+};
+#define ZUnderlinePatternMask 0xFF00
diff --git a/libs/FontLabel/ZAttributedString.m b/libs/FontLabel/ZAttributedString.m
new file mode 100644 (file)
index 0000000..79f0323
--- /dev/null
@@ -0,0 +1,596 @@
+//
+//  ZAttributedString.m
+//  FontLabel
+//
+//  Created by Kevin Ballard on 9/22/09.
+//  Copyright 2009 Zynga Game Networks. All rights reserved.
+//
+
+#import "ZAttributedString.h"
+#import "ZAttributedStringPrivate.h"
+
+@interface ZAttributedString ()
+- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index;
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName;
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
+                                                       inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName;
+@end
+
+@interface ZAttributedString ()
+@property (nonatomic, readonly) NSArray *attributes;
+@end
+
+@implementation ZAttributedString
+@synthesize string = _buffer;
+@synthesize attributes = _attributes;
+
+- (id)initWithAttributedString:(ZAttributedString *)attr {
+       NSParameterAssert(attr != nil);
+       if ((self = [super init])) {
+               _buffer = [attr->_buffer mutableCopy];
+               _attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES];
+       }
+       return self;
+}
+
+- (id)initWithString:(NSString *)str {
+       return [self initWithString:str attributes:nil];
+}
+
+- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes {
+       if ((self = [super init])) {
+               _buffer = [str mutableCopy];
+               _attributes = [[NSMutableArray alloc] initWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
+       }
+       return self;
+}
+
+- (id)init {
+       return [self initWithString:@"" attributes:nil];
+}
+
+- (id)initWithCoder:(NSCoder *)decoder {
+       if ((self = [super init])) {
+               _buffer = [[decoder decodeObjectForKey:@"buffer"] mutableCopy];
+               _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
+       }
+       return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+       [aCoder encodeObject:_buffer forKey:@"buffer"];
+       [aCoder encodeObject:_attributes forKey:@"attributes"];
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+       return [self retain];
+}
+
+- (id)mutableCopyWithZone:(NSZone *)zone {
+       return [(ZMutableAttributedString *)[ZMutableAttributedString allocWithZone:zone] initWithAttributedString:self];
+}
+
+- (NSUInteger)length {
+       return [_buffer length];
+}
+
+- (NSString *)description {
+       NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]*2];
+       NSRange range = NSMakeRange(0, 0);
+       for (NSUInteger i = 0; i <= [_attributes count]; i++) {
+               range.location = NSMaxRange(range);
+               ZAttributeRun *run;
+               if (i < [_attributes count]) {
+                       run = [_attributes objectAtIndex:i];
+                       range.length = run.index - range.location;
+               } else {
+                       run = nil;
+                       range.length = [_buffer length] - range.location;
+               }
+               if (range.length > 0) {
+                       [components addObject:[NSString stringWithFormat:@"\"%@\"", [_buffer substringWithRange:range]]];
+               }
+               if (run != nil) {
+                       NSMutableArray *attrDesc = [NSMutableArray arrayWithCapacity:[run.attributes count]];
+                       for (id key in run.attributes) {
+                               [attrDesc addObject:[NSString stringWithFormat:@"%@: %@", key, [run.attributes objectForKey:key]]];
+                       }
+                       [components addObject:[NSString stringWithFormat:@"{%@}", [attrDesc componentsJoinedByString:@", "]]];
+               }
+       }
+       return [NSString stringWithFormat:@"%@", [components componentsJoinedByString:@" "]];
+}
+
+- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
+       NSParameterAssert(attributeName != nil);
+       return [[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:attributeName] objectForKey:attributeName];
+}
+
+- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
+       NSParameterAssert(attributeName != nil);
+       return [[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:attributeName] objectForKey:attributeName];
+}
+
+- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange {
+       if (NSMaxRange(aRange) > [_buffer length]) {
+               @throw [NSException exceptionWithName:NSRangeException reason:@"range was outisde of the attributed string" userInfo:nil];
+       }
+       ZMutableAttributedString *newStr = [self mutableCopy];
+       if (aRange.location > 0) {
+               [newStr deleteCharactersInRange:NSMakeRange(0, aRange.location)];
+       }
+       if (NSMaxRange(aRange) < [_buffer length]) {
+               [newStr deleteCharactersInRange:NSMakeRange(aRange.length, [_buffer length] - NSMaxRange(aRange))];
+       }
+       return [newStr autorelease];
+}
+
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
+       return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:nil]];
+}
+
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
+       return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:nil]];
+}
+
+#if Z_BLOCKS
+// Warning: this code has not been tested. The only guarantee is that it compiles.
+- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
+                               usingBlock:(void (^)(id, NSRange, BOOL*))block {
+       if (opts & ZAttributedStringEnumerationLongestEffectiveRangeNotRequired) {
+               [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
+                       id value = [attrs objectForKey:attrName];
+                       if (value != nil) {
+                               block(value, range, stop);
+                       }
+               }];
+       } else {
+               __block id oldValue = nil;
+               __block NSRange effectiveRange = NSMakeRange(0, 0);
+               [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
+                       id value = [attrs objectForKey:attrName];
+                       if (oldValue == nil) {
+                               oldValue = value;
+                               effectiveRange = range;
+                       } else if (value != nil && [oldValue isEqual:value]) {
+                               // combine the attributes
+                               effectiveRange = NSUnionRange(effectiveRange, range);
+                       } else {
+                               BOOL innerStop = NO;
+                               block(oldValue, effectiveRange, &innerStop);
+                               if (innerStop) {
+                                       *stop = YES;
+                                       oldValue = nil;
+                               } else {
+                                       oldValue = value;
+                               }
+                       }
+               }];
+               if (oldValue != nil) {
+                       BOOL innerStop = NO; // necessary for the block, but unused
+                       block(oldValue, effectiveRange, &innerStop);
+               }
+       }
+}
+
+- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
+                                               usingBlock:(void (^)(NSDictionary*, NSRange, BOOL*))block {
+       // copy the attributes so we can mutate the string if necessary during enumeration
+       // also clip the array during copy to only the subarray of attributes that cover the requested range
+       NSArray *attrs;
+       if (NSEqualRanges(enumerationRange, NSMakeRange(0, 0))) {
+               attrs = [NSArray arrayWithArray:_attributes];
+       } else {
+               // in this binary search, last is the first run after the range
+               NSUInteger first = 0, last = [_attributes count];
+               while (last > first+1) {
+                       NSUInteger pivot = (last + first) / 2;
+                       ZAttributeRun *run = [_attributes objectAtIndex:pivot];
+                       if (run.index < enumerationRange.location) {
+                               first = pivot;
+                       } else if (run.index >= NSMaxRange(enumerationRange)) {
+                               last = pivot;
+                       }
+               }
+               attrs = [_attributes subarrayWithRange:NSMakeRange(first, last-first)];
+       }
+       if (opts & ZAttributedStringEnumerationReverse) {
+               NSUInteger end = [_buffer length];
+               for (ZAttributeRun *run in [attrs reverseObjectEnumerator]) {
+                       BOOL stop = NO;
+                       NSUInteger start = run.index;
+                       // clip to enumerationRange
+                       start = MAX(start, enumerationRange.location);
+                       end = MIN(end, NSMaxRange(enumerationRange));
+                       block(run.attributes, NSMakeRange(start, end - start), &stop);
+                       if (stop) break;
+                       end = run.index;
+               }
+       } else {
+               NSUInteger start = 0;
+               ZAttributeRun *run = [attrs objectAtIndex:0];
+               NSInteger offset = 0;
+               NSInteger oldLength = [_buffer length];
+               for (NSUInteger i = 1;;i++) {
+                       NSUInteger end;
+                       if (i >= [attrs count]) {
+                               end = oldLength;
+                       } else {
+                               end = [[attrs objectAtIndex:i] index];
+                       }
+                       BOOL stop = NO;
+                       NSUInteger clippedStart = MAX(start, enumerationRange.location);
+                       NSUInteger clippedEnd = MIN(end, NSMaxRange(enumerationRange));
+                       block(run.attributes, NSMakeRange(clippedStart + offset, clippedEnd - start), &stop);
+                       if (stop || i >= [attrs count]) break;
+                       start = end;
+                       NSUInteger newLength = [_buffer length];
+                       offset += (newLength - oldLength);
+                       oldLength = newLength;
+               }
+       }
+}
+#endif
+
+- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString {
+       return ([_buffer isEqualToString:otherString->_buffer] && [_attributes isEqualToArray:otherString->_attributes]);
+}
+
+- (BOOL)isEqual:(id)object {
+       return [object isKindOfClass:[ZAttributedString class]] && [self isEqualToAttributedString:(ZAttributedString *)object];
+}
+
+#pragma mark -
+
+- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index {
+       NSUInteger first = 0, last = [_attributes count];
+       while (last > first + 1) {
+               NSUInteger pivot = (last + first) / 2;
+               ZAttributeRun *run = [_attributes objectAtIndex:pivot];
+               if (run.index > index) {
+                       last = pivot;
+               } else if (run.index < index) {
+                       first = pivot;
+               } else {
+                       first = pivot;
+                       break;
+               }
+       }
+       return first;
+}
+
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName {
+       if (index >= [_buffer length]) {
+               @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
+       }
+       NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
+       ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
+       if (aRange != NULL) {
+               aRange->location = run.index;
+               runIndex++;
+               if (runIndex < [_attributes count]) {
+                       aRange->length = [[_attributes objectAtIndex:runIndex] index] - aRange->location;
+               } else {
+                       aRange->length = [_buffer length] - aRange->location;
+               }
+       }
+       return run.attributes;
+}
+- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
+                                                       inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName {
+       if (index >= [_buffer length]) {
+               @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
+       } else if (NSMaxRange(rangeLimit) > [_buffer length]) {
+               @throw [NSException exceptionWithName:NSRangeException reason:@"rangeLimit beyond range of attributed string" userInfo:nil];
+       }
+       NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
+       ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
+       if (aRange != NULL) {
+               if (attributeName != nil) {
+                       id value = [run.attributes objectForKey:attributeName];
+                       NSUInteger endRunIndex = runIndex+1;
+                       runIndex--;
+                       // search backwards
+                       while (1) {
+                               if (run.index <= rangeLimit.location) {
+                                       break;
+                               }
+                               ZAttributeRun *prevRun = [_attributes objectAtIndex:runIndex];
+                               id prevValue = [prevRun.attributes objectForKey:attributeName];
+                               if (prevValue == value || (value != nil && [prevValue isEqual:value])) {
+                                       runIndex--;
+                                       run = prevRun;
+                               } else {
+                                       break;
+                               }
+                       }
+                       // search forwards
+                       ZAttributeRun *endRun = nil;
+                       while (endRunIndex < [_attributes count]) {
+                               ZAttributeRun *nextRun = [_attributes objectAtIndex:endRunIndex];
+                               if (nextRun.index >= NSMaxRange(rangeLimit)) {
+                                       endRun = nextRun;
+                                       break;
+                               }
+                               id nextValue = [nextRun.attributes objectForKey:attributeName];
+                               if (nextValue == value || (value != nil && [nextValue isEqual:value])) {
+                                       endRunIndex++;
+                               } else {
+                                       endRun = nextRun;
+                                       break;
+                               }
+                       }
+                       aRange->location = MAX(run.index, rangeLimit.location);
+                       aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
+               } else {
+                       // with no attribute name, we don't need to do any real searching,
+                       // as we already guarantee each run has unique attributes.
+                       // just make sure to clip the range to the rangeLimit
+                       aRange->location = MAX(run.index, rangeLimit.location);
+                       ZAttributeRun *endRun = (runIndex+1 < [_attributes count] ? [_attributes objectAtIndex:runIndex+1] : nil);
+                       aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
+               }
+       }
+       return run.attributes;
+}
+
+- (void)dealloc {
+       [_buffer release];
+       [_attributes release];
+       [super dealloc];
+}
+@end
+
+@interface ZMutableAttributedString ()
+- (void)cleanupAttributesInRange:(NSRange)range;
+- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range;
+- (void)offsetRunsInRange:(NSRange )range byOffset:(NSInteger)offset;
+@end
+
+@implementation ZMutableAttributedString
+- (id)copyWithZone:(NSZone *)zone {
+       return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self];
+}
+
+- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range {
+       range = [self rangeOfAttributeRunsForRange:range];
+       for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
+               [run.attributes setObject:value forKey:name];
+       }
+       [self cleanupAttributesInRange:range];
+}
+
+- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range {
+       range = [self rangeOfAttributeRunsForRange:range];
+       for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
+               [run.attributes addEntriesFromDictionary:attributes];
+       }
+       [self cleanupAttributesInRange:range];
+}
+
+- (void)appendAttributedString:(ZAttributedString *)str {
+       [self insertAttributedString:str atIndex:[_buffer length]];
+}
+
+- (void)deleteCharactersInRange:(NSRange)range {
+       NSRange runRange = [self rangeOfAttributeRunsForRange:range];
+       [_buffer replaceCharactersInRange:range withString:@""];
+       [_attributes removeObjectsInRange:runRange];
+       for (NSUInteger i = runRange.location; i < [_attributes count]; i++) {
+               ZAttributeRun *run = [_attributes objectAtIndex:i];
+               ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:(run.index - range.length) attributes:run.attributes];
+               [_attributes replaceObjectAtIndex:i withObject:newRun];
+               [newRun release];
+       }
+       [self cleanupAttributesInRange:NSMakeRange(runRange.location, 0)];
+}
+
+- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx {
+       [self replaceCharactersInRange:NSMakeRange(idx, 0) withAttributedString:str];
+}
+
+- (void)removeAttribute:(NSString *)name range:(NSRange)range {
+       range = [self rangeOfAttributeRunsForRange:range];
+       for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
+               [run.attributes removeObjectForKey:name];
+       }
+       [self cleanupAttributesInRange:range];
+}
+
+- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str {
+       NSRange replaceRange = [self rangeOfAttributeRunsForRange:range];
+       NSInteger offset = [str->_buffer length] - range.length;
+       [_buffer replaceCharactersInRange:range withString:str->_buffer];
+       [_attributes replaceObjectsInRange:replaceRange withObjectsFromArray:str->_attributes];
+       NSRange newRange = NSMakeRange(replaceRange.location, [str->_attributes count]);
+       [self offsetRunsInRange:newRange byOffset:range.location];
+       [self offsetRunsInRange:NSMakeRange(NSMaxRange(newRange), [_attributes count] - NSMaxRange(newRange)) byOffset:offset];
+       [self cleanupAttributesInRange:NSMakeRange(newRange.location, 0)];
+       [self cleanupAttributesInRange:NSMakeRange(NSMaxRange(newRange), 0)];
+}
+
+- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
+       [self replaceCharactersInRange:range withAttributedString:[[[ZAttributedString alloc] initWithString:str] autorelease]];
+}
+
+- (void)setAttributedString:(ZAttributedString *)str {
+       [_buffer release], _buffer = [str->_buffer mutableCopy];
+       [_attributes release], _attributes = [str->_attributes mutableCopy];
+}
+
+- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range {
+       range = [self rangeOfAttributeRunsForRange:range];
+       for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
+               [run.attributes setDictionary:attributes];
+       }
+       [self cleanupAttributesInRange:range];
+}
+
+#pragma mark -
+
+// splits the existing runs to provide one or more new runs for the given range
+- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range {
+       NSParameterAssert(NSMaxRange(range) <= [_buffer length]);
+       
+       // find (or create) the first run
+       NSUInteger first = 0;
+       ZAttributeRun *lastRun = nil;
+       for (;;first++) {
+               if (first >= [_attributes count]) {
+                       // we didn't find a run
+                       first = [_attributes count];
+                       ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
+                       [_attributes addObject:newRun];
+                       [newRun release];
+                       break;
+               }
+               ZAttributeRun *run = [_attributes objectAtIndex:first];
+               if (run.index == range.location) {
+                       break;
+               } else if (run.index > range.location) {
+                       ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
+                       [_attributes insertObject:newRun atIndex:first];
+                       [newRun release];
+                       break;
+               }
+               lastRun = run;
+       }
+       
+       if (((ZAttributeRun *)[_attributes lastObject]).index < NSMaxRange(range)) {
+               NSRange subrange = NSMakeRange(first, [_attributes count] - first);
+               if (NSMaxRange(range) < [_buffer length]) {
+                       ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range) attributes:[[_attributes lastObject] attributes]];
+                       [_attributes addObject:newRun];
+                       [newRun release];
+               }
+               return subrange;
+       } else {
+               // find the last run within and the first run after the range
+               NSUInteger lastIn = first, firstAfter = [_attributes count]-1;
+               while (firstAfter > lastIn + 1) {
+                       NSUInteger idx = (firstAfter + lastIn) / 2;
+                       ZAttributeRun *run = [_attributes objectAtIndex:idx];
+                       if (run.index < range.location) {
+                               lastIn = idx;
+                       } else if (run.index > range.location) {
+                               firstAfter = idx;
+                       } else {
+                               // this is definitively the first run after the range
+                               firstAfter = idx;
+                               break;
+                       }
+               }
+               if ([[_attributes objectAtIndex:firstAfter] index] > NSMaxRange(range)) {
+                       // the first after is too far after, insert another run!
+                       ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
+                                                                                                                         attributes:[[_attributes objectAtIndex:firstAfter-1] attributes]];
+                       [_attributes insertObject:newRun atIndex:firstAfter];
+                       [newRun release];
+               }
+               return NSMakeRange(lastIn, firstAfter - lastIn);
+       }
+}
+
+- (void)cleanupAttributesInRange:(NSRange)range {
+       // expand the range to include one surrounding attribute on each side
+       if (range.location > 0) {
+               range.location -= 1;
+               range.length += 1;
+       }
+       if (NSMaxRange(range) < [_attributes count]) {
+               range.length += 1;
+       } else {
+               // make sure the range is capped to the attributes count
+               range.length = [_attributes count] - range.location;
+       }
+       if (range.length == 0) return;
+       ZAttributeRun *lastRun = [_attributes objectAtIndex:range.location];
+       for (NSUInteger i = range.location+1; i < NSMaxRange(range);) {
+               ZAttributeRun *run = [_attributes objectAtIndex:i];
+               if ([lastRun.attributes isEqualToDictionary:run.attributes]) {
+                       [_attributes removeObjectAtIndex:i];
+                       range.length -= 1;
+               } else {
+                       lastRun = run;
+                       i++;
+               }
+       }
+}
+
+- (void)offsetRunsInRange:(NSRange)range byOffset:(NSInteger)offset {
+       for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
+               ZAttributeRun *run = [_attributes objectAtIndex:i];
+               ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:run.index + offset attributes:run.attributes];
+               [_attributes replaceObjectAtIndex:i withObject:newRun];
+               [newRun release];
+       }
+}
+@end
+
+@implementation ZAttributeRun
+@synthesize index = _index;
+@synthesize attributes = _attributes;
+
++ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
+       return [[[self alloc] initWithIndex:idx attributes:attrs] autorelease];
+}
+
+- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
+       NSParameterAssert(idx >= 0);
+       if ((self = [super init])) {
+               _index = idx;
+               if (attrs == nil) {
+                       _attributes = [[NSMutableDictionary alloc] init];
+               } else {
+                       _attributes = [attrs mutableCopy];
+               }
+       }
+       return self;
+}
+
+- (id)initWithCoder:(NSCoder *)decoder {
+       if ((self = [super init])) {
+               _index = [[decoder decodeObjectForKey:@"index"] unsignedIntegerValue];
+               _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
+       }
+       return self;
+}
+
+- (id)init {
+       return [self initWithIndex:0 attributes:[NSDictionary dictionary]];
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+       return [[ZAttributeRun allocWithZone:zone] initWithIndex:_index attributes:_attributes];
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+       [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:_index] forKey:@"index"];
+       [aCoder encodeObject:_attributes forKey:@"attributes"];
+}
+
+- (NSString *)description {
+       NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]];
+       for (id key in _attributes) {
+               [components addObject:[NSString stringWithFormat:@"%@=%@", key, [_attributes objectForKey:key]]];
+       }
+       return [NSString stringWithFormat:@"<%@: %p index=%lu attributes={%@}>",
+                       NSStringFromClass([self class]), self, (unsigned long)_index, [components componentsJoinedByString:@" "]];
+}
+
+- (BOOL)isEqual:(id)object {
+       if (![object isKindOfClass:[ZAttributeRun class]]) return NO;
+       ZAttributeRun *other = (ZAttributeRun *)object;
+       return _index == other->_index && [_attributes isEqualToDictionary:other->_attributes];
+}
+
+- (void)dealloc {
+       [_attributes release];
+       [super dealloc];
+}
+@end
+
+NSString * const ZFontAttributeName = @"ZFontAttributeName";
+NSString * const ZForegroundColorAttributeName = @"ZForegroundColorAttributeName";
+NSString * const ZBackgroundColorAttributeName = @"ZBackgroundColorAttributeName";
+NSString * const ZUnderlineStyleAttributeName = @"ZUnderlineStyleAttributeName";
diff --git a/libs/FontLabel/ZAttributedStringPrivate.h b/libs/FontLabel/ZAttributedStringPrivate.h
new file mode 100644 (file)
index 0000000..1021d7b
--- /dev/null
@@ -0,0 +1,24 @@
+//
+//  ZAttributedStringPrivate.h
+//  FontLabel
+//
+//  Created by Kevin Ballard on 9/23/09.
+//  Copyright 2009 Zynga Game Networks. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "ZAttributedString.h"
+
+@interface ZAttributeRun : NSObject <NSCopying, NSCoding> {
+       NSUInteger _index;
+       NSMutableDictionary *_attributes;
+}
+@property (nonatomic, readonly) NSUInteger index;
+@property (nonatomic, readonly) NSMutableDictionary *attributes;
++ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs;
+- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs;
+@end
+
+@interface ZAttributedString (ZAttributedStringPrivate)
+@property (nonatomic, readonly) NSArray *attributes;
+@end
diff --git a/libs/FontLabel/ZFont.h b/libs/FontLabel/ZFont.h
new file mode 100644 (file)
index 0000000..05ae823
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  ZFont.h
+//  FontLabel
+//
+//  Created by Kevin Ballard on 7/2/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@interface ZFont : NSObject {
+       CGFontRef _cgFont;
+       CGFloat _pointSize;
+       CGFloat _ratio;
+       NSString *_familyName;
+       NSString *_fontName;
+       NSString *_postScriptName;
+}
+@property (nonatomic, readonly) CGFontRef cgFont;
+@property (nonatomic, readonly) CGFloat pointSize;
+@property (nonatomic, readonly) CGFloat ascender;
+@property (nonatomic, readonly) CGFloat descender;
+@property (nonatomic, readonly) CGFloat leading;
+@property (nonatomic, readonly) CGFloat xHeight;
+@property (nonatomic, readonly) CGFloat capHeight;
+@property (nonatomic, readonly) NSString *familyName;
+@property (nonatomic, readonly) NSString *fontName;
+@property (nonatomic, readonly) NSString *postScriptName;
++ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize;
++ (ZFont *)fontWithUIFont:(UIFont *)uiFont;
+- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize;
+- (ZFont *)fontWithSize:(CGFloat)fontSize;
+@end
diff --git a/libs/FontLabel/ZFont.m b/libs/FontLabel/ZFont.m
new file mode 100644 (file)
index 0000000..793b13a
--- /dev/null
@@ -0,0 +1,170 @@
+//
+//  ZFont.m
+//  FontLabel
+//
+//  Created by Kevin Ballard on 7/2/09.
+//  Copyright © 2009 Zynga Game Networks
+//
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "ZFont.h"
+
+@interface ZFont ()
+@property (nonatomic, readonly) CGFloat ratio;
+- (NSString *)copyNameTableEntryForID:(UInt16)nameID;
+@end
+
+@implementation ZFont
+@synthesize cgFont=_cgFont, pointSize=_pointSize, ratio=_ratio;
+
++ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize {
+       return [[[self alloc] initWithCGFont:cgFont size:fontSize] autorelease];
+}
+
++ (ZFont *)fontWithUIFont:(UIFont *)uiFont {
+       NSParameterAssert(uiFont != nil);
+       CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName);
+       ZFont *zFont = [[self alloc] initWithCGFont:cgFont size:uiFont.pointSize];
+       CGFontRelease(cgFont);
+       return [zFont autorelease];
+}
+
+- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize {
+       if ((self = [super init])) {
+               _cgFont = CGFontRetain(cgFont);
+               _pointSize = fontSize;
+               _ratio = fontSize/CGFontGetUnitsPerEm(cgFont);
+       }
+       return self;
+}
+
+- (id)init {
+       NSAssert(NO, @"-init is not valid for ZFont");
+       return nil;
+}
+
+- (CGFloat)ascender {
+       return ceilf(self.ratio * CGFontGetAscent(self.cgFont));
+}
+
+- (CGFloat)descender {
+       return floorf(self.ratio * CGFontGetDescent(self.cgFont));
+}
+
+- (CGFloat)leading {
+       return (self.ascender - self.descender);
+}
+
+- (CGFloat)capHeight {
+       return ceilf(self.ratio * CGFontGetCapHeight(self.cgFont));
+}
+
+- (CGFloat)xHeight {
+       return ceilf(self.ratio * CGFontGetXHeight(self.cgFont));
+}
+
+- (NSString *)familyName {
+       if (_familyName == nil) {
+               _familyName = [self copyNameTableEntryForID:1];
+       }
+       return _familyName;
+}
+
+- (NSString *)fontName {
+       if (_fontName == nil) {
+               _fontName = [self copyNameTableEntryForID:4];
+       }
+       return _fontName;
+}
+
+- (NSString *)postScriptName {
+       if (_postScriptName == nil) {
+               _postScriptName = [self copyNameTableEntryForID:6];
+       }
+       return _postScriptName;
+}
+
+- (ZFont *)fontWithSize:(CGFloat)fontSize {
+       if (fontSize == self.pointSize) return self;
+       NSParameterAssert(fontSize > 0.0);
+       return [[[ZFont alloc] initWithCGFont:self.cgFont size:fontSize] autorelease];
+}
+
+- (BOOL)isEqual:(id)object {
+       if (![object isKindOfClass:[ZFont class]]) return NO;
+       ZFont *font = (ZFont *)object;
+       return (font.cgFont == self.cgFont && font.pointSize == self.pointSize);
+}
+
+- (NSString *)copyNameTableEntryForID:(UInt16)aNameID {
+       CFDataRef nameTable = CGFontCopyTableForTag(self.cgFont, 'name');
+       NSAssert1(nameTable != NULL, @"CGFontCopyTableForTag returned NULL for 'name' tag in font %@",
+                          [(id)CFCopyDescription(self.cgFont) autorelease]);
+       const UInt8 * const bytes = CFDataGetBytePtr(nameTable);
+       NSAssert1(OSReadBigInt16(bytes, 0) == 0, @"name table for font %@ has bad version number",
+                          [(id)CFCopyDescription(self.cgFont) autorelease]);
+       const UInt16 count = OSReadBigInt16(bytes, 2);
+       const UInt16 stringOffset = OSReadBigInt16(bytes, 4);
+       const UInt8 * const nameRecords = &bytes[6];
+       UInt16 nameLength = 0;
+       UInt16 nameOffset = 0;
+       NSStringEncoding encoding = 0;
+       for (UInt16 idx = 0; idx < count; idx++) {
+               const uintptr_t recordOffset = 12 * idx;
+               const UInt16 nameID = OSReadBigInt16(nameRecords, recordOffset + 6);
+               if (nameID != aNameID) continue;
+               const UInt16 platformID = OSReadBigInt16(nameRecords, recordOffset + 0);
+               const UInt16 platformSpecificID = OSReadBigInt16(nameRecords, recordOffset + 2);
+               encoding = 0;
+               // for now, we only support a subset of encodings
+               switch (platformID) {
+                       case 0: // Unicode
+                               encoding = NSUTF16StringEncoding;
+                               break;
+                       case 1: // Macintosh
+                               switch (platformSpecificID) {
+                                       case 0:
+                                               encoding = NSMacOSRomanStringEncoding;
+                                               break;
+                               }
+                       case 3: // Microsoft
+                               switch (platformSpecificID) {
+                                       case 1:
+                                               encoding = NSUTF16StringEncoding;
+                                               break;
+                               }
+               }
+               if (encoding == 0) continue;
+               nameLength = OSReadBigInt16(nameRecords, recordOffset + 8);
+               nameOffset = OSReadBigInt16(nameRecords, recordOffset + 10);
+               break;
+       }
+       NSString *result = nil;
+       if (nameOffset > 0) {
+               const UInt8 *nameBytes = &bytes[stringOffset + nameOffset];
+               result = [[NSString alloc] initWithBytes:nameBytes length:nameLength encoding:encoding];
+       }
+       CFRelease(nameTable);
+       return result;
+}
+
+- (void)dealloc {
+       CGFontRelease(_cgFont);
+       [_familyName release];
+       [_fontName release];
+       [_postScriptName release];
+       [super dealloc];
+}
+@end
diff --git a/libs/TouchJSON/CDataScanner.h b/libs/TouchJSON/CDataScanner.h
new file mode 100644 (file)
index 0000000..41f68e8
--- /dev/null
@@ -0,0 +1,71 @@
+//
+//  CDataScanner.h
+//  TouchCode
+//
+//  Created by Jonathan Wight on 04/16/08.
+//  Copyright 2008 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+// NSScanner
+
+@interface CDataScanner : NSObject {
+       NSData *data;
+
+       u_int8_t *start;
+       u_int8_t *end;
+       u_int8_t *current;
+       NSUInteger length;
+}
+
+@property (readwrite, nonatomic, retain) NSData *data;
+@property (readwrite, nonatomic, assign) NSUInteger scanLocation;
+@property (readonly, nonatomic, assign) NSUInteger bytesRemaining;
+@property (readonly, nonatomic, assign) BOOL isAtEnd;
+
+- (id)initWithData:(NSData *)inData;
+
+- (unichar)currentCharacter;
+- (unichar)scanCharacter;
+- (BOOL)scanCharacter:(unichar)inCharacter;
+
+- (BOOL)scanUTF8String:(const char *)inString intoString:(NSString **)outValue;
+- (BOOL)scanString:(NSString *)inString intoString:(NSString **)outValue;
+- (BOOL)scanCharactersFromSet:(NSCharacterSet *)inSet intoString:(NSString **)outValue; // inSet must only contain 7-bit ASCII characters
+
+- (BOOL)scanUpToString:(NSString *)string intoString:(NSString **)outValue;
+- (BOOL)scanUpToCharactersFromSet:(NSCharacterSet *)set intoString:(NSString **)outValue; // inSet must only contain 7-bit ASCII characters
+
+- (BOOL)scanNumber:(NSNumber **)outValue;
+- (BOOL)scanDecimalNumber:(NSDecimalNumber **)outValue;
+
+- (BOOL)scanDataOfLength:(NSUInteger)inLength intoData:(NSData **)outData;
+
+- (void)skipWhitespace;
+
+- (NSString *)remainingString;
+- (NSData *)remainingData;
+
+@end
diff --git a/libs/TouchJSON/CDataScanner.m b/libs/TouchJSON/CDataScanner.m
new file mode 100644 (file)
index 0000000..b3cee6f
--- /dev/null
@@ -0,0 +1,340 @@
+//
+//  CDataScanner.m
+//  TouchCode
+//
+//  Created by Jonathan Wight on 04/16/08.
+//  Copyright 2008 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CDataScanner.h"
+
+#import "CDataScanner_Extensions.h"
+
+@interface CDataScanner ()
+@end
+
+#pragma mark -
+
+inline static unichar CharacterAtPointer(void *start, void *end)
+    {
+    #pragma unused(end)
+
+    const u_int8_t theByte = *(u_int8_t *)start;
+    if (theByte & 0x80)
+        {
+        // TODO -- UNICODE!!!! (well in theory nothing todo here)
+        }
+    const unichar theCharacter = theByte;
+    return(theCharacter);
+    }
+
+    static NSCharacterSet *sDoubleCharacters = NULL;
+
+    @implementation CDataScanner
+
+- (id)init
+    {
+    if ((self = [super init]) != NULL)
+        {
+        }
+    return(self);
+    }
+
+- (id)initWithData:(NSData *)inData;
+    {
+    if ((self = [self init]) != NULL)
+        {
+        [self setData:inData];
+        }
+    return(self);
+    }
+
+    + (void)initialize
+    {
+    if (sDoubleCharacters == NULL)
+        {
+        sDoubleCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789eE-+."] retain];
+        }
+    }
+
+- (void)dealloc
+    {
+    [data release];
+    data = NULL;
+    //
+    [super dealloc];
+    }
+
+- (NSUInteger)scanLocation
+    {
+    return(current - start);
+    }
+
+- (NSUInteger)bytesRemaining
+    {
+    return(end - current);
+    }
+
+- (NSData *)data
+    {
+    return(data);
+    }
+
+- (void)setData:(NSData *)inData
+    {
+    if (data != inData)
+        {
+        [data release];
+        data = [inData retain];
+        }
+
+    if (data)
+        {
+        start = (u_int8_t *)data.bytes;
+        end = start + data.length;
+        current = start;
+        length = data.length;
+        }
+    else
+        {
+        start = NULL;
+        end = NULL;
+        current = NULL;
+        length = 0;
+        }
+    }
+
+- (void)setScanLocation:(NSUInteger)inScanLocation
+    {
+    current = start + inScanLocation;
+    }
+
+- (BOOL)isAtEnd
+    {
+    return(self.scanLocation >= length);
+    }
+
+- (unichar)currentCharacter
+    {
+    return(CharacterAtPointer(current, end));
+    }
+
+#pragma mark -
+
+- (unichar)scanCharacter
+    {
+    const unichar theCharacter = CharacterAtPointer(current++, end);
+    return(theCharacter);
+    }
+
+- (BOOL)scanCharacter:(unichar)inCharacter
+    {
+    unichar theCharacter = CharacterAtPointer(current, end);
+    if (theCharacter == inCharacter)
+        {
+        ++current;
+        return(YES);
+        }
+    else
+        return(NO);
+    }
+
+- (BOOL)scanUTF8String:(const char *)inString intoString:(NSString **)outValue
+    {
+    const size_t theLength = strlen(inString);
+    if ((size_t)(end - current) < theLength)
+        return(NO);
+    if (strncmp((char *)current, inString, theLength) == 0)
+        {
+        current += theLength;
+        if (outValue)
+            *outValue = [NSString stringWithUTF8String:inString];
+        return(YES);
+        }
+    return(NO);
+    }
+
+- (BOOL)scanString:(NSString *)inString intoString:(NSString **)outValue
+    {
+    if ((size_t)(end - current) < inString.length)
+        return(NO);
+    if (strncmp((char *)current, [inString UTF8String], inString.length) == 0)
+        {
+        current += inString.length;
+        if (outValue)
+            *outValue = inString;
+        return(YES);
+        }
+    return(NO);
+    }
+
+- (BOOL)scanCharactersFromSet:(NSCharacterSet *)inSet intoString:(NSString **)outValue
+    {
+    u_int8_t *P;
+    for (P = current; P < end && [inSet characterIsMember:*P] == YES; ++P)
+        ;
+
+    if (P == current)
+        {
+        return(NO);
+        }
+
+    if (outValue)
+        {
+        *outValue = [[[NSString alloc] initWithBytes:current length:P - current encoding:NSUTF8StringEncoding] autorelease];
+        }
+
+    current = P;
+
+    return(YES);
+    }
+
+- (BOOL)scanUpToString:(NSString *)inString intoString:(NSString **)outValue
+    {
+    const char *theToken = [inString UTF8String];
+    const char *theResult = strnstr((char *)current, theToken, end - current);
+    if (theResult == NULL)
+        {
+        return(NO);
+        }
+
+    if (outValue)
+        {
+        *outValue = [[[NSString alloc] initWithBytes:current length:theResult - (char *)current encoding:NSUTF8StringEncoding] autorelease];
+        }
+
+    current = (u_int8_t *)theResult;
+
+    return(YES);
+    }
+
+- (BOOL)scanUpToCharactersFromSet:(NSCharacterSet *)inSet intoString:(NSString **)outValue
+    {
+    u_int8_t *P;
+    for (P = current; P < end && [inSet characterIsMember:*P] == NO; ++P)
+        ;
+
+    if (P == current)
+        {
+        return(NO);
+        }
+
+    if (outValue)
+        {
+        *outValue = [[[NSString alloc] initWithBytes:current length:P - current encoding:NSUTF8StringEncoding] autorelease];
+        }
+
+    current = P;
+
+    return(YES);
+    }
+
+- (BOOL)scanNumber:(NSNumber **)outValue
+        {
+        NSString *theString = NULL;
+        if ([self scanCharactersFromSet:sDoubleCharacters intoString:&theString])
+            {
+            if ([theString rangeOfString:@"."].location != NSNotFound)
+                {
+                if (outValue)
+                    {
+                    *outValue = [NSDecimalNumber decimalNumberWithString:theString];
+                    }
+                return(YES);
+                }
+            else if ([theString rangeOfString:@"-"].location != NSNotFound)
+                {
+                if (outValue != NULL)
+                    {
+                    *outValue = [NSNumber numberWithLongLong:[theString longLongValue]];
+                    }
+                return(YES);
+                }
+            else
+                {
+                if (outValue != NULL)
+                    {
+                    *outValue = [NSNumber numberWithUnsignedLongLong:strtoull([theString UTF8String], NULL, 0)];
+                    }
+                return(YES);
+                }
+            
+            }
+        return(NO);
+        }
+            
+- (BOOL)scanDecimalNumber:(NSDecimalNumber **)outValue;
+        {
+        NSString *theString = NULL;
+        if ([self scanCharactersFromSet:sDoubleCharacters intoString:&theString])
+            {
+            if (outValue)
+                {
+                *outValue = [NSDecimalNumber decimalNumberWithString:theString];
+                }
+            return(YES);
+            }
+        return(NO);
+        }
+
+- (BOOL)scanDataOfLength:(NSUInteger)inLength intoData:(NSData **)outData;
+        {
+        if (self.bytesRemaining < inLength)
+            {
+            return(NO);
+            }
+        
+        if (outData)
+            {
+            *outData = [NSData dataWithBytes:current length:inLength];
+            }
+
+        current += inLength;
+        return(YES);
+        }
+
+
+- (void)skipWhitespace
+    {
+    u_int8_t *P;
+    for (P = current; P < end && (isspace(*P)); ++P)
+        ;
+
+    current = P;
+    }
+
+- (NSString *)remainingString
+    {
+    NSData *theRemainingData = [NSData dataWithBytes:current length:end - current];
+    NSString *theString = [[[NSString alloc] initWithData:theRemainingData encoding:NSUTF8StringEncoding] autorelease];
+    return(theString);
+    }
+
+- (NSData *)remainingData;
+    {
+    NSData *theRemainingData = [NSData dataWithBytes:current length:end - current];
+    return(theRemainingData);
+    }
+
+    @end
diff --git a/libs/TouchJSON/Extensions/CDataScanner_Extensions.h b/libs/TouchJSON/Extensions/CDataScanner_Extensions.h
new file mode 100644 (file)
index 0000000..cde1dbb
--- /dev/null
@@ -0,0 +1,40 @@
+//
+//  CDataScanner_Extensions.h
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/08/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CDataScanner.h"
+
+@interface CDataScanner (CDataScanner_Extensions)
+
+- (BOOL)scanCStyleComment:(NSString **)outComment;
+- (BOOL)scanCPlusPlusStyleComment:(NSString **)outComment;
+
+- (NSUInteger)lineOfScanLocation;
+- (NSDictionary *)userInfoForScanLocation;
+
+@end
diff --git a/libs/TouchJSON/Extensions/CDataScanner_Extensions.m b/libs/TouchJSON/Extensions/CDataScanner_Extensions.m
new file mode 100644 (file)
index 0000000..90dbbda
--- /dev/null
@@ -0,0 +1,135 @@
+//
+//  CDataScanner_Extensions.m
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/08/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CDataScanner_Extensions.h"
+
+#define LF 0x000a // Line Feed
+#define FF 0x000c // Form Feed
+#define CR 0x000d // Carriage Return
+#define NEL 0x0085 // Next Line
+#define LS 0x2028 // Line Separator
+#define PS 0x2029 // Paragraph Separator
+
+@implementation CDataScanner (CDataScanner_Extensions)
+
+- (BOOL)scanCStyleComment:(NSString **)outComment
+{
+if ([self scanString:@"/*" intoString:NULL] == YES)
+       {
+       NSString *theComment = NULL;
+       if ([self scanUpToString:@"*/" intoString:&theComment] == NO)
+               [NSException raise:NSGenericException format:@"Started to scan a C style comment but it wasn't terminated."];
+               
+       if ([theComment rangeOfString:@"/*"].location != NSNotFound)
+               [NSException raise:NSGenericException format:@"C style comments should not be nested."];
+       
+       if ([self scanString:@"*/" intoString:NULL] == NO)
+               [NSException raise:NSGenericException format:@"C style comment did not end correctly."];
+               
+       if (outComment != NULL)
+               *outComment = theComment;
+
+       return(YES);
+       }
+else
+       {
+       return(NO);
+       }
+}
+
+- (BOOL)scanCPlusPlusStyleComment:(NSString **)outComment
+    {
+    if ([self scanString:@"//" intoString:NULL] == YES)
+        {
+        unichar theCharacters[] = { LF, FF, CR, NEL, LS, PS, };
+        NSCharacterSet *theLineBreaksCharacterSet = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithCharacters:theCharacters length:sizeof(theCharacters) / sizeof(*theCharacters)]];
+
+        NSString *theComment = NULL;
+        [self scanUpToCharactersFromSet:theLineBreaksCharacterSet intoString:&theComment];
+        [self scanCharactersFromSet:theLineBreaksCharacterSet intoString:NULL];
+
+        if (outComment != NULL)
+            *outComment = theComment;
+
+        return(YES);
+        }
+    else
+        {
+        return(NO);
+        }
+    }
+
+- (NSUInteger)lineOfScanLocation
+    {
+    NSUInteger theLine = 0;
+    for (const u_int8_t *C = start; C < current; ++C)
+        {
+        // TODO: JIW What about MS-DOS line endings you bastard! (Also other unicode line endings)
+        if (*C == '\n' || *C == '\r')
+            {
+            ++theLine;
+            }
+        }
+    return(theLine);
+    }
+
+- (NSDictionary *)userInfoForScanLocation
+    {
+    NSUInteger theLine = 0;
+    const u_int8_t *theLineStart = start;
+    for (const u_int8_t *C = start; C < current; ++C)
+        {
+        if (*C == '\n' || *C == '\r')
+            {
+            theLineStart = C - 1;
+            ++theLine;
+            }
+        }
+
+    NSUInteger theCharacter = current - theLineStart;
+
+    NSRange theStartRange = NSIntersectionRange((NSRange){ .location = MAX((NSInteger)self.scanLocation - 20, 0), .length = 20 + (NSInteger)self.scanLocation - 20 }, (NSRange){ .location = 0, .length = self.data.length });
+    NSRange theEndRange = NSIntersectionRange((NSRange){ .location = self.scanLocation, .length = 20 }, (NSRange){ .location = 0, .length = self.data.length });
+
+
+    NSString *theSnippet = [NSString stringWithFormat:@"%@!HERE>!%@",
+        [[[NSString alloc] initWithData:[self.data subdataWithRange:theStartRange] encoding:NSUTF8StringEncoding] autorelease],
+        [[[NSString alloc] initWithData:[self.data subdataWithRange:theEndRange] encoding:NSUTF8StringEncoding] autorelease]
+        ];
+
+    NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
+        [NSNumber numberWithUnsignedInteger:theLine], @"line",
+        [NSNumber numberWithUnsignedInteger:theCharacter], @"character",
+        [NSNumber numberWithUnsignedInteger:self.scanLocation], @"location",
+        theSnippet, @"snippet",
+        NULL];
+    return(theUserInfo);    
+    }
+
+@end
diff --git a/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.h b/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.h
new file mode 100644 (file)
index 0000000..6e611d0
--- /dev/null
@@ -0,0 +1,37 @@
+//
+//  NSDictionary_JSONExtensions.h
+//  TouchCode
+//
+//  Created by Jonathan Wight on 04/17/08.
+//  Copyright 2008 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSDictionary (NSDictionary_JSONExtensions)
+
++ (id)dictionaryWithJSONData:(NSData *)inData error:(NSError **)outError;
++ (id)dictionaryWithJSONString:(NSString *)inJSON error:(NSError **)outError;
+
+@end
diff --git a/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.m b/libs/TouchJSON/Extensions/NSDictionary_JSONExtensions.m
new file mode 100644 (file)
index 0000000..c0bb43c
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  NSDictionary_JSONExtensions.m
+//  TouchCode
+//
+//  Created by Jonathan Wight on 04/17/08.
+//  Copyright 2008 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSDictionary_JSONExtensions.h"
+
+#import "CJSONDeserializer.h"
+
+@implementation NSDictionary (NSDictionary_JSONExtensions)
+
++ (id)dictionaryWithJSONData:(NSData *)inData error:(NSError **)outError
+    {
+    return([[CJSONDeserializer deserializer] deserialize:inData error:outError]);
+    }
+
++ (id)dictionaryWithJSONString:(NSString *)inJSON error:(NSError **)outError;
+    {
+    NSData *theData = [inJSON dataUsingEncoding:NSUTF8StringEncoding];
+    return([self dictionaryWithJSONData:theData error:outError]);
+    }
+
+@end
diff --git a/libs/TouchJSON/JSON/CJSONDeserializer.h b/libs/TouchJSON/JSON/CJSONDeserializer.h
new file mode 100644 (file)
index 0000000..0c3ed02
--- /dev/null
@@ -0,0 +1,63 @@
+//
+//  CJSONDeserializer.h
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/15/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "CJSONScanner.h"
+
+extern NSString *const kJSONDeserializerErrorDomain /* = @"CJSONDeserializerErrorDomain" */;
+
+enum {
+    kJSONDeserializationOptions_MutableContainers = kJSONScannerOptions_MutableContainers,
+    kJSONDeserializationOptions_MutableLeaves = kJSONScannerOptions_MutableLeaves,
+};
+typedef NSUInteger EJSONDeserializationOptions;
+
+@class CJSONScanner;
+
+@interface CJSONDeserializer : NSObject {
+    CJSONScanner *scanner;
+    EJSONDeserializationOptions options;
+}
+
+@property (readwrite, nonatomic, retain) CJSONScanner *scanner;
+/// Object to return instead when a null encountered in the JSON. Defaults to NSNull. Setting to null causes the scanner to skip null values.
+@property (readwrite, nonatomic, retain) id nullObject;
+/// JSON must be encoded in Unicode (UTF-8, UTF-16 or UTF-32). Use this if you expect to get the JSON in another encoding.
+@property (readwrite, nonatomic, assign) NSStringEncoding allowedEncoding;
+@property (readwrite, nonatomic, assign) EJSONDeserializationOptions options;
+
++ (id)deserializer;
+
+- (id)deserialize:(NSData *)inData error:(NSError **)outError;
+
+- (id)deserializeAsDictionary:(NSData *)inData error:(NSError **)outError;
+- (id)deserializeAsArray:(NSData *)inData error:(NSError **)outError;
+
+@end
diff --git a/libs/TouchJSON/JSON/CJSONDeserializer.m b/libs/TouchJSON/JSON/CJSONDeserializer.m
new file mode 100644 (file)
index 0000000..27a2d03
--- /dev/null
@@ -0,0 +1,161 @@
+//
+//  CJSONDeserializer.m
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/15/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CJSONDeserializer.h"
+
+#import "CJSONScanner.h"
+#import "CDataScanner.h"
+
+NSString *const kJSONDeserializerErrorDomain  = @"CJSONDeserializerErrorDomain";
+
+@interface CJSONDeserializer ()
+@end
+
+@implementation CJSONDeserializer
+
+@synthesize scanner;
+@synthesize options;
+
++ (id)deserializer
+    {
+    return([[[self alloc] init] autorelease]);
+    }
+
+- (id)init
+    {
+    if ((self = [super init]) != NULL)
+        {
+        }
+    return(self);
+    }
+
+- (void)dealloc
+    {
+    [scanner release];
+    scanner = NULL;
+    //
+    [super dealloc];
+    }
+
+#pragma mark -
+
+- (CJSONScanner *)scanner
+    {
+    if (scanner == NULL)
+        {
+        scanner = [[CJSONScanner alloc] init];
+        }
+    return(scanner);
+    }
+
+- (id)nullObject
+    {
+    return(self.scanner.nullObject);
+    }
+
+- (void)setNullObject:(id)inNullObject
+    {
+    self.scanner.nullObject = inNullObject;
+    }
+
+#pragma mark -
+
+- (NSStringEncoding)allowedEncoding
+    {
+    return(self.scanner.allowedEncoding);
+    }
+
+- (void)setAllowedEncoding:(NSStringEncoding)inAllowedEncoding
+    {
+    self.scanner.allowedEncoding = inAllowedEncoding;
+    }
+
+#pragma mark -
+
+- (id)deserialize:(NSData *)inData error:(NSError **)outError
+    {
+    if (inData == NULL || [inData length] == 0)
+        {
+        if (outError)
+            *outError = [NSError errorWithDomain:kJSONDeserializerErrorDomain code:kJSONScannerErrorCode_NothingToScan userInfo:NULL];
+
+        return(NULL);
+        }
+    if ([self.scanner setData:inData error:outError] == NO)
+        {
+        return(NULL);
+        }
+    id theObject = NULL;
+    if ([self.scanner scanJSONObject:&theObject error:outError] == YES)
+        return(theObject);
+    else
+        return(NULL);
+    }
+
+- (id)deserializeAsDictionary:(NSData *)inData error:(NSError **)outError
+    {
+    if (inData == NULL || [inData length] == 0)
+        {
+        if (outError)
+            *outError = [NSError errorWithDomain:kJSONDeserializerErrorDomain code:kJSONScannerErrorCode_NothingToScan userInfo:NULL];
+
+        return(NULL);
+        }
+    if ([self.scanner setData:inData error:outError] == NO)
+        {
+        return(NULL);
+        }
+    NSDictionary *theDictionary = NULL;
+    if ([self.scanner scanJSONDictionary:&theDictionary error:outError] == YES)
+        return(theDictionary);
+    else
+        return(NULL);
+    }
+
+- (id)deserializeAsArray:(NSData *)inData error:(NSError **)outError
+    {
+    if (inData == NULL || [inData length] == 0)
+        {
+        if (outError)
+            *outError = [NSError errorWithDomain:kJSONDeserializerErrorDomain code:kJSONScannerErrorCode_NothingToScan userInfo:NULL];
+
+        return(NULL);
+        }
+    if ([self.scanner setData:inData error:outError] == NO)
+        {
+        return(NULL);
+        }
+    NSArray *theArray = NULL;
+    if ([self.scanner scanJSONArray:&theArray error:outError] == YES)
+        return(theArray);
+    else
+        return(NULL);
+    }
+
+@end
diff --git a/libs/TouchJSON/JSON/CJSONScanner.h b/libs/TouchJSON/JSON/CJSONScanner.h
new file mode 100644 (file)
index 0000000..d410893
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  CJSONScanner.h
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/07/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CDataScanner.h"
+
+enum {
+    kJSONScannerOptions_MutableContainers = 0x1,
+    kJSONScannerOptions_MutableLeaves = 0x2,
+};
+typedef NSUInteger EJSONScannerOptions;
+
+/// CDataScanner subclass that understands JSON syntax natively. You should generally use CJSONDeserializer instead of this class. (TODO - this could have been a category?)
+@interface CJSONScanner : CDataScanner {
+       BOOL strictEscapeCodes;
+    id nullObject;
+       NSStringEncoding allowedEncoding;
+    EJSONScannerOptions options;
+}
+
+@property (readwrite, nonatomic, assign) BOOL strictEscapeCodes;
+@property (readwrite, nonatomic, retain) id nullObject;
+@property (readwrite, nonatomic, assign) NSStringEncoding allowedEncoding;
+@property (readwrite, nonatomic, assign) EJSONScannerOptions options;
+
+- (BOOL)setData:(NSData *)inData error:(NSError **)outError;
+
+- (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError;
+- (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError;
+- (BOOL)scanJSONArray:(NSArray **)outArray error:(NSError **)outError;
+- (BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError;
+- (BOOL)scanJSONNumberConstant:(NSNumber **)outNumberConstant error:(NSError **)outError;
+
+@end
+
+extern NSString *const kJSONScannerErrorDomain /* = @"kJSONScannerErrorDomain" */;
+
+typedef enum {
+    
+    // Fundamental scanning errors
+    kJSONScannerErrorCode_NothingToScan = -11, 
+    kJSONScannerErrorCode_CouldNotDecodeData = -12, 
+    kJSONScannerErrorCode_CouldNotSerializeData = -13,
+    kJSONScannerErrorCode_CouldNotSerializeObject = -14, 
+    kJSONScannerErrorCode_CouldNotScanObject = -15, 
+    
+    // Dictionary scanning
+    kJSONScannerErrorCode_DictionaryStartCharacterMissing = -101, 
+    kJSONScannerErrorCode_DictionaryKeyScanFailed = -102, 
+    kJSONScannerErrorCode_DictionaryKeyNotTerminated = -103, 
+    kJSONScannerErrorCode_DictionaryValueScanFailed = -104, 
+    kJSONScannerErrorCode_DictionaryKeyValuePairNoDelimiter = -105, 
+    kJSONScannerErrorCode_DictionaryNotTerminated = -106, 
+    
+    // Array scanning
+    kJSONScannerErrorCode_ArrayStartCharacterMissing = -201, 
+    kJSONScannerErrorCode_ArrayValueScanFailed = -202, 
+    kJSONScannerErrorCode_ArrayValueIsNull = -203, 
+    kJSONScannerErrorCode_ArrayNotTerminated = -204,
+    
+    // String scanning
+    kJSONScannerErrorCode_StringNotStartedWithBackslash = -301, 
+    kJSONScannerErrorCode_StringUnicodeNotDecoded = -302, 
+    kJSONScannerErrorCode_StringUnknownEscapeCode = -303, 
+    kJSONScannerErrorCode_StringNotTerminated = -304,
+    
+    // Number scanning
+    kJSONScannerErrorCode_NumberNotScannable = -401
+    
+} EJSONScannerErrorCode;
diff --git a/libs/TouchJSON/JSON/CJSONScanner.m b/libs/TouchJSON/JSON/CJSONScanner.m
new file mode 100644 (file)
index 0000000..c5ffeb4
--- /dev/null
@@ -0,0 +1,676 @@
+//
+//  CJSONScanner.m
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/07/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CJSONScanner.h"
+
+#import "CDataScanner_Extensions.h"
+
+#if !defined(TREAT_COMMENTS_AS_WHITESPACE)
+#define TREAT_COMMENTS_AS_WHITESPACE 0
+#endif // !defined(TREAT_COMMENTS_AS_WHITESPACE)
+
+NSString *const kJSONScannerErrorDomain = @"kJSONScannerErrorDomain";
+
+inline static int HexToInt(char inCharacter)
+    {
+    int theValues[] = { 0x0 /* 48 '0' */, 0x1 /* 49 '1' */, 0x2 /* 50 '2' */, 0x3 /* 51 '3' */, 0x4 /* 52 '4' */, 0x5 /* 53 '5' */, 0x6 /* 54 '6' */, 0x7 /* 55 '7' */, 0x8 /* 56 '8' */, 0x9 /* 57 '9' */, -1 /* 58 ':' */, -1 /* 59 ';' */, -1 /* 60 '<' */, -1 /* 61 '=' */, -1 /* 62 '>' */, -1 /* 63 '?' */, -1 /* 64 '@' */, 0xa /* 65 'A' */, 0xb /* 66 'B' */, 0xc /* 67 'C' */, 0xd /* 68 'D' */, 0xe /* 69 'E' */, 0xf /* 70 'F' */, -1 /* 71 'G' */, -1 /* 72 'H' */, -1 /* 73 'I' */, -1 /* 74 'J' */, -1 /* 75 'K' */, -1 /* 76 'L' */, -1 /* 77 'M' */, -1 /* 78 'N' */, -1 /* 79 'O' */, -1 /* 80 'P' */, -1 /* 81 'Q' */, -1 /* 82 'R' */, -1 /* 83 'S' */, -1 /* 84 'T' */, -1 /* 85 'U' */, -1 /* 86 'V' */, -1 /* 87 'W' */, -1 /* 88 'X' */, -1 /* 89 'Y' */, -1 /* 90 'Z' */, -1 /* 91 '[' */, -1 /* 92 '\' */, -1 /* 93 ']' */, -1 /* 94 '^' */, -1 /* 95 '_' */, -1 /* 96 '`' */, 0xa /* 97 'a' */, 0xb /* 98 'b' */, 0xc /* 99 'c' */, 0xd /* 100 'd' */, 0xe /* 101 'e' */, 0xf /* 102 'f' */, };
+    if (inCharacter >= '0' && inCharacter <= 'f')
+        return(theValues[inCharacter - '0']);
+    else
+        return(-1);
+    }
+
+@interface CJSONScanner ()
+- (BOOL)scanNotQuoteCharactersIntoString:(NSString **)outValue;
+@end
+
+#pragma mark -
+
+@implementation CJSONScanner
+
+@synthesize strictEscapeCodes;
+@synthesize nullObject;
+@synthesize allowedEncoding;
+@synthesize options;
+
+- (id)init
+    {
+    if ((self = [super init]) != NULL)
+        {
+        strictEscapeCodes = NO;
+        nullObject = [[NSNull null] retain];
+        }
+    return(self);
+    }
+
+- (void)dealloc
+    {
+    [nullObject release];
+    nullObject = NULL;
+    //
+    [super dealloc];
+    }
+
+#pragma mark -
+
+- (BOOL)setData:(NSData *)inData error:(NSError **)outError;
+    {
+    NSData *theData = inData;
+    if (theData && theData.length >= 4)
+        {
+        // This code is lame, but it works. Because the first character of any JSON string will always be a (ascii) control character we can work out the Unicode encoding by the bit pattern. See section 3 of http://www.ietf.org/rfc/rfc4627.txt
+        const char *theChars = theData.bytes;
+        NSStringEncoding theEncoding = NSUTF8StringEncoding;
+        if (theChars[0] != 0 && theChars[1] == 0)
+            {
+            if (theChars[2] != 0 && theChars[3] == 0)
+                theEncoding = NSUTF16LittleEndianStringEncoding;
+            else if (theChars[2] == 0 && theChars[3] == 0)
+                theEncoding = NSUTF32LittleEndianStringEncoding;
+            }
+        else if (theChars[0] == 0 && theChars[2] == 0 && theChars[3] != 0)
+            {
+            if (theChars[1] == 0)
+                theEncoding = NSUTF32BigEndianStringEncoding;
+            else if (theChars[1] != 0)
+                theEncoding = NSUTF16BigEndianStringEncoding;
+            }
+            
+        NSString *theString = [[NSString alloc] initWithData:theData encoding:theEncoding];
+        if (theString == NULL && self.allowedEncoding != 0)
+            {
+            theString = [[NSString alloc] initWithData:theData encoding:self.allowedEncoding];
+            }
+        theData = [theString dataUsingEncoding:NSUTF8StringEncoding];
+        [theString release];
+        }
+
+    if (theData)
+        {
+        [super setData:theData];
+        return(YES);
+        }
+    else
+        {
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan data. Data wasn't encoded properly?", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_CouldNotDecodeData userInfo:theUserInfo];
+            }
+        return(NO);
+        }
+    }
+
+- (void)setData:(NSData *)inData
+    {
+    [self setData:inData error:NULL];
+    }
+
+#pragma mark -
+
+- (BOOL)scanJSONObject:(id *)outObject error:(NSError **)outError
+    {
+    BOOL theResult = YES;
+
+    [self skipWhitespace];
+
+    id theObject = NULL;
+
+    const unichar C = [self currentCharacter];
+    switch (C)
+        {
+        case 't':
+            if ([self scanUTF8String:"true" intoString:NULL])
+                {
+                theObject = [NSNumber numberWithBool:YES];
+                }
+            break;
+        case 'f':
+            if ([self scanUTF8String:"false" intoString:NULL])
+                {
+                theObject = [NSNumber numberWithBool:NO];
+                }
+            break;
+        case 'n':
+            if ([self scanUTF8String:"null" intoString:NULL])
+                {
+                theObject = self.nullObject;
+                }
+            break;
+        case '\"':
+        case '\'':
+            theResult = [self scanJSONStringConstant:&theObject error:outError];
+            break;
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case '-':
+            theResult = [self scanJSONNumberConstant:&theObject error:outError];
+            break;
+        case '{':
+            theResult = [self scanJSONDictionary:&theObject error:outError];
+            break;
+        case '[':
+            theResult = [self scanJSONArray:&theObject error:outError];
+            break;
+        default:
+            theResult = NO;
+            if (outError)
+                {
+                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                    @"Could not scan object. Character not a valid JSON character.", NSLocalizedDescriptionKey,
+                    NULL];
+                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_CouldNotScanObject userInfo:theUserInfo];
+                }
+            break;
+        }
+
+    if (outObject != NULL)
+        *outObject = theObject;
+
+    return(theResult);
+    }
+
+- (BOOL)scanJSONDictionary:(NSDictionary **)outDictionary error:(NSError **)outError
+    {
+    NSUInteger theScanLocation = [self scanLocation];
+
+    [self skipWhitespace];
+
+    if ([self scanCharacter:'{'] == NO)
+        {
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan dictionary. Dictionary that does not start with '{' character.", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryStartCharacterMissing userInfo:theUserInfo];
+            }
+        return(NO);
+        }
+
+    NSMutableDictionary *theDictionary = [[NSMutableDictionary alloc] init];
+
+    while ([self currentCharacter] != '}')
+        {
+        [self skipWhitespace];
+        
+        if ([self currentCharacter] == '}')
+            break;
+
+        NSString *theKey = NULL;
+        if ([self scanJSONStringConstant:&theKey error:outError] == NO)
+            {
+            [self setScanLocation:theScanLocation];
+            if (outError)
+                {
+                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                    @"Could not scan dictionary. Failed to scan a key.", NSLocalizedDescriptionKey,
+                    NULL];
+                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryKeyScanFailed userInfo:theUserInfo];
+                }
+            [theDictionary release];
+            return(NO);
+            }
+
+        [self skipWhitespace];
+
+        if ([self scanCharacter:':'] == NO)
+            {
+            [self setScanLocation:theScanLocation];
+            if (outError)
+                {
+                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                    @"Could not scan dictionary. Key was not terminated with a ':' character.", NSLocalizedDescriptionKey,
+                    NULL];
+                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryKeyNotTerminated userInfo:theUserInfo];
+                }
+            [theDictionary release];
+            return(NO);
+            }
+
+        id theValue = NULL;
+        if ([self scanJSONObject:&theValue error:outError] == NO)
+            {
+            [self setScanLocation:theScanLocation];
+            if (outError)
+                {
+                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                    @"Could not scan dictionary. Failed to scan a value.", NSLocalizedDescriptionKey,
+                    NULL];
+                    
+                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryValueScanFailed userInfo:theUserInfo];
+                }
+            [theDictionary release];
+            return(NO);
+            }
+
+        if (theValue == NULL && self.nullObject == NULL)
+            {
+            // If the value is a null and nullObject is also null then we're skipping this key/value pair.
+            }
+        else
+            {
+            [theDictionary setValue:theValue forKey:theKey];
+            }
+
+        [self skipWhitespace];
+        if ([self scanCharacter:','] == NO)
+            {
+            if ([self currentCharacter] != '}')
+                {
+                [self setScanLocation:theScanLocation];
+                if (outError)
+                    {
+                    NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                        @"Could not scan dictionary. Key value pairs not delimited with a ',' character.", NSLocalizedDescriptionKey,
+                        NULL];
+                    [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                    *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryKeyValuePairNoDelimiter userInfo:theUserInfo];
+                    }
+                [theDictionary release];
+                return(NO);
+                }
+            break;
+            }
+        else
+            {
+            [self skipWhitespace];
+            if ([self currentCharacter] == '}')
+                break;
+            }
+        }
+
+    if ([self scanCharacter:'}'] == NO)
+        {
+        [self setScanLocation:theScanLocation];
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan dictionary. Dictionary not terminated by a '}' character.", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_DictionaryNotTerminated userInfo:theUserInfo];
+            }
+        [theDictionary release];
+        return(NO);
+        }
+
+    if (outDictionary != NULL)
+        {
+        if (self.options & kJSONScannerOptions_MutableContainers)
+            {
+            *outDictionary = [theDictionary autorelease];
+            }
+        else
+            {
+            *outDictionary = [[theDictionary copy] autorelease];
+            [theDictionary release];
+            }
+        }
+    else
+        {
+        [theDictionary release];
+        }
+
+    return(YES);
+    }
+
+- (BOOL)scanJSONArray:(NSArray **)outArray error:(NSError **)outError
+    {
+    NSUInteger theScanLocation = [self scanLocation];
+
+    [self skipWhitespace];
+
+    if ([self scanCharacter:'['] == NO)
+        {
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan array. Array not started by a '[' character.", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayStartCharacterMissing userInfo:theUserInfo];
+            }
+        return(NO);
+        }
+
+    NSMutableArray *theArray = [[NSMutableArray alloc] init];
+
+    [self skipWhitespace];
+    while ([self currentCharacter] != ']')
+        {
+        NSString *theValue = NULL;
+        if ([self scanJSONObject:&theValue error:outError] == NO)
+            {
+            [self setScanLocation:theScanLocation];
+            if (outError)
+                {
+                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                    @"Could not scan array. Could not scan a value.", NSLocalizedDescriptionKey,
+                    NULL];
+                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayValueScanFailed userInfo:theUserInfo];
+                }
+            [theArray release];
+            return(NO);
+            }
+            
+        if (theValue == NULL)
+            {
+            if (self.nullObject != NULL)
+                {
+                if (outError)
+                    {
+                    NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                        @"Could not scan array. Value is NULL.", NSLocalizedDescriptionKey,
+                        NULL];
+                    [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                    *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayValueIsNull userInfo:theUserInfo];
+                    }
+                [theArray release];
+                return(NO);
+                }
+            }
+        else
+            {
+            [theArray addObject:theValue];
+            }
+        
+        [self skipWhitespace];
+        if ([self scanCharacter:','] == NO)
+            {
+            [self skipWhitespace];
+            if ([self currentCharacter] != ']')
+                {
+                [self setScanLocation:theScanLocation];
+                if (outError)
+                    {
+                    NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                        @"Could not scan array. Array not terminated by a ']' character.", NSLocalizedDescriptionKey,
+                        NULL];
+                    [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                    *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayNotTerminated userInfo:theUserInfo];
+                    }
+                [theArray release];
+                return(NO);
+                }
+            
+            break;
+            }
+        [self skipWhitespace];
+        }
+
+    [self skipWhitespace];
+
+    if ([self scanCharacter:']'] == NO)
+        {
+        [self setScanLocation:theScanLocation];
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan array. Array not terminated by a ']' character.", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_ArrayNotTerminated userInfo:theUserInfo];
+            }
+        [theArray release];
+        return(NO);
+        }
+
+    if (outArray != NULL)
+        {
+        if (self.options & kJSONScannerOptions_MutableContainers)
+            {
+            *outArray = [theArray autorelease];
+            }
+        else
+            {
+            *outArray = [[theArray copy] autorelease];
+            [theArray release];
+            }
+        }
+    else
+        {
+        [theArray release];
+        }
+    return(YES);
+    }
+
+- (BOOL)scanJSONStringConstant:(NSString **)outStringConstant error:(NSError **)outError
+    {
+    NSUInteger theScanLocation = [self scanLocation];
+
+    [self skipWhitespace];
+
+    NSMutableString *theString = [[NSMutableString alloc] init];
+
+    if ([self scanCharacter:'"'] == NO)
+        {
+        [self setScanLocation:theScanLocation];
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan string constant. String not started by a '\"' character.", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringNotStartedWithBackslash userInfo:theUserInfo];
+            }
+        [theString release];
+        return(NO);
+        }
+
+    while ([self scanCharacter:'"'] == NO)
+        {
+        NSString *theStringChunk = NULL;
+        if ([self scanNotQuoteCharactersIntoString:&theStringChunk])
+            {
+            CFStringAppend((CFMutableStringRef)theString, (CFStringRef)theStringChunk);
+            }
+        else if ([self scanCharacter:'\\'] == YES)
+            {
+            unichar theCharacter = [self scanCharacter];
+            switch (theCharacter)
+                {
+                case '"':
+                case '\\':
+                case '/':
+                    break;
+                case 'b':
+                    theCharacter = '\b';
+                    break;
+                case 'f':
+                    theCharacter = '\f';
+                    break;
+                case 'n':
+                    theCharacter = '\n';
+                    break;
+                case 'r':
+                    theCharacter = '\r';
+                    break;
+                case 't':
+                    theCharacter = '\t';
+                    break;
+                case 'u':
+                    {
+                    theCharacter = 0;
+
+                    int theShift;
+                    for (theShift = 12; theShift >= 0; theShift -= 4)
+                        {
+                        const int theDigit = HexToInt([self scanCharacter]);
+                        if (theDigit == -1)
+                            {
+                            [self setScanLocation:theScanLocation];
+                            if (outError)
+                                {
+                                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                    @"Could not scan string constant. Unicode character could not be decoded.", NSLocalizedDescriptionKey,
+                                    NULL];
+                                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringUnicodeNotDecoded userInfo:theUserInfo];
+                                }
+                            [theString release];
+                            return(NO);
+                            }
+                        theCharacter |= (theDigit << theShift);
+                        }
+                    }
+                    break;
+                default:
+                    {
+                    if (strictEscapeCodes == YES)
+                        {
+                        [self setScanLocation:theScanLocation];
+                        if (outError)
+                            {
+                            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                                @"Could not scan string constant. Unknown escape code.", NSLocalizedDescriptionKey,
+                                NULL];
+                            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringUnknownEscapeCode userInfo:theUserInfo];
+                            }
+                        [theString release];
+                        return(NO);
+                        }
+                    }
+                    break;
+                }
+            CFStringAppendCharacters((CFMutableStringRef)theString, &theCharacter, 1);
+            }
+        else
+            {
+            if (outError)
+                {
+                NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                    @"Could not scan string constant. No terminating double quote character.", NSLocalizedDescriptionKey,
+                    NULL];
+                [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+                *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_StringNotTerminated userInfo:theUserInfo];
+                }
+            [theString release];
+            return(NO);
+            }
+        }
+        
+    if (outStringConstant != NULL)
+        {
+        if (self.options & kJSONScannerOptions_MutableLeaves)
+            {
+            *outStringConstant = [theString autorelease];
+            }
+        else
+            {
+            *outStringConstant = [[theString copy] autorelease];
+            [theString release];
+            }
+        }
+    else
+        {
+        [theString release];
+        }
+
+    return(YES);
+    }
+
+- (BOOL)scanJSONNumberConstant:(NSNumber **)outNumberConstant error:(NSError **)outError
+    {
+    NSNumber *theNumber = NULL;
+
+    [self skipWhitespace];
+
+    if ([self scanNumber:&theNumber] == YES)
+        {
+        if (outNumberConstant != NULL)
+            *outNumberConstant = theNumber;
+        return(YES);
+        }
+    else
+        {
+        if (outError)
+            {
+            NSMutableDictionary *theUserInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                @"Could not scan number constant.", NSLocalizedDescriptionKey,
+                NULL];
+            [theUserInfo addEntriesFromDictionary:self.userInfoForScanLocation];
+            *outError = [NSError errorWithDomain:kJSONScannerErrorDomain code:kJSONScannerErrorCode_NumberNotScannable userInfo:theUserInfo];
+            }
+        return(NO);
+        }
+    }
+
+#if TREAT_COMMENTS_AS_WHITESPACE
+- (void)skipWhitespace
+    {
+    [super skipWhitespace];
+    [self scanCStyleComment:NULL];
+    [self scanCPlusPlusStyleComment:NULL];
+    [super skipWhitespace];
+    }
+#endif // TREAT_COMMENTS_AS_WHITESPACE
+
+#pragma mark -
+
+- (BOOL)scanNotQuoteCharactersIntoString:(NSString **)outValue
+    {
+    u_int8_t *P;
+    for (P = current; P < end && *P != '\"' && *P != '\\'; ++P)
+        ;
+
+    if (P == current)
+        {
+        return(NO);
+        }
+
+    if (outValue)
+        {
+        *outValue = [[[NSString alloc] initWithBytes:current length:P - current encoding:NSUTF8StringEncoding] autorelease];
+        }
+        
+    current = P;
+
+    return(YES);
+    }
+
+@end
diff --git a/libs/TouchJSON/JSON/CJSONSerializer.h b/libs/TouchJSON/JSON/CJSONSerializer.h
new file mode 100644 (file)
index 0000000..748a85c
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  CJSONSerializer.h
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/07/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface CJSONSerializer : NSObject {
+}
+
++ (id)serializer;
+
+- (BOOL)isValidJSONObject:(id)inObject;
+
+/// Take any JSON compatible object (generally NSNull, NSNumber, NSString, NSArray and NSDictionary) and produce an NSData containing the serialized JSON.
+- (NSData *)serializeObject:(id)inObject error:(NSError **)outError;
+
+- (NSData *)serializeNull:(NSNull *)inNull error:(NSError **)outError;
+- (NSData *)serializeNumber:(NSNumber *)inNumber error:(NSError **)outError;
+- (NSData *)serializeString:(NSString *)inString error:(NSError **)outError;
+- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError;
+- (NSData *)serializeDictionary:(NSDictionary *)inDictionary error:(NSError **)outError;
+
+@end
+
+typedef enum {
+    CJSONSerializerErrorCouldNotSerializeDataType = -1,
+    CJSONSerializerErrorCouldNotSerializeObject = -1
+} CJSONSerializerError;
diff --git a/libs/TouchJSON/JSON/CJSONSerializer.m b/libs/TouchJSON/JSON/CJSONSerializer.m
new file mode 100644 (file)
index 0000000..952b3c2
--- /dev/null
@@ -0,0 +1,342 @@
+//
+//  CJSONSerializer.m
+//  TouchCode
+//
+//  Created by Jonathan Wight on 12/07/2005.
+//  Copyright 2005 toxicsoftware.com. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CJSONSerializer.h"
+
+#import "JSONRepresentation.h"
+
+static NSData *kNULL = NULL;
+static NSData *kFalse = NULL;
+static NSData *kTrue = NULL;
+
+@implementation CJSONSerializer
+
++ (void)initialize
+    {
+    NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];
+
+    if (self == [CJSONSerializer class])
+        {
+        if (kNULL == NULL)
+            kNULL = [[NSData alloc] initWithBytesNoCopy:(void *)"null" length:4 freeWhenDone:NO];
+        if (kFalse == NULL)
+            kFalse = [[NSData alloc] initWithBytesNoCopy:(void *)"false" length:5 freeWhenDone:NO];
+        if (kTrue == NULL)
+            kTrue = [[NSData alloc] initWithBytesNoCopy:(void *)"true" length:4 freeWhenDone:NO];
+
+        [thePool release];
+        }
+    }
+
++ (id)serializer
+    {
+    return([[[self alloc] init] autorelease]);
+    }
+    
+- (BOOL)isValidJSONObject:(id)inObject
+    {
+    if ([inObject isKindOfClass:[NSNull class]])
+        {
+        return(YES);
+        }
+    else if ([inObject isKindOfClass:[NSNumber class]])
+        {
+        return(YES);
+        }
+    else if ([inObject isKindOfClass:[NSString class]])
+        {
+        return(YES);
+        }
+    else if ([inObject isKindOfClass:[NSArray class]])
+        {
+        return(YES);
+        }
+    else if ([inObject isKindOfClass:[NSDictionary class]])
+        {
+        return(YES);
+        }
+    else if ([inObject isKindOfClass:[NSData class]])
+        {
+        return(YES);
+        }
+    else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)])
+        {
+        return(YES);
+        }
+    else
+        {
+        return(NO);
+        }
+    }
+
+- (NSData *)serializeObject:(id)inObject error:(NSError **)outError
+    {
+    NSData *theResult = NULL;
+
+    if ([inObject isKindOfClass:[NSNull class]])
+        {
+        theResult = [self serializeNull:inObject error:outError];
+        }
+    else if ([inObject isKindOfClass:[NSNumber class]])
+        {
+        theResult = [self serializeNumber:inObject error:outError];
+        }
+    else if ([inObject isKindOfClass:[NSString class]])
+        {
+        theResult = [self serializeString:inObject error:outError];
+        }
+    else if ([inObject isKindOfClass:[NSArray class]])
+        {
+        theResult = [self serializeArray:inObject error:outError];
+        }
+    else if ([inObject isKindOfClass:[NSDictionary class]])
+        {
+        theResult = [self serializeDictionary:inObject error:outError];
+        }
+    else if ([inObject isKindOfClass:[NSData class]])
+        {
+        NSString *theString = [[[NSString alloc] initWithData:inObject encoding:NSUTF8StringEncoding] autorelease];
+        theResult = [self serializeString:theString error:outError];
+        }
+    else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)])
+        {
+        theResult = [inObject JSONDataRepresentation];
+        }
+    else
+        {
+        if (outError)
+            {
+            NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
+                [NSString stringWithFormat:@"Cannot serialize data of type '%@'", NSStringFromClass([inObject class])], NSLocalizedDescriptionKey,
+                NULL];
+            *outError = [NSError errorWithDomain:@"TODO_DOMAIN" code:CJSONSerializerErrorCouldNotSerializeDataType userInfo:theUserInfo];
+            }
+        return(NULL);
+        }
+    if (theResult == NULL)
+        {
+        if (outError)
+            {
+            NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
+                [NSString stringWithFormat:@"Could not serialize object '%@'", inObject], NSLocalizedDescriptionKey,
+                NULL];
+            *outError = [NSError errorWithDomain:@"TODO_DOMAIN" code:CJSONSerializerErrorCouldNotSerializeObject userInfo:theUserInfo];
+            }
+        return(NULL);
+        }
+    return(theResult);
+    }
+
+- (NSData *)serializeNull:(NSNull *)inNull error:(NSError **)outError
+    {
+    #pragma unused (inNull, outError)
+    return(kNULL);
+    }
+
+- (NSData *)serializeNumber:(NSNumber *)inNumber error:(NSError **)outError
+    {
+    #pragma unused (outError)
+    NSData *theResult = NULL;
+    switch (CFNumberGetType((CFNumberRef)inNumber))
+        {
+        case kCFNumberCharType:
+            {
+            int theValue = [inNumber intValue];
+            if (theValue == 0)
+                theResult = kFalse;
+            else if (theValue == 1)
+                theResult = kTrue;
+            else
+                theResult = [[inNumber stringValue] dataUsingEncoding:NSASCIIStringEncoding];
+            }
+            break;
+        case kCFNumberFloat32Type:
+        case kCFNumberFloat64Type:
+        case kCFNumberFloatType:
+        case kCFNumberDoubleType:
+        case kCFNumberSInt8Type:
+        case kCFNumberSInt16Type:
+        case kCFNumberSInt32Type:
+        case kCFNumberSInt64Type:
+        case kCFNumberShortType:
+        case kCFNumberIntType:
+        case kCFNumberLongType:
+        case kCFNumberLongLongType:
+        case kCFNumberCFIndexType:
+        default:
+            theResult = [[inNumber stringValue] dataUsingEncoding:NSASCIIStringEncoding];
+            break;
+        }
+    return(theResult);
+    }
+
+- (NSData *)serializeString:(NSString *)inString error:(NSError **)outError
+    {
+    #pragma unused (outError)
+
+    const char *theUTF8String = [inString UTF8String];
+
+    NSMutableData *theData = [NSMutableData dataWithLength:strlen(theUTF8String) * 2 + 2];
+
+    char *theOutputStart = [theData mutableBytes];
+    char *OUT = theOutputStart;
+
+    *OUT++ = '"';
+
+    for (const char *IN = theUTF8String; IN && *IN != '\0'; ++IN)
+        {
+        switch (*IN)
+            {
+            case '\\':
+                {
+                *OUT++ = '\\';
+                *OUT++ = '\\';
+                }
+                break;
+            case '\"':
+                {
+                *OUT++ = '\\';
+                *OUT++ = '\"';
+                }
+                break;
+            case '/':
+                {
+                *OUT++ = '\\';
+                *OUT++ = '/';
+                }
+                break;
+            case '\b':
+                {
+                *OUT++ = '\\';
+                *OUT++ = 'b';
+                }
+                break;
+            case '\f':
+                {
+                *OUT++ = '\\';
+                *OUT++ = 'f';
+                }
+                break;
+            case '\n':
+                {
+                *OUT++ = '\\';
+                *OUT++ = 'n';
+                }
+                break;
+            case '\r':
+                {
+                *OUT++ = '\\';
+                *OUT++ = 'r';
+                }
+                break;
+            case '\t':
+                {
+                *OUT++ = '\\';
+                *OUT++ = 't';
+                }
+                break;
+            default:
+                {
+                *OUT++ = *IN;
+                }
+                break;
+            }
+        }
+
+    *OUT++ = '"';
+
+    theData.length = OUT - theOutputStart;
+    return(theData);
+    }
+
+- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError
+    {
+    NSMutableData *theData = [NSMutableData data];
+
+    [theData appendBytes:"[" length:1];
+
+    NSEnumerator *theEnumerator = [inArray objectEnumerator];
+    id theValue = NULL;
+    NSUInteger i = 0;
+    while ((theValue = [theEnumerator nextObject]) != NULL)
+        {
+        NSData *theValueData = [self serializeObject:theValue error:outError];
+        if (theValueData == NULL)
+            {
+            return(NULL);
+            }
+        [theData appendData:theValueData];
+        if (++i < [inArray count])
+            [theData appendBytes:"," length:1];
+        }
+
+    [theData appendBytes:"]" length:1];
+
+    return(theData);
+    }
+
+- (NSData *)serializeDictionary:(NSDictionary *)inDictionary error:(NSError **)outError
+    {
+    NSMutableData *theData = [NSMutableData data];
+
+    [theData appendBytes:"{" length:1];
+
+    NSArray *theKeys = [inDictionary allKeys];
+    NSEnumerator *theEnumerator = [theKeys objectEnumerator];
+    NSString *theKey = NULL;
+    while ((theKey = [theEnumerator nextObject]) != NULL)
+        {
+        id theValue = [inDictionary objectForKey:theKey];
+        
+        NSData *theKeyData = [self serializeString:theKey error:outError];
+        if (theKeyData == NULL)
+            {
+            return(NULL);
+            }
+        NSData *theValueData = [self serializeObject:theValue error:outError];
+        if (theValueData == NULL)
+            {
+            return(NULL);
+            }
+        
+        
+        [theData appendData:theKeyData];
+        [theData appendBytes:":" length:1];
+        [theData appendData:theValueData];
+        
+        if (theKey != [theKeys lastObject])
+            [theData appendData:[@"," dataUsingEncoding:NSASCIIStringEncoding]];
+        }
+
+    [theData appendBytes:"}" length:1];
+
+    return(theData);
+    }
+
+@end
diff --git a/libs/TouchJSON/JSON/JSONRepresentation.h b/libs/TouchJSON/JSON/JSONRepresentation.h
new file mode 100644 (file)
index 0000000..a83d76f
--- /dev/null
@@ -0,0 +1,18 @@
+//
+//  JSONRepresentation.h
+//  TouchJSON
+//
+//  Created by Jonathan Wight on 10/15/10.
+//  Copyright 2010 toxicsoftware.com. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol JSONRepresentation
+
+@optional
+- (id)initWithJSONDataRepresentation:(NSData *)inJSONData;
+
+- (NSData *)JSONDataRepresentation;
+
+@end
diff --git a/libs/cocos2d/CCAction.h b/libs/cocos2d/CCAction.h
new file mode 100644 (file)
index 0000000..51bad8e
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2010 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#include <sys/time.h>
+#import <Foundation/Foundation.h>
+
+#import "ccTypes.h"
+
+enum {
+       //! Default tag
+       kCCActionTagInvalid = -1,
+};
+
+/** Base class for CCAction objects.
+ */
+@interface CCAction : NSObject <NSCopying>
+{
+       id                      originalTarget_;
+       id                      target_;
+       NSInteger       tag_;
+}
+
+/** The "target". The action will modify the target properties.
+ The target will be set with the 'startWithTarget' method.
+ When the 'stop' method is called, target will be set to nil.
+ The target is 'assigned', it is not 'retained'.
+ */
+@property (nonatomic,readonly,assign) id target;
+
+/** The original target, since target can be nil.
+ Is the target that were used to run the action. Unless you are doing something complex, like CCActionManager, you should NOT call this method.
+ @since v0.8.2
+*/
+@property (nonatomic,readonly,assign) id originalTarget;
+
+
+/** The action tag. An identifier of the action */
+@property (nonatomic,readwrite,assign) NSInteger tag;
+
+/** Allocates and initializes the action */
++(id) action;
+
+/** Initializes the action */
+-(id) init;
+
+-(id) copyWithZone: (NSZone*) zone;
+
+//! return YES if the action has finished
+-(BOOL) isDone;
+//! called before the action start. It will also set the target.
+-(void) startWithTarget:(id)target;
+//! called after the action has finished. It will set the 'target' to nil.
+//! IMPORTANT: You should never call "[action stop]" manually. Instead, use: "[target stopAction:action];"
+-(void) stop;
+//! called every frame with it's delta time. DON'T override unless you know what you are doing.
+-(void) step: (ccTime) dt;
+//! called once per frame. time a value between 0 and 1
+//! For example: 
+//! * 0 means that the action just started
+//! * 0.5 means that the action is in the middle
+//! * 1 means that the action is over
+-(void) update: (ccTime) time;
+
+@end
+
+/** Base class actions that do have a finite time duration.
+ Possible actions:
+   - An action with a duration of 0 seconds
+   - An action with a duration of 35.5 seconds
+ Infitite time actions are valid
+ */
+@interface CCFiniteTimeAction : CCAction <NSCopying>
+{
+       //! duration in seconds
+       ccTime duration_;
+}
+//! duration in seconds of the action
+@property (nonatomic,readwrite) ccTime duration;
+
+/** returns a reversed action */
+- (CCFiniteTimeAction*) reverse;
+@end
+
+
+@class CCActionInterval;
+/** Repeats an action for ever.
+ To repeat the an action for a limited number of times use the Repeat action.
+ @warning This action can't be Sequenceable because it is not an IntervalAction
+ */
+@interface CCRepeatForever : CCAction <NSCopying>
+{
+       CCActionInterval *innerAction_;
+}
+/** Inner action */
+@property (nonatomic, readwrite, retain) CCActionInterval *innerAction;
+
+/** creates the action */
++(id) actionWithAction: (CCActionInterval*) action;
+/** initializes the action */
+-(id) initWithAction: (CCActionInterval*) action;
+@end
+
+/** Changes the speed of an action, making it take longer (speed>1)
+ or less (speed<1) time.
+ Useful to simulate 'slow motion' or 'fast forward' effect.
+ @warning This action can't be Sequenceable because it is not an CCIntervalAction
+ */
+@interface CCSpeed : CCAction <NSCopying>
+{
+       CCActionInterval        *innerAction_;
+       float speed_;
+}
+/** alter the speed of the inner function in runtime */
+@property (nonatomic,readwrite) float speed;
+/** Inner action of CCSpeed */
+@property (nonatomic, readwrite, retain) CCActionInterval *innerAction;
+
+/** creates the action */
++(id) actionWithAction: (CCActionInterval*) action speed:(float)rate;
+/** initializes the action */
+-(id) initWithAction: (CCActionInterval*) action speed:(float)rate;
+@end
+
+@class CCNode;
+/** CCFollow is an action that "follows" a node.
+ Eg:
+       [layer runAction: [CCFollow actionWithTarget:hero]];
+ Instead of using CCCamera as a "follower", use this action instead.
+ @since v0.99.2
+ */
+@interface CCFollow : CCAction <NSCopying>
+{
+       /* node to follow */
+       CCNode  *followedNode_;
+       
+       /* whether camera should be limited to certain area */
+       BOOL boundarySet;
+       
+       /* if screensize is bigger than the boundary - update not needed */
+       BOOL boundaryFullyCovered;
+       
+       /* fast access to the screen dimensions */
+       CGPoint halfScreenSize;
+       CGPoint fullScreenSize;
+       
+       /* world boundaries */
+       float leftBoundary;
+       float rightBoundary;
+       float topBoundary;
+       float bottomBoundary;
+}
+
+/** alter behavior - turn on/off boundary */
+@property (nonatomic,readwrite) BOOL boundarySet;
+
+/** creates the action with no boundary set */
++(id) actionWithTarget:(CCNode *)followedNode;
+
+/** creates the action with a set boundary */
++(id) actionWithTarget:(CCNode *)followedNode worldBoundary:(CGRect)rect;
+
+/** initializes the action */
+-(id) initWithTarget:(CCNode *)followedNode;
+
+/** initializes the action with a set boundary */
+-(id) initWithTarget:(CCNode *)followedNode worldBoundary:(CGRect)rect;
+
+@end
+
diff --git a/libs/cocos2d/CCAction.m b/libs/cocos2d/CCAction.m
new file mode 100644 (file)
index 0000000..0187dbd
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2010 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+
+#import <Availability.h>
+#import "CCDirector.h"
+#import "ccMacros.h"
+#import "CCAction.h"
+#import "CCActionInterval.h"
+#import "Support/CGPointExtension.h"
+
+//
+// Action Base Class
+//
+#pragma mark -
+#pragma mark Action
+@implementation CCAction
+
+@synthesize tag = tag_, target = target_, originalTarget = originalTarget_;
+
++(id) action
+{
+       return [[[self alloc] init] autorelease];
+}
+
+-(id) init
+{
+       if( (self=[super init]) ) {     
+               originalTarget_ = target_ = nil;
+               tag_ = kCCActionTagInvalid;
+       }
+       return self;
+}
+
+-(void) dealloc
+{
+       CCLOGINFO(@"cocos2d: deallocing %@", self);
+       [super dealloc];
+}
+
+-(NSString*) description
+{
+       return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i>", [self class], self, tag_];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] init];
+       copy.tag = tag_;
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       originalTarget_ = target_ = aTarget;
+}
+
+-(void) stop
+{
+       target_ = nil;
+}
+
+-(BOOL) isDone
+{
+       return YES;
+}
+
+-(void) step: (ccTime) dt
+{
+       NSLog(@"[Action step]. override me");
+}
+
+-(void) update: (ccTime) time
+{
+       NSLog(@"[Action update]. override me");
+}
+@end
+
+//
+// FiniteTimeAction
+//
+#pragma mark -
+#pragma mark FiniteTimeAction
+@implementation CCFiniteTimeAction
+@synthesize duration = duration_;
+
+- (CCFiniteTimeAction*) reverse
+{
+       CCLOG(@"cocos2d: FiniteTimeAction#reverse: Implement me");
+       return nil;
+}
+@end
+
+
+//
+// RepeatForever
+//
+#pragma mark -
+#pragma mark RepeatForever
+@implementation CCRepeatForever
+@synthesize innerAction=innerAction_;
++(id) actionWithAction: (CCActionInterval*) action
+{
+       return [[[self alloc] initWithAction: action] autorelease];
+}
+
+-(id) initWithAction: (CCActionInterval*) action
+{
+       if( (self=[super init]) )       
+               self.innerAction = action;
+
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithAction:[[innerAction_ copy] autorelease] ];
+    return copy;
+}
+
+-(void) dealloc
+{
+       [innerAction_ release];
+       [super dealloc];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [innerAction_ startWithTarget:target_];
+}
+
+-(void) step:(ccTime) dt
+{
+       [innerAction_ step: dt];
+       if( [innerAction_ isDone] ) {
+               ccTime diff = dt + innerAction_.duration - innerAction_.elapsed;
+               [innerAction_ startWithTarget:target_];
+               
+               // to prevent jerk. issue #390
+               [innerAction_ step: diff];
+       }
+}
+
+
+-(BOOL) isDone
+{
+       return NO;
+}
+
+- (CCActionInterval *) reverse
+{
+       return [CCRepeatForever actionWithAction:[innerAction_ reverse]];
+}
+@end
+
+//
+// Speed
+//
+#pragma mark -
+#pragma mark Speed
+@implementation CCSpeed
+@synthesize speed=speed_;
+@synthesize innerAction=innerAction_;
+
++(id) actionWithAction: (CCActionInterval*) action speed:(float)r
+{
+       return [[[self alloc] initWithAction: action speed:r] autorelease];
+}
+
+-(id) initWithAction: (CCActionInterval*) action speed:(float)r
+{
+       if( (self=[super init]) ) {
+               self.innerAction = action;
+               speed_ = r;
+       }
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithAction:[[innerAction_ copy] autorelease] speed:speed_];
+    return copy;
+}
+
+-(void) dealloc
+{
+       [innerAction_ release];
+       [super dealloc];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [innerAction_ startWithTarget:target_];
+}
+
+-(void) stop
+{
+       [innerAction_ stop];
+       [super stop];
+}
+
+-(void) step:(ccTime) dt
+{
+       [innerAction_ step: dt * speed_];
+}
+
+-(BOOL) isDone
+{
+       return [innerAction_ isDone];
+}
+
+- (CCActionInterval *) reverse
+{
+       return [CCSpeed actionWithAction:[innerAction_ reverse] speed:speed_];
+}
+@end
+
+//
+// Follow
+//
+#pragma mark -
+#pragma mark Follow
+@implementation CCFollow
+
+@synthesize boundarySet;
+
++(id) actionWithTarget:(CCNode *) fNode
+{
+       return [[[self alloc] initWithTarget:fNode] autorelease];
+}
+
++(id) actionWithTarget:(CCNode *) fNode worldBoundary:(CGRect)rect
+{
+       return [[[self alloc] initWithTarget:fNode worldBoundary:rect] autorelease];
+}
+
+-(id) initWithTarget:(CCNode *)fNode
+{
+       if( (self=[super init]) ) {
+       
+               followedNode_ = [fNode retain];
+               boundarySet = FALSE;
+               boundaryFullyCovered = FALSE;
+               
+               CGSize s = [[CCDirector sharedDirector] winSize];
+               fullScreenSize = CGPointMake(s.width, s.height);
+               halfScreenSize = ccpMult(fullScreenSize, .5f);
+       }
+       
+       return self;
+}
+
+-(id) initWithTarget:(CCNode *)fNode worldBoundary:(CGRect)rect
+{
+       if( (self=[super init]) ) {
+       
+               followedNode_ = [fNode retain];
+               boundarySet = TRUE;
+               boundaryFullyCovered = FALSE;
+               
+               CGSize winSize = [[CCDirector sharedDirector] winSize];
+               fullScreenSize = CGPointMake(winSize.width, winSize.height);
+               halfScreenSize = ccpMult(fullScreenSize, .5f);
+               
+               leftBoundary = -((rect.origin.x+rect.size.width) - fullScreenSize.x);
+               rightBoundary = -rect.origin.x ;
+               topBoundary = -rect.origin.y;
+               bottomBoundary = -((rect.origin.y+rect.size.height) - fullScreenSize.y);
+               
+               if(rightBoundary < leftBoundary)
+               {
+                       // screen width is larger than world's boundary width
+                       //set both in the middle of the world
+                       rightBoundary = leftBoundary = (leftBoundary + rightBoundary) / 2;
+               }
+               if(topBoundary < bottomBoundary)
+               {
+                       // screen width is larger than world's boundary width
+                       //set both in the middle of the world
+                       topBoundary = bottomBoundary = (topBoundary + bottomBoundary) / 2;
+               }
+               
+               if( (topBoundary == bottomBoundary) && (leftBoundary == rightBoundary) )
+                       boundaryFullyCovered = TRUE;
+       }
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] init];
+       copy.tag = tag_;
+       return copy;
+}
+
+-(void) step:(ccTime) dt
+{
+#define CLAMP(x,y,z) MIN(MAX(x,y),z)
+       
+       if(boundarySet)
+       {
+               // whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
+               if(boundaryFullyCovered)
+                       return;
+               
+               CGPoint tempPos = ccpSub( halfScreenSize, followedNode_.position);
+               [target_ setPosition:ccp(CLAMP(tempPos.x,leftBoundary,rightBoundary), CLAMP(tempPos.y,bottomBoundary,topBoundary))];
+       }
+       else
+               [target_ setPosition:ccpSub( halfScreenSize, followedNode_.position )];
+       
+#undef CLAMP
+}
+
+
+-(BOOL) isDone
+{
+       return !followedNode_.isRunning;
+}
+
+-(void) stop
+{
+       target_ = nil;
+       [super stop];
+}
+
+-(void) dealloc
+{
+       [followedNode_ release];
+       [super dealloc];
+}
+
+@end
+
+
diff --git a/libs/cocos2d/CCActionCamera.h b/libs/cocos2d/CCActionCamera.h
new file mode 100644 (file)
index 0000000..1ea83a7
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2010 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#import "CCActionInterval.h"
+
+@class CCCamera;
+
+/** Base class for CCCamera actions
+ */
+@interface CCActionCamera : CCActionInterval <NSCopying>
+{      
+       float centerXOrig_;
+       float centerYOrig_;
+       float centerZOrig_;
+       
+       float eyeXOrig_;
+       float eyeYOrig_;
+       float eyeZOrig_;
+       
+       float upXOrig_;
+       float upYOrig_;
+       float upZOrig_;
+}
+@end
+
+/** CCOrbitCamera action
+ Orbits the camera around the center of the screen using spherical coordinates
+ */
+@interface CCOrbitCamera : CCActionCamera <NSCopying>
+{
+       float radius_;
+       float deltaRadius_;
+       float angleZ_;
+       float deltaAngleZ_;
+       float angleX_;
+       float deltaAngleX_;
+       
+       float radZ_;
+       float radDeltaZ_;
+       float radX_;
+       float radDeltaX_;
+       
+}
+/** creates a CCOrbitCamera action with radius, delta-radius,  z, deltaZ, x, deltaX */
++(id) actionWithDuration:(float) t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx;
+/** initializes a CCOrbitCamera action with radius, delta-radius,  z, deltaZ, x, deltaX */
+-(id) initWithDuration:(float) t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx;
+/** positions the camera according to spherical coordinates */
+-(void) sphericalRadius:(float*) r zenith:(float*) zenith azimuth:(float*) azimuth;
+@end
diff --git a/libs/cocos2d/CCActionCamera.m b/libs/cocos2d/CCActionCamera.m
new file mode 100644 (file)
index 0000000..4dafc4e
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2010 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+
+#import "CCActionCamera.h"
+#import "CCNode.h"
+#import "CCCamera.h"
+#import "ccMacros.h"
+
+//
+// CameraAction
+//
+@implementation CCActionCamera
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       CCCamera *camera = [target_ camera];
+       [camera centerX:&centerXOrig_ centerY:&centerYOrig_ centerZ:&centerZOrig_];
+       [camera eyeX:&eyeXOrig_ eyeY:&eyeYOrig_ eyeZ:&eyeZOrig_];
+       [camera upX:&upXOrig_ upY:&upYOrig_ upZ: &upZOrig_];
+}
+
+-(id) reverse
+{
+       return [CCReverseTime actionWithAction:self];
+}
+@end
+
+@implementation CCOrbitCamera
++(id) actionWithDuration:(float)t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx
+{
+       return [[[self alloc] initWithDuration:t radius:r deltaRadius:dr angleZ:z deltaAngleZ:dz angleX:x deltaAngleX:dx] autorelease];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       return [[[self class] allocWithZone: zone] initWithDuration:duration_ radius:radius_ deltaRadius:deltaRadius_ angleZ:angleZ_ deltaAngleZ:deltaAngleZ_ angleX:angleX_ deltaAngleX:deltaAngleX_];
+}
+
+
+-(id) initWithDuration:(float)t radius:(float)r deltaRadius:(float) dr angleZ:(float)z deltaAngleZ:(float)dz angleX:(float)x deltaAngleX:(float)dx
+{
+       if((self=[super initWithDuration:t]) ) {
+       
+               radius_ = r;
+               deltaRadius_ = dr;
+               angleZ_ = z;
+               deltaAngleZ_ = dz;
+               angleX_ = x;
+               deltaAngleX_ = dx;
+
+               radDeltaZ_ = (CGFloat)CC_DEGREES_TO_RADIANS(dz);
+               radDeltaX_ = (CGFloat)CC_DEGREES_TO_RADIANS(dx);
+       }
+       
+       return self;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       float r, zenith, azimuth;
+       
+       [self sphericalRadius: &r zenith:&zenith azimuth:&azimuth];
+       
+#if 0 // isnan() is not supported on the simulator, and isnan() always returns false.
+       if( isnan(radius_) )
+               radius_ = r;
+       
+       if( isnan( angleZ_) )
+               angleZ_ = (CGFloat)CC_RADIANS_TO_DEGREES(zenith);
+       
+       if( isnan( angleX_ ) )
+               angleX_ = (CGFloat)CC_RADIANS_TO_DEGREES(azimuth);
+#endif
+
+       radZ_ = (CGFloat)CC_DEGREES_TO_RADIANS(angleZ_);
+       radX_ = (CGFloat)CC_DEGREES_TO_RADIANS(angleX_);
+}
+
+-(void) update: (ccTime) dt
+{
+       float r = (radius_ + deltaRadius_ * dt) *[CCCamera getZEye];
+       float za = radZ_ + radDeltaZ_ * dt;
+       float xa = radX_ + radDeltaX_ * dt;
+
+       float i = sinf(za) * cosf(xa) * r + centerXOrig_;
+       float j = sinf(za) * sinf(xa) * r + centerYOrig_;
+       float k = cosf(za) * r + centerZOrig_;
+
+       [[target_ camera] setEyeX:i eyeY:j eyeZ:k];     
+}
+
+-(void) sphericalRadius:(float*) newRadius zenith:(float*) zenith azimuth:(float*) azimuth
+{
+       float ex, ey, ez, cx, cy, cz, x, y, z;
+       float r; // radius
+       float s;
+       
+       CCCamera *camera = [target_ camera];
+       [camera eyeX:&ex eyeY:&ey eyeZ:&ez];
+       [camera centerX:&cx centerY:&cy centerZ:&cz];
+       
+       x = ex-cx;
+       y = ey-cy;
+       z = ez-cz;
+       
+       r = sqrtf( x*x + y*y + z*z);
+       s = sqrtf( x*x + y*y);
+       if(s==0.0f)
+               s = FLT_EPSILON;
+       if(r==0.0f)
+               r = FLT_EPSILON;
+
+       *zenith = acosf( z/r);
+       if( x < 0 )
+               *azimuth = (float)M_PI - asinf(y/s);
+       else
+               *azimuth = asinf(y/s);
+                                       
+       *newRadius = r / [CCCamera getZEye];                                    
+}
+@end
diff --git a/libs/cocos2d/CCActionEase.h b/libs/cocos2d/CCActionEase.h
new file mode 100644 (file)
index 0000000..fced701
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2009 Jason Booth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCActionInterval.h"
+
+/** Base class for Easing actions
+ */
+@interface CCActionEase : CCActionInterval <NSCopying>
+{
+       CCActionInterval * other;
+}
+/** creates the action */
++(id) actionWithAction: (CCActionInterval*) action;
+/** initializes the action */
+-(id) initWithAction: (CCActionInterval*) action;
+@end
+
+/** Base class for Easing actions with rate parameters
+ */
+@interface CCEaseRateAction :  CCActionEase <NSCopying>
+{
+       float   rate;
+}
+/** rate value for the actions */
+@property (nonatomic,readwrite,assign) float rate;
+/** Creates the action with the inner action and the rate parameter */
++(id) actionWithAction: (CCActionInterval*) action rate:(float)rate;
+/** Initializes the action with the inner action and the rate parameter */
+-(id) initWithAction: (CCActionInterval*) action rate:(float)rate;
+@end
+
+/** CCEaseIn action with a rate
+ */
+@interface CCEaseIn : CCEaseRateAction <NSCopying> {} @end
+
+/** CCEaseOut action with a rate
+ */
+@interface CCEaseOut : CCEaseRateAction <NSCopying> {} @end
+
+/** CCEaseInOut action with a rate
+ */
+@interface CCEaseInOut : CCEaseRateAction <NSCopying> {} @end
+
+/** CCEase Exponential In
+ */
+@interface CCEaseExponentialIn : CCActionEase <NSCopying> {} @end
+/** Ease Exponential Out
+ */
+@interface CCEaseExponentialOut : CCActionEase <NSCopying> {} @end
+/** Ease Exponential InOut
+ */
+@interface CCEaseExponentialInOut : CCActionEase <NSCopying> {} @end
+/** Ease Sine In
+ */
+@interface CCEaseSineIn : CCActionEase <NSCopying> {} @end
+/** Ease Sine Out
+ */
+@interface CCEaseSineOut : CCActionEase <NSCopying> {} @end
+/** Ease Sine InOut
+ */
+@interface CCEaseSineInOut : CCActionEase <NSCopying> {} @end
+
+/** Ease Elastic abstract class
+ @since v0.8.2
+ */
+@interface CCEaseElastic : CCActionEase <NSCopying>
+{
+       float period_;
+}
+
+/** period of the wave in radians. default is 0.3 */
+@property (nonatomic,readwrite) float period;
+
+/** Creates the action with the inner action and the period in radians (default is 0.3) */
++(id) actionWithAction: (CCActionInterval*) action period:(float)period;
+/** Initializes the action with the inner action and the period in radians (default is 0.3) */
+-(id) initWithAction: (CCActionInterval*) action period:(float)period;
+@end
+
+/** Ease Elastic In action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseElasticIn : CCEaseElastic <NSCopying> {} @end
+/** Ease Elastic Out action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseElasticOut : CCEaseElastic <NSCopying> {} @end
+/** Ease Elastic InOut action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseElasticInOut : CCEaseElastic <NSCopying> {} @end
+
+/** CCEaseBounce abstract class.
+ @since v0.8.2
+*/
+@interface CCEaseBounce : CCActionEase <NSCopying> {} @end
+
+/** CCEaseBounceIn action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+*/
+@interface CCEaseBounceIn : CCEaseBounce <NSCopying> {} @end
+
+/** EaseBounceOut action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseBounceOut : CCEaseBounce <NSCopying> {} @end
+
+/** CCEaseBounceInOut action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseBounceInOut : CCEaseBounce <NSCopying> {} @end
+
+/** CCEaseBackIn action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseBackIn : CCActionEase <NSCopying> {} @end
+
+/** CCEaseBackOut action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseBackOut : CCActionEase <NSCopying> {} @end
+
+/** CCEaseBackInOut action.
+ @warning This action doesn't use a bijective fucntion. Actions like Sequence might have an unexpected result when used with this action.
+ @since v0.8.2
+ */
+@interface CCEaseBackInOut : CCActionEase <NSCopying> {} @end
+
diff --git a/libs/cocos2d/CCActionEase.m b/libs/cocos2d/CCActionEase.m
new file mode 100644 (file)
index 0000000..f28be11
--- /dev/null
@@ -0,0 +1,534 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2009 Jason Booth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/*
+ * Elastic, Back and Bounce actions based on code from:
+ * http://github.com/NikhilK/silverlightfx/
+ *
+ * by http://github.com/NikhilK
+ */
+
+#import "CCActionEase.h"
+
+#ifndef M_PI_X_2
+#define M_PI_X_2 (float)M_PI * 2.0f
+#endif
+
+#pragma mark EaseAction
+
+//
+// EaseAction
+//
+@implementation CCActionEase
+
++(id) actionWithAction: (CCActionInterval*) action
+{
+       return [[[self alloc] initWithAction: action] autorelease ];
+}
+
+-(id) initWithAction: (CCActionInterval*) action
+{
+       NSAssert( action!=nil, @"Ease: arguments must be non-nil");
+  
+       if( (self=[super initWithDuration: action.duration]) )
+               other = [action retain];
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[other copy] autorelease]];
+       return copy;
+}
+
+-(void) dealloc
+{
+       [other release];
+       [super dealloc];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [other startWithTarget:target_];
+}
+
+-(void) stop
+{
+       [other stop];
+       [super stop];
+}
+
+-(void) update: (ccTime) t
+{
+       [other update: t];
+}
+
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithAction: [other reverse]];
+}
+@end
+
+
+#pragma mark -
+#pragma mark EaseRate
+
+//
+// EaseRateAction
+//
+@implementation CCEaseRateAction
+@synthesize rate;
++(id) actionWithAction: (CCActionInterval*) action rate:(float)aRate
+{
+       return [[[self alloc] initWithAction: action rate:aRate] autorelease ];
+}
+
+-(id) initWithAction: (CCActionInterval*) action rate:(float)aRate
+{
+       if( (self=[super initWithAction:action ]) )
+               self.rate = aRate;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[other copy] autorelease] rate:rate];
+       return copy;
+}
+
+-(void) dealloc
+{
+       [super dealloc];
+}
+
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithAction: [other reverse] rate:1/rate];
+}
+@end
+
+//
+// EeseIn
+//
+@implementation CCEaseIn
+-(void) update: (ccTime) t
+{
+       [other update: powf(t,rate)];
+}
+@end
+
+//
+// EaseOut
+//
+@implementation CCEaseOut
+-(void) update: (ccTime) t
+{
+       [other update: powf(t,1/rate)];
+}
+@end
+
+//
+// EaseInOut
+//
+@implementation CCEaseInOut
+-(void) update: (ccTime) t
+{      
+       int sign =1;
+       int r = (int) rate;
+       if (r % 2 == 0)
+               sign = -1;
+       t *= 2;
+       if (t < 1) 
+               [other update: 0.5f * powf (t, rate)];
+       else
+               [other update: sign*0.5f * (powf (t-2, rate) + sign*2)];        
+}
+
+// InOut and OutIn are symmetrical
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithAction: [other reverse] rate:rate];
+}
+
+@end
+
+#pragma mark -
+#pragma mark EaseExponential
+
+//
+// EaseExponentialIn
+//
+@implementation CCEaseExponentialIn
+-(void) update: (ccTime) t
+{
+       [other update: (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseExponentialOut actionWithAction: [other reverse]];
+}
+@end
+
+//
+// EaseExponentialOut
+//
+@implementation CCEaseExponentialOut
+-(void) update: (ccTime) t
+{
+       [other update: (t==1) ? 1 : (-powf(2, -10 * t/1) + 1)];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseExponentialIn actionWithAction: [other reverse]];
+}
+@end
+
+//
+// EaseExponentialInOut
+//
+@implementation CCEaseExponentialInOut
+-(void) update: (ccTime) t
+{
+       t /= 0.5f;
+       if (t < 1)
+               t = 0.5f * powf(2, 10 * (t - 1));
+       else
+               t = 0.5f * (-powf(2, -10 * (t -1) ) + 2);
+       
+       [other update:t];
+}
+@end
+
+
+#pragma mark -
+#pragma mark EaseSin actions
+
+//
+// EaseSineIn
+//
+@implementation CCEaseSineIn
+-(void) update: (ccTime) t
+{
+       [other update:-1*cosf(t * (float)M_PI_2) +1];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseSineOut actionWithAction: [other reverse]];
+}
+@end
+
+//
+// EaseSineOut
+//
+@implementation CCEaseSineOut
+-(void) update: (ccTime) t
+{
+       [other update:sinf(t * (float)M_PI_2)];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseSineIn actionWithAction: [other reverse]];
+}
+@end
+
+//
+// EaseSineInOut
+//
+@implementation CCEaseSineInOut
+-(void) update: (ccTime) t
+{
+       [other update:-0.5f*(cosf( (float)M_PI*t) - 1)];
+}
+@end
+
+#pragma mark -
+#pragma mark EaseElastic actions
+
+//
+// EaseElastic
+//
+@implementation CCEaseElastic
+
+@synthesize period = period_;
+
++(id) actionWithAction: (CCActionInterval*) action
+{
+       return [[[self alloc] initWithAction:action period:0.3f] autorelease];
+}
+
++(id) actionWithAction: (CCActionInterval*) action period:(float)period
+{
+       return [[[self alloc] initWithAction:action period:period] autorelease];
+}
+
+-(id) initWithAction: (CCActionInterval*) action
+{
+       return [self initWithAction:action period:0.3f];
+}
+
+-(id) initWithAction: (CCActionInterval*) action period:(float)period
+{
+       if( (self=[super initWithAction:action]) )
+               period_ = period;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[other copy] autorelease] period:period_];
+       return copy;
+}
+
+-(CCActionInterval*) reverse
+{
+       NSAssert(NO,@"Override me");
+       return nil;
+}
+
+@end
+
+//
+// EaseElasticIn
+//
+
+@implementation CCEaseElasticIn
+-(void) update: (ccTime) t
+{      
+       ccTime newT = 0;
+       if (t == 0 || t == 1)
+               newT = t;
+               
+       else {
+               float s = period_ / 4;
+               t = t - 1;
+               newT = -powf(2, 10 * t) * sinf( (t-s) *M_PI_X_2 / period_);
+       }
+       [other update:newT];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseElasticOut actionWithAction: [other reverse] period:period_];
+}
+
+@end
+
+//
+// EaseElasticOut
+//
+@implementation CCEaseElasticOut
+
+-(void) update: (ccTime) t
+{      
+       ccTime newT = 0;
+       if (t == 0 || t == 1) {
+               newT = t;
+               
+       } else {
+               float s = period_ / 4;
+               newT = powf(2, -10 * t) * sinf( (t-s) *M_PI_X_2 / period_) + 1;
+       }
+       [other update:newT];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseElasticIn actionWithAction: [other reverse] period:period_];
+}
+
+@end
+
+//
+// EaseElasticInOut
+//
+@implementation CCEaseElasticInOut
+-(void) update: (ccTime) t
+{
+       ccTime newT = 0;
+       
+       if( t == 0 || t == 1 )
+               newT = t;
+       else {
+               t = t * 2;
+               if(! period_ )
+                       period_ = 0.3f * 1.5f;
+               ccTime s = period_ / 4;
+               
+               t = t -1;
+               if( t < 0 )
+                       newT = -0.5f * powf(2, 10 * t) * sinf((t - s) * M_PI_X_2 / period_);
+               else
+                       newT = powf(2, -10 * t) * sinf((t - s) * M_PI_X_2 / period_) * 0.5f + 1;
+       }
+       [other update:newT];    
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseElasticInOut actionWithAction: [other reverse] period:period_];
+}
+
+@end
+
+#pragma mark -
+#pragma mark EaseBounce actions
+
+//
+// EaseBounce
+//
+@implementation CCEaseBounce
+-(ccTime) bounceTime:(ccTime) t
+{
+       if (t < 1 / 2.75) {
+               return 7.5625f * t * t;
+       }
+       else if (t < 2 / 2.75) {
+               t -= 1.5f / 2.75f;
+               return 7.5625f * t * t + 0.75f;
+       }
+       else if (t < 2.5 / 2.75) {
+               t -= 2.25f / 2.75f;
+               return 7.5625f * t * t + 0.9375f;
+       }
+
+       t -= 2.625f / 2.75f;
+       return 7.5625f * t * t + 0.984375f;
+}
+@end
+
+//
+// EaseBounceIn
+//
+
+@implementation CCEaseBounceIn
+
+-(void) update: (ccTime) t
+{
+       ccTime newT = 1 - [self bounceTime:1-t];        
+       [other update:newT];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseBounceOut actionWithAction: [other reverse]];
+}
+
+@end
+
+@implementation CCEaseBounceOut
+
+-(void) update: (ccTime) t
+{
+       ccTime newT = [self bounceTime:t];      
+       [other update:newT];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseBounceIn actionWithAction: [other reverse]];
+}
+
+@end
+
+@implementation CCEaseBounceInOut
+
+-(void) update: (ccTime) t
+{
+       ccTime newT = 0;
+       if (t < 0.5) {
+               t = t * 2;
+               newT = (1 - [self bounceTime:1-t] ) * 0.5f;
+       } else
+               newT = [self bounceTime:t * 2 - 1] * 0.5f + 0.5f;
+       
+       [other update:newT];
+}
+@end
+
+#pragma mark -
+#pragma mark Ease Back actions
+
+//
+// EaseBackIn
+//
+@implementation CCEaseBackIn
+
+-(void) update: (ccTime) t
+{
+       ccTime overshoot = 1.70158f;
+       [other update: t * t * ((overshoot + 1) * t - overshoot)];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseBackOut actionWithAction: [other reverse]];
+}
+@end
+
+//
+// EaseBackOut
+//
+@implementation CCEaseBackOut
+-(void) update: (ccTime) t
+{
+       ccTime overshoot = 1.70158f;
+       
+       t = t - 1;
+       [other update: t * t * ((overshoot + 1) * t + overshoot) + 1];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCEaseBackIn actionWithAction: [other reverse]];
+}
+@end
+
+//
+// EaseBackInOut
+//
+@implementation CCEaseBackInOut
+
+-(void) update: (ccTime) t
+{
+       ccTime overshoot = 1.70158f * 1.525f;
+       
+       t = t * 2;
+       if (t < 1)
+               [other update: (t * t * ((overshoot + 1) * t - overshoot)) / 2];
+       else {
+               t = t - 2;
+               [other update: (t * t * ((overshoot + 1) * t + overshoot)) / 2 + 1];
+       }
+}
+@end
diff --git a/libs/cocos2d/CCActionGrid.h b/libs/cocos2d/CCActionGrid.h
new file mode 100644 (file)
index 0000000..13b6bc7
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2009 On-Core
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCActionInterval.h"
+#import "CCActionInstant.h"
+#import "CCGrid.h"
+
+@class CCGridBase;
+
+/** Base class for Grid actions */
+@interface CCGridAction : CCActionInterval
+{
+       ccGridSize gridSize_;
+}
+
+/** size of the grid */
+@property (nonatomic,readwrite) ccGridSize gridSize;
+
+/** creates the action with size and duration */
++(id) actionWithSize:(ccGridSize)size duration:(ccTime)d;
+/** initializes the action with size and duration */
+-(id) initWithSize:(ccGridSize)gridSize duration:(ccTime)d;
+/** returns the grid */
+-(CCGridBase *)grid;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** Base class for CCGrid3D actions.
+ Grid3D actions can modify a non-tiled grid.
+ */
+@interface CCGrid3DAction : CCGridAction
+{
+}
+
+/** returns the vertex than belongs to certain position in the grid */
+-(ccVertex3F)vertex:(ccGridSize)pos;
+/** returns the non-transformed vertex than belongs to certain position in the grid */
+-(ccVertex3F)originalVertex:(ccGridSize)pos;
+/** sets a new vertex to a certain position of the grid */
+-(void)setVertex:(ccGridSize)pos vertex:(ccVertex3F)vertex;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** Base class for CCTiledGrid3D actions */
+@interface CCTiledGrid3DAction : CCGridAction
+{
+}
+
+/** returns the tile that belongs to a certain position of the grid */
+-(ccQuad3)tile:(ccGridSize)pos;
+/** returns the non-transformed tile that belongs to a certain position of the grid */
+-(ccQuad3)originalTile:(ccGridSize)pos;
+/** sets a new tile to a certain position of the grid */
+-(void)setTile:(ccGridSize)pos coords:(ccQuad3)coords;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCAccelDeccelAmplitude action */
+@interface CCAccelDeccelAmplitude : CCActionInterval
+{
+       float                   rate;
+       CCActionInterval *other;
+}
+
+/** amplitude rate */
+@property (nonatomic,readwrite) float rate;
+
+/** creates the action with an inner action that has the amplitude property, and a duration time */
++(id)actionWithAction:(CCAction*)action duration:(ccTime)d;
+/** initializes the action with an inner action that has the amplitude property, and a duration time */
+-(id)initWithAction:(CCAction*)action duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCAccelAmplitude action */
+@interface CCAccelAmplitude : CCActionInterval
+{
+       float                   rate;
+       CCActionInterval *other;
+}
+
+/** amplitude rate */
+@property (nonatomic,readwrite) float rate;
+
+/** creates the action with an inner action that has the amplitude property, and a duration time */
++(id)actionWithAction:(CCAction*)action duration:(ccTime)d;
+/** initializes the action with an inner action that has the amplitude property, and a duration time */
+-(id)initWithAction:(CCAction*)action duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCDeccelAmplitude action */
+@interface CCDeccelAmplitude : CCActionInterval
+{
+       float                   rate;
+       CCActionInterval *other;
+}
+
+/** amplitude rate */
+@property (nonatomic,readwrite) float rate;
+
+/** creates the action with an inner action that has the amplitude property, and a duration time */
++(id)actionWithAction:(CCAction*)action duration:(ccTime)d;
+/** initializes the action with an inner action that has the amplitude property, and a duration time */
+-(id)initWithAction:(CCAction*)action duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCStopGrid action.
+ Don't call this action if another grid action is active.
+ Call if you want to remove the the grid effect. Example:
+ [Sequence actions:[Lens ...], [StopGrid action], nil];
+ */
+@interface CCStopGrid : CCActionInstant
+{
+}
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCReuseGrid action */
+@interface CCReuseGrid : CCActionInstant
+{
+       int t;
+}
+/** creates an action with the number of times that the current grid will be reused */
++(id) actionWithTimes: (int) times;
+/** initializes an action with the number of times that the current grid will be reused */
+-(id) initWithTimes: (int) times;
+@end
diff --git a/libs/cocos2d/CCActionGrid.m b/libs/cocos2d/CCActionGrid.m
new file mode 100644 (file)
index 0000000..b2d8f98
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2009 On-Core
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCActionGrid.h"
+#import "CCDirector.h"
+
+#pragma mark -
+#pragma mark GridAction
+
+@implementation CCGridAction
+
+@synthesize gridSize = gridSize_;
+
++(id) actionWithSize:(ccGridSize)size duration:(ccTime)d
+{
+       return [[[self alloc] initWithSize:size duration:d ] autorelease];
+}
+
+-(id) initWithSize:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithDuration:d]) )
+       {
+               gridSize_ = gSize;
+       }
+       
+       return self;
+}
+
+-(void)startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+
+       CCGridBase *newgrid = [self grid];
+       
+       CCNode *t = (CCNode*) target_;
+       CCGridBase *targetGrid = [t grid];
+       
+       if ( targetGrid && targetGrid.reuseGrid > 0 )
+       {
+               if ( targetGrid.active && targetGrid.gridSize.x == gridSize_.x && targetGrid.gridSize.y == gridSize_.y && [targetGrid isKindOfClass:[newgrid class]] )
+                       [targetGrid reuse];
+               else
+                       [NSException raise:@"GridBase" format:@"Cannot reuse grid"];
+       }
+       else
+       {
+               if ( targetGrid && targetGrid.active )
+                       targetGrid.active = NO;
+               
+               t.grid = newgrid;
+               t.grid.active = YES;
+       }       
+}
+
+-(CCGridBase *)grid
+{
+       [NSException raise:@"GridBase" format:@"Abstract class needs implementation"];
+       return nil;
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCReverseTime actionWithAction:self];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSize:gridSize_ duration:duration_];
+       return copy;
+}
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Grid3DAction
+
+@implementation CCGrid3DAction
+
+-(CCGridBase *)grid
+{
+       return [CCGrid3D gridWithSize:gridSize_];
+}
+
+-(ccVertex3F)vertex:(ccGridSize)pos
+{
+       CCGrid3D *g = (CCGrid3D *)[target_ grid];
+       return [g vertex:pos];
+}
+
+-(ccVertex3F)originalVertex:(ccGridSize)pos
+{
+       CCGrid3D *g = (CCGrid3D *)[target_ grid];
+       return [g originalVertex:pos];
+}
+
+-(void)setVertex:(ccGridSize)pos vertex:(ccVertex3F)vertex
+{
+       CCGrid3D *g = (CCGrid3D *)[target_ grid];
+       return [g setVertex:pos vertex:vertex];
+}
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark TiledGrid3DAction
+
+@implementation CCTiledGrid3DAction
+
+-(CCGridBase *)grid
+{
+       return [CCTiledGrid3D gridWithSize:gridSize_];
+}
+
+-(ccQuad3)tile:(ccGridSize)pos
+{
+       CCTiledGrid3D *g = (CCTiledGrid3D *)[target_ grid];
+       return [g tile:pos];
+}
+
+-(ccQuad3)originalTile:(ccGridSize)pos
+{
+       CCTiledGrid3D *g = (CCTiledGrid3D *)[target_ grid];
+       return [g originalTile:pos];
+}
+
+-(void)setTile:(ccGridSize)pos coords:(ccQuad3)coords
+{
+       CCTiledGrid3D *g = (CCTiledGrid3D *)[target_ grid];
+       [g setTile:pos coords:coords];
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+@interface CCActionInterval (Amplitude)
+-(void)setAmplitudeRate:(CGFloat)amp;
+-(CGFloat)getAmplitudeRate;
+@end
+
+@implementation CCActionInterval (Amplitude)
+-(void)setAmplitudeRate:(CGFloat)amp
+{
+       [NSException raise:@"IntervalAction (Amplitude)" format:@"Abstract class needs implementation"];
+}
+
+-(CGFloat)getAmplitudeRate
+{
+       [NSException raise:@"IntervalAction (Amplitude)" format:@"Abstract class needs implementation"];
+       return 0;
+}
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark AccelDeccelAmplitude
+
+@implementation CCAccelDeccelAmplitude
+
+@synthesize rate;
+
++(id)actionWithAction:(CCAction*)action duration:(ccTime)d
+{
+       return [[[self alloc] initWithAction:action duration:d ] autorelease];
+}
+
+-(id)initWithAction:(CCAction *)action duration:(ccTime)d
+{
+       if ( (self = [super initWithDuration:d]) )
+       {
+               rate = 1.0f;
+               other = [action retain];
+       }
+       
+       return self;
+}
+
+-(void)dealloc
+{
+       [other release];
+       [super dealloc];
+}
+
+-(void)startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [other startWithTarget:target_];
+}
+
+-(void) update: (ccTime) time
+{
+       float f = time*2;
+       
+       if (f > 1)
+       {
+               f -= 1;
+               f = 1 - f;
+       }
+       
+       [other setAmplitudeRate:powf(f, rate)];
+       [other update:time];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCAccelDeccelAmplitude actionWithAction:[other reverse] duration:duration_];
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark AccelAmplitude
+
+@implementation CCAccelAmplitude
+
+@synthesize rate;
+
++(id)actionWithAction:(CCAction*)action duration:(ccTime)d
+{
+       return [[[self alloc] initWithAction:action duration:d ] autorelease];
+}
+
+-(id)initWithAction:(CCAction *)action duration:(ccTime)d
+{
+       if ( (self = [super initWithDuration:d]) )
+       {
+               rate = 1.0f;
+               other = [action retain];
+       }
+       
+       return self;
+}
+
+-(void)dealloc
+{
+       [other release];
+       [super dealloc];
+}
+
+-(void)startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [other startWithTarget:target_];
+}
+
+-(void) update: (ccTime) time
+{
+       [other setAmplitudeRate:powf(time, rate)];
+       [other update:time];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCAccelAmplitude actionWithAction:[other reverse] duration:self.duration];
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark DeccelAmplitude
+
+@implementation CCDeccelAmplitude
+
+@synthesize rate;
+
++(id)actionWithAction:(CCAction*)action duration:(ccTime)d
+{
+       return [[[self alloc] initWithAction:action duration:d ] autorelease];
+}
+
+-(id)initWithAction:(CCAction *)action duration:(ccTime)d
+{
+       if ( (self = [super initWithDuration:d]) )
+       {
+               rate = 1.0f;
+               other = [action retain];
+       }
+       
+       return self;
+}
+
+-(void)dealloc
+{
+       [other release];
+       [super dealloc];
+}
+
+-(void)startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [other startWithTarget:target_];
+}
+
+-(void) update: (ccTime) time
+{
+       [other setAmplitudeRate:powf((1-time), rate)];
+       [other update:time];
+}
+
+- (CCActionInterval*) reverse
+{
+       return [CCDeccelAmplitude actionWithAction:[other reverse] duration:self.duration];
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark StopGrid
+
+@implementation CCStopGrid
+
+-(void)startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+
+       if ( [[self target] grid] && [[[self target] grid] active] ) {
+               [[[self target] grid] setActive: NO];
+               
+//             [[self target] setGrid: nil];
+       }
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark ReuseGrid
+
+@implementation CCReuseGrid
+
++(id)actionWithTimes:(int)times
+{
+       return [[[self alloc] initWithTimes:times ] autorelease];
+}
+
+-(id)initWithTimes:(int)times
+{
+       if ( (self = [super init]) )
+               t = times;
+       
+       return self;
+}
+
+-(void)startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+
+       CCNode *myTarget = (CCNode*) [self target];
+       if ( myTarget.grid && myTarget.grid.active )
+               myTarget.grid.reuseGrid += t;
+}
+
+@end
diff --git a/libs/cocos2d/CCActionGrid3D.h b/libs/cocos2d/CCActionGrid3D.h
new file mode 100644 (file)
index 0000000..a8003f4
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2009 On-Core
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCActionGrid.h"
+
+/** CCWaves3D action */
+@interface CCWaves3D : CCGrid3DAction
+{
+       int waves;
+       float amplitude;
+       float amplitudeRate;
+}
+
+/** amplitude of the wave */
+@property (nonatomic,readwrite) float amplitude;
+/** amplitude rate of the wave */
+@property (nonatomic,readwrite) float amplitudeRate;
+
++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCFlipX3D action */
+@interface CCFlipX3D : CCGrid3DAction
+{
+}
+
+/** creates the action with duration */
++(id) actionWithDuration:(ccTime)d;
+/** initizlies the action with duration */
+-(id) initWithDuration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCFlipY3D action */
+@interface CCFlipY3D : CCFlipX3D
+{
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCLens3D action */
+@interface CCLens3D : CCGrid3DAction
+{
+       CGPoint position_;
+       CGPoint positionInPixels_;
+       float   radius_;
+       float   lensEffect_;
+       BOOL    dirty_;
+}
+
+/** lens effect. Defaults to 0.7 - 0 means no effect, 1 is very strong effect */
+@property (nonatomic,readwrite) float lensEffect;
+/** lens center position in Points */
+@property (nonatomic,readwrite) CGPoint position;
+
+/** creates the action with center position in Points, radius, a grid size and duration */
++(id)actionWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d;
+/** initializes the action with center position in Points, radius, a grid size and duration */
+-(id)initWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCRipple3D action */
+@interface CCRipple3D : CCGrid3DAction
+{
+       CGPoint position_;
+       CGPoint positionInPixels_;
+       float   radius_;
+       int             waves_;
+       float   amplitude_;
+       float   amplitudeRate_;
+}
+
+/** center position in Points */
+@property (nonatomic,readwrite) CGPoint position;
+/** amplitude */
+@property (nonatomic,readwrite) float amplitude;
+/** amplitude rate */
+@property (nonatomic,readwrite) float amplitudeRate;
+
+/** creates the action with a position in points, radius, number of waves, amplitude, a grid size and duration */
++(id)actionWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+/** initializes the action with a position in points, radius, number of waves, amplitude, a grid size and duration */
+-(id)initWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCShaky3D action */
+@interface CCShaky3D : CCGrid3DAction
+{
+       int randrange;
+       BOOL    shakeZ;
+}
+
+/** creates the action with a range, shake Z vertices, a grid and duration */
++(id)actionWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d;
+/** initializes the action with a range, shake Z vertices, a grid and duration */
+-(id)initWithRange:(int)range shakeZ:(BOOL)shakeZ grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCLiquid action */
+@interface CCLiquid : CCGrid3DAction
+{
+       int waves;
+       float amplitude;
+       float amplitudeRate;
+       
+}
+
+/** amplitude */
+@property (nonatomic,readwrite) float amplitude;
+/** amplitude rate */
+@property (nonatomic,readwrite) float amplitudeRate;
+
+/** creates the action with amplitude, a grid and duration */
++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+/** initializes the action with amplitude, a grid and duration */
+-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCWaves action */
+@interface CCWaves : CCGrid3DAction
+{
+       int             waves;
+       float   amplitude;
+       float   amplitudeRate;
+       BOOL    vertical;
+       BOOL    horizontal;
+}
+
+/** amplitude */
+@property (nonatomic,readwrite) float amplitude;
+/** amplitude rate */
+@property (nonatomic,readwrite) float amplitudeRate;
+
+/** initializes the action with amplitude, horizontal sin, vertical sin, a grid and duration */
++(id)actionWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d;
+/** creates the action with amplitude, horizontal sin, vertical sin, a grid and duration */
+-(id)initWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
+
+////////////////////////////////////////////////////////////
+
+/** CCTwirl action */
+@interface CCTwirl : CCGrid3DAction
+{
+       CGPoint position_;
+       CGPoint positionInPixels_;
+       int             twirls_;
+       float   amplitude_;
+       float   amplitudeRate_;
+}
+
+/** twirl center */
+@property (nonatomic,readwrite) CGPoint position;
+/** amplitude */
+@property (nonatomic,readwrite) float amplitude;
+/** amplitude rate */
+@property (nonatomic,readwrite) float amplitudeRate;
+
+/** creates the action with center position, number of twirls, amplitude, a grid size and duration */
++(id)actionWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+/** initializes the action with center position, number of twirls, amplitude, a grid size and duration */
+-(id)initWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d;
+
+@end
diff --git a/libs/cocos2d/CCActionGrid3D.m b/libs/cocos2d/CCActionGrid3D.m
new file mode 100644 (file)
index 0000000..1d4a783
--- /dev/null
@@ -0,0 +1,659 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2009 On-Core
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCActionGrid3D.h"
+#import "ccMacros.h"
+#import "Support/CGPointExtension.h"
+
+#pragma mark -
+#pragma mark Waves3D
+
+@implementation CCWaves3D
+
+@synthesize amplitude;
+@synthesize amplitudeRate;
+
++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               waves = wav;
+               amplitude = amp;
+               amplitudeRate = 1.0f;
+       }
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_];
+       return copy;
+}
+
+
+-(void)update:(ccTime)time
+{
+       int i, j;
+       
+       for( i = 0; i < (gridSize_.x+1); i++ )
+       {
+               for( j = 0; j < (gridSize_.y+1); j++ )
+               {
+                       ccVertex3F      v = [self originalVertex:ccg(i,j)];
+                       v.z += (sinf((CGFloat)M_PI*time*waves*2 + (v.y+v.x) * .01f) * amplitude * amplitudeRate);
+                       [self setVertex:ccg(i,j) vertex:v];
+               }
+       }
+}
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark FlipX3D
+
+@implementation CCFlipX3D
+
++(id) actionWithDuration:(ccTime)d
+{
+       return [[[self alloc] initWithSize:ccg(1,1) duration:d] autorelease];
+}
+
+-(id) initWithDuration:(ccTime)d
+{
+       return [super initWithSize:ccg(1,1) duration:d];
+}
+
+-(id)initWithSize:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( gSize.x != 1 || gSize.y != 1 )
+       {
+               [NSException raise:@"FlipX3D" format:@"Grid size must be (1,1)"];
+       }
+       
+       return [super initWithSize:gSize duration:d];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSize:gridSize_ duration:duration_];
+       return copy;
+}
+
+
+-(void)update:(ccTime)time
+{
+       CGFloat angle = (CGFloat)M_PI * time; // 180 degrees
+       CGFloat mz = sinf( angle );
+       angle = angle / 2.0f;     // x calculates degrees from 0 to 90
+       CGFloat mx = cosf( angle );
+       
+       ccVertex3F      v0, v1, v, diff;
+       
+       v0 = [self originalVertex:ccg(1,1)];
+       v1 = [self originalVertex:ccg(0,0)];
+       
+       CGFloat x0 = v0.x;
+       CGFloat x1 = v1.x;
+       CGFloat x;
+       ccGridSize      a, b, c, d;
+       
+       if ( x0 > x1 )
+       {
+               // Normal Grid
+               a = ccg(0,0);
+               b = ccg(0,1);
+               c = ccg(1,0);
+               d = ccg(1,1);
+               x = x0;
+       }
+       else
+       {
+               // Reversed Grid
+               c = ccg(0,0);
+               d = ccg(0,1);
+               a = ccg(1,0);
+               b = ccg(1,1);
+               x = x1;
+       }
+       
+       diff.x = ( x - x * mx );
+       diff.z = fabsf( floorf( (x * mz) / 4.0f ) );
+       
+// bottom-left
+       v = [self originalVertex:a];
+       v.x = diff.x;
+       v.z += diff.z;
+       [self setVertex:a vertex:v];
+       
+// upper-left
+       v = [self originalVertex:b];
+       v.x = diff.x;
+       v.z += diff.z;
+       [self setVertex:b vertex:v];
+       
+// bottom-right
+       v = [self originalVertex:c];
+       v.x -= diff.x;
+       v.z -= diff.z;
+       [self setVertex:c vertex:v];
+       
+// upper-right
+       v = [self originalVertex:d];
+       v.x -= diff.x;
+       v.z -= diff.z;
+       [self setVertex:d vertex:v];
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark FlipY3D
+
+@implementation CCFlipY3D
+
+-(void)update:(ccTime)time
+{
+       CGFloat angle = (CGFloat)M_PI * time; // 180 degrees
+       CGFloat mz = sinf( angle );
+       angle = angle / 2.0f;     // x calculates degrees from 0 to 90
+       CGFloat my = cosf( angle );
+       
+       ccVertex3F      v0, v1, v, diff;
+       
+       v0 = [self originalVertex:ccg(1,1)];
+       v1 = [self originalVertex:ccg(0,0)];
+       
+       CGFloat y0 = v0.y;
+       CGFloat y1 = v1.y;
+       CGFloat y;
+       ccGridSize      a, b, c, d;
+       
+       if ( y0 > y1 )
+       {
+               // Normal Grid
+               a = ccg(0,0);
+               b = ccg(0,1);
+               c = ccg(1,0);
+               d = ccg(1,1);
+               y = y0;
+       }
+       else
+       {
+               // Reversed Grid
+               b = ccg(0,0);
+               a = ccg(0,1);
+               d = ccg(1,0);
+               c = ccg(1,1);
+               y = y1;
+       }
+       
+       diff.y = y - y * my;
+       diff.z = fabsf( floorf( (y * mz) / 4.0f ) );
+       
+       // bottom-left
+       v = [self originalVertex:a];
+       v.y = diff.y;
+       v.z += diff.z;
+       [self setVertex:a vertex:v];
+       
+       // upper-left
+       v = [self originalVertex:b];
+       v.y -= diff.y;
+       v.z -= diff.z;
+       [self setVertex:b vertex:v];
+       
+       // bottom-right
+       v = [self originalVertex:c];
+       v.y = diff.y;
+       v.z += diff.z;
+       [self setVertex:c vertex:v];
+       
+       // upper-right
+       v = [self originalVertex:d];
+       v.y -= diff.y;
+       v.z -= diff.z;
+       [self setVertex:d vertex:v];
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Lens3D
+
+@implementation CCLens3D
+
+@synthesize lensEffect=lensEffect_;
+
++(id)actionWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithPosition:pos radius:r grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               position_ = ccp(-1,-1);
+               self.position = pos;
+               radius_ = r;
+               lensEffect_ = 0.7f;
+               dirty_ = YES;
+       }
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ radius:radius_ grid:gridSize_ duration:duration_];
+       return copy;
+}
+
+-(void) setPosition:(CGPoint)pos
+{
+       if( ! CGPointEqualToPoint(pos, position_) ) {
+               position_ = pos;
+               positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR();
+               positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR();
+               
+               dirty_ = YES;
+       }
+}
+
+-(CGPoint) position
+{
+       return position_;
+}
+       
+-(void)update:(ccTime)time
+{
+       if ( dirty_ )
+       {
+               int i, j;
+               
+               for( i = 0; i < gridSize_.x+1; i++ )
+               {
+                       for( j = 0; j < gridSize_.y+1; j++ )
+                       {
+                               ccVertex3F      v = [self originalVertex:ccg(i,j)];
+                               CGPoint vect = ccpSub(positionInPixels_, ccp(v.x,v.y));
+                               CGFloat r = ccpLength(vect);
+                               
+                               if ( r < radius_ )
+                               {
+                                       r = radius_ - r;
+                                       CGFloat pre_log = r / radius_;
+                                       if ( pre_log == 0 ) pre_log = 0.001f;
+                                       float l = logf(pre_log) * lensEffect_;
+                                       float new_r = expf( l ) * radius_;
+                                       
+                                       if ( ccpLength(vect) > 0 )
+                                       {
+                                               vect = ccpNormalize(vect);
+                                               CGPoint new_vect = ccpMult(vect, new_r);
+                                               v.z += ccpLength(new_vect) * lensEffect_;
+                                       }
+                               }
+                               
+                               [self setVertex:ccg(i,j) vertex:v];
+                       }
+               }
+               
+               dirty_ = NO;
+       }
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Ripple3D
+
+@implementation CCRipple3D
+
+@synthesize amplitude = amplitude_;
+@synthesize amplitudeRate = amplitudeRate_;
+
++(id)actionWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithPosition:pos radius:r waves:wav amplitude:amp grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               self.position = pos;
+               radius_ = r;
+               waves_ = wav;
+               amplitude_ = amp;
+               amplitudeRate_ = 1.0f;
+       }
+       
+       return self;
+}
+
+-(CGPoint) position
+{
+       return position_;
+}
+
+-(void) setPosition:(CGPoint)pos
+{
+       position_ = pos;
+       positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR();
+       positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR();
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ radius:radius_ waves:waves_ amplitude:amplitude_ grid:gridSize_ duration:duration_];
+       return copy;
+}
+
+
+-(void)update:(ccTime)time
+{
+       int i, j;
+       
+       for( i = 0; i < (gridSize_.x+1); i++ )
+       {
+               for( j = 0; j < (gridSize_.y+1); j++ )
+               {
+                       ccVertex3F      v = [self originalVertex:ccg(i,j)];
+                       CGPoint vect = ccpSub(positionInPixels_, ccp(v.x,v.y));
+                       CGFloat r = ccpLength(vect);
+                       
+                       if ( r < radius_ )
+                       {
+                               r = radius_ - r;
+                               CGFloat rate = powf( r / radius_, 2);
+                               v.z += (sinf( time*(CGFloat)M_PI*waves_*2 + r * 0.1f) * amplitude_ * amplitudeRate_ * rate );
+                       }
+                       
+                       [self setVertex:ccg(i,j) vertex:v];
+               }
+       }
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Shaky3D
+
+@implementation CCShaky3D
+
++(id)actionWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithRange:range shakeZ:sz grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               randrange = range;
+               shakeZ = sz;
+       }
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithRange:randrange shakeZ:shakeZ grid:gridSize_ duration:duration_];
+       return copy;
+}
+
+-(void)update:(ccTime)time
+{
+       int i, j;
+       
+       for( i = 0; i < (gridSize_.x+1); i++ )
+       {
+               for( j = 0; j < (gridSize_.y+1); j++ )
+               {
+                       ccVertex3F      v = [self originalVertex:ccg(i,j)];
+                       v.x += ( rand() % (randrange*2) ) - randrange;
+                       v.y += ( rand() % (randrange*2) ) - randrange;
+                       if( shakeZ )
+                               v.z += ( rand() % (randrange*2) ) - randrange;
+                       
+                       [self setVertex:ccg(i,j) vertex:v];
+               }
+       }
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Liquid
+
+@implementation CCLiquid
+
+@synthesize amplitude;
+@synthesize amplitudeRate;
+
++(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               waves = wav;
+               amplitude = amp;
+               amplitudeRate = 1.0f;
+       }
+       
+       return self;
+}
+
+-(void)update:(ccTime)time
+{
+       int i, j;
+       
+       for( i = 1; i < gridSize_.x; i++ )
+       {
+               for( j = 1; j < gridSize_.y; j++ )
+               {
+                       ccVertex3F      v = [self originalVertex:ccg(i,j)];
+                       v.x = (v.x + (sinf(time*(CGFloat)M_PI*waves*2 + v.x * .01f) * amplitude * amplitudeRate));
+                       v.y = (v.y + (sinf(time*(CGFloat)M_PI*waves*2 + v.y * .01f) * amplitude * amplitudeRate));
+                       [self setVertex:ccg(i,j) vertex:v];
+               }
+       }
+}      
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_];
+       return copy;
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Waves
+
+@implementation CCWaves
+
+@synthesize amplitude;
+@synthesize amplitudeRate;
+
++(id)actionWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithWaves:wav amplitude:amp horizontal:h vertical:v grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               waves = wav;
+               amplitude = amp;
+               amplitudeRate = 1.0f;
+               horizontal = h;
+               vertical = v;
+       }
+       
+       return self;
+}
+
+-(void)update:(ccTime)time
+{
+       int i, j;
+       
+       for( i = 0; i < (gridSize_.x+1); i++ )
+       {
+               for( j = 0; j < (gridSize_.y+1); j++ )
+               {
+                       ccVertex3F      v = [self originalVertex:ccg(i,j)];
+                       
+                       if ( vertical )
+                               v.x = (v.x + (sinf(time*(CGFloat)M_PI*waves*2 + v.y * .01f) * amplitude * amplitudeRate));
+                       
+                       if ( horizontal )
+                               v.y = (v.y + (sinf(time*(CGFloat)M_PI*waves*2 + v.x * .01f) * amplitude * amplitudeRate));
+                                       
+                       [self setVertex:ccg(i,j) vertex:v];
+               }
+       }
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude horizontal:horizontal vertical:vertical grid:gridSize_ duration:duration_];
+       return copy;
+}
+
+@end
+
+////////////////////////////////////////////////////////////
+
+#pragma mark -
+#pragma mark Twirl
+
+@implementation CCTwirl
+
+@synthesize amplitude = amplitude_;
+@synthesize amplitudeRate = amplitudeRate_;
+
++(id)actionWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
+{
+       return [[[self alloc] initWithPosition:pos twirls:t amplitude:amp grid:gridSize duration:d] autorelease];
+}
+
+-(id)initWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
+{
+       if ( (self = [super initWithSize:gSize duration:d]) )
+       {
+               self.position = pos;
+               twirls_ = t;
+               amplitude_ = amp;
+               amplitudeRate_ = 1.0f;
+       }
+       
+       return self;
+}
+
+-(void) setPosition:(CGPoint)pos
+{
+       position_ = pos;
+       positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR();
+       positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR();
+}
+
+-(CGPoint) position
+{
+       return position_;
+}
+
+-(void)update:(ccTime)time
+{
+       int i, j;
+       CGPoint c = positionInPixels_;
+       
+       for( i = 0; i < (gridSize_.x+1); i++ )
+       {
+               for( j = 0; j < (gridSize_.y+1); j++ )
+               {
+                       ccVertex3F v = [self originalVertex:ccg(i,j)];
+                       
+                       CGPoint avg = ccp(i-(gridSize_.x/2.0f), j-(gridSize_.y/2.0f));
+                       CGFloat r = ccpLength( avg );
+                       
+                       CGFloat amp = 0.1f * amplitude_ * amplitudeRate_;
+                       CGFloat a = r * cosf( (CGFloat)M_PI/2.0f + time * (CGFloat)M_PI * twirls_ * 2 ) * amp;
+                       
+                       float cosA = cosf(a);
+                       float sinA = sinf(a);
+                       
+                       CGPoint d = {
+                               sinA * (v.y-c.y) + cosA * (v.x-c.x),
+                               cosA * (v.y-c.y) - sinA * (v.x-c.x)
+                       };
+                       
+                       v.x = c.x + d.x;
+                       v.y = c.y + d.y;
+                       
+                       [self setVertex:ccg(i,j) vertex:v];
+               }
+       }
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_
+                                                                                                                                         twirls:twirls_
+                                                                                                                                  amplitude:amplitude_
+                                                                                                                                               grid:gridSize_
+                                                                                                                                       duration:duration_];
+       return copy;
+}
+
+
+@end
diff --git a/libs/cocos2d/CCActionInstant.h b/libs/cocos2d/CCActionInstant.h
new file mode 100644 (file)
index 0000000..5a1bc2d
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2010 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCAction.h"
+
+/** Instant actions are immediate actions. They don't have a duration like
+ the CCIntervalAction actions.
+*/ 
+@interface CCActionInstant : CCFiniteTimeAction <NSCopying>
+{
+}
+@end
+
+/** Show the node
+ */
+ @interface CCShow : CCActionInstant
+{
+}
+@end
+
+/** Hide the node
+ */
+@interface CCHide : CCActionInstant
+{
+}
+@end
+
+/** Toggles the visibility of a node
+ */
+@interface CCToggleVisibility : CCActionInstant
+{
+}
+@end
+
+/** Flips the sprite horizontally
+ @since v0.99.0
+ */
+@interface CCFlipX : CCActionInstant
+{
+       BOOL    flipX;
+}
++(id) actionWithFlipX:(BOOL)x;
+-(id) initWithFlipX:(BOOL)x;
+@end
+
+/** Flips the sprite vertically
+ @since v0.99.0
+ */
+@interface CCFlipY : CCActionInstant
+{
+       BOOL    flipY;
+}
++(id) actionWithFlipY:(BOOL)y;
+-(id) initWithFlipY:(BOOL)y;
+@end
+
+/** Places the node in a certain position
+ */
+@interface CCPlace : CCActionInstant <NSCopying>
+{
+       CGPoint position;
+}
+/** creates a Place action with a position */
++(id) actionWithPosition: (CGPoint) pos;
+/** Initializes a Place action with a position */
+-(id) initWithPosition: (CGPoint) pos;
+@end
+
+/** Calls a 'callback'
+ */
+@interface CCCallFunc : CCActionInstant <NSCopying>
+{
+       id targetCallback_;
+       SEL selector_;
+}
+
+/** Target that will be called */
+@property (nonatomic, readwrite, retain) id targetCallback;
+
+/** creates the action with the callback */
++(id) actionWithTarget: (id) t selector:(SEL) s;
+/** initializes the action with the callback */
+-(id) initWithTarget: (id) t selector:(SEL) s;
+/** exeuctes the callback */
+-(void) execute;
+@end
+
+/** Calls a 'callback' with the node as the first argument.
+ N means Node
+ */
+@interface CCCallFuncN : CCCallFunc
+{
+}
+@end
+
+typedef void (*CC_CALLBACK_ND)(id, SEL, id, void *);
+/** Calls a 'callback' with the node as the first argument and the 2nd argument is data.
+ * ND means: Node and Data. Data is void *, so it could be anything.
+ */
+@interface CCCallFuncND : CCCallFuncN
+{
+       void                    *data_;
+       CC_CALLBACK_ND  callbackMethod_;
+}
+
+/** Invocation object that has the target#selector and the parameters */
+@property (nonatomic,readwrite) CC_CALLBACK_ND callbackMethod;
+
+/** creates the action with the callback and the data to pass as an argument */
++(id) actionWithTarget: (id) t selector:(SEL) s data:(void*)d;
+/** initializes the action with the callback and the data to pass as an argument */
+-(id) initWithTarget:(id) t selector:(SEL) s data:(void*) d;
+@end
+
+/** Calls a 'callback' with an object as the first argument.
+ O means Object.
+ @since v0.99.5
+ */
+@interface CCCallFuncO : CCCallFunc
+{
+       id      object_;
+}
+/** object to be passed as argument */
+@property (nonatomic, readwrite, retain) id object;
+
+/** creates the action with the callback and the object to pass as an argument */
++(id) actionWithTarget: (id) t selector:(SEL) s object:(id)object;
+/** initializes the action with the callback and the object to pass as an argument */
+-(id) initWithTarget:(id) t selector:(SEL) s object:(id)object;
+
+@end
+
+#pragma mark Blocks Support
+
+#if NS_BLOCKS_AVAILABLE
+
+/** Executes a callback using a block.
+ */
+@interface CCCallBlock : CCActionInstant<NSCopying>
+{
+       void (^block_)();
+}
+
+/** creates the action with the specified block, to be used as a callback.
+ The block will be "copied".
+ */
++(id) actionWithBlock:(void(^)())block;
+
+/** initialized the action with the specified block, to be used as a callback.
+ The block will be "copied".
+ */
+-(id) initWithBlock:(void(^)())block;
+
+/** executes the callback */
+-(void) execute;
+@end
+
+@class CCNode;
+
+/** Executes a callback using a block with a single CCNode parameter.
+ */
+@interface CCCallBlockN : CCActionInstant<NSCopying>
+{
+       void (^block_)(CCNode *);
+}
+
+/** creates the action with the specified block, to be used as a callback.
+ The block will be "copied".
+ */
++(id) actionWithBlock:(void(^)(CCNode *node))block;
+
+/** initialized the action with the specified block, to be used as a callback.
+ The block will be "copied".
+ */
+-(id) initWithBlock:(void(^)(CCNode *node))block;
+
+/** executes the callback */
+-(void) execute;
+@end
+
+#endif
diff --git a/libs/cocos2d/CCActionInstant.m b/libs/cocos2d/CCActionInstant.m
new file mode 100644 (file)
index 0000000..e7f6fad
--- /dev/null
@@ -0,0 +1,477 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2010 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCBlockSupport.h"
+#import "CCActionInstant.h"
+#import "CCNode.h"
+#import "CCSprite.h"
+
+
+//
+// InstantAction
+//
+#pragma mark CCActionInstant
+
+@implementation CCActionInstant
+
+-(id) init
+{
+       if( (self=[super init]) )       
+               duration_ = 0;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] init];
+       return copy;
+}
+
+- (BOOL) isDone
+{
+       return YES;
+}
+
+-(void) step: (ccTime) dt
+{
+       [self update: 1];
+}
+
+-(void) update: (ccTime) t
+{
+       // ignore
+}
+
+-(CCFiniteTimeAction*) reverse
+{
+       return [[self copy] autorelease];
+}
+@end
+
+//
+// Show
+//
+#pragma mark CCShow
+
+@implementation CCShow
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       ((CCNode *)target_).visible = YES;
+}
+
+-(CCFiniteTimeAction*) reverse
+{
+       return [CCHide action];
+}
+@end
+
+//
+// Hide
+//
+#pragma mark CCHide
+
+@implementation CCHide
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       ((CCNode *)target_).visible = NO;
+}
+
+-(CCFiniteTimeAction*) reverse
+{
+       return [CCShow action];
+}
+@end
+
+//
+// ToggleVisibility
+//
+#pragma mark CCToggleVisibility
+
+@implementation CCToggleVisibility
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       ((CCNode *)target_).visible = !((CCNode *)target_).visible;
+}
+@end
+
+//
+// FlipX
+//
+#pragma mark CCFlipX
+
+@implementation CCFlipX
++(id) actionWithFlipX:(BOOL)x
+{
+       return [[[self alloc] initWithFlipX:x] autorelease];
+}
+
+-(id) initWithFlipX:(BOOL)x
+{
+       if(( self=[super init]))
+               flipX = x;
+       
+       return self;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [(CCSprite*)aTarget setFlipX:flipX];
+}
+
+-(CCFiniteTimeAction*) reverse
+{
+       return [CCFlipX actionWithFlipX:!flipX];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithFlipX:flipX];
+       return copy;
+}
+@end
+
+//
+// FlipY
+//
+#pragma mark CCFlipY
+
+@implementation CCFlipY
++(id) actionWithFlipY:(BOOL)y
+{
+       return [[[self alloc] initWithFlipY:y] autorelease];
+}
+
+-(id) initWithFlipY:(BOOL)y
+{
+       if(( self=[super init]))
+               flipY = y;
+       
+       return self;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [(CCSprite*)aTarget setFlipY:flipY];
+}
+
+-(CCFiniteTimeAction*) reverse
+{
+       return [CCFlipY actionWithFlipY:!flipY];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithFlipY:flipY];
+       return copy;
+}
+@end
+
+
+//
+// Place
+//
+#pragma mark CCPlace
+
+@implementation CCPlace
++(id) actionWithPosition: (CGPoint) pos
+{
+       return [[[self alloc]initWithPosition:pos]autorelease];
+}
+
+-(id) initWithPosition: (CGPoint) pos
+{
+       if( (self=[super init]) )
+               position = pos;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithPosition: position];
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       ((CCNode *)target_).position = position;
+}
+
+@end
+
+//
+// CallFunc
+//
+#pragma mark CCCallFunc
+
+@implementation CCCallFunc
+
+@synthesize targetCallback = targetCallback_;
+
++(id) actionWithTarget: (id) t selector:(SEL) s
+{
+       return [[[self alloc] initWithTarget: t selector: s] autorelease];
+}
+
+-(id) initWithTarget: (id) t selector:(SEL) s
+{
+       if( (self=[super init]) ) {
+               self.targetCallback = t;
+               selector_ = s;
+       }
+       return self;
+}
+
+-(NSString*) description
+{
+       return [NSString stringWithFormat:@"<%@ = %08X | Tag = %i | target = %@ | selector = %@>",
+                       [self class],
+                       self,
+                       tag_,
+                       [targetCallback_ class],
+                       NSStringFromSelector(selector_)
+                       ];
+}
+
+-(void) dealloc
+{
+       [targetCallback_ release];
+       [super dealloc];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithTarget:targetCallback_ selector:selector_];
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [self execute];
+}
+
+-(void) execute
+{
+       [targetCallback_ performSelector:selector_];
+}
+@end
+
+//
+// CallFuncN
+//
+#pragma mark CCCallFuncN
+
+@implementation CCCallFuncN
+
+-(void) execute
+{
+       [targetCallback_ performSelector:selector_ withObject:target_];
+}
+@end
+
+//
+// CallFuncND
+//
+#pragma mark CCCallFuncND
+
+@implementation CCCallFuncND
+
+@synthesize callbackMethod = callbackMethod_;
+
++(id) actionWithTarget:(id)t selector:(SEL)s data:(void*)d
+{
+       return [[[self alloc] initWithTarget:t selector:s data:d] autorelease];
+}
+
+-(id) initWithTarget:(id)t selector:(SEL)s data:(void*)d
+{
+       if( (self=[super initWithTarget:t selector:s]) ) {
+               data_ = d;
+
+#if COCOS2D_DEBUG
+               NSMethodSignature * sig = [t methodSignatureForSelector:s]; // added
+               NSAssert(sig !=0 , @"Signature not found for selector - does it have the following form? -(void)name:(id)sender data:(void*)data");
+#endif
+               callbackMethod_ = (CC_CALLBACK_ND) [t methodForSelector:s];
+       }
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithTarget:targetCallback_ selector:selector_ data:data_];
+       return copy;
+}
+
+-(void) dealloc
+{
+       // nothing to dealloc really. Everything is dealloc on super (CCCallFuncN)
+       [super dealloc];
+}
+
+-(void) execute
+{
+       callbackMethod_(targetCallback_,selector_,target_, data_);
+}
+@end
+
+@implementation CCCallFuncO
+@synthesize  object = object_;
+
++(id) actionWithTarget: (id) t selector:(SEL) s object:(id)object
+{
+       return [[[self alloc] initWithTarget:t selector:s object:object] autorelease];
+}
+
+-(id) initWithTarget:(id) t selector:(SEL) s object:(id)object
+{
+       if( (self=[super initWithTarget:t selector:s] ) )
+               self.object = object;
+       
+       return self;
+}
+
+- (void) dealloc
+{
+       [object_ release];
+       [super dealloc];
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithTarget:targetCallback_ selector:selector_ object:object_];
+       return copy;
+}
+
+
+-(void) execute
+{
+       [targetCallback_ performSelector:selector_ withObject:object_];
+}
+
+@end
+
+
+#pragma mark -
+#pragma mark Blocks
+
+#if NS_BLOCKS_AVAILABLE
+
+#pragma mark CCCallBlock
+
+@implementation CCCallBlock
+
++(id) actionWithBlock:(void(^)())block
+{
+       return [[[self alloc] initWithBlock:block] autorelease];
+}
+
+-(id) initWithBlock:(void(^)())block
+{
+       if ((self = [super init]))
+               block_ = [block copy];
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithBlock:block_];
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [self execute];
+}
+
+-(void) execute
+{
+       block_();
+}
+
+-(void) dealloc
+{
+       [block_ release];
+       [super dealloc];
+}
+
+@end
+
+#pragma mark CCCallBlockN
+
+@implementation CCCallBlockN
+
++(id) actionWithBlock:(void(^)(CCNode *node))block
+{
+       return [[[self alloc] initWithBlock:block] autorelease];
+}
+
+-(id) initWithBlock:(void(^)(CCNode *node))block
+{
+       if ((self = [super init]))
+               block_ = [block copy];
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCActionInstant *copy = [[[self class] allocWithZone: zone] initWithBlock:block_];
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [self execute];
+}
+
+-(void) execute
+{
+       block_(target_);
+}
+
+-(void) dealloc
+{
+       [block_ release];
+       [super dealloc];
+}
+
+@end
+
+
+#endif // NS_BLOCKS_AVAILABLE
diff --git a/libs/cocos2d/CCActionInterval.h b/libs/cocos2d/CCActionInterval.h
new file mode 100644 (file)
index 0000000..c667963
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2011 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+#import "CCNode.h"
+#import "CCAction.h"
+#import "CCProtocols.h"
+
+#include <sys/time.h>
+
+/** An interval action is an action that takes place within a certain period of time.
+It has an start time, and a finish time. The finish time is the parameter
+duration plus the start time.
+
+These CCActionInterval actions have some interesting properties, like:
+ - They can run normally (default)
+ - They can run reversed with the reverse method
+ - They can run with the time altered with the Accelerate, AccelDeccel and Speed actions.
+
+For example, you can simulate a Ping Pong effect running the action normally and
+then running it again in Reverse mode.
+
+Example:
+       CCAction * pingPongAction = [CCSequence actions: action, [action reverse], nil];
+*/
+@interface CCActionInterval: CCFiniteTimeAction <NSCopying>
+{
+       ccTime  elapsed_;
+       BOOL    firstTick_;
+}
+
+/** how many seconds had elapsed since the actions started to run. */
+@property (nonatomic,readonly) ccTime elapsed;
+
+/** creates the action */
++(id) actionWithDuration: (ccTime) d;
+/** initializes the action */
+-(id) initWithDuration: (ccTime) d;
+/** returns YES if the action has finished */
+-(BOOL) isDone;
+/** returns a reversed action */
+- (CCActionInterval*) reverse;
+@end
+
+/** Runs actions sequentially, one after another
+ */
+@interface CCSequence : CCActionInterval <NSCopying>
+{
+       CCFiniteTimeAction *actions_[2];
+       ccTime split_;
+       int last_;
+}
+/** helper contructor to create an array of sequenceable actions */
++(id) actions: (CCFiniteTimeAction*) action1, ... NS_REQUIRES_NIL_TERMINATION;
+/** helper contructor to create an array of sequenceable actions given an array */
++(id) actionsWithArray: (NSArray*) actions;
+/** creates the action */
++(id) actionOne:(CCFiniteTimeAction*)actionOne two:(CCFiniteTimeAction*)actionTwo;
+/** initializes the action */
+-(id) initOne:(CCFiniteTimeAction*)actionOne two:(CCFiniteTimeAction*)actionTwo;
+@end
+
+
+/** Repeats an action a number of times.
+ * To repeat an action forever use the CCRepeatForever action.
+ */
+@interface CCRepeat : CCActionInterval <NSCopying>
+{
+       NSUInteger times_;
+       NSUInteger total_;
+       CCFiniteTimeAction *innerAction_;
+}
+
+/** Inner action */
+@property (nonatomic,readwrite,retain) CCFiniteTimeAction *innerAction;
+
+/** creates a CCRepeat action. Times is an unsigned integer between 1 and MAX_UINT */
++(id) actionWithAction:(CCFiniteTimeAction*)action times: (NSUInteger)times;
+/** initializes a CCRepeat action. Times is an unsigned integer between 1 and MAX_UINT */
+-(id) initWithAction:(CCFiniteTimeAction*)action times: (NSUInteger)times;
+@end
+
+/** Spawn a new action immediately
+ */
+@interface CCSpawn : CCActionInterval <NSCopying>
+{
+       CCFiniteTimeAction *one_;
+       CCFiniteTimeAction *two_;
+}
+/** helper constructor to create an array of spawned actions */
++(id) actions: (CCFiniteTimeAction*) action1, ... NS_REQUIRES_NIL_TERMINATION;
+/** helper contructor to create an array of spawned actions given an array */
++(id) actionsWithArray: (NSArray*) actions;
+/** creates the Spawn action */
++(id) actionOne: (CCFiniteTimeAction*) one two:(CCFiniteTimeAction*) two;
+/** initializes the Spawn action with the 2 actions to spawn */
+-(id) initOne: (CCFiniteTimeAction*) one two:(CCFiniteTimeAction*) two;
+@end
+
+/**  Rotates a CCNode object to a certain angle by modifying it's
+ rotation attribute.
+ The direction will be decided by the shortest angle.
+*/ 
+@interface CCRotateTo : CCActionInterval <NSCopying>
+{
+       float dstAngle_;
+       float startAngle_;
+       float diffAngle_;
+}
+/** creates the action */
++(id) actionWithDuration:(ccTime)duration angle:(float)angle;
+/** initializes the action */
+-(id) initWithDuration:(ccTime)duration angle:(float)angle;
+@end
+
+/** Rotates a CCNode object clockwise a number of degrees by modiying it's rotation attribute.
+*/
+@interface CCRotateBy : CCActionInterval <NSCopying>
+{
+       float angle_;
+       float startAngle_;
+}
+/** creates the action */
++(id) actionWithDuration:(ccTime)duration angle:(float)deltaAngle;
+/** initializes the action */
+-(id) initWithDuration:(ccTime)duration angle:(float)deltaAngle;
+@end
+
+/** Moves a CCNode object to the position x,y. x and y are absolute coordinates by modifying it's position attribute.
+*/
+@interface CCMoveTo : CCActionInterval <NSCopying>
+{
+       CGPoint endPosition_;
+       CGPoint startPosition_;
+       CGPoint delta_;
+}
+/** creates the action */
++(id) actionWithDuration:(ccTime)duration position:(CGPoint)position;
+/** initializes the action */
+-(id) initWithDuration:(ccTime)duration position:(CGPoint)position;
+@end
+
+/**  Moves a CCNode object x,y pixels by modifying it's position attribute.
+ x and y are relative to the position of the object.
+ Duration is is seconds.
+*/ 
+@interface CCMoveBy : CCMoveTo <NSCopying>
+{
+}
+/** creates the action */
++(id) actionWithDuration: (ccTime)duration position:(CGPoint)deltaPosition;
+/** initializes the action */
+-(id) initWithDuration: (ccTime)duration position:(CGPoint)deltaPosition;
+@end
+
+/** Skews a CCNode object to given angles by modifying it's skewX and skewY attributes
+ @since v1.0
+ */
+@interface CCSkewTo : CCActionInterval <NSCopying>
+{
+       float skewX_;
+       float skewY_;
+       float startSkewX_;
+       float startSkewY_;
+       float endSkewX_;
+       float endSkewY_;
+       float deltaX_;
+       float deltaY_;
+}
+/** creates the action */
++(id) actionWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy;
+/** initializes the action */
+-(id) initWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy;
+@end
+
+/** Skews a CCNode object by skewX and skewY degrees
+ @since v1.0
+ */
+@interface CCSkewBy : CCSkewTo <NSCopying>
+{
+}
+@end
+
+/** Moves a CCNode object simulating a parabolic jump movement by modifying it's position attribute.
+*/
+ @interface CCJumpBy : CCActionInterval <NSCopying>
+{
+       CGPoint startPosition_;
+       CGPoint delta_;
+       ccTime height_;
+       NSUInteger jumps_;
+}
+/** creates the action */
++(id) actionWithDuration: (ccTime)duration position:(CGPoint)position height:(ccTime)height jumps:(NSUInteger)jumps;
+/** initializes the action */
+-(id) initWithDuration: (ccTime)duration position:(CGPoint)position height:(ccTime)height jumps:(NSUInteger)jumps;
+@end
+
+/** Moves a CCNode object to a parabolic position simulating a jump movement by modifying it's position attribute.
+*/ 
+ @interface CCJumpTo : CCJumpBy <NSCopying>
+{
+}
+@end
+
+/** bezier configuration structure
+ */
+typedef struct _ccBezierConfig {
+       //! end position of the bezier
+       CGPoint endPosition;
+       //! Bezier control point 1
+       CGPoint controlPoint_1;
+       //! Bezier control point 2
+       CGPoint controlPoint_2;
+} ccBezierConfig;
+
+/** An action that moves the target with a cubic Bezier curve by a certain distance.
+ */
+@interface CCBezierBy : CCActionInterval <NSCopying>
+{
+       ccBezierConfig config_;
+       CGPoint startPosition_;
+}
+
+/** creates the action with a duration and a bezier configuration */
++(id) actionWithDuration: (ccTime) t bezier:(ccBezierConfig) c;
+
+/** initializes the action with a duration and a bezier configuration */
+-(id) initWithDuration: (ccTime) t bezier:(ccBezierConfig) c;
+@end
+
+/** An action that moves the target with a cubic Bezier curve to a destination point.
+ @since v0.8.2
+ */
+@interface CCBezierTo : CCBezierBy
+{
+}
+@end
+
+/** Scales a CCNode object to a zoom factor by modifying it's scale attribute.
+ @warning This action doesn't support "reverse"
+ */
+@interface CCScaleTo : CCActionInterval <NSCopying>
+{
+       float scaleX_;
+       float scaleY_;
+       float startScaleX_;
+       float startScaleY_;
+       float endScaleX_;
+       float endScaleY_;
+       float deltaX_;
+       float deltaY_;
+}
+/** creates the action with the same scale factor for X and Y */
++(id) actionWithDuration: (ccTime)duration scale:(float) s;
+/** initializes the action with the same scale factor for X and Y */
+-(id) initWithDuration: (ccTime)duration scale:(float) s;
+/** creates the action with and X factor and a Y factor */
++(id) actionWithDuration: (ccTime)duration scaleX:(float) sx scaleY:(float)sy;
+/** initializes the action with and X factor and a Y factor */
+-(id) initWithDuration: (ccTime)duration scaleX:(float) sx scaleY:(float)sy;
+@end
+
+/** Scales a CCNode object a zoom factor by modifying it's scale attribute.
+*/
+@interface CCScaleBy : CCScaleTo <NSCopying>
+{
+}
+@end
+
+/** Blinks a CCNode object by modifying it's visible attribute
+*/
+@interface CCBlink : CCActionInterval <NSCopying>
+{
+       NSUInteger times_;
+}
+/** creates the action */
++(id) actionWithDuration: (ccTime)duration blinks:(NSUInteger)blinks;
+/** initilizes the action */
+-(id) initWithDuration: (ccTime)duration blinks:(NSUInteger)blinks;
+@end
+
+/** Fades In an object that implements the CCRGBAProtocol protocol. It modifies the opacity from 0 to 255.
+ The "reverse" of this action is FadeOut
+ */
+@interface CCFadeIn : CCActionInterval <NSCopying>
+{
+}
+@end
+
+/** Fades Out an object that implements the CCRGBAProtocol protocol. It modifies the opacity from 255 to 0.
+ The "reverse" of this action is FadeIn
+*/
+@interface CCFadeOut : CCActionInterval <NSCopying>
+{
+}
+@end
+
+/** Fades an object that implements the CCRGBAProtocol protocol. It modifies the opacity from the current value to a custom one.
+ @warning This action doesn't support "reverse"
+ */
+@interface CCFadeTo : CCActionInterval <NSCopying>
+{
+       GLubyte toOpacity_;
+       GLubyte fromOpacity_;
+}
+/** creates an action with duration and opactiy */
++(id) actionWithDuration:(ccTime)duration opacity:(GLubyte)opactiy;
+/** initializes the action with duration and opacity */
+-(id) initWithDuration:(ccTime)duration opacity:(GLubyte)opacity;
+@end
+
+/** Tints a CCNode that implements the CCNodeRGB protocol from current tint to a custom one.
+ @warning This action doesn't support "reverse"
+ @since v0.7.2
+*/
+@interface CCTintTo : CCActionInterval <NSCopying>
+{
+       ccColor3B to_;
+       ccColor3B from_;
+}
+/** creates an action with duration and color */
++(id) actionWithDuration:(ccTime)duration red:(GLubyte)red green:(GLubyte)green blue:(GLubyte)blue;
+/** initializes the action with duration and color */
+-(id) initWithDuration:(ccTime)duration red:(GLubyte)red green:(GLubyte)green blue:(GLubyte)blue;
+@end
+
+/** Tints a CCNode that implements the CCNodeRGB protocol from current tint to a custom one.
+ @since v0.7.2
+ */
+@interface CCTintBy : CCActionInterval <NSCopying>
+{
+       GLshort deltaR_, deltaG_, deltaB_;
+       GLshort fromR_, fromG_, fromB_;
+}
+/** creates an action with duration and color */
++(id) actionWithDuration:(ccTime)duration red:(GLshort)deltaRed green:(GLshort)deltaGreen blue:(GLshort)deltaBlue;
+/** initializes the action with duration and color */
+-(id) initWithDuration:(ccTime)duration red:(GLshort)deltaRed green:(GLshort)deltaGreen blue:(GLshort)deltaBlue;
+@end
+
+/** Delays the action a certain amount of seconds
+*/
+@interface CCDelayTime : CCActionInterval <NSCopying>
+{
+}
+@end
+
+/** Executes an action in reverse order, from time=duration to time=0
+ @warning Use this action carefully. This action is not
+ sequenceable. Use it as the default "reversed" method
+ of your own actions, but using it outside the "reversed"
+ scope is not recommended.
+*/
+@interface CCReverseTime : CCActionInterval <NSCopying>
+{
+       CCFiniteTimeAction * other_;
+}
+/** creates the action */
++(id) actionWithAction: (CCFiniteTimeAction*) action;
+/** initializes the action */
+-(id) initWithAction: (CCFiniteTimeAction*) action;
+@end
+
+
+@class CCAnimation;
+@class CCTexture2D;
+/** Animates a sprite given the name of an Animation */
+@interface CCAnimate : CCActionInterval <NSCopying>
+{
+       CCAnimation *animation_;
+       id origFrame_;
+       BOOL restoreOriginalFrame_;
+}
+/** animation used for the animage */
+@property (readwrite,nonatomic,retain) CCAnimation * animation;
+
+/** creates the action with an Animation and will restore the original frame when the animation is over */
++(id) actionWithAnimation:(CCAnimation*) a;
+/** initializes the action with an Animation and will restore the original frame when the animtion is over */
+-(id) initWithAnimation:(CCAnimation*) a;
+/** creates the action with an Animation */
++(id) actionWithAnimation:(CCAnimation*) a restoreOriginalFrame:(BOOL)b;
+/** initializes the action with an Animation */
+-(id) initWithAnimation:(CCAnimation*) a restoreOriginalFrame:(BOOL)b;
+/** creates an action with a duration, animation and depending of the restoreOriginalFrame, it will restore the original frame or not.
+ The 'delay' parameter of the animation will be overrided by the duration parameter.
+ @since v0.99.0
+ */
++(id) actionWithDuration:(ccTime)duration animation:(CCAnimation*)animation restoreOriginalFrame:(BOOL)b;
+/** initializes an action with a duration, animation and depending of the restoreOriginalFrame, it will restore the original frame or not.
+ The 'delay' parameter of the animation will be overrided by the duration parameter.
+ @since v0.99.0
+ */
+-(id) initWithDuration:(ccTime)duration animation:(CCAnimation*)animation restoreOriginalFrame:(BOOL)b;
+@end
diff --git a/libs/cocos2d/CCActionInterval.m b/libs/cocos2d/CCActionInterval.m
new file mode 100644 (file)
index 0000000..ef374bf
--- /dev/null
@@ -0,0 +1,1354 @@
+/*
+ * cocos2d for iPhone: http://www.cocos2d-iphone.org
+ *
+ * Copyright (c) 2008-2011 Ricardo Quesada
+ * Copyright (c) 2011 Zynga Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+
+#import "CCActionInterval.h"
+#import "CCSprite.h"
+#import "CCSpriteFrame.h"
+#import "CCAnimation.h"
+#import "CCNode.h"
+#import "Support/CGPointExtension.h"
+
+//
+// IntervalAction
+//
+#pragma mark -
+#pragma mark IntervalAction
+@implementation CCActionInterval
+
+@synthesize elapsed = elapsed_;
+
+-(id) init
+{
+       NSAssert(NO, @"IntervalActionInit: Init not supported. Use InitWithDuration");
+       [self release];
+       return nil;
+}
+
++(id) actionWithDuration: (ccTime) d
+{
+       return [[[self alloc] initWithDuration:d ] autorelease];
+}
+
+-(id) initWithDuration: (ccTime) d
+{
+       if( (self=[super init]) ) {
+               duration_ = d;
+               
+               // prevent division by 0
+               // This comparison could be in step:, but it might decrease the performance
+               // by 3% in heavy based action games.
+               if( duration_ == 0 )
+                       duration_ = FLT_EPSILON;
+               elapsed_ = 0;
+               firstTick_ = YES;
+       }
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] ];
+       return copy;
+}
+
+- (BOOL) isDone
+{
+       return (elapsed_ >= duration_);
+}
+
+-(void) step: (ccTime) dt
+{
+       if( firstTick_ ) {
+               firstTick_ = NO;
+               elapsed_ = 0;
+       } else
+               elapsed_ += dt;
+
+       [self update: MIN(1, elapsed_/duration_)];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       elapsed_ = 0.0f;
+       firstTick_ = YES;
+}
+
+- (CCActionInterval*) reverse
+{
+       NSAssert(NO, @"CCIntervalAction: reverse not implemented.");
+       return nil;
+}
+@end
+
+//
+// Sequence
+//
+#pragma mark -
+#pragma mark Sequence
+@implementation CCSequence
++(id) actions: (CCFiniteTimeAction*) action1, ...
+{
+       va_list params;
+       va_start(params,action1);
+       
+       CCFiniteTimeAction *now;
+       CCFiniteTimeAction *prev = action1;
+       
+       while( action1 ) {
+               now = va_arg(params,CCFiniteTimeAction*);
+               if ( now )
+                       prev = [self actionOne: prev two: now];
+               else
+                       break;
+       }
+       va_end(params);
+       return prev;
+}
+
++(id) actionsWithArray: (NSArray*) actions
+{
+       CCFiniteTimeAction *prev = [actions objectAtIndex:0];
+       
+       for (int i = 1; i < [actions count]; i++)
+               prev = [self actionOne:prev two:[actions objectAtIndex:i]];
+       
+       return prev;
+}
+
++(id) actionOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two
+{      
+       return [[[self alloc] initOne:one two:two ] autorelease];
+}
+
+-(id) initOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two
+{
+       NSAssert( one!=nil && two!=nil, @"Sequence: arguments must be non-nil");
+       NSAssert( one!=actions_[0] && one!=actions_[1], @"Sequence: re-init using the same parameters is not supported");
+       NSAssert( two!=actions_[1] && two!=actions_[0], @"Sequence: re-init using the same parameters is not supported");
+               
+       ccTime d = [one duration] + [two duration];
+       
+       if( (self=[super initWithDuration: d]) ) {
+
+               // XXX: Supports re-init without leaking. Fails if one==one_ || two==two_
+               [actions_[0] release];
+               [actions_[1] release];
+
+               actions_[0] = [one retain];
+               actions_[1] = [two retain];
+       }
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone:zone] initOne:[[actions_[0] copy] autorelease] two:[[actions_[1] copy] autorelease] ];
+       return copy;
+}
+
+-(void) dealloc
+{
+       [actions_[0] release];
+       [actions_[1] release];
+       [super dealloc];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];        
+       split_ = [actions_[0] duration] / duration_;
+       last_ = -1;
+}
+
+-(void) stop
+{
+       [actions_[0] stop];
+       [actions_[1] stop];
+       [super stop];
+}
+
+-(void) update: (ccTime) t
+{
+       int found = 0;
+       ccTime new_t = 0.0f;
+       
+       if( t >= split_ ) {
+               found = 1;
+               if ( split_ == 1 )
+                       new_t = 1;
+               else
+                       new_t = (t-split_) / (1 - split_ );
+       } else {
+               found = 0;
+               if( split_ != 0 )
+                       new_t = t / split_;
+               else
+                       new_t = 1;
+       }
+       
+       if (last_ == -1 && found==1)    {
+               [actions_[0] startWithTarget:target_];
+               [actions_[0] update:1.0f];
+               [actions_[0] stop];
+       }
+
+       if (last_ != found ) {
+               if( last_ != -1 ) {
+                       [actions_[last_] update: 1.0f];
+                       [actions_[last_] stop];
+               }
+               [actions_[found] startWithTarget:target_];
+       }
+       [actions_[found] update: new_t];
+       last_ = found;
+}
+
+- (CCActionInterval *) reverse
+{
+       return [[self class] actionOne: [actions_[1] reverse] two: [actions_[0] reverse ] ];
+}
+@end
+
+//
+// Repeat
+//
+#pragma mark -
+#pragma mark CCRepeat
+@implementation CCRepeat
+@synthesize innerAction=innerAction_;
+
++(id) actionWithAction:(CCFiniteTimeAction*)action times:(NSUInteger)times
+{
+       return [[[self alloc] initWithAction:action times:times] autorelease];
+}
+
+-(id) initWithAction:(CCFiniteTimeAction*)action times:(NSUInteger)times
+{
+       ccTime d = [action duration] * times;
+
+       if( (self=[super initWithDuration: d ]) ) {
+               times_ = times;
+               self.innerAction = action;
+
+               total_ = 0;
+       }
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone:zone] initWithAction:[[innerAction_ copy] autorelease] times:times_];
+       return copy;
+}
+
+-(void) dealloc
+{
+       [innerAction_ release];
+       [super dealloc];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       total_ = 0;
+       [super startWithTarget:aTarget];
+       [innerAction_ startWithTarget:aTarget];
+}
+
+-(void) stop
+{    
+    [innerAction_ stop];
+       [super stop];
+}
+
+
+// issue #80. Instead of hooking step:, hook update: since it can be called by any 
+// container action like Repeat, Sequence, AccelDeccel, etc..
+-(void) update:(ccTime) dt
+{
+       ccTime t = dt * times_;
+       if( t > total_+1 ) {
+               [innerAction_ update:1.0f];
+               total_++;
+               [innerAction_ stop];
+               [innerAction_ startWithTarget:target_];
+               
+               // repeat is over ?
+               if( total_== times_ )
+                       // so, set it in the original position
+                       [innerAction_ update:0];
+               else {
+                       // no ? start next repeat with the right update
+                       // to prevent jerk (issue #390)
+                       [innerAction_ update: t-total_];
+               }
+
+       } else {
+               
+               float r = fmodf(t, 1.0f);
+               
+               // fix last repeat position
+               // else it could be 0.
+               if( dt== 1.0f) {
+                       r = 1.0f;
+                       total_++; // this is the added line
+               }
+               [innerAction_ update: MIN(r,1)];
+       }
+}
+
+-(BOOL) isDone
+{
+       return ( total_ == times_ );
+}
+
+- (CCActionInterval *) reverse
+{
+       return [[self class] actionWithAction:[innerAction_ reverse] times:times_];
+}
+@end
+
+//
+// Spawn
+//
+#pragma mark -
+#pragma mark Spawn
+
+@implementation CCSpawn
++(id) actions: (CCFiniteTimeAction*) action1, ...
+{
+       va_list params;
+       va_start(params,action1);
+       
+       CCFiniteTimeAction *now;
+       CCFiniteTimeAction *prev = action1;
+       
+       while( action1 ) {
+               now = va_arg(params,CCFiniteTimeAction*);
+               if ( now )
+                       prev = [self actionOne: prev two: now];
+               else
+                       break;
+       }
+       va_end(params);
+       return prev;
+}
+
++(id) actionsWithArray: (NSArray*) actions
+{
+       CCFiniteTimeAction *prev = [actions objectAtIndex:0];
+       
+       for (int i = 1; i < [actions count]; i++)
+               prev = [self actionOne:prev two:[actions objectAtIndex:i]];
+       
+       return prev;
+}
+
++(id) actionOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two
+{      
+       return [[[self alloc] initOne:one two:two ] autorelease];
+}
+
+-(id) initOne: (CCFiniteTimeAction*) one two: (CCFiniteTimeAction*) two
+{
+       NSAssert( one!=nil && two!=nil, @"Spawn: arguments must be non-nil");
+       NSAssert( one!=one_ && one!=two_, @"Spawn: reinit using same parameters is not supported");
+       NSAssert( two!=two_ && two!=one_, @"Spawn: reinit using same parameters is not supported");
+
+       ccTime d1 = [one duration];
+       ccTime d2 = [two duration];     
+       
+       [super initWithDuration: MAX(d1,d2)];
+
+       // XXX: Supports re-init without leaking. Fails if one==one_ || two==two_
+       [one_ release];
+       [two_ release];
+
+       one_ = one;
+       two_ = two;
+
+       if( d1 > d2 )
+               two_ = [CCSequence actionOne:two two:[CCDelayTime actionWithDuration: (d1-d2)] ];
+       else if( d1 < d2)
+               one_ = [CCSequence actionOne:one two: [CCDelayTime actionWithDuration: (d2-d1)] ];
+       
+       [one_ retain];
+       [two_ retain];
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initOne: [[one_ copy] autorelease] two: [[two_ copy] autorelease] ];
+       return copy;
+}
+
+-(void) dealloc
+{
+       [one_ release];
+       [two_ release];
+       [super dealloc];
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       [one_ startWithTarget:target_];
+       [two_ startWithTarget:target_];
+}
+
+-(void) stop
+{
+       [one_ stop];
+       [two_ stop];
+       [super stop];
+}
+
+-(void) update: (ccTime) t
+{
+       [one_ update:t];
+       [two_ update:t];
+}
+
+- (CCActionInterval *) reverse
+{
+       return [[self class] actionOne: [one_ reverse] two: [two_ reverse ] ];
+}
+@end
+
+//
+// RotateTo
+//
+#pragma mark -
+#pragma mark RotateTo
+
+@implementation CCRotateTo
++(id) actionWithDuration: (ccTime) t angle:(float) a
+{      
+       return [[[self alloc] initWithDuration:t angle:a ] autorelease];
+}
+
+-(id) initWithDuration: (ccTime) t angle:(float) a
+{
+       if( (self=[super initWithDuration: t]) )
+               dstAngle_ = a;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] angle:dstAngle_];
+       return copy;
+}
+
+-(void) startWithTarget:(CCNode *)aTarget
+{
+       [super startWithTarget:aTarget];
+       
+       startAngle_ = [target_ rotation];
+       if (startAngle_ > 0)
+               startAngle_ = fmodf(startAngle_, 360.0f);
+       else
+               startAngle_ = fmodf(startAngle_, -360.0f);
+       
+       diffAngle_ =dstAngle_ - startAngle_;
+       if (diffAngle_ > 180)
+               diffAngle_ -= 360;
+       if (diffAngle_ < -180)
+               diffAngle_ += 360;
+}
+-(void) update: (ccTime) t
+{
+       [target_ setRotation: startAngle_ + diffAngle_ * t];
+}
+@end
+
+
+//
+// RotateBy
+//
+#pragma mark -
+#pragma mark RotateBy
+
+@implementation CCRotateBy
++(id) actionWithDuration: (ccTime) t angle:(float) a
+{      
+       return [[[self alloc] initWithDuration:t angle:a ] autorelease];
+}
+
+-(id) initWithDuration: (ccTime) t angle:(float) a
+{
+       if( (self=[super initWithDuration: t]) )
+               angle_ = a;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] angle: angle_];
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       startAngle_ = [target_ rotation];
+}
+
+-(void) update: (ccTime) t
+{      
+       // XXX: shall I add % 360
+       [target_ setRotation: (startAngle_ +angle_ * t )];
+}
+
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithDuration:duration_ angle:-angle_];
+}
+
+@end
+
+//
+// MoveTo
+//
+#pragma mark -
+#pragma mark MoveTo
+
+@implementation CCMoveTo
++(id) actionWithDuration: (ccTime) t position: (CGPoint) p
+{      
+       return [[[self alloc] initWithDuration:t position:p ] autorelease];
+}
+
+-(id) initWithDuration: (ccTime) t position: (CGPoint) p
+{
+       if( (self=[super initWithDuration: t]) )
+               endPosition_ = p;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] position: endPosition_];
+       return copy;
+}
+
+-(void) startWithTarget:(CCNode *)aTarget
+{
+       [super startWithTarget:aTarget];
+       startPosition_ = [(CCNode*)target_ position];
+       delta_ = ccpSub( endPosition_, startPosition_ );
+}
+
+-(void) update: (ccTime) t
+{      
+       [target_ setPosition: ccp( (startPosition_.x + delta_.x * t ), (startPosition_.y + delta_.y * t ) )];
+}
+@end
+
+//
+// MoveBy
+//
+#pragma mark -
+#pragma mark MoveBy
+
+@implementation CCMoveBy
++(id) actionWithDuration: (ccTime) t position: (CGPoint) p
+{      
+       return [[[self alloc] initWithDuration:t position:p ] autorelease];
+}
+
+-(id) initWithDuration: (ccTime) t position: (CGPoint) p
+{
+       if( (self=[super initWithDuration: t]) )
+               delta_ = p;
+       
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration: [self duration] position: delta_];
+       return copy;
+}
+
+-(void) startWithTarget:(CCNode *)aTarget
+{
+       CGPoint dTmp = delta_;
+       [super startWithTarget:aTarget];
+       delta_ = dTmp;
+}
+
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithDuration:duration_ position:ccp( -delta_.x, -delta_.y)];
+}
+@end
+
+
+//
+// SkewTo
+//
+#pragma mark -
+#pragma mark SkewTo
+
+@implementation CCSkewTo
++(id) actionWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy 
+{
+       return [[[self alloc] initWithDuration: t skewX:sx skewY:sy] autorelease];
+}
+
+-(id) initWithDuration:(ccTime)t skewX:(float)sx skewY:(float)sy 
+{
+       if( (self=[super initWithDuration:t]) ) {       
+               endSkewX_ = sx;
+               endSkewY_ = sy;
+       }
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] skewX:endSkewX_ skewY:endSkewY_];
+       return copy;
+}
+
+-(void) startWithTarget:(CCNode *)aTarget
+{
+       [super startWithTarget:aTarget];
+       
+       startSkewX_ = [target_ skewX];
+       
+       if (startSkewX_ > 0)
+               startSkewX_ = fmodf(startSkewX_, 180.0f);
+       else
+               startSkewX_ = fmodf(startSkewX_, -180.0f);
+       
+       deltaX_ = endSkewX_ - startSkewX_;
+       
+       if ( deltaX_ > 180 ) {
+               deltaX_ -= 360;
+       }
+       if ( deltaX_ < -180 ) {
+               deltaX_ += 360;
+       }
+       
+       startSkewY_ = [target_ skewY];
+               
+       if (startSkewY_ > 0)
+               startSkewY_ = fmodf(startSkewY_, 360.0f);
+       else
+               startSkewY_ = fmodf(startSkewY_, -360.0f);
+       
+       deltaY_ = endSkewY_ - startSkewY_;
+       
+       if ( deltaY_ > 180 ) {
+               deltaY_ -= 360;
+       }
+       if ( deltaY_ < -180 ) {
+               deltaY_ += 360;
+       }
+}
+
+-(void) update: (ccTime) t
+{
+       [target_ setSkewX: (startSkewX_ + deltaX_ * t ) ];
+       [target_ setSkewY: (startSkewY_ + deltaY_ * t ) ];
+}
+
+@end
+
+//
+// CCSkewBy
+//
+@implementation CCSkewBy
+
+-(id) initWithDuration:(ccTime)t skewX:(float)deltaSkewX skewY:(float)deltaSkewY
+{
+       if( (self=[super initWithDuration:t skewX:deltaSkewX skewY:deltaSkewY]) ) {     
+               skewX_ = deltaSkewX;
+               skewY_ = deltaSkewY;
+       }
+       return self;
+}
+
+-(void) startWithTarget:(CCNode *)aTarget
+{
+       [super startWithTarget:aTarget];
+       deltaX_ = skewX_;
+       deltaY_ = skewY_;
+       endSkewX_ = startSkewX_ + deltaX_;
+       endSkewY_ = startSkewY_ + deltaY_;
+}
+
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithDuration:duration_ skewX:-skewX_ skewY:-skewY_];
+}
+@end
+
+
+//
+// JumpBy
+//
+#pragma mark -
+#pragma mark JumpBy
+
+@implementation CCJumpBy
++(id) actionWithDuration: (ccTime) t position: (CGPoint) pos height: (ccTime) h jumps:(NSUInteger)j
+{
+       return [[[self alloc] initWithDuration: t position: pos height: h jumps:j] autorelease];
+}
+
+-(id) initWithDuration: (ccTime) t position: (CGPoint) pos height: (ccTime) h jumps:(NSUInteger)j
+{
+       if( (self=[super initWithDuration:t]) ) {
+               delta_ = pos;
+               height_ = h;
+               jumps_ = j;
+       }
+       return self;
+}
+
+-(id) copyWithZone: (NSZone*) zone
+{
+       CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] position:delta_ height:height_ jumps:jumps_];
+       return copy;
+}
+
+-(void) startWithTarget:(id)aTarget
+{
+       [super startWithTarget:aTarget];
+       startPosition_ = [(CCNode*)target_ position];
+}
+
+-(void) update: (ccTime) t
+{
+       // Sin jump. Less realistic
+//     ccTime y = height * fabsf( sinf(t * (CGFloat)M_PI * jumps ) );
+//     y += delta.y * t;
+//     ccTime x = delta.x * t;
+//     [target setPosition: ccp( startPosition.x + x, startPosition.y + y )];  
+       
+       // parabolic jump (since v0.8.2)
+       ccTime frac = fmodf( t * jumps_, 1.0f );
+       ccTime y = height_ * 4 * frac * (1 - frac);
+       y += delta_.y * t;
+       ccTime x = delta_.x * t;
+       [target_ setPosition: ccp( startPosition_.x + x, startPosition_.y + y )];
+       
+}
+
+-(CCActionInterval*) reverse
+{
+       return [[self class] actionWithDuration:duration_ position: ccp(-delta_.x,-delta_.y) height:height_ jumps:jumps_];
+}
+@end
+
+//
+// JumpTo
+//
+#pragma mark -
+#pragma mark JumpTo
+
+@implementation CCJumpTo
+-(void) startWithTarget:(CCNode *)aTarget
+{
+       [super startWithTarget:aTarget];
+       delta_ = ccp( delta_.x - startPosition_.x, delta_.y - startPosition_.y );
+}
+@end
+
+
+#pragma mark -
+#pragma mark BezierBy
+
+// Bezier cubic formula:
+//     ((1 - t) + t)3 = 1 
+// Expands to… 
+//   (1 - t)3 + 3t(1-t)2 + 3t2(1 - t) + t3 = 1 
+static inline float bezierat( float a, float b, float c, float d, ccTime t )
+{
+       return (powf(1-t,3) * a + 
+                       3*t*(powf(1-t,2))*b + 
+                       3*powf(t,2)*(1-t)*c +
+                       powf(t,3)*d );
+}
+
+//
+// BezierBy
+//
+@implementation CCBezierBy
++(id) actionWithDuration: (ccTime) t bezier:(ccBezierConfig) c
+{  &nb