Configurable logging (LogTo).
authorJens Alfke <jens@mooseyard.com>
Thu Mar 20 09:05:58 2008 -0700 (2008-03-20)
changeset 1e55a17cdabd2
parent 0 d84d25d6cdbb
child 2 3d3dcc3116d5
Configurable logging (LogTo).
Added "my_" prefix to category method names.
Added MurmurHash.
Added UniqueWindowController.
Bug fixes.
Base64.h
Base64.m
GraphicsUtils.m
Logging.h
Logging.m
MurmurHash.c
MurmurHash.h
Target.m
Test.h
Test.m
TimeIntervalFormatter.h
TimeIntervalFormatter.m
UniqueWindowController.h
UniqueWindowController.m
     1.1 --- a/Base64.h	Sat Mar 08 21:04:41 2008 -0800
     1.2 +++ b/Base64.h	Thu Mar 20 09:05:58 2008 -0700
     1.3 @@ -9,15 +9,18 @@
     1.4  #import <Cocoa/Cocoa.h>
     1.5  
     1.6  
     1.7 -@interface NSData (Base64)
     1.8 +//NOTE: Using this requires linking against /usr/lib/libcrypto.dylib.
     1.9  
    1.10 -- (NSString *)base64String;
    1.11 -- (NSString *)base64StringWithNewlines:(BOOL)encodeWithNewlines;
    1.12  
    1.13 -- (NSData *)decodeBase64;
    1.14 -- (NSData *)decodeBase64WithNewLines:(BOOL)encodedWithNewlines;
    1.15 +@interface NSData (MYBase64)
    1.16  
    1.17 -- (NSString *)hexString;
    1.18 -- (NSString *)hexDump;
    1.19 +- (NSString *)my_base64String;
    1.20 +- (NSString *)my_base64StringWithNewlines:(BOOL)encodeWithNewlines;
    1.21 +
    1.22 +- (NSData *)my_decodeBase64;
    1.23 +- (NSData *)my_decodeBase64WithNewLines:(BOOL)encodedWithNewlines;
    1.24 +
    1.25 +- (NSString *)my_hexString;
    1.26 +- (NSString *)my_hexDump;
    1.27  
    1.28  @end
     2.1 --- a/Base64.m	Sat Mar 08 21:04:41 2008 -0800
     2.2 +++ b/Base64.m	Thu Mar 20 09:05:58 2008 -0700
     2.3 @@ -10,11 +10,13 @@
     2.4  //
     2.5  
     2.6  #import "Base64.h"
     2.7 +
     2.8 +//NOTE: Using this requires linking against /usr/lib/libcrypto.dylib.
     2.9  #import <openssl/bio.h>
    2.10  #import <openssl/evp.h>
    2.11  
    2.12  
    2.13 -@implementation NSData (Base64)
    2.14 +@implementation NSData (MYBase64)
    2.15  
    2.16  
    2.17  /**
    2.18 @@ -24,9 +26,9 @@
    2.19   * Code courtesy of DaveDribin (http://www.dribin.org/dave/)
    2.20   * Taken from http://www.cocoadev.com/index.pl?BaseSixtyFour
    2.21   **/
    2.22 -- (NSString *)base64String
    2.23 +- (NSString *)my_base64String
    2.24  {
    2.25 -    return [self base64StringWithNewlines: YES];
    2.26 +    return [self my_base64StringWithNewlines: YES];
    2.27  }
    2.28  
    2.29  /**
    2.30 @@ -36,7 +38,7 @@
    2.31   * Code courtesy of DaveDribin (http://www.dribin.org/dave/)
    2.32   * Taken from http://www.cocoadev.com/index.pl?BaseSixtyFour
    2.33   **/
    2.34 -- (NSString *)base64StringWithNewlines:(BOOL)encodeWithNewlines
    2.35 +- (NSString *)my_base64StringWithNewlines:(BOOL)encodeWithNewlines
    2.36  {
    2.37      // Create a memory buffer which will contain the Base64 encoded string
    2.38      BIO * mem = BIO_new(BIO_s_mem());
    2.39 @@ -61,12 +63,12 @@
    2.40      return base64String;
    2.41  }
    2.42  
    2.43 -- (NSData *)decodeBase64
    2.44 +- (NSData *)my_decodeBase64
    2.45  {
    2.46 -    return [self decodeBase64WithNewLines:YES];
    2.47 +    return [self my_decodeBase64WithNewLines:YES];
    2.48  }
    2.49  
    2.50 -- (NSData *)decodeBase64WithNewLines:(BOOL)encodedWithNewlines
    2.51 +- (NSData *)my_decodeBase64WithNewLines:(BOOL)encodedWithNewlines
    2.52  {
    2.53      // Create a memory buffer containing Base64 encoded string data
    2.54      BIO * mem = BIO_new_mem_buf((void *) [self bytes], [self length]);
    2.55 @@ -90,7 +92,7 @@
    2.56  }
    2.57  
    2.58  
    2.59 -- (NSString *)hexString
    2.60 +- (NSString *)my_hexString
    2.61  {
    2.62      const UInt8 *bytes = self.bytes;
    2.63      NSUInteger length = self.length;
    2.64 @@ -102,7 +104,7 @@
    2.65              autorelease];
    2.66  }
    2.67  
    2.68 -- (NSString *)hexDump
    2.69 +- (NSString *)my_hexDump
    2.70  {
    2.71      NSMutableString *ret=[NSMutableString stringWithCapacity:[self length]*2];
    2.72      /* dumps size bytes of *data to string. Looks like:
     3.1 --- a/GraphicsUtils.m	Sat Mar 08 21:04:41 2008 -0800
     3.2 +++ b/GraphicsUtils.m	Thu Mar 20 09:05:58 2008 -0700
     3.3 @@ -30,7 +30,7 @@
     3.4  - (NSImage*) my_shrunkToFitIn: (NSSize) maxSize
     3.5  {
     3.6      NSSize size = self.size;
     3.7 -    float scale = MIN( size.width/maxSize.width, size.height/maxSize.height );
     3.8 +    float scale = MIN( maxSize.width/size.width, maxSize.height/size.height );
     3.9      if( scale >= 1.0 )
    3.10          return self;
    3.11      
     4.1 --- a/Logging.h	Sat Mar 08 21:04:41 2008 -0800
     4.2 +++ b/Logging.h	Thu Mar 20 09:05:58 2008 -0700
     4.3 @@ -12,12 +12,23 @@
     4.4  NSString* LOC( NSString *key );     // Localized string lookup
     4.5  
     4.6  
     4.7 -#define Log(FMT,ARGS...) do{if(__builtin_expect(gShouldLog,0)) _Log(FMT,##ARGS);}while(0)
     4.8 +#define Log(FMT,ARGS...) do{if(__builtin_expect(_gShouldLog,0)) _Log(FMT,##ARGS);}while(0)
     4.9  #define Warn Warn
    4.10  
    4.11  void AlwaysLog( NSString *msg, ... ) __attribute__((format(__NSString__, 1, 2)));
    4.12  
    4.13 +#define LogTo(DOMAIN,FMT,ARGS...) do{if(__builtin_expect(_gShouldLog,0)) _LogTo(@""#DOMAIN,FMT,##ARGS);}while(0)
    4.14  
    4.15 -extern int gShouldLog;
    4.16 +BOOL _WillLogTo( NSString *domain );
    4.17 +BOOL EnableLog( BOOL enable );
    4.18 +#define EnableLogTo( DOMAIN, VALUE )  _EnableLogTo(@""#DOMAIN, VALUE)
    4.19 +#define WillLogTo( DOMAIN )  _WillLogTo(@""#DOMAIN)
    4.20 +
    4.21 +
    4.22 +// internals; don't use directly
    4.23 +extern int _gShouldLog;
    4.24  void _Log( NSString *msg, ... ) __attribute__((format(__NSString__, 1, 2)));
    4.25  void Warn( NSString *msg, ... ) __attribute__((format(__NSString__, 1, 2)));
    4.26 +void _LogTo( NSString *domain, NSString *msg, ... ) __attribute__((format(__NSString__, 2, 3)));
    4.27 +BOOL _WillLogTo( NSString *domain );
    4.28 +BOOL _EnableLogTo( NSString *domain, BOOL enable );
     5.1 --- a/Logging.m	Sat Mar 08 21:04:41 2008 -0800
     5.2 +++ b/Logging.m	Thu Mar 20 09:05:58 2008 -0700
     5.3 @@ -23,7 +23,9 @@
     5.4  }
     5.5  
     5.6  
     5.7 -int gShouldLog = -1;
     5.8 +int _gShouldLog = -1;
     5.9 +static BOOL sConsole;
    5.10 +static NSMutableSet *sEnabledDomains;
    5.11  
    5.12  
    5.13  static BOOL isConsole( int fd )
    5.14 @@ -37,9 +39,65 @@
    5.15  }
    5.16  
    5.17  
    5.18 -static void _Logv( NSString *msg, va_list args )
    5.19 +static void InitLogging()
    5.20  {
    5.21 -    if( isConsole(STDERR_FILENO) ) {
    5.22 +    if( _gShouldLog != -1 )
    5.23 +        return;
    5.24 +
    5.25 +    NSAutoreleasePool *pool = [NSAutoreleasePool new];
    5.26 +    _gShouldLog = NO;
    5.27 +    sEnabledDomains = [[NSMutableSet alloc] init];
    5.28 +    NSDictionary *dflts = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
    5.29 +    for( NSString *key in dflts ) {
    5.30 +        if( [key hasPrefix: @"Log"] ) {
    5.31 +            BOOL value = [[NSUserDefaults standardUserDefaults] boolForKey: key];
    5.32 +            if( key.length==3 )
    5.33 +                _gShouldLog = value;
    5.34 +            else if( value )
    5.35 +                [sEnabledDomains addObject: [key substringFromIndex: 3]];
    5.36 +        }
    5.37 +    }
    5.38 +    sConsole = isConsole(STDERR_FILENO);
    5.39 +    
    5.40 +    Log(@"Logging enabled in domains: {%@}", 
    5.41 +        [[[sEnabledDomains allObjects] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)] 
    5.42 +                componentsJoinedByString: @", "]);
    5.43 +    [pool drain];
    5.44 +}
    5.45 +
    5.46 +
    5.47 +BOOL EnableLog( BOOL enable )
    5.48 +{
    5.49 +    if( _gShouldLog == -1 )
    5.50 +        InitLogging();
    5.51 +    BOOL old = _gShouldLog;
    5.52 +    _gShouldLog = enable;
    5.53 +    return old;
    5.54 +}
    5.55 +
    5.56 +BOOL _WillLogTo( NSString *domain )
    5.57 +{
    5.58 +    if( _gShouldLog == -1 )
    5.59 +        InitLogging();
    5.60 +    return _gShouldLog && [sEnabledDomains containsObject: domain];
    5.61 +}
    5.62 +
    5.63 +BOOL _EnableLogTo( NSString *domain, BOOL enable )
    5.64 +{
    5.65 +    if( _gShouldLog == -1 )
    5.66 +        InitLogging();
    5.67 +    BOOL old = [sEnabledDomains containsObject: domain];
    5.68 +    if( enable )
    5.69 +        [sEnabledDomains addObject: domain];
    5.70 +    else
    5.71 +        [sEnabledDomains removeObject: domain];
    5.72 +    return old;
    5.73 +}
    5.74 +
    5.75 +
    5.76 +static void _Logv( NSString *prefix, NSString *msg, va_list args )
    5.77 +{
    5.78 +    if( sConsole ) {
    5.79          NSAutoreleasePool *pool = [NSAutoreleasePool new];
    5.80          static NSDateFormatter *sTimestampFormat;
    5.81          if( ! sTimestampFormat ) {
    5.82 @@ -49,8 +107,10 @@
    5.83          NSDate *now = [[NSDate alloc] init];
    5.84          NSString *timestamp = [sTimestampFormat stringFromDate: now];
    5.85          [now release];
    5.86 +        NSString *separator = prefix.length ?@": " :@"";
    5.87          msg = [[NSString alloc] initWithFormat: msg arguments: args];
    5.88 -        NSString *finalMsg = [[NSString alloc] initWithFormat: @"%@| %@\n", timestamp,msg];
    5.89 +        NSString *finalMsg = [[NSString alloc] initWithFormat: @"%@| %@%@%@\n", 
    5.90 +                              timestamp,prefix,separator,msg];
    5.91          fputs([finalMsg UTF8String], stderr);
    5.92          [finalMsg release];
    5.93          [msg release];
    5.94 @@ -64,32 +124,42 @@
    5.95  {
    5.96      va_list args;
    5.97      va_start(args,msg);
    5.98 -    _Logv(msg,args);
    5.99 +    _Logv(@"",msg,args);
   5.100      va_end(args);
   5.101  }
   5.102  
   5.103  
   5.104  void _Log( NSString *msg, ... )
   5.105  {
   5.106 -    if( gShouldLog == -1 )
   5.107 -        gShouldLog = [[NSUserDefaults standardUserDefaults] boolForKey: @"Log"];
   5.108 -    
   5.109 -    if( gShouldLog ) {
   5.110 +    if( _gShouldLog == -1 )
   5.111 +        InitLogging();
   5.112 +    if( _gShouldLog ) {
   5.113          va_list args;
   5.114          va_start(args,msg);
   5.115 -        _Logv(msg,args);
   5.116 +        _Logv(@"",msg,args);
   5.117          va_end(args);
   5.118      }
   5.119  }
   5.120  
   5.121  
   5.122 +void _LogTo( NSString *domain, NSString *msg, ... )
   5.123 +{
   5.124 +    if( _gShouldLog == -1 )
   5.125 +        InitLogging();
   5.126 +    if( _gShouldLog && [sEnabledDomains containsObject: domain] ) {
   5.127 +        va_list args;
   5.128 +        va_start(args,msg);
   5.129 +        _Logv(domain, msg, args);
   5.130 +        va_end(args);
   5.131 +    }
   5.132 +}
   5.133 +
   5.134 +
   5.135  void Warn( NSString *msg, ... )
   5.136  {
   5.137 -    msg = [@"WARNING*** " stringByAppendingString: msg];
   5.138 -    
   5.139      va_list args;
   5.140      va_start(args,msg);
   5.141 -    _Logv(msg,args);
   5.142 +    _Logv(@"WARNING*** ",msg,args);
   5.143      va_end(args);
   5.144  }
   5.145  
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/MurmurHash.c	Thu Mar 20 09:05:58 2008 -0700
     6.3 @@ -0,0 +1,80 @@
     6.4 +/*
     6.5 + *  MurmurHash.c
     6.6 + *  MYUtilities
     6.7 + *
     6.8 + *  This file created by Jens Alfke on 3/17/08.
     6.9 + *  Algorithm & source code by Austin Appleby, released to public domain.
    6.10 + *  <http://murmurhash.googlepages.com/>
    6.11 + *  Downloaded 3/16/2008.
    6.12 + *  Modified slightly by Jens Alfke (use standard uint32_t and size_t types;
    6.13 + *  change 'm' and 'r' to #defines for better C compatibility.)
    6.14 + *
    6.15 + */
    6.16 +
    6.17 +#include "MurmurHash.h"
    6.18 +
    6.19 +
    6.20 +//-----------------------------------------------------------------------------
    6.21 +// MurmurHash2, by Austin Appleby
    6.22 +
    6.23 +// Note - This code makes a few assumptions about how your machine behaves -
    6.24 +
    6.25 +// 1. We can read a 4-byte value from any address without crashing
    6.26 +// 2. sizeof(int) == 4      **Jens: I fixed this by changing 'unsigned int' to 'uint32_t'**
    6.27 +
    6.28 +// And it has a few limitations -
    6.29 +
    6.30 +// 1. It will not work incrementally.
    6.31 +// 2. It will not produce the same results on little-endian and big-endian
    6.32 +//    machines.
    6.33 +
    6.34 +uint32_t MurmurHash2 ( const void * key, size_t len, uint32_t seed )
    6.35 +{
    6.36 +    // 'm' and 'r' are mixing constants generated offline.
    6.37 +    // They're not really 'magic', they just happen to work well.
    6.38 +    
    6.39 +    #define m 0x5bd1e995
    6.40 +    #define r 24
    6.41 +    
    6.42 +    // Initialize the hash to a 'random' value
    6.43 +    
    6.44 +    uint32_t h = seed ^ len;
    6.45 +    
    6.46 +    // Mix 4 bytes at a time into the hash
    6.47 +    
    6.48 +    const unsigned char * data = (const unsigned char *)key;
    6.49 +    
    6.50 +    while(len >= 4)
    6.51 +    {
    6.52 +        uint32_t k = *(uint32_t *)data;
    6.53 +        
    6.54 +        k *= m; 
    6.55 +        k ^= k >> r; 
    6.56 +        k *= m; 
    6.57 +        
    6.58 +        h *= m; 
    6.59 +        h ^= k;
    6.60 +        
    6.61 +        data += 4;
    6.62 +        len -= 4;
    6.63 +    }
    6.64 +    
    6.65 +    // Handle the last few bytes of the input array
    6.66 +    
    6.67 +    switch(len)
    6.68 +    {
    6.69 +	case 3: h ^= data[2] << 16;
    6.70 +	case 2: h ^= data[1] << 8;
    6.71 +	case 1: h ^= data[0];
    6.72 +                h *= m;
    6.73 +    };
    6.74 +    
    6.75 +    // Do a few final mixes of the hash to ensure the last few
    6.76 +    // bytes are well-incorporated.
    6.77 +    
    6.78 +    h ^= h >> 13;
    6.79 +    h *= m;
    6.80 +    h ^= h >> 15;
    6.81 +    
    6.82 +    return h;
    6.83 +} 
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/MurmurHash.h	Thu Mar 20 09:05:58 2008 -0700
     7.3 @@ -0,0 +1,23 @@
     7.4 +/*
     7.5 + *  MurmurHash.h
     7.6 + *  MYUtilities
     7.7 + *
     7.8 + *  This file created by Jens Alfke on 3/17/08.
     7.9 + *  Algorithm & source code by Austin Appleby, released to public domain.
    7.10 + *  <http://murmurhash.googlepages.com/>
    7.11 + *
    7.12 + */
    7.13 +
    7.14 +#include <stdint.h>
    7.15 +#include <sys/types.h>
    7.16 +
    7.17 +/** An extremely efficient general-purpose hash function.
    7.18 +    Murmurhash is claimed to be more than twice as fast as the nearest competitor,
    7.19 +    and to offer better-distributed output with fewer collisions.
    7.20 +    It is, however not suitable for cryptographic use.
    7.21 +    Hash values will differ between bit- and little-endian CPUs, so they shouldn't
    7.22 +    be stored persistently or transmitted over the network.
    7.23 + 
    7.24 +    Written by Austin Appleby: <http://murmurhash.googlepages.com/> */
    7.25 +
    7.26 +uint32_t MurmurHash2 ( const void * key, size_t len, uint32_t seed );
     8.1 --- a/Target.m	Sat Mar 08 21:04:41 2008 -0800
     8.2 +++ b/Target.m	Thu Mar 20 09:05:58 2008 -0700
     8.3 @@ -28,26 +28,30 @@
     8.4  
     8.5  id $calltarget( NSInvocation *target, id param )
     8.6  {
     8.7 +    id result = nil;
     8.8      if( target && target.target ) {
     8.9 -        [target setArgument: &param atIndex: 2];
    8.10 -        [target invoke];
    8.11 -        
    8.12 -        NSMethodSignature *sig = target.methodSignature;
    8.13 -        NSUInteger returnLength = sig.methodReturnLength;
    8.14 -        if( returnLength==0 )
    8.15 -            return nil; // void
    8.16 -        else {
    8.17 -            const char *returnType = sig.methodReturnType;
    8.18 -            if( returnType[0]=='@' ) {
    8.19 -                id returnObject;
    8.20 -                [target getReturnValue: &returnObject];
    8.21 -                return returnObject;
    8.22 +        [target retain];
    8.23 +        @try{
    8.24 +            [target setArgument: &param atIndex: 2];
    8.25 +            [target invoke];
    8.26 +            
    8.27 +            NSMethodSignature *sig = target.methodSignature;
    8.28 +            NSUInteger returnLength = sig.methodReturnLength;
    8.29 +            if( returnLength==0 ) {
    8.30 +                result = nil; // void
    8.31              } else {
    8.32 -                UInt8 returnBuffer[returnLength];
    8.33 -                [target getReturnValue: &returnBuffer];
    8.34 -                return [NSValue valueWithBytes: &returnBuffer objCType: returnType];
    8.35 +                const char *returnType = sig.methodReturnType;
    8.36 +                if( returnType[0]=='@' ) {
    8.37 +                    [target getReturnValue: &result];
    8.38 +                } else {
    8.39 +                    UInt8 returnBuffer[returnLength];
    8.40 +                    [target getReturnValue: &returnBuffer];
    8.41 +                    result = [NSValue valueWithBytes: &returnBuffer objCType: returnType];
    8.42 +                }
    8.43              }
    8.44 +        }@finally{
    8.45 +            [target release];
    8.46          }
    8.47 -    } else
    8.48 -        return nil;
    8.49 +    }
    8.50 +    return result;
    8.51  }
     9.1 --- a/Test.h	Sat Mar 08 21:04:41 2008 -0800
     9.2 +++ b/Test.h	Thu Mar 20 09:05:58 2008 -0700
     9.3 @@ -17,8 +17,10 @@
     9.4      To run only tests without starting the main program, add the argument "Test_Only". */
     9.5  #if DEBUG
     9.6  void RunTestCases( int argc, const char **argv );
     9.7 +extern BOOL gRunningTestCase;
     9.8  #else
     9.9  #define RunTestCases(ARGC,ARGV)
    9.10 +#define gRunningTestCase NO
    9.11  #endif
    9.12  
    9.13  /** The TestCase() macro declares a test case.
    10.1 --- a/Test.m	Sat Mar 08 21:04:41 2008 -0800
    10.2 +++ b/Test.m	Thu Mar 20 09:05:58 2008 -0700
    10.3 @@ -11,11 +11,15 @@
    10.4  
    10.5  #if DEBUG
    10.6  
    10.7 +BOOL gRunningTestCase;
    10.8 +
    10.9  struct TestCaseLink *gAllTestCases;
   10.10  static int sPassed, sFailed;
   10.11  
   10.12  static BOOL RunTestCase( struct TestCaseLink *test )
   10.13  {
   10.14 +    BOOL oldLogging = EnableLog(YES);
   10.15 +    gRunningTestCase = YES;
   10.16      if( test->testptr ) {
   10.17          NSAutoreleasePool *pool = [NSAutoreleasePool new];
   10.18          Log(@"=== Testing %s ...",test->name);
   10.19 @@ -37,6 +41,8 @@
   10.20              test->testptr = NULL;       // prevents test from being run again
   10.21          }
   10.22      }
   10.23 +    gRunningTestCase = NO;
   10.24 +    EnableLog(oldLogging);
   10.25      return test->passed;
   10.26  }
   10.27  
   10.28 @@ -63,7 +69,6 @@
   10.29  
   10.30  void RunTestCases( int argc, const char **argv )
   10.31  {
   10.32 -    gShouldLog = YES;
   10.33      sPassed = sFailed = 0;
   10.34      BOOL stopAfterTests = NO;
   10.35      for( int i=1; i<argc; i++ ) {
    11.1 --- a/TimeIntervalFormatter.h	Sat Mar 08 21:04:41 2008 -0800
    11.2 +++ b/TimeIntervalFormatter.h	Thu Mar 20 09:05:58 2008 -0700
    11.3 @@ -16,4 +16,6 @@
    11.4  - (void) setShowsMinutes: (BOOL)showsMinutes;
    11.5  - (void) setShowsFractionalSeconds: (BOOL)showsFractionalSeconds;
    11.6  
    11.7 ++ (NSString*) formatTimeInterval: (NSTimeInterval)interval;
    11.8 +
    11.9  @end
    12.1 --- a/TimeIntervalFormatter.m	Sat Mar 08 21:04:41 2008 -0800
    12.2 +++ b/TimeIntervalFormatter.m	Thu Mar 20 09:05:58 2008 -0700
    12.3 @@ -11,6 +11,15 @@
    12.4  @implementation TimeIntervalFormatter
    12.5  
    12.6  
    12.7 +- (id) init
    12.8 +{
    12.9 +    self = [super init];
   12.10 +    if (self != nil) {
   12.11 +        _showsMinutes = YES;
   12.12 +    }
   12.13 +    return self;
   12.14 +}
   12.15 +
   12.16  - (void) awakeFromNib
   12.17  {
   12.18      _showsMinutes = YES;
   12.19 @@ -19,6 +28,14 @@
   12.20  - (void) setShowsMinutes: (BOOL)show                {_showsMinutes = show;}
   12.21  - (void) setShowsFractionalSeconds: (BOOL)show      {_showsFractionalSeconds = show;}
   12.22  
   12.23 ++ (NSString*) formatTimeInterval: (NSTimeInterval)interval
   12.24 +{
   12.25 +    TimeIntervalFormatter *fmt = [[self alloc] init];
   12.26 +    NSString *result = [fmt stringForObjectValue: [NSNumber numberWithDouble: interval]];
   12.27 +    [fmt release];
   12.28 +    return result;
   12.29 +}
   12.30 +
   12.31  
   12.32  - (NSString*) stringForObjectValue: (id)object
   12.33  {
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/UniqueWindowController.h	Thu Mar 20 09:05:58 2008 -0700
    13.3 @@ -0,0 +1,25 @@
    13.4 +//
    13.5 +//  UniqueWindowController.h
    13.6 +//  Cloudy
    13.7 +//
    13.8 +//  Created by Jens Alfke on 3/14/08.
    13.9 +//  Copyright 2008 __MyCompanyName__. All rights reserved.
   13.10 +//
   13.11 +
   13.12 +#import <Cocoa/Cocoa.h>
   13.13 +
   13.14 +
   13.15 +@interface UniqueWindowController : NSWindowController
   13.16 +
   13.17 ++ (UniqueWindowController*) instanceWith: (id)model;
   13.18 ++ (UniqueWindowController*) openWith: (id)model;
   13.19 +
   13.20 +@end
   13.21 +
   13.22 +
   13.23 +@interface UniqueWindowController (Abstract)
   13.24 +
   13.25 +- (id) initWith: (id)model;
   13.26 +@property (readonly) id model;
   13.27 +
   13.28 +@end
   13.29 \ No newline at end of file
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/UniqueWindowController.m	Thu Mar 20 09:05:58 2008 -0700
    14.3 @@ -0,0 +1,47 @@
    14.4 +//
    14.5 +//  UniqueWindowController.m
    14.6 +//  Cloudy
    14.7 +//
    14.8 +//  Created by Jens Alfke on 3/14/08.
    14.9 +//  Copyright 2008 __MyCompanyName__. All rights reserved.
   14.10 +//
   14.11 +
   14.12 +#import "UniqueWindowController.h"
   14.13 +
   14.14 +
   14.15 +@implementation UniqueWindowController
   14.16 +
   14.17 +
   14.18 ++ (UniqueWindowController*) instanceWith: (id)model
   14.19 +{
   14.20 +    for( NSWindow *window in [NSApp windows] ) {
   14.21 +        id delegate = window.delegate;
   14.22 +        if( window.isVisible && [delegate isKindOfClass: [self class]] ) {
   14.23 +            UniqueWindowController *c = delegate;
   14.24 +            if( c.model == model )
   14.25 +                return c;
   14.26 +        }
   14.27 +    }
   14.28 +    return nil;
   14.29 +}
   14.30 +
   14.31 +
   14.32 ++ (UniqueWindowController*) openWith: (id)model
   14.33 +{
   14.34 +    UniqueWindowController *w = [self instanceWith: model];
   14.35 +    if( ! w ) {
   14.36 +        w = [[self alloc] initWith: model];
   14.37 +        [w showWindow: self];
   14.38 +    }
   14.39 +    [w.window makeKeyAndOrderFront: self];
   14.40 +    return w;
   14.41 +}
   14.42 +
   14.43 +
   14.44 +- (void) windowWillClose: (NSNotification*)n
   14.45 +{
   14.46 +    [self autorelease];
   14.47 +}
   14.48 +
   14.49 +
   14.50 +@end