<shameless plug>
VideoBuffet, Fat Apps' QuickTime movie browser and player, is now available on the Mac App Store. Check it out, or buy it now.
<shameless plug>
@interface MainWindowController : NSWindowController
{
NSTimer* myTimer;
}
- (void) doSomeStuff;
@end
@implementation MainWindowController
- (id) initWithWindow:(NSWindow*)window
{
self = [super initWithWindow:window];
if( self ) {
const NSTimeInterval timerInterval = 10.0f;
myTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval
target:self
selector:@selector(doSomeStuff)
userInfo:nil
repeats:YES];
}
return self;
}
- (void) dealloc
{
[myTimer invalidate], myTimer = nil;
[super dealloc];
}
- (void) doSomeStuff
{
NSLog( @"doing stuff" );
}
@end
- (void) windowWillClose:(NSNotification*)notification
{
[myTimer invalidate], myTimer = nil;
}
Use the NSWindow makeFirstResponder: method, not this method, to make an object the first responder. Never invoke this method directly.
[self becomeFirstResponder]; // Nope, never do that.
[[self window] makeFirstResponder:self]; // That'll do, pig.
Notifies the receiver that it’s about to become first responder in its NSWindow.
The default implementation returns YES, accepting first responder status. Subclasses can override this method to update state or perform some action such as highlighting the selection, or to return NO, refusing first responder status.
<shameless plug>
For the next 48 hours, Fat Apps is giving away PuzzleTiles, PuzzleTiles HD, and PuzzleTiles/Mac for free. Grab 'em while you can!
</shameless plug>
@interface StandardOutputRedirector : NSObject { int redirectionPipe[2]; int oldStandardOutput; int oldStandardError; BOOL redirecting; NSMutableString* redirectedOutput; } - (void) startRedirecting; - (void) stopRedirecting; - (NSString*) output; - (void) clearOutput; @end @implementation StandardOutputRedirector enum { READ, WRITE }; #pragma mark - Memory Management - (id) init { if( (self = [super init]) ) { redirectedOutput = [[NSMutableString alloc] init]; if( pipe( redirectionPipe ) != -1 ) { oldStandardOutput = dup( fileno(stdout) ); oldStandardError = dup( fileno(stderr) ); } setbuf( stdout, NULL ); setbuf( stderr, NULL ); } return self; } - (void) dealloc { if( redirecting ) { [self stopRedirecting]; } if( oldStandardOutput > 0 ) { close( oldStandardOutput ); } if( oldStandardError > 0 ) { close( oldStandardError ); } if( redirectionPipe[READ] > 0 ) { close( redirectionPipe[READ] ); } if( redirectionPipe[WRITE] > 0 ) { close( redirectionPipe[WRITE] ); } [redirectedOutput release]; [super dealloc]; } #pragma mark - Public methods - (void) startRedirecting { if( redirecting ) return; dup2( redirectionPipe[WRITE], fileno(stdout) ); dup2( redirectionPipe[WRITE], fileno(stderr) ); redirecting = true; } - (void) stopRedirecting { if( !redirecting ) return; dup2( oldStandardOutput, fileno(stdout) ); dup2( oldStandardError, fileno(stderr) ); redirecting = false; } - (NSString*) output { const size_t bufferSize = 4096; char buffer[bufferSize]; fcntl( redirectionPipe[READ], F_SETFL, O_NONBLOCK ); ssize_t bytesRead = read( redirectionPipe[READ], buffer, bufferSize - 1 ); while( bytesRead > 0 ) { buffer[bytesRead] = 0; NSString* tempString = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; [redirectedOutput appendString:tempString]; bytesRead = read( redirectionPipe[READ], buffer, bufferSize ); } return [NSString stringWithFormat:@"%@", redirectedOutput]; } - (void) clearOutput { [redirectedOutput setString:@""]; } @end
class IOutputRedirector { public: IOutputRedirector() {} virtual ~IOutputRedirector(){} virtual void StartRedirecting() = 0; virtual void StopRedirecting() = 0; virtual std::string GetOutput() = 0; virtual void ClearOutput() = 0; };For our new class, we just need a couple of ints to hold our pipe file numbers, a couple of ints for the saved stdout/stderr file numbers, a bool to indicate that we’re redirecting, and a std::string which we redirect into.
class CStdoutRedirector : public IOutputRedirector { public: CStdoutRedirector(); virtual ~CStdoutRedirector(); virtual void StartRedirecting(); virtual void StopRedirecting(); virtual std::string GetOutput(); virtual void ClearOutput(); private: int _pipe[2]; int _oldStdOut; int _oldStdErr; bool _redirecting; std::string _redirectedOutput; };In the constructor we create a pipe, and call dup to copy the stdout and stderr file descriptors, so that we can restore them later. The setbuf calls are there to disable buffering. stdout is typically line-buffered whilst stderr is typically unbuffered, resulting in the combined output not being properly interleaved otherwise.
CStdoutRedirector::CStdoutRedirector() : IOutputRedirector() , _oldStdOut( 0 ) , _oldStdErr( 0 ) , _redirecting( false ) { _pipe[READ] = 0; _pipe[WRITE] = 0; if( pipe( _pipe ) != -1 ) { _oldStdOut = dup( fileno(stdout) ); _oldStdErr = dup( fileno(stderr) ); } setbuf( stdout, NULL ); setbuf( stderr, NULL ); }The destructor does some cleanup (stopping the redirection and closing files).
CStdoutRedirector::~CStdoutRedirector() { if( _redirecting ) { StopRedirecting(); } if( _oldStdOut > 0 ) { close( _oldStdOut ); } if( _oldStdErr > 0 ) { close( _oldStdErr ); } if( _pipe[READ] > 0 ) { close( _pipe[READ] ); } if( _pipe[WRITE] > 0 ) { close( _pipe[WRITE] ); } }StartRedirecting is where part of the magic is. We call dup2 to copy the stdout and stderr file descriptors to the write end of our pipe. This is how the redirection works: after the dup2 calls, any writes to stdout/stderr go instead to the write end of our pipe.
void CStdoutRedirector::StartRedirecting() { if( _redirecting ) return; dup2( _pipe[WRITE], fileno(stdout) ); dup2( _pipe[WRITE], fileno(stderr) ); _redirecting = true; }StopRedirecting copies the stdout and stderr file descriptors back to the ones we saved in the constructor, resulting in restoring stdout/stderr.
void CStdoutRedirector::StopRedirecting() { if( !_redirecting ) return; dup2( _oldStdOut, fileno(stdout) ); dup2( _oldStdErr, fileno(stderr) ); _redirecting = false; }GetOutput is where the rest of the magic lies. We call fcntl to set the read end of our pipe to nonblocking mode (otherwise the read call will block if the pipe is empty). Then we call read in a loop into a little temporary buffer, NULL terminate it, and append it to a string, which we then return.
string CStdoutRedirector::GetOutput() { const size_t bufSize = 4096; char buf[bufSize]; fcntl( _pipe[READ], F_SETFL, O_NONBLOCK ); ssize_t bytesRead = read( _pipe[READ], buf, bufSize - 1 ); while( bytesRead > 0 ) { buf[bytesRead] = 0; _redirectedOutput += buf; bytesRead = read( _pipe[READ], buf, bufSize ); } return _redirectedOutput; }ClearOutput simply clears out the string.
void CStdoutRedirector::ClearOutput() { _redirectedOutput.clear(); }There is one caveat to this method: if the internal buffer fills up, subsequent output (printf, for example) will block until space is freed up in the buffer. This buffer appears to be 16k in size, though I imagine it varies depending on OS. So basically, if you intend to redirect more than 16k at once. well... that won’t work. Calling GetOutput clears the buffer out, so call it often, if you can.
int RedirectorTest() { IOutputRedirector theRedirector; std::cout << "This is not redirected" << std::endl; theRedirector.StartRedirecting(); std::cout << "This is redirected"; assert( theRedirector.GetOutput() == "This is redirected" && "cout not redirected!" ); theRedirector.ClearOutput(); assert( theRedirector.GetOutput().length() == 0 && "redirector not cleared" ); std::cerr << "Redirected Error"; assert( theRedirector.GetOutput() == "Redirected Error" && "cerr not redirected" ); theRedirector.ClearOutput(); assert( theRedirector.GetOutput().length() == 0 && "redirector not cleared" ); theRedirector.StopRedirecting(); std::cout << "This is also not redirected"; assert( theRedirector.GetOutput().length() == 0 && "redirection not stopped!" ); theRedirector.StartRedirecting(); std::cout << "cout"; std::cerr << "cerr"; std::cout << "cout again"; std::string output = theRedirector.GetOutput(); assert( theRedirector.GetOutput() == "coutcerrcout again" && "cout, err not interleaved properly" ); return 0; }Based on that test I wrote the following interface I’d use for my test class:
class IOutputRedirector { public: IOutputRedirector() {} virtual ~IOutputRedirector(){} virtual void StartRedirecting() = 0; virtual void StopRedirecting() = 0; virtual std::string GetOutput() = 0; virtual void ClearOutput() = 0; };I Googled around for various techniques on how to do this, and the most promising one involved calling rdbuf on the cout and cerr streams. In this way, you can specify a different streambuf, and I use that of an ostringstream. It looks a little somethin’ like this:
class CCoutRedirector : public IOutputRedirector { public: CCoutRedirector(); virtual ~CCoutRedirector(); virtual void StartRedirecting(); virtual void StopRedirecting(); virtual std::string GetOutput(); virtual void ClearOutput(); private: std::streambuf* m_pOldCoutStreamBuf; std::streambuf* m_pOldCerrStreamBuf; std::ostringstream m_stream; }; CCoutRedirector::CCoutRedirector() : IOutputRedirector() , m_pOldCoutStreamBuf( NULL ) , m_pOldCerrStreamBuf( NULL ) { } CCoutRedirector::~CCoutRedirector() { StopRedirecting(); } void CCoutRedirector::StartRedirecting() { if( m_pOldCoutStreamBuf != NULL || m_pOldCerrStreamBuf != NULL ) { return; } m_pOldCoutStreamBuf = std::cout.rdbuf(); m_pOldCerrStreamBuf = std::cerr.rdbuf(); streambuf* newOutbuf = m_stream.rdbuf(); cout.rdbuf( newOutbuf ); cerr.rdbuf( newOutbuf ); } void CCoutRedirector::StopRedirecting() { if( m_pOldCoutStreamBuf != NULL ) { cout.rdbuf( m_pOldCoutStreamBuf ); m_pOldCoutStreamBuf = NULL; } if( m_pOldCerrStreamBuf != NULL ) { cerr.rdbuf( m_pOldCerrStreamBuf ); m_pOldCerrStreamBuf = NULL; } } string CCoutRedirector::GetOutput() { return m_stream.str(); } void CCoutRedirector::ClearOutput() { m_stream.str(""); }
const int MAX_ROWS = 8; const int MAX_COLUMNS = 8; @interface GameState : NSObject { // stuff tileGrid[MAX_ROWS][MAX_COLUMNS]; // more stuff } @end
// DebugLog is almost a drop-in replacement for NSLog // DebugLog(); // DebugLog(@“here”); // DebugLog(@“value: %d”, x); // Unfortunately this doesn’t work: DebugLog(aStringVariable); you have to do this instead: DebugLog(@“%@“, aStringVariable); // Shamelessly stolen (and edited) from http://iphoneincubator.com/blog/debugging/the-evolution-of-a-replacement-for-nslog #ifdef DEBUG #define DebugLog(fmt, ...) NSLog((@“%s:%d “ fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); #define DebugLogFunc() NSLog( @“%s:%d”, __PRETTY_FUNCTION__, __LINE__ ); #else #define DebugLog(...) #define DebugLogFunc() #endif // AlwaysLog always displays output regardless of the DEBUG setting #define AlwaysLog(fmt, ...) NSLog((@“%s:%d “ fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){ mainMenu = [[MainMenu alloc] initWithNibName: @"MainMenu-iPad" bundle:nil]; }else{ mainMenu = [[MainMenu alloc] initWithNibName: @"MainMenu" bundle:nil]; }
mainMenu = [[MainMenu alloc] initWithNibName: @"MainMenu" bundle:nil];
typedef void (^WillPresentBlock)(void); typedef void (^DidPresentBlock)(void); typedef void (^DidCancelBlock)(void); typedef void (^ClickedButtonBlock)(NSInteger); typedef void (^WillDismissBlock)(NSInteger); typedef void (^DidDismissBlock)(NSInteger);
@interface ZPAlertView : UIAlertView{ WillPresentBlock willPresentBlock; DidPresentBlock didPresentBlock; DidCancelBlock didCancelBlock; ClickedButtonBlock clickedButtonBlock; WillDismissBlock willDismissBlock; DidDismissBlock didDismissBlock; } @property (nonatomic, copy) WillPresentBlock willPresentBlock; @property (nonatomic, copy) DidPresentBlock didPresentBlock; @property (nonatomic, copy) DidCancelBlock didCancelBlock; @property (nonatomic, copy) ClickedButtonBlock clickedButtonBlock; @property (nonatomic, copy) WillDismissBlock willDismissBlock; @property (nonatomic, copy) DidDismissBlock didDismissBlock; @end
@synthesize willPresentBlock; @synthesize didPresentBlock; @synthesize didCancelBlock; @synthesize clickedButtonBlock; @synthesize willDismissBlock; @synthesize didDismissBlock;
- (void) dealloc { [willPresentBlock release]; [didPresentBlock release]; [didCancelBlock release]; [clickedButtonBlock release]; [willDismissBlock release]; [didDismissBlock release]; [super dealloc]; }
- (void) show { self.delegate = self; [super show]; }
- (void) willPresentAlertView:(UIAlertView *)alertView { if( willPresentBlock != nil ) { willPresentBlock(); } } - (void) didPresentAlertView:(UIAlertView *)alertView { if( didPresentBlock != nil ) { didPresentBlock(); } } - (void) alertViewCancel:(UIAlertView *)alertView { if( didCancelBlock != nil ) { didCancelBlock(); } } - (void) alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if( clickedButtonBlock != nil ) { clickedButtonBlock(buttonIndex); } } - (void) alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex { if( willDismissBlock != nil ) { willDismissBlock(buttonIndex); } } - (void) alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { if( didDismissBlock != nil ) { didDismissBlock(buttonIndex); } }
- (void) doLengthyOperationPrompt { enum { ButtonIndexCancel = 0, ButtonIndexDoItNow, ButtonIndexDoItLater }; ZPAlertView *anAlert = [[ZPAlertView alloc] initWithTitle:@"Warning!" message:@"Would you like to perform a really lengthy operation?" delegate:nil cancelButtonTitle:@"Nope" otherButtonTitles:@"Yeah, sure", @"Meh, maybe later", nil]; anAlert.didDismissBlock = ^(NSInteger buttonIndex){ switch( buttonIndex ) { case ButtonIndexDoItNow: [self performLengthyOperation]; break; case ButtonIndexDoItLater: [self scheduleLengthyOperationForLater]; break; } }; [anAlert show]; [anAlert release]; }
// UIImage+Resizing.h @interface UIImage (Resizing) - (UIImage*) scaledImageWithSize:(CGSize)newSize; @end // UIImage+Resizing.m #import "UIImage+Resizing.h" @implementation UIImage (Resizing) - (UIImage*) scaledImageWithSize:(CGSize)newSize { UIGraphicsBeginImageContextWithOptions( newSize, NO, 0.0 ); CGRect scaledImageRect = CGRectMake( 0.0, 0.0, newSize.width, newSize.height ); [self drawInRect:scaledImageRect]; UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return scaledImage; } @end