Index: releases/Pester/1.1b6/Source/BDAlias.h
===================================================================
--- releases/Pester/1.1b6/Source/BDAlias.h	(revision 365)
+++ releases/Pester/1.1b6/Source/BDAlias.h	(revision 365)
@@ -0,0 +1,77 @@
+/*
+    Copyright (c) 2001, bDistributed.com, Inc.
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or
+    without modification, are permitted provided that the following
+    conditions are met:
+
+    *   Redistributions of source code must retain the above
+        copyright notice, this list of conditions and the following
+        disclaimer.
+    
+    *   Redistributions in binary form must reproduce the above
+        copyright notice, this list of conditions and the following
+        disclaimer in the documentation and/or other materials
+        provided with the distribution.
+    
+    *   Neither the name of bDistributed.com, Inc. nor the names of
+        its contributors may be used to endorse or promote products
+        derived from this software without specific prior written
+        permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+    CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+    OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+    OF SUCH DAMAGE.
+*/
+
+#import <Foundation/Foundation.h>
+#import <CoreServices/CoreServices.h>
+
+@interface BDAlias : NSObject
+{
+	AliasHandle _alias;
+}
+
+- (id)initWithAliasHandle:(AliasHandle)alias; // designated initializer
+- (id)initWithData:(NSData *)data;
+- (id)initWithPath:(NSString *)fullPath;
+- (id)initWithPath:(NSString *)path relativeToPath:(NSString *)relPath;
+- (id)initWithFSRef:(FSRef *)ref;
+- (id)initWithFSRef:(FSRef *)ref relativeToFSRef:(FSRef *)relRef;
+
+- (void)dealloc;
+
+- (AliasHandle)alias;
+- (void)setAlias:(AliasHandle)newAlias;
+
+- (NSData *)aliasData;
+- (void)setAliasData:(NSData *)newAliasData;
+
+- (BOOL)aliasIsEqual:(AliasHandle)otherAlias;
+- (BOOL)aliasDataIsEqual:(NSData *)data;
+- (BOOL)isEqual:(id)object;
+
+- (NSString *)displayNameWithKindString:(NSString **)outKindString;
+
+- (NSString *)fullPath;
+- (NSString *)fullPathRelativeToPath:(NSString *)relPath;
+
++ (BDAlias *)aliasWithAliasHandle:(AliasHandle)alias;
++ (BDAlias *)aliasWithData:(NSData *)data;
++ (BDAlias *)aliasWithPath:(NSString *)fullPath;
++ (BDAlias *)aliasWithPath:(NSString *)path relativeToPath:(NSString *)relPath;
++ (BDAlias *)aliasWithFSRef:(FSRef *)ref;
++ (BDAlias *)aliasWithFSRef:(FSRef *)ref relativeToFSRef:(FSRef *)relRef;
+
+@end
Index: releases/Pester/1.1b6/Source/BDAlias.m
===================================================================
--- releases/Pester/1.1b6/Source/BDAlias.m	(revision 365)
+++ releases/Pester/1.1b6/Source/BDAlias.m	(revision 365)
@@ -0,0 +1,368 @@
+/*
+    Copyright (c) 2001, bDistributed.com, Inc.
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or
+    without modification, are permitted provided that the following
+    conditions are met:
+
+    *   Redistributions of source code must retain the above
+        copyright notice, this list of conditions and the following
+        disclaimer.
+    
+    *   Redistributions in binary form must reproduce the above
+        copyright notice, this list of conditions and the following
+        disclaimer in the documentation and/or other materials
+        provided with the distribution.
+    
+    *   Neither the name of bDistributed.com, Inc. nor the names of
+        its contributors may be used to endorse or promote products
+        derived from this software without specific prior written
+        permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+    CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+    OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+    OF SUCH DAMAGE.
+*/
+
+#include <assert.h>
+
+#import "BDAlias.h"
+
+
+static Handle DataToHandle(CFDataRef inData);
+static CFDataRef HandleToData(Handle inHandle);
+
+static OSStatus PathToFSRef(CFStringRef inPath, FSRef *outRef);
+static CFStringRef FSRefToPathCopy(const FSRef *inRef);
+
+
+static Handle DataToHandle(CFDataRef inData)
+{
+    CFIndex	len;
+    Handle	handle = NULL;
+    
+    if (inData == NULL) {
+        return NULL;
+    }
+    
+    len = CFDataGetLength(inData);
+    
+    handle = NewHandle(len);
+    
+    if ((handle != NULL) && (len > 0)) {
+        HLock(handle);
+        BlockMoveData(CFDataGetBytePtr(inData), *handle, len);
+        HUnlock(handle);
+    }
+    
+    return handle;
+}
+
+static CFDataRef HandleToData(Handle inHandle)
+{
+    CFDataRef	data = NULL;
+    CFIndex	len;
+    SInt8	handleState;
+    
+    if (inHandle == NULL) {
+        return NULL;
+    }
+    
+    len = GetHandleSize(inHandle);
+    
+    handleState = HGetState(inHandle);
+    
+    HLock(inHandle);
+    
+    data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *) *inHandle, len);
+    
+    HSetState(inHandle, handleState);
+    
+    return data;
+}
+
+static OSStatus PathToFSRef(CFStringRef inPath, FSRef *outRef)
+{
+    CFURLRef	tempURL = NULL;
+    Boolean	gotRef = false;
+    
+    tempURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inPath,
+                                            kCFURLPOSIXPathStyle, false);
+    
+    if (tempURL == NULL) {
+        return fnfErr;
+    }
+    
+    gotRef = CFURLGetFSRef(tempURL, outRef);
+    
+    if (gotRef == false) {
+        return fnfErr;
+    }
+    
+    return noErr;
+}
+
+static CFStringRef FSRefToPathCopy(const FSRef *inRef)
+{
+    CFURLRef	tempURL = NULL;
+    CFStringRef	result = NULL;
+    
+    if (inRef != NULL) {
+        tempURL = CFURLCreateFromFSRef(kCFAllocatorDefault, inRef);
+        
+        if (tempURL == NULL) {
+            return NULL;
+        }
+        
+        result = CFURLCopyFileSystemPath(tempURL, kCFURLPOSIXPathStyle);
+        
+        CFRelease(tempURL);
+    }
+    
+    return result;
+}
+
+
+@implementation BDAlias
+
+- (id)initWithAliasHandle:(AliasHandle)alias
+{
+    id ret = [super init];
+    
+    if (ret != nil) {
+        _alias = alias;
+    }
+    
+    return ret;
+}
+
+- (id)initWithData:(NSData *)data
+{
+    return [self initWithAliasHandle:(AliasHandle)DataToHandle((CFDataRef) data)];
+}
+
+- (id)initWithPath:(NSString *)fullPath
+{
+    OSStatus	anErr = noErr;
+    FSRef		ref;
+    
+    anErr = PathToFSRef((CFStringRef) fullPath, &ref);
+    
+    if (anErr != noErr) {
+        return nil;
+    }
+    
+    return [self initWithFSRef:&ref];;
+}
+
+- (id)initWithPath:(NSString *)path relativeToPath:(NSString *)relPath
+{
+    OSStatus	anErr = noErr;
+    FSRef		ref, relRef;
+    
+    anErr = PathToFSRef((CFStringRef) [relPath stringByAppendingPathComponent:path],
+                        &ref);
+    
+    if (anErr != noErr) {
+        return nil;
+    }
+    
+    anErr = PathToFSRef((CFStringRef) relPath, &relRef);
+    
+    if (anErr != noErr) {
+        return nil;
+    }
+    
+    return [self initWithFSRef:&ref relativeToFSRef:&relRef];
+}
+
+- (id)initWithFSRef:(FSRef *)ref
+{
+    return [self initWithFSRef:ref relativeToFSRef:NULL];
+}
+
+- (id)initWithFSRef:(FSRef *)ref relativeToFSRef:(FSRef *)relRef
+{
+    OSStatus	anErr = noErr;
+    AliasHandle	alias = NULL;
+    
+    anErr = FSNewAlias(relRef, ref, &alias);
+    
+    if (anErr != noErr) {
+        return nil;
+    }
+    
+    return [self initWithAliasHandle:alias];
+}
+
+- (void)dealloc
+{
+    if (_alias != NULL) {
+        DisposeHandle((Handle) _alias);
+        _alias = NULL;
+    }
+    
+    [super dealloc];
+}
+
+- (AliasHandle)alias
+{
+    return _alias;
+}
+
+- (void)setAlias:(AliasHandle)newAlias
+{
+    if (_alias != NULL) {
+        DisposeHandle((Handle) _alias);
+    }
+    
+    _alias = newAlias;
+}
+
+- (NSData *)aliasData
+{
+    NSData *result;
+    
+    result = (NSData *)HandleToData((Handle) _alias);
+    
+    return [result autorelease];
+}
+
+- (void)setAliasData:(NSData *)newAliasData
+{
+    [self setAlias:(AliasHandle) DataToHandle((CFDataRef) newAliasData)];
+}
+
+- (NSString *)displayNameWithKindString:(NSString **)outKindString;
+{
+    AliasHandle alias = [self alias];
+    FSRef ref;
+    Boolean wasChanged;
+    CFStringRef name;
+
+    if (alias == NULL) return nil;
+    if (FSResolveAlias(NULL, alias, &ref, &wasChanged) != noErr) return nil;
+
+    if (LSCopyDisplayNameForRef(&ref, &name) != noErr) return nil;
+    [(NSString *)name autorelease];
+
+    if (outKindString != NULL) {
+        if (LSCopyKindStringForRef(&ref, (CFStringRef *)outKindString) != noErr) return nil;
+        [*outKindString autorelease];
+    }
+
+    return (NSString *)name;
+}
+
+- (NSString *)fullPath
+{
+    return [self fullPathRelativeToPath:nil];
+}
+
+- (NSString *)fullPathRelativeToPath:(NSString *)relPath
+{
+    OSStatus	anErr = noErr;
+    FSRef	relPathRef;
+    FSRef	tempRef;
+    NSString	*result = nil;
+    Boolean	wasChanged;
+    
+    if (_alias != NULL) {
+        if (relPath != nil) {
+            anErr = PathToFSRef((CFStringRef)relPath, &relPathRef);
+            
+            if (anErr != noErr) {
+                return NULL;
+            }
+            
+            anErr = FSResolveAlias(&relPathRef, _alias, &tempRef, &wasChanged);
+        } else {
+            anErr = FSResolveAlias(NULL, _alias, &tempRef, &wasChanged);
+        }
+        
+        if (anErr != noErr) {
+            return NULL;
+        }
+        
+        result = (NSString *)FSRefToPathCopy(&tempRef);
+    }
+    
+    return [result autorelease];
+}
+
+- (BOOL)aliasIsEqual:(AliasHandle)otherAlias;
+{
+    AliasHandle alias = [self alias];
+    FSRef ref, otherRef;
+    Boolean wasChanged;
+
+    if (alias == otherAlias) return YES;
+    if (alias == NULL || otherAlias == NULL) return NO;
+    if (FSResolveAlias(NULL, alias, &ref, &wasChanged) != noErr) return NO;
+    if (FSResolveAlias(NULL, otherAlias, &otherRef, &wasChanged) != noErr) return NO;
+    return (FSCompareFSRefs(&ref, &otherRef) == noErr);
+}
+
+- (BOOL)aliasDataIsEqual:(NSData *)data;
+{
+    AliasHandle otherAlias;
+    const UInt8 *aliasPtr = CFDataGetBytePtr((CFDataRef)data);
+    BOOL result;
+    if (aliasPtr == NULL) {
+        otherAlias = (AliasHandle)DataToHandle((CFDataRef)data);
+    } else {
+        otherAlias = (AliasHandle)&aliasPtr;
+    }
+    result = [self aliasIsEqual: otherAlias];
+    if (aliasPtr == NULL) DisposeHandle((Handle)otherAlias);
+    return result;
+}
+
+- (BOOL)isEqual:(id)object;
+{
+    if (![object isKindOfClass: [BDAlias class]]) return NO;
+    return [self aliasIsEqual: [object alias]];
+}
+
++ (BDAlias *)aliasWithAliasHandle:(AliasHandle)alias
+{
+    return [[[BDAlias alloc] initWithAliasHandle:alias] autorelease];
+}
+
++ (BDAlias *)aliasWithData:(NSData *)data
+{
+    return [[[BDAlias alloc] initWithData:data] autorelease];
+}
+
++ (BDAlias *)aliasWithPath:(NSString *)fullPath
+{
+    return [[[BDAlias alloc] initWithPath:fullPath] autorelease];
+}
+
++ (BDAlias *)aliasWithPath:(NSString *)path relativeToPath:(NSString *)relPath
+{
+    return [[[BDAlias alloc] initWithPath:path relativeToPath:relPath] autorelease];
+}
+
++ (BDAlias *)aliasWithFSRef:(FSRef *)ref
+{
+    return [[[BDAlias alloc] initWithFSRef:ref] autorelease];
+}
+
++ (BDAlias *)aliasWithFSRef:(FSRef *)ref relativeToFSRef:(FSRef *)relRef
+{
+    return [[[BDAlias alloc] initWithFSRef:ref relativeToFSRef:relRef] autorelease];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/English.lproj/Alarms.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Alarms.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Alarms.nib/classes.nib	(revision 365)
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>remove</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>PSAlarmsController</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>alarmList</key>
+				<string>NSTableView</string>
+				<key>removeButton</key>
+				<string>NSButton</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSWindowController</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRTableDelegate</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>tableView</key>
+				<string>NJRTableView</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSObject</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRScrollView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSScrollView</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRTableView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>typeSelectDisplay</key>
+				<string>id</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSTableView</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSTableView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSControl</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Alarms.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Alarms.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Alarms.nib/info.nib	(revision 365)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../../Pester.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>9</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9B18</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Calendar.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Calendar.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Calendar.nib/classes.nib	(revision 365)
@@ -0,0 +1,21 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = NJROffCenterPanel; LANGUAGE = ObjC; SUPERCLASS = NSPanel; }, 
+        {CLASS = NSObject; LANGUAGE = ObjC; }, 
+        {
+            ACTIONS = {nextMonth = id; nextYear = id; previousMonth = id; previousYear = id; }; 
+            CLASS = OACalendarView; 
+            LANGUAGE = ObjC; 
+            SUPERCLASS = NSControl; 
+        }, 
+        {
+            ACTIONS = {cancel = id; close = id; today = id; }; 
+            CLASS = PSCalendarController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {calendarView = OACalendarView; delegate = id; okButton = NSButton; }; 
+            SUPERCLASS = NSWindowController; 
+        }
+    ); 
+    IBVersion = 1; 
+}
Index: releases/Pester/1.1b6/Source/English.lproj/Calendar.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Calendar.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Calendar.nib/info.nib	(revision 365)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>57 27 356 240 0 0 1280 832 </string>
+	<key>IBFramework Version</key>
+	<string>291.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>5</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6I32</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Credits.html
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Credits.html	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Credits.html	(revision 365)
@@ -0,0 +1,3 @@
+<font face="Lucida Grande" size="-1"><b>Icon by</b> <a href="mailto:excellence@global.co.za">Diederik Vemer</a>.</font><br>
+<font face="Lucida Grande" size="-1"><b>Thanks to</b> <a href="mailto:ryanwilcox@mac.com">Ryan Wilcox</a>, <a href="mailto:bhines@alumni.ucsd.edu">Ben Hines</a>,<br>
+<a href="mailto:fdj@elgato.com">Florian Dejako</a> and <a href="mailto:qdc@eckysoft.com">Quentin D. Carnicelli</a>.</font>
Index: releases/Pester/1.1b6/Source/English.lproj/MainMenu.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/MainMenu.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/MainMenu.nib/classes.nib	(revision 365)
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>stopVoicePreview</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NJRVoicePopUpButton</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSPopUpButton</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSDisclosureButtonCell</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSButtonCell</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>dateCompleted</key>
+				<string>NSPopUpButton</string>
+				<key>doScriptChanged</key>
+				<string>id</string>
+				<key>doSpeakChanged</key>
+				<string>id</string>
+				<key>editAlertChanged</key>
+				<string>id</string>
+				<key>inAtChanged</key>
+				<string>id</string>
+				<key>playSoundChanged</key>
+				<string>id</string>
+				<key>setAlarm</key>
+				<string>NSButton</string>
+				<key>setSoundRepetitionCount</key>
+				<string>id</string>
+				<key>showCalendar</key>
+				<string>NSButton</string>
+				<key>showVolume</key>
+				<string>NSButton</string>
+				<key>silence</key>
+				<string>id</string>
+				<key>toggleAlertEditor</key>
+				<string>id</string>
+				<key>update</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>PSAlarmSetController</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>alertTabs</key>
+				<string>NSTabView</string>
+				<key>alertView</key>
+				<string>NSTextField</string>
+				<key>bounceDockIcon</key>
+				<string>NSButton</string>
+				<key>cancelButton</key>
+				<string>NSButton</string>
+				<key>displayMessage</key>
+				<string>NSButtonCell</string>
+				<key>doScript</key>
+				<string>NSButtonCell</string>
+				<key>doSpeak</key>
+				<string>NSButtonCell</string>
+				<key>editAlert</key>
+				<string>NSButtonCell</string>
+				<key>inAtMatrix</key>
+				<string>NSMatrix</string>
+				<key>messageField</key>
+				<string>NSTextField</string>
+				<key>playSound</key>
+				<string>NSButtonCell</string>
+				<key>removeMessageButton</key>
+				<string>NSButton</string>
+				<key>script</key>
+				<string>NJRFSObjectSelector</string>
+				<key>scriptSelectButton</key>
+				<string>NSButton</string>
+				<key>setButton</key>
+				<string>NSButton</string>
+				<key>sound</key>
+				<string>NJRQTMediaPopUpButton</string>
+				<key>soundRepetitionStepper</key>
+				<string>NSStepper</string>
+				<key>soundRepetitions</key>
+				<string>NJRValidatingField</string>
+				<key>soundRepetitionsLabel</key>
+				<string>NSTextField</string>
+				<key>soundVolumeButton</key>
+				<string>NSButton</string>
+				<key>timeCalendarButton</key>
+				<string>NSButton</string>
+				<key>timeDate</key>
+				<string>NSTextField</string>
+				<key>timeDateCompletions</key>
+				<string>NSPopUpButton</string>
+				<key>timeInterval</key>
+				<string>NJRIntervalField</string>
+				<key>timeIntervalRepeats</key>
+				<string>NSButton</string>
+				<key>timeIntervalUnits</key>
+				<string>NSPopUpButton</string>
+				<key>timeOfDay</key>
+				<string>NSTextField</string>
+				<key>timeSummary</key>
+				<string>NSTextField</string>
+				<key>voice</key>
+				<string>NJRVoicePopUpButton</string>
+				<key>wakeUp</key>
+				<string>NSButtonCell</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSWindowController</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>clearAllEntries</key>
+				<string>id</string>
+				<key>removeEntry</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NJRHistoryTrackingComboBox</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSComboBox</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSMenuItem</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>stopSoundPreview</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NJRQTMediaPopUpButton</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>preview</key>
+				<string>NSMovieView</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSPopUpButton</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSObject</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRUnfocusableMovieView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSMovieView</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRIntervalField</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>intervalUnits</key>
+				<string>NSPopUpButton</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NJRValidatingField</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRValidatingField</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSTextField</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>checkForUpdates</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>SUUpdater</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>orderFrontAlarmsPanel</key>
+				<string>id</string>
+				<key>orderFrontPreferencesPanel</key>
+				<string>id</string>
+				<key>orderFrontSetAlarmPanel</key>
+				<string>id</string>
+				<key>stopAlerts</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>PSApplication</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>alarmSetController</key>
+				<string>PSAlarmSetController</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSApplication</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRIntegerFilter</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSNumberFormatter</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>select</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NJRFSObjectSelector</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSButton</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/MainMenu.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/MainMenu.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/MainMenu.nib/info.nib	(revision 365)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../../Pester.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>507</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9B18</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Movie alert.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Movie alert.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Movie alert.nib/classes.nib	(revision 365)
@@ -0,0 +1,15 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = NJRCenteringMovieView; LANGUAGE = ObjC; SUPERCLASS = NSMovieView; }, 
+        {
+            ACTIONS = {close = id; }; 
+            CLASS = PSMovieAlertController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {movieView = NSMovieView; }; 
+            SUPERCLASS = NSWindowController; 
+        }, 
+        {CLASS = "java.lang.Object"; LANGUAGE = Java; }
+    ); 
+    IBVersion = 1; 
+}
Index: releases/Pester/1.1b6/Source/English.lproj/Movie alert.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Movie alert.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Movie alert.nib/info.nib	(revision 365)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>36 16 356 240 0 0 1280 832 </string>
+	<key>IBFramework Version</key>
+	<string>291.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>10</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6G30</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Notifier.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Notifier.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Notifier.nib/classes.nib	(revision 365)
@@ -0,0 +1,34 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = NJRIntegerFilter; LANGUAGE = ObjC; SUPERCLASS = NSNumberFormatter; }, 
+        {
+            CLASS = NJRIntervalField; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {intervalUnits = NSPopUpButton; }; 
+            SUPERCLASS = NSTextField; 
+        }, 
+        {
+            ACTIONS = {
+                close = id; 
+                snooze = id; 
+                snoozeIntervalUnitsChanged = id; 
+                snoozeUntil = id; 
+                stopRepeating = id; 
+            }; 
+            CLASS = PSAlarmNotifierController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                dateField = NSTextField; 
+                intervalField = NSTextField; 
+                messageField = NSTextField; 
+                nextDateField = NSTextField; 
+                okButton = NSButton; 
+                snoozeButton = NSButton; 
+                snoozeIntervalField = NJRIntervalField; 
+            }; 
+            SUPERCLASS = NSWindowController; 
+        }
+    ); 
+    IBVersion = 1; 
+}
Index: releases/Pester/1.1b6/Source/English.lproj/Notifier.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Notifier.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Notifier.nib/info.nib	(revision 365)
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>5 6 356 240 0 0 1280 832 </string>
+	<key>IBFramework Version</key>
+	<string>291.0</string>
+	<key>IBGroupedObjects</key>
+	<dict>
+		<key>1</key>
+		<array>
+			<string>64</string>
+			<string>80</string>
+		</array>
+		<key>2</key>
+		<array>
+			<string>66</string>
+			<string>79</string>
+			<string>82</string>
+		</array>
+	</dict>
+	<key>IBLastGroupID</key>
+	<string>3</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>5</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6I32</string>
+	<key>IBUserGuides</key>
+	<dict>
+		<key>0</key>
+		<dict>
+			<key>guideLocations</key>
+			<array>
+				<string>Horizontal:64.000000</string>
+			</array>
+			<key>guidesLocked</key>
+			<false/>
+		</dict>
+		<key>5</key>
+		<dict>
+			<key>guideLocations</key>
+			<array>
+				<string>Horizontal:87.000000</string>
+				<string>Horizontal:108.000000</string>
+				<string>Horizontal:66.000000</string>
+				<string>Vertical:92.000000</string>
+				<string>Horizontal:133.000000</string>
+			</array>
+			<key>guidesLocked</key>
+			<false/>
+		</dict>
+	</dict>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Preferences.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Preferences.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Preferences.nib/classes.nib	(revision 365)
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>clear</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NJRHotKeyField</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSTextField</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSObject</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>hotKeySet</key>
+				<string>NJRHotKeyField</string>
+			</dict>
+			<key>CLASS</key>
+			<string>PSPreferencesController</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>setAlarmHotKey</key>
+				<string>NJRHotKeyField</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSWindowController</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Preferences.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Preferences.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Preferences.nib/info.nib	(revision 365)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../../Pester.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>6</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9B18</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Read Me.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Read Me.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Read Me.nib/classes.nib	(revision 365)
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSObject</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>contentsClicked</key>
+				<string>NSTableView</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NJRReadMeController</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>body</key>
+				<string>NSTextView</string>
+				<key>bodyBox</key>
+				<string>NSBox</string>
+				<key>contents</key>
+				<string>NSTableView</string>
+				<key>contentsBox</key>
+				<string>NSBox</string>
+				<key>progress</key>
+				<string>NSProgressIndicator</string>
+				<key>progressTabs</key>
+				<string>NSTabView</string>
+				<key>splitter</key>
+				<string>NSSplitView</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSWindowController</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSTableView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSControl</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NJRSplitView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSSplitView</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Read Me.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Read Me.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Read Me.nib/info.nib	(revision 365)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../../Pester.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>7</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9B18</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Snooze until.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Snooze until.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Snooze until.nib/classes.nib	(revision 365)
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>close</key>
+				<string>id</string>
+				<key>dateCompleted</key>
+				<string>NSPopUpButton</string>
+				<key>showCalendar</key>
+				<string>NSButton</string>
+				<key>snooze</key>
+				<string>NSButton</string>
+				<key>update</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>PSSnoozeUntilController</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>messageField</key>
+				<string>NSTextField</string>
+				<key>snoozeButton</key>
+				<string>NSButton</string>
+				<key>timeCalendarButton</key>
+				<string>NSButton</string>
+				<key>timeDate</key>
+				<string>NSTextField</string>
+				<key>timeDateCompletions</key>
+				<string>NSPopUpButton</string>
+				<key>timeOfDay</key>
+				<string>NSTextField</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSWindowController</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSMenuItem</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>NSObject</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Snooze until.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Snooze until.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Snooze until.nib/info.nib	(revision 365)
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../../Pester.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>105</integer>
+		<integer>22</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9B18</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/English.lproj/Volume.nib/classes.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Volume.nib/classes.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Volume.nib/classes.nib	(revision 365)
@@ -0,0 +1,14 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = NSObject; LANGUAGE = ObjC; }, 
+        {
+            ACTIONS = {volumeSet = id; }; 
+            CLASS = PSVolumeController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {contentView = NSView; delegate = id; volumeSlider = NSSlider; }; 
+            SUPERCLASS = NSWindowController; 
+        }
+    ); 
+    IBVersion = 1; 
+}
Index: releases/Pester/1.1b6/Source/English.lproj/Volume.nib/info.nib
===================================================================
--- releases/Pester/1.1b6/Source/English.lproj/Volume.nib/info.nib	(revision 365)
+++ releases/Pester/1.1b6/Source/English.lproj/Volume.nib/info.nib	(revision 365)
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>99 33 356 240 0 0 1280 832 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>13</key>
+		<string>622 500 41 112 0 0 1280 832 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>291.0</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>13</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>6I32</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/Info-Pester.plist
===================================================================
--- releases/Pester/1.1b6/Source/Info-Pester.plist	(revision 365)
+++ releases/Pester/1.1b6/Source/Info-Pester.plist	(revision 365)
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>Pester</string>
+	<key>CFBundleGetInfoString</key>
+	<string>Pester 1.1b6, © 2002–07 Nicholas Riley</string>
+	<key>CFBundleIconFile</key>
+	<string>Application icon</string>
+	<key>CFBundleIdentifier</key>
+	<string>net.sabi.Pester</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>Pester</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.1b6</string>
+	<key>CFBundleSignature</key>
+	<string>Pest</string>
+	<key>CFBundleVersion</key>
+	<string>22</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>PSApplication</string>
+	<key>SUFeedURL</key>
+	<string>http://web.sabi.net/nriley/software/Pester/updates.xml</string>
+	<key>SUExpectsDSASignature</key>
+	<true/>
+	<key>SUPublicDSAKey</key>
+	<string>-----BEGIN PUBLIC KEY-----
+MIIDOjCCAi0GByqGSM44BAEwggIgAoIBAQCz8OxP7pTvOJ5Q2yW0b12P8hozumkQ
+Nw02JzlIwCL4XbCNyQ6d/cSTnGANeD4XVR6YGDm/jwEizzsLVfrp48NJQY19vVRR
+ZA+lFkuIOr9dkXfIB679h/t/O/wlvv9GclQibdhYEJ5Ggq0HPWEX0n+FYX8Ee92Z
+oKzDm3Wn18kOpDXIRin3jW09lXPWXTDNETZrc/6u3S5V4Hc4xtS4bFVbfQ5MDg40
+FsdebcX3FA5kx6lKd5SuaMOU1Wgxj8L+hk3AlQ2imZOak4YVYPq8vgy5i5FxPmk/
+mW7PVWONU7xRpF5jb9WolNQjPDskHa4yPeV65Gzu86yDz+/FTBZAtqsDAhUAphtw
+rgzaIAvJe5zkkbfymsQFVAUCggEAOaufvAzV0L4FUNtyeZW+zC11poHYB9bBGKQN
+MoKiDRHkysL6Pu7QGTiiaLllVjZVyzFm7EvjUsVr1iwtRq7Th/KHrUtrQsNKzE72
+JFGtWINPe+sNH/Yu7qp/uVW6NmCPJiyfg3uDf/QdIzoiya1iqqiz9Tx62Nio0oN9
+DaY5ofKQ0W0QY+1MlAqwWk0OJoyVoQPtpYGRLJ2bd1UbtxPjVmq5a7KwVr8TueyB
+SMbnLZnBYLrOebnQQROiJvXSuydGsGj63dmvYR9YakBTy8CeI4WITFR8oD4VTfzo
+ii6hJuIb9rl87fTUHOCKUxJ1GTWFNBwggZs2IMSS2XMaEFlfrQOCAQUAAoIBAC15
+92bN3oQiZVy49E1VVFt69TLjUVi2bQWIEaM529QQEYJQ7yRN2rmuSa48O/8MbUGD
+b2H5VoN9v42uzm5+rFDDq4AwltsxlHHzoKGV9rCbo07dIqosmtIpwg/uETY1o6EF
+wl25FfzK7r8uG9e0lbz1mJuuhJfS0HnIrgmHdo+Vuv18HRAO1Q1NRPvGK2CsC2uX
+0xRIhLqHS+/W9ibe2Q0m+TRKBWRB3lWoKjCWbv8BztdeccKiPT2xEiRKIBl/fxLQ
+YSz77RTaBpnV48SKS36fulgcQNKHllEG/MtdHn66TLLHplNai8XvAvCC6H/0yKHF
+ncNZEp14qWoKVKn3CXk=
+-----END PUBLIC KEY-----</string>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/Manip.pm
===================================================================
--- releases/Pester/1.1b6/Source/Manip.pm	(revision 365)
+++ releases/Pester/1.1b6/Source/Manip.pm	(revision 365)
@@ -0,0 +1,7577 @@
+﻿package Date::Manip;
+# Copyright (c) 1995-2007 Sullivan Beck.  All rights reserved.
+# This program is free software; you can redistribute it and/or modify it
+# under the same terms as Perl itself.
+
+###########################################################################
+###########################################################################
+
+use vars qw($OS %Lang %Holiday %Events %Curr %Cnf %Zone $VERSION @ISA @EXPORT);
+
+# Determine the type of OS...
+$OS="Unix";
+$OS="Windows"  if ((defined $^O and
+                    $^O =~ /MSWin32/i ||
+                    $^O =~ /Windows_95/i ||
+                    $^O =~ /Windows_NT/i) ||
+                   (defined $ENV{OS} and
+                    $ENV{OS} =~ /MSWin32/i ||
+                    $ENV{OS} =~ /Windows_95/i ||
+                    $ENV{OS} =~ /Windows_NT/i));
+$OS="Unix"     if (defined $^O and
+                   $^O =~ /cygwin/i);
+$OS="Netware"  if (defined $^O and
+                   $^O =~ /NetWare/i);
+$OS="Mac"      if ((defined $^O and
+                    $^O =~ /MacOS/i) ||
+                   (defined $ENV{OS} and
+                    $ENV{OS} =~ /MacOS/i));
+$OS="MPE"      if (defined $^O and
+                   $^O =~ /MPE/i);
+$OS="OS2"      if (defined $^O and
+                   $^O =~ /os2/i);
+$OS="VMS"      if (defined $^O and
+                   $^O =~ /VMS/i);
+$OS="AIX"      if (defined $^O and
+                   $^O =~ /aix/i);
+
+# Determine if we're doing taint checking
+$Date::Manip::NoTaint = eval { local $^W=0; eval("#" . substr($^X, 0, 0)); 1 };
+
+###########################################################################
+# CUSTOMIZATION
+###########################################################################
+#
+# See the section of the POD documentation section CUSTOMIZING DATE::MANIP
+# below for a complete description of each of these variables.
+
+
+# Location of a the global config file.  Tilde (~) expansions are allowed.
+# This should be set in Date_Init arguments.
+$Cnf{"GlobalCnf"}="";
+$Cnf{"IgnoreGlobalCnf"}="";
+
+# Name of a personal config file and the path to search for it.  Tilde (~)
+# expansions are allowed.  This should be set in Date_Init arguments or in
+# the global config file.
+
+@Date::Manip::DatePath=();
+if ($OS eq "Windows") {
+  $Cnf{"PathSep"}         = ";";
+  $Cnf{"PersonalCnf"}     = "Manip.cnf";
+  $Cnf{"PersonalCnfPath"} = ".";
+
+} elsif ($OS eq "Netware") {
+  $Cnf{"PathSep"}         = ";";
+  $Cnf{"PersonalCnf"}     = "Manip.cnf";
+  $Cnf{"PersonalCnfPath"} = ".";
+
+} elsif ($OS eq "MPE") {
+  $Cnf{"PathSep"}         = ":";
+  $Cnf{"PersonalCnf"}     = "Manip.cnf";
+  $Cnf{"PersonalCnfPath"} = ".";
+
+} elsif ($OS eq "OS2") {
+  $Cnf{"PathSep"}         = ":";
+  $Cnf{"PersonalCnf"}     = "Manip.cnf";
+  $Cnf{"PersonalCnfPath"} = ".";
+
+} elsif ($OS eq "Mac") {
+  $Cnf{"PathSep"}         = ":";
+  $Cnf{"PersonalCnf"}     = "Manip.cnf";
+  $Cnf{"PersonalCnfPath"} = ".";
+
+} elsif ($OS eq "VMS") {
+  # VMS doesn't like files starting with "."
+  $Cnf{"PathSep"}         = ",";
+  $Cnf{"PersonalCnf"}     = "Manip.cnf";
+  $Cnf{"PersonalCnfPath"} = "/sys\$login";
+
+} else {
+  # Unix
+  $Cnf{"PathSep"}         = ":";
+  $Cnf{"PersonalCnf"}     = ".DateManip.cnf";
+  $Cnf{"PersonalCnfPath"} = ".:~";
+  @Date::Manip::DatePath=qw(/bin /usr/bin /usr/local/bin);
+}
+
+### Date::Manip variables set in the global or personal config file
+
+# Which language to use when parsing dates.
+$Cnf{"Language"}="English";
+
+# 12/10 = Dec 10 (US) or Oct 12 (anything else)
+$Cnf{"DateFormat"}="US";
+
+# Local timezone
+$Cnf{"TZ"}="";
+
+# Timezone to work in (""=local, "IGNORE", or a timezone)
+$Cnf{"ConvTZ"}="";
+
+# Date::Manip internal format (0=YYYYMMDDHH:MN:SS, 1=YYYYHHMMDDHHMNSS)
+$Cnf{"Internal"}=0;
+
+# First day of the week (1=monday, 7=sunday).  ISO 8601 says monday.
+$Cnf{"FirstDay"}=1;
+
+# First and last day of the work week  (1=monday, 7=sunday)
+$Cnf{"WorkWeekBeg"}=1;
+$Cnf{"WorkWeekEnd"}=5;
+
+# If non-nil, a work day is treated as 24 hours long (WorkDayBeg/WorkDayEnd
+# ignored)
+$Cnf{"WorkDay24Hr"}=0;
+
+# Start and end time of the work day (any time format allowed, seconds
+# ignored)
+$Cnf{"WorkDayBeg"}="08:00";
+$Cnf{"WorkDayEnd"}="17:00";
+
+# If "today" is a holiday, we look either to "tomorrow" or "yesterday" for
+# the nearest business day.  By default, we'll always look "tomorrow"
+# first.
+$Cnf{"TomorrowFirst"}=1;
+
+# Erase the old holidays
+$Cnf{"EraseHolidays"}="";
+
+# Set this to non-zero to be produce completely backwards compatible deltas
+$Cnf{"DeltaSigns"}=0;
+
+# If this is 0, use the ISO 8601 standard that Jan 4 is in week 1.  If 1,
+# make week 1 contain Jan 1.
+$Cnf{"Jan1Week1"}=0;
+
+# 2 digit years fall into the 100 year period given by [ CURR-N,
+# CURR+(99-N) ] where N is 0-99.  Default behavior is 89, but other useful
+# numbers might be 0 (forced to be this year or later) and 99 (forced to be
+# this year or earlier).  It can also be set to "c" (current century) or
+# "cNN" (i.e.  c18 forces the year to bet 1800-1899).  Also accepts the
+# form cNNNN to give the 100 year period NNNN to NNNN+99.
+$Cnf{"YYtoYYYY"}=89;
+
+# Set this to 1 if you want a long-running script to always update the
+# timezone.  This will slow Date::Manip down.  Read the POD documentation.
+$Cnf{"UpdateCurrTZ"}=0;
+
+# Use an international character set.
+$Cnf{"IntCharSet"}=0;
+
+# Use this to force the current date to be set to this:
+$Cnf{"ForceDate"}="";
+
+# Use this to make "today" mean "today at midnight".
+$Cnf{"TodayIsMidnight"}=0;
+
+###########################################################################
+
+require 5.000;
+require Exporter;
+@ISA = qw(Exporter);
+@EXPORT = qw(
+   DateManipVersion
+   Date_Init
+   ParseDateString
+   ParseDate
+   ParseRecur
+   Date_Cmp
+   DateCalc
+   ParseDateDelta
+   UnixDate
+   Delta_Format
+   Date_GetPrev
+   Date_GetNext
+   Date_SetTime
+   Date_SetDateField
+   Date_IsHoliday
+   Events_List
+
+   Date_DaysInMonth
+   Date_DayOfWeek
+   Date_SecsSince1970
+   Date_SecsSince1970GMT
+   Date_DaysSince1BC
+   Date_DayOfYear
+   Date_DaysInYear
+   Date_WeekOfYear
+   Date_LeapYear
+   Date_DaySuffix
+   Date_ConvTZ
+   Date_TimeZone
+   Date_IsWorkDay
+   Date_NextWorkDay
+   Date_PrevWorkDay
+   Date_NearestWorkDay
+   Date_NthDayOfYear
+);
+use strict;
+use integer;
+use Carp;
+
+use IO::File;
+
+$VERSION="5.47";
+
+########################################################################
+########################################################################
+
+$Curr{"InitLang"}      = 1;     # Whether a language is being init'ed
+$Curr{"InitDone"}      = 0;     # Whether Init_Date has been called
+$Curr{"InitFilesRead"} = 0;
+$Curr{"ResetWorkDay"}  = 1;
+$Curr{"Debug"}         = "";
+$Curr{"DebugVal"}      = "";
+
+$Holiday{"year"}       = 0;
+$Holiday{"dates"}      = {};
+$Holiday{"desc"}       = {};
+
+$Events{"raw"}         = [];
+$Events{"parsed"}      = 0;
+$Events{"dates"}       = [];
+$Events{"recur"}       = [];
+
+########################################################################
+########################################################################
+# THESE ARE THE MAIN ROUTINES
+########################################################################
+########################################################################
+
+# Get rid of a problem with old versions of perl
+no strict "vars";
+# This sorts from longest to shortest element
+sub sortByLength {
+  return (length $b <=> length $a);
+}
+use strict "vars";
+
+sub DateManipVersion {
+  print "DEBUG: DateManipVersion\n"  if ($Curr{"Debug"} =~ /trace/);
+  return $VERSION;
+}
+
+sub Date_Init {
+  print "DEBUG: Date_Init\n"  if ($Curr{"Debug"} =~ /trace/);
+  $Curr{"Debug"}="";
+
+  my(@args)=@_;
+  $Curr{"InitDone"}=1;
+  local($_)=();
+  my($internal,$firstday)=();
+  my($var,$val,$file,@tmp)=();
+
+  # InitFilesRead = 0    : no conf files read yet
+  #                 1    : global read, no personal read
+  #                 2    : personal read
+
+  $Cnf{"EraseHolidays"}=0;
+  foreach (@args) {
+    s/\s*$//;
+    s/^\s*//;
+    /^(\S+) \s* = \s* (.*)$/x;
+    ($var,$val)=($1,$2);
+    if ($var =~ /^GlobalCnf$/i) {
+      $Cnf{"GlobalCnf"}=$val;
+      if ($val) {
+        $Curr{"InitFilesRead"}=0;
+        &EraseHolidays();
+      }
+    } elsif ($var =~ /^PathSep$/i) {
+      $Cnf{"PathSep"}=$val;
+    } elsif ($var =~ /^PersonalCnf$/i) {
+      $Cnf{"PersonalCnf"}=$val;
+      $Curr{"InitFilesRead"}=1  if ($Curr{"InitFilesRead"}==2);
+    } elsif ($var =~ /^PersonalCnfPath$/i) {
+      $Cnf{"PersonalCnfPath"}=$val;
+      $Curr{"InitFilesRead"}=1  if ($Curr{"InitFilesRead"}==2);
+    } elsif ($var =~ /^IgnoreGlobalCnf$/i) {
+      $Curr{"InitFilesRead"}=1  if ($Curr{"InitFilesRead"}==0);
+      $Cnf{"IgnoreGlobalCnf"}=1;
+    } elsif ($var =~ /^EraseHolidays$/i) {
+      &EraseHolidays();
+    } else {
+      push(@tmp,$_);
+    }
+  }
+  @args=@tmp;
+
+  # Read global config file
+  if ($Curr{"InitFilesRead"}<1  &&  ! $Cnf{"IgnoreGlobalCnf"}) {
+    $Curr{"InitFilesRead"}=1;
+
+    if ($Cnf{"GlobalCnf"}) {
+      $file=&ExpandTilde($Cnf{"GlobalCnf"});
+      &Date_InitFile($file)  if ($file);
+    }
+  }
+
+  # Read personal config file
+  if ($Curr{"InitFilesRead"}<2) {
+    $Curr{"InitFilesRead"}=2;
+
+    if ($Cnf{"PersonalCnf"}  and  $Cnf{"PersonalCnfPath"}) {
+      $file=&SearchPath($Cnf{"PersonalCnf"},$Cnf{"PersonalCnfPath"},"r");
+      &Date_InitFile($file)  if ($file);
+    }
+  }
+
+  foreach (@args) {
+    s/\s*$//;
+    s/^\s*//;
+    /^(\S+) \s* = \s* (.*)$/x;
+    ($var,$val)=($1,$2);
+    $val=""  if (! defined $val);
+    &Date_SetConfigVariable($var,$val);
+  }
+
+  confess "ERROR: Unknown FirstDay in Date::Manip.\n"
+    if (! &IsInt($Cnf{"FirstDay"},1,7));
+  confess "ERROR: Unknown WorkWeekBeg in Date::Manip.\n"
+    if (! &IsInt($Cnf{"WorkWeekBeg"},1,7));
+  confess "ERROR: Unknown WorkWeekEnd in Date::Manip.\n"
+    if (! &IsInt($Cnf{"WorkWeekEnd"},1,7));
+  confess "ERROR: Invalid WorkWeek in Date::Manip.\n"
+    if ($Cnf{"WorkWeekEnd"} <= $Cnf{"WorkWeekBeg"});
+
+  my(%lang,
+     $tmp,%tmp,$tmp2,@tmp2,
+     $i,$j,@tmp3,
+     $zonesrfc,@zones)=();
+
+  my($L)=$Cnf{"Language"};
+
+  if ($Curr{"InitLang"}) {
+    $Curr{"InitLang"}=0;
+
+    if ($L eq "English") {
+      &Date_Init_English(\%lang);
+
+    } elsif ($L eq "French") {
+      &Date_Init_French(\%lang);
+
+    } elsif ($L eq "Swedish") {
+      &Date_Init_Swedish(\%lang);
+
+    } elsif ($L eq "German") {
+      &Date_Init_German(\%lang);
+
+    } elsif ($L eq "Polish") {
+      &Date_Init_Polish(\%lang);
+
+    } elsif ($L eq "Dutch"  ||
+             $L eq "Nederlands") {
+      &Date_Init_Dutch(\%lang);
+
+    } elsif ($L eq "Spanish") {
+      &Date_Init_Spanish(\%lang);
+
+    } elsif ($L eq "Portuguese") {
+      &Date_Init_Portuguese(\%lang);
+
+    } elsif ($L eq "Romanian") {
+      &Date_Init_Romanian(\%lang);
+
+    } elsif ($L eq "Italian") {
+      &Date_Init_Italian(\%lang);
+
+    } elsif ($L eq "Russian") {
+      &Date_Init_Russian(\%lang);
+
+    } elsif ($L eq "Turkish") {
+      &Date_Init_Turkish(\%lang);
+
+    } elsif ($L eq "Danish") {
+      &Date_Init_Danish(\%lang);
+
+    } elsif ($L eq "Catalan") {
+      &Date_Init_Catalan(\%lang);
+
+    } else {
+      confess "ERROR: Unknown language in Date::Manip.\n";
+    }
+
+    #  variables for months
+    #   Month   = "(jan|january|feb|february ... )"
+    #   MonL    = [ "Jan","Feb",... ]
+    #   MonthL  = [ "January","February", ... ]
+    #   MonthH  = { "january"=>1, "jan"=>1, ... }
+
+    $Lang{$L}{"MonthH"}={};
+    $Lang{$L}{"MonthL"}=[];
+    $Lang{$L}{"MonL"}=[];
+    &Date_InitLists([$lang{"month_name"},
+                     $lang{"month_abb"}],
+                    \$Lang{$L}{"Month"},"lc,sort,back",
+                    [$Lang{$L}{"MonthL"},
+                     $Lang{$L}{"MonL"}],
+                    [$Lang{$L}{"MonthH"},1]);
+
+    #  variables for day of week
+    #   Week   = "(mon|monday|tue|tuesday ... )"
+    #   WL     = [ "M","T",... ]
+    #   WkL    = [ "Mon","Tue",... ]
+    #   WeekL  = [ "Monday","Tudesday",... ]
+    #   WeekH  = { "monday"=>1,"mon"=>1,"m"=>1,... }
+
+    $Lang{$L}{"WeekH"}={};
+    $Lang{$L}{"WeekL"}=[];
+    $Lang{$L}{"WkL"}=[];
+    $Lang{$L}{"WL"}=[];
+    &Date_InitLists([$lang{"day_name"},
+                     $lang{"day_abb"}],
+                    \$Lang{$L}{"Week"},"lc,sort,back",
+                    [$Lang{$L}{"WeekL"},
+                     $Lang{$L}{"WkL"}],
+                    [$Lang{$L}{"WeekH"},1]);
+    &Date_InitLists([$lang{"day_char"}],
+                    "","lc",
+                    [$Lang{$L}{"WL"}],
+                    [\%tmp,1]);
+    %{ $Lang{$L}{"WeekH"} } =
+      (%{ $Lang{$L}{"WeekH"} },%tmp);
+
+    #  variables for last
+    #   Last      = "(last)"
+    #   LastL     = [ "last" ]
+    #   Each      = "(each)"
+    #   EachL     = [ "each" ]
+    #  variables for day of month
+    #   DoM       = "(1st|first ... 31st)"
+    #   DoML      = [ "1st","2nd",... "31st" ]
+    #   DoMH      = { "1st"=>1,"first"=>1, ... "31st"=>31 }
+    #  variables for week of month
+    #   WoM       = "(1st|first| ... 5th|last)"
+    #   WoMH      = { "1st"=>1, ... "5th"=>5,"last"=>-1 }
+
+    $Lang{$L}{"LastL"}=$lang{"last"};
+    &Date_InitStrings($lang{"last"},
+                      \$Lang{$L}{"Last"},"lc,sort");
+
+    $Lang{$L}{"EachL"}=$lang{"each"};
+    &Date_InitStrings($lang{"each"},
+                      \$Lang{$L}{"Each"},"lc,sort");
+
+    $Lang{$L}{"DoMH"}={};
+    $Lang{$L}{"DoML"}=[];
+    &Date_InitLists([$lang{"num_suff"},
+                     $lang{"num_word"}],
+                    \$Lang{$L}{"DoM"},"lc,sort,back,escape",
+                    [$Lang{$L}{"DoML"},
+                     \@tmp],
+                    [$Lang{$L}{"DoMH"},1]);
+
+    @tmp=();
+    foreach $tmp (keys %{ $Lang{$L}{"DoMH"} }) {
+      $tmp2=$Lang{$L}{"DoMH"}{$tmp};
+      if ($tmp2<6) {
+        $Lang{$L}{"WoMH"}{$tmp} = $tmp2;
+        push(@tmp,$tmp);
+      }
+    }
+    foreach $tmp (@{ $Lang{$L}{"LastL"} }) {
+      $Lang{$L}{"WoMH"}{$tmp} = -1;
+      push(@tmp,$tmp);
+    }
+    &Date_InitStrings(\@tmp,\$Lang{$L}{"WoM"},
+                      "lc,sort,back,escape");
+
+    #  variables for AM or PM
+    #   AM      = "(am)"
+    #   PM      = "(pm)"
+    #   AmPm    = "(am|pm)"
+    #   AMstr   = "AM"
+    #   PMstr   = "PM"
+
+    &Date_InitStrings($lang{"am"},\$Lang{$L}{"AM"},"lc,sort,escape");
+    &Date_InitStrings($lang{"pm"},\$Lang{$L}{"PM"},"lc,sort,escape");
+    &Date_InitStrings([ @{$lang{"am"}},@{$lang{"pm"}} ],\$Lang{$L}{"AmPm"},
+                      "lc,back,sort,escape");
+    $Lang{$L}{"AMstr"}=$lang{"am"}[0];
+    $Lang{$L}{"PMstr"}=$lang{"pm"}[0];
+
+    #  variables for expressions used in parsing deltas
+    #    Yabb   = "(?:y|yr|year|years)"
+    #    Mabb   = similar for months
+    #    Wabb   = similar for weeks
+    #    Dabb   = similar for days
+    #    Habb   = similar for hours
+    #    MNabb  = similar for minutes
+    #    Sabb   = similar for seconds
+    #    Repl   = { "abb"=>"replacement" }
+    # Whenever an abbreviation could potentially refer to two different
+    # strings (M standing for Minutes or Months), the abbreviation must
+    # be listed in Repl instead of in the appropriate Xabb values.  This
+    # only applies to abbreviations which are substrings of other values
+    # (so there is no confusion between Mn and Month).
+
+    &Date_InitStrings($lang{"years"}  ,\$Lang{$L}{"Yabb"}, "lc,sort");
+    &Date_InitStrings($lang{"months"} ,\$Lang{$L}{"Mabb"}, "lc,sort");
+    &Date_InitStrings($lang{"weeks"}  ,\$Lang{$L}{"Wabb"}, "lc,sort");
+    &Date_InitStrings($lang{"days"}   ,\$Lang{$L}{"Dabb"}, "lc,sort");
+    &Date_InitStrings($lang{"hours"}  ,\$Lang{$L}{"Habb"}, "lc,sort");
+    &Date_InitStrings($lang{"minutes"},\$Lang{$L}{"MNabb"},"lc,sort");
+    &Date_InitStrings($lang{"seconds"},\$Lang{$L}{"Sabb"}, "lc,sort");
+    $Lang{$L}{"Repl"}={};
+    &Date_InitHash($lang{"replace"},undef,"lc",$Lang{$L}{"Repl"});
+
+    #  variables for special dates that are offsets from now
+    #    Now      = "now"
+    #    Today    = "today"
+    #    Offset   = "(yesterday|tomorrow)"
+    #    OffsetH  = { "yesterday"=>"-0:0:0:1:0:0:0",... ]
+    #    Times    = "(noon|midnight)"
+    #    TimesH   = { "noon"=>"12:00:00","midnight"=>"00:00:00" }
+    #    SepHM    = hour/minute separator
+    #    SepMS    = minute/second separator
+    #    SepSS    = second/fraction separator
+
+    $Lang{$L}{"TimesH"}={};
+    &Date_InitHash($lang{"times"},
+                   \$Lang{$L}{"Times"},"lc,sort,back",
+                   $Lang{$L}{"TimesH"});
+    &Date_InitStrings($lang{"now"},\$Lang{$L}{"Now"},"lc,sort");
+    &Date_InitStrings($lang{"today"},\$Lang{$L}{"Today"},"lc,sort");
+    $Lang{$L}{"OffsetH"}={};
+    &Date_InitHash($lang{"offset"},
+                   \$Lang{$L}{"Offset"},"lc,sort,back",
+                   $Lang{$L}{"OffsetH"});
+    $Lang{$L}{"SepHM"}=$lang{"sephm"};
+    $Lang{$L}{"SepMS"}=$lang{"sepms"};
+    $Lang{$L}{"SepSS"}=$lang{"sepss"};
+
+    #  variables for time zones
+    #    zones      = regular expression with all zone names (EST)
+    #    n2o        = a hash of all parsable zone names with their offsets
+    #    tzones     = reguar expression with all tzdata timezones (US/Eastern)
+    #    tz2z       = hash of all tzdata timezones to full timezone (EST#EDT)
+
+    $zonesrfc=
+      "idlw   -1200 ".  # International Date Line West
+      "nt     -1100 ".  # Nome
+      "hst    -1000 ".  # Hawaii Standard
+      "cat    -1000 ".  # Central Alaska
+      "ahst   -1000 ".  # Alaska-Hawaii Standard
+      "akst   -0900 ".  # Alaska Standard
+      "yst    -0900 ".  # Yukon Standard
+      "hdt    -0900 ".  # Hawaii Daylight
+      "akdt   -0800 ".  # Alaska Daylight
+      "ydt    -0800 ".  # Yukon Daylight
+      "pst    -0800 ".  # Pacific Standard
+      "pdt    -0700 ".  # Pacific Daylight
+      "mst    -0700 ".  # Mountain Standard
+      "mdt    -0600 ".  # Mountain Daylight
+      "cst    -0600 ".  # Central Standard
+      "cdt    -0500 ".  # Central Daylight
+      "est    -0500 ".  # Eastern Standard
+      "act    -0500 ".  # Brazil, Acre
+      "pet    -0500 ".  # Peruvian time
+      "sat    -0400 ".  # Chile
+      "clst   -0400 ".  # Chile Standard
+      "bot    -0400 ".  # Bolivia
+      "amt    -0400 ".  # Brazil, Amazon
+      "acst   -0400 ".  # Brazil, Acre Daylight
+      "edt    -0400 ".  # Eastern Daylight
+      "ast    -0400 ".  # Atlantic Standard
+      #"nst   -0330 ".  # Newfoundland Standard      nst=North Sumatra    +0630
+      "nft    -0330 ".  # Newfoundland
+      #"gst   -0300 ".  # Greenland Standard         gst=Guam Standard    +1000
+      "cldt   -0300 ".  # Chile Daylight
+      #"bst   -0300 ".  # Brazil Standard            bst=British Summer   +0100
+      "brt    -0300 ".  # Brazil Standard (official time)
+      #"brst   -0300 ".  # Brazil Standard
+      "adt    -0300 ".  # Atlantic Daylight
+      "art    -0300 ".  # Argentina
+      "amst   -0300 ".  # Brazil, Amazon Daylight
+      "uyt    -0300 ".  # Uruguay
+      "ndt    -0230 ".  # Newfoundland Daylight
+      "brst   -0200 ".  # Brazil Daylight (official time)
+      "fnt    -0200 ".  # Brazil, Fernando de Noronha
+      "at     -0200 ".  # Azores
+      "yust   -0200 ".  # Uruguay
+      "wat    -0100 ".  # West Africa
+      "fnst   -0100 ".  # Brazil, Fernando de Noronha Daylight
+      "gmt    +0000 ".  # Greenwich Mean
+      "ut     +0000 ".  # Universal
+      "utc    +0000 ".  # Universal (Coordinated)
+      "wet    +0000 ".  # Western European
+      "cet    +0100 ".  # Central European
+      "fwt    +0100 ".  # French Winter
+      "met    +0100 ".  # Middle European
+      "mez    +0100 ".  # Middle European
+      "mewt   +0100 ".  # Middle European Winter
+      "swt    +0100 ".  # Swedish Winter
+      "bst    +0100 ".  # British Summer             bst=Brazil standard  -0300
+      "gb     +0100 ".  # GMT with daylight savings
+      "west   +0100 ".  # Western European Daylight
+      "eet    +0200 ".  # Eastern Europe, USSR Zone 1
+      "cest   +0200 ".  # Central European Summer
+      "fst    +0200 ".  # French Summer
+      "ist    +0200 ".  # Israel standard
+      "mest   +0200 ".  # Middle European Summer
+      "mesz   +0200 ".  # Middle European Summer
+      "metdst +0200 ".  # An alias for mest used by HP-UX
+      "sast   +0200 ".  # South African Standard
+      "sst    +0200 ".  # Swedish Summer             sst=South Sumatra    +0700
+      "bt     +0300 ".  # Baghdad, USSR Zone 2
+      "eest   +0300 ".  # Eastern Europe Summer
+      "eetdst +0300 ".  # An alias for eest used by HP-UX
+      "eetedt +0300 ".  # Eastern Europe, USSR Zone 1
+      "idt    +0300 ".  # Israel Daylight
+      "msk    +0300 ".  # Moscow
+      "eat    +0300 ".  # East Africa
+      "it     +0330 ".  # Iran
+      "zp4    +0400 ".  # USSR Zone 3
+      "msd    +0400 ".  # Moscow Daylight
+      "zp5    +0500 ".  # USSR Zone 4
+      "ist    +0530 ".  # Indian Standard
+      "zp6    +0600 ".  # USSR Zone 5
+      "novt   +0600 ".  # Novosibirsk winter time zone, Russia
+      "nst    +0630 ".  # North Sumatra              nst=Newfoundland Std -0330
+      #"sst   +0700 ".  # South Sumatra, USSR Zone 6 sst=Swedish Summer   +0200
+      "javt   +0700 ".  # Java
+      "ict    +0700 ".  # Indo China Time
+      "novst  +0700 ".  # Novosibirsk summer time zone, Russia
+      "krat   +0700 ".  # Krasnoyarsk, Russia
+      "myt    +0800 ".  # Malaysia
+      "hkt    +0800 ".  # Hong Kong
+      "sgt    +0800 ".  # Singapore
+      "cct    +0800 ".  # China Coast, USSR Zone 7
+      "krast  +0800 ".  # Krasnoyarsk, Russia Daylight
+      "awst   +0800 ".  # Australian Western Standard
+      "wst    +0800 ".  # West Australian Standard
+      "pht    +0800 ".  # Asia Manila
+      "kst    +0900 ".  # Republic of Korea
+      "jst    +0900 ".  # Japan Standard, USSR Zone 8
+      "rok    +0900 ".  # Republic of Korea
+      "acst   +0930 ".  # Australian Central Standard
+      "cast   +0930 ".  # Central Australian Standard
+      "aest   +1000 ".  # Australian Eastern Standard
+      "east   +1000 ".  # Eastern Australian Standard
+      "gst    +1000 ".  # Guam Standard, USSR Zone 9 gst=Greenland Std    -0300
+      "chst   +1000 ".  # Guam Standard, USSR Zone 9 gst=Greenland Std    -0300
+      "acdt   +1030 ".  # Australian Central Daylight
+      "cadt   +1030 ".  # Central Australian Daylight
+      "aedt   +1100 ".  # Australian Eastern Daylight
+      "eadt   +1100 ".  # Eastern Australian Daylight
+      "idle   +1200 ".  # International Date Line East
+      "nzst   +1200 ".  # New Zealand Standard
+      "nzt    +1200 ".  # New Zealand
+      "nzdt   +1300 ".  # New Zealand Daylight
+      "z +0000 ".
+      "a +0100 b +0200 c +0300 d +0400 e +0500 f +0600 g +0700 h +0800 ".
+      "i +0900 k +1000 l +1100 m +1200 ".
+      "n -0100 o -0200 p -0300 q -0400 r -0500 s -0600 t -0700 u -0800 ".
+      "v -0900 w -1000 x -1100 y -1200";
+
+    $Zone{"n2o"} = {};
+    ($Zone{"zones"},%{ $Zone{"n2o"} })=
+      &Date_Regexp($zonesrfc,"sort,lc,under,back",
+                   "keys");
+
+    $tmp=
+      "US/Pacific  PST8PDT ".
+      "US/Mountain MST7MDT ".
+      "US/Central  CST6CDT ".
+      "US/Eastern  EST5EDT ".
+      "Canada/Pacific  PST8PDT ".
+      "Canada/Mountain MST7MDT ".
+      "Canada/Central  CST6CDT ".
+      "Canada/Eastern  EST5EDT";
+
+    $Zone{"tz2z"} = {};
+    ($Zone{"tzones"},%{ $Zone{"tz2z"} })=
+      &Date_Regexp($tmp,"lc,under,back","keys");
+    $Cnf{"TZ"}=&Date_TimeZone;
+
+    #  misc. variables
+    #    At     = "(?:at)"
+    #    Of     = "(?:in|of)"
+    #    On     = "(?:on)"
+    #    Future = "(?:in)"
+    #    Later  = "(?:later)"
+    #    Past   = "(?:ago)"
+    #    Next   = "(?:next)"
+    #    Prev   = "(?:last|previous)"
+
+    &Date_InitStrings($lang{"at"},    \$Lang{$L}{"At"},     "lc,sort");
+    &Date_InitStrings($lang{"on"},    \$Lang{$L}{"On"},     "lc,sort");
+    &Date_InitStrings($lang{"future"},\$Lang{$L}{"Future"}, "lc,sort");
+    &Date_InitStrings($lang{"later"}, \$Lang{$L}{"Later"},  "lc,sort");
+    &Date_InitStrings($lang{"past"},  \$Lang{$L}{"Past"},   "lc,sort");
+    &Date_InitStrings($lang{"next"},  \$Lang{$L}{"Next"},   "lc,sort");
+    &Date_InitStrings($lang{"prev"},  \$Lang{$L}{"Prev"},   "lc,sort");
+    &Date_InitStrings($lang{"of"},    \$Lang{$L}{"Of"},     "lc,sort");
+
+    #  calc mode variables
+    #    Approx   = "(?:approximately)"
+    #    Exact    = "(?:exactly)"
+    #    Business = "(?:business)"
+
+    &Date_InitStrings($lang{"exact"},   \$Lang{$L}{"Exact"},   "lc,sort");
+    &Date_InitStrings($lang{"approx"},  \$Lang{$L}{"Approx"},  "lc,sort");
+    &Date_InitStrings($lang{"business"},\$Lang{$L}{"Business"},"lc,sort");
+
+    ############### END OF LANGUAGE INITIALIZATION
+  }
+
+  if ($Curr{"ResetWorkDay"}) {
+    my($h1,$m1,$h2,$m2)=();
+    if ($Cnf{"WorkDay24Hr"}) {
+      ($Curr{"WDBh"},$Curr{"WDBm"})=(0,0);
+      ($Curr{"WDEh"},$Curr{"WDEm"})=(24,0);
+      $Curr{"WDlen"}=24*60;
+      $Cnf{"WorkDayBeg"}="00:00";
+      $Cnf{"WorkDayEnd"}="23:59";
+
+    } else {
+      confess "ERROR: Invalid WorkDayBeg in Date::Manip.\n"
+        if (! (($h1,$m1)=&CheckTime($Cnf{"WorkDayBeg"})));
+      $Cnf{"WorkDayBeg"}="$h1:$m1";
+      confess "ERROR: Invalid WorkDayEnd in Date::Manip.\n"
+        if (! (($h2,$m2)=&CheckTime($Cnf{"WorkDayEnd"})));
+      $Cnf{"WorkDayEnd"}="$h2:$m2";
+
+      ($Curr{"WDBh"},$Curr{"WDBm"})=($h1,$m1);
+      ($Curr{"WDEh"},$Curr{"WDEm"})=($h2,$m2);
+
+      # Work day length = h1:m1  or  0:len (len minutes)
+      $h1=$h2-$h1;
+      $m1=$m2-$m1;
+      if ($m1<0) {
+        $h1--;
+        $m1+=60;
+      }
+      $Curr{"WDlen"}=$h1*60+$m1;
+    }
+    $Curr{"ResetWorkDay"}=0;
+  }
+
+  # current time
+  my($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst,$ampm,$wk)=();
+  if ($Cnf{"ForceDate"}=~
+      /^(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2}):(\d{2})$/) {
+       ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6);
+  } else {
+    ($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst)=localtime(time);
+    $y+=1900;
+    $m++;
+  }
+  &Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
+  $Curr{"Y"}=$y;
+  $Curr{"M"}=$m;
+  $Curr{"D"}=$d;
+  $Curr{"H"}=$h;
+  $Curr{"Mn"}=$mn;
+  $Curr{"S"}=$s;
+  $Curr{"AmPm"}=$ampm;
+  $Curr{"Now"}=&Date_Join($y,$m,$d,$h,$mn,$s);
+  if ($Cnf{"TodayIsMidnight"}) {
+    $Curr{"Today"}=&Date_Join($y,$m,$d,0,0,0);
+  } else {
+    $Curr{"Today"}=$Curr{"Now"};
+  }
+
+  $Curr{"Debug"}=$Curr{"DebugVal"};
+
+  # If we're in array context, let's return a list of config variables
+  # that could be passed to Date_Init to get the same state as we're
+  # currently in.
+  if (wantarray) {
+    # Some special variables that have to be in a specific order
+    my(@special)=qw(IgnoreGlobalCnf GlobalCnf PersonalCnf PersonalCnfPath);
+    my(%tmp)=map { $_,1 } @special;
+    my(@tmp,$key,$val);
+    foreach $key (@special) {
+      $val=$Cnf{$key};
+      push(@tmp,"$key=$val");
+    }
+    foreach $key (keys %Cnf) {
+      next  if (exists $tmp{$key});
+      $val=$Cnf{$key};
+      push(@tmp,"$key=$val");
+    }
+    return @tmp;
+  }
+  return ();
+}
+
+sub ParseDateString {
+  print "DEBUG: ParseDateString\n"  if ($Curr{"Debug"} =~ /trace/);
+  local($_)=@_;
+  return ""  if (! $_);
+
+  my($y,$m,$d,$h,$mn,$s,$i,$wofm,$dofw,$wk,$tmp,$z,$num,$err,$iso,$ampm)=();
+  my($date,$z2,$delta,$from,$falsefrom,$to,$which,$midnight)=();
+
+  # We only need to reinitialize if we have to determine what NOW is.
+  &Date_Init()  if (! $Curr{"InitDone"}  or  $Cnf{"UpdateCurrTZ"});
+
+  my($L)=$Cnf{"Language"};
+  my($type)=$Cnf{"DateFormat"};
+
+  # Mode is set in DateCalc.  ParseDate only overrides it if the string
+  # contains a mode.
+  if      ($Lang{$L}{"Exact"}  &&
+           s/$Lang{$L}{"Exact"}//) {
+    $Curr{"Mode"}=0;
+  } elsif ($Lang{$L}{"Approx"}  &&
+           s/$Lang{$L}{"Approx"}//) {
+    $Curr{"Mode"}=1;
+  } elsif ($Lang{$L}{"Business"}  &&
+           s/$Lang{$L}{"Business"}//) {
+    $Curr{"Mode"}=2;
+  } elsif (! exists $Curr{"Mode"}) {
+    $Curr{"Mode"}=0;
+  }
+
+  # Unfortunately, some deltas can be parsed as dates.  An example is
+  #    1 second  ==  1 2nd  ==  1 2
+  # But, some dates can be parsed as deltas.  The most important being:
+  #    1998010101:00:00
+  #
+  # We'll check to see if a "date" can be parsed as a delta.  If so, we'll
+  # assume that it is a delta (since they are much simpler, it is much
+  # less likely that we'll mistake a delta for a date than vice versa)
+  # unless it is an ISO-8601 date.
+  #
+  # This is important because we are using DateCalc to test whether a
+  # string is a date or a delta.  Dates are tested first, so we need to
+  # be able to pass a delta into this routine and have it correctly NOT
+  # interpreted as a date.
+  #
+  # We will insist that the string contain something other than digits and
+  # colons so that the following will get correctly interpreted as a date
+  # rather than a delta:
+  #     12:30
+  #     19980101
+
+  $delta="";
+  $delta=&ParseDateDelta($_)  if (/[^:0-9]/);
+
+  # Put parse in a simple loop for an easy exit.
+ PARSE: {
+    my(@tmp)=&Date_Split($_);
+    if (@tmp) {
+      ($y,$m,$d,$h,$mn,$s)=@tmp;
+      last PARSE;
+    }
+
+    # Fundamental regular expressions
+
+    my($month)=$Lang{$L}{"Month"};          # (jan|january|...)
+    my(%month)=%{ $Lang{$L}{"MonthH"} };    # { jan=>1, ... }
+    my($week)=$Lang{$L}{"Week"};            # (mon|monday|...)
+    my(%week)=%{ $Lang{$L}{"WeekH"} };      # { mon=>1, monday=>1, ... }
+    my($wom)=$Lang{$L}{"WoM"};              # (1st|...|fifth|last)
+    my(%wom)=%{ $Lang{$L}{"WoMH"} };        # { 1st=>1,... fifth=>5,last=>-1 }
+    my($dom)=$Lang{$L}{"DoM"};              # (1st|first|...31st)
+    my(%dom)=%{ $Lang{$L}{"DoMH"} };        # { 1st=>1, first=>1, ... }
+    my($ampmexp)=$Lang{$L}{"AmPm"};         # (am|pm)
+    my($timeexp)=$Lang{$L}{"Times"};        # (noon|midnight)
+    my($now)=$Lang{$L}{"Now"};              # now
+    my($today)=$Lang{$L}{"Today"};          # today
+    my($offset)=$Lang{$L}{"Offset"};        # (yesterday|tomorrow)
+    my($zone)=$Zone{"zones"};               # (edt|est|...)
+    my($day)='\s*'.$Lang{$L}{"Dabb"};       # \s*(?:d|day|days)
+    my($mabb)='\s*'.$Lang{$L}{"Mabb"};      # \s*(?:mon|month|months)
+    my($wkabb)='\s*'.$Lang{$L}{"Wabb"};     # \s*(?:w|wk|week|weeks)
+    my($next)='\s*'.$Lang{$L}{"Next"};      # \s*(?:next)
+    my($prev)='\s*'.$Lang{$L}{"Prev"};      # \s*(?:last|previous)
+    my($past)='\s*'.$Lang{$L}{"Past"};      # \s*(?:ago)
+    my($future)='\s*'.$Lang{$L}{"Future"};  # \s*(?:in)
+    my($later)='\s*'.$Lang{$L}{"Later"};    # \s*(?:later)
+    my($at)=$Lang{$L}{"At"};                # (?:at)
+    my($of)='\s*'.$Lang{$L}{"Of"};          # \s*(?:in|of)
+    my($on)='(?:\s*'.$Lang{$L}{"On"}.'\s*|\s+)';
+                                            # \s*(?:on)\s*    or  \s+
+    my($last)='\s*'.$Lang{$L}{"Last"};      # \s*(?:last)
+    my($hm)=$Lang{$L}{"SepHM"};             # :
+    my($ms)=$Lang{$L}{"SepMS"};             # :
+    my($ss)=$Lang{$L}{"SepSS"};             # .
+
+    # Other regular expressions
+
+    my($D4)='(\d{4})';            # 4 digits      (yr)
+    my($YY)='(\d{4}|\d{2})';      # 2 or 4 digits (yr)
+    my($DD)='(\d{2})';            # 2 digits      (mon/day/hr/min/sec)
+    my($D) ='(\d{1,2})';          # 1 or 2 digit  (mon/day/hr)
+    my($FS)="(?:$ss\\d+)?";       # fractional secs
+    my($sep)='[\/.-]';            # non-ISO8601 m/d/yy separators
+    # absolute time zone     +0700 (GMT)
+    my($hzone)='(?:[0-1][0-9]|2[0-3])';                    # 00 - 23
+    my($mzone)='(?:[0-5][0-9])';                           # 00 - 59
+    my($zone2)='(?:\s*([+-](?:'."$hzone$mzone|$hzone:$mzone|$hzone))".
+                                                           # +0700 +07:00 -07
+      '(?:\s*\([^)]+\))?)';                                # (GMT)
+
+    # A regular expression for the time EXCEPT for the hour part
+    my($mnsec)="$hm$DD(?:$ms$DD$FS)?(?:\\s*$ampmexp)?";
+
+    # A special regular expression for /YYYY:HH:MN:SS used by Apache
+    my($apachetime)='(/\d{4}):' . "$DD$hm$DD$ms$DD";
+
+    my($time)="";
+    $ampm="";
+    $date="";
+
+    # Substitute all special time expressions.
+    if (/(^|[^a-z])$timeexp($|[^a-z])/i) {
+      $tmp=$2;
+      $tmp=$Lang{$L}{"TimesH"}{lc($tmp)};
+      s/(^|[^a-z])$timeexp($|[^a-z])/$1 $tmp $3/i;
+    }
+
+    # Remove some punctuation
+    s/[,]/ /g;
+
+    # When we have a digit followed immediately by a timezone (7EST), we
+    # will put a space between the digit, EXCEPT in the case of a single
+    # character military timezone.  If the single character is followed
+    # by anything, no space is added.
+    $tmp = "";
+    while ( s/^(.*?\d)$zone(\s|$|[0-9])/$3/i ) {
+      my($bef,$z,$aft) = ($1,$2,$3);
+      if (length($z) != 1  ||  length($aft) == 0) {
+        $tmp .= "$bef $z";
+      } else {
+        $tmp .= "$bef$z";
+      }
+    }
+    $_ = "$tmp$_";
+    $zone = '\s+' . $zone . '(?:\s+|$)';
+
+    # Remove the time
+    $iso=1;
+    $midnight=0;
+    $from="24${hm}00(?:${ms}00)?";
+    $falsefrom="${hm}24${ms}00";   # Don't trap XX:24:00
+    $to="00${hm}00${ms}00";
+    $midnight=1  if (!/$falsefrom/  &&  s/$from/$to/);
+
+    $h=$mn=$s=0;
+    if (/$D$mnsec/i || /$ampmexp/i) {
+      $iso=0;
+      $tmp=0;
+      $tmp=1  if (/$mnsec$zone2?\s*$/i  or /$mnsec$zone\s*$/i);
+      $tmp=0  if (/$ampmexp/i);
+      if (s/$apachetime$zone()/$1 /i                            ||
+          s/$apachetime$zone2?/$1 /i                            ||
+          s/(^|[^a-z])$at\s*$D$mnsec$zone()/$1 /i               ||
+          s/(^|[^a-z])$at\s*$D$mnsec$zone2?/$1 /i               ||
+          s/(^|[^0-9])(\d)$mnsec$zone()/$1 /i                   ||
+          s/(^|[^0-9])(\d)$mnsec$zone2?/$1 /i                   ||
+          (s/(t)$D$mnsec$zone()/$1 /i and (($iso=$tmp) || 1))   ||
+          (s/(t)$D$mnsec$zone2?/$1 /i and (($iso=$tmp) || 1))   ||
+          (s/()$DD$mnsec$zone()/ /i and (($iso=$tmp) || 1))     ||
+          (s/()$DD$mnsec$zone2?/ /i and (($iso=$tmp) || 1))     ||
+          s/(^|$at\s*|\s+)$D()()\s*$ampmexp$zone()/ /i          ||
+          s/(^|$at\s*|\s+)$D()()\s*$ampmexp$zone2?/ /i          ||
+          0
+         ) {
+        ($h,$mn,$s,$ampm,$z,$z2)=($2,$3,$4,$5,$6,$7);
+        if (defined ($z)) {
+          if ($z =~ /^[+-]\d{2}:\d{2}$/) {
+            $z=~ s/://;
+          } elsif ($z =~ /^[+-]\d{2}$/) {
+            $z .= "00";
+          }
+        }
+        $time=1;
+        &Date_TimeCheck(\$h,\$mn,\$s,\$ampm);
+        $y=$m=$d="";
+        # We're going to be calling TimeCheck again below (when we check the
+        # final date), so get rid of $ampm so that we don't have an error
+        # due to "15:30:00 PM".  It'll get reset below.
+        $ampm="";
+        if (/^\s*$/) {
+          &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+          last PARSE;
+        }
+      }
+    }
+    $time=0  if ($time ne "1");
+    s/\s+$//;
+    s/^\s+//;
+
+    # if a zone was found, get rid of the regexps
+    if ($z) {
+      $zone="";
+      $zone2="";
+    }
+
+    # dateTtime ISO 8601 formats
+    my($orig)=$_;
+
+    # Parse ISO 8601 dates now (which may still have a zone stuck to it).
+    if ( ($iso && /^([0-9-]+(?:W[0-9-]+)?)$zone?$/i)   ||
+         ($iso && /^([0-9-]+(?:W[0-9-]+)?)$zone2?$/i)  ||
+         ($iso && /^([0-9-]+(?:T[0-9-]+)?)$zone?$/i)   ||
+         ($iso && /^([0-9-]+(?:T[0-9-]+)?)$zone2?$/i)  ||
+         ($iso && /^([0-9-]+)T$zone?$/i)   ||
+         ($iso && /^([0-9-]+)T$zone2?$/i)  ||
+         0) {
+
+      # If we already got a timezone, don't get another one.
+      my(@z);
+      if ($z) {
+        @z=($z,$z2);
+        $z="";
+      }
+      ($_,$z,$z2) = ($1,$2,$3);
+      ($z,$z2)=@z  if (@z);
+
+      s,([0-9])\s*-,$1 ,g; # Change all ISO8601 seps to spaces
+      s/^\s+//;
+      s/\s+$//;
+
+      if (/^$D4\s*$DD\s*$DD\s*t?$DD(?:$DD(?:$DD(\d*))?)?$/i ||
+          /^$DD\s+$DD\s*$DD\s*t?$DD(?:$DD(?:$DD(\d*))?)?$/i ||
+          0
+         ) {
+        # ISO 8601 Dates with times
+        #    YYYYMMDDtHHMNSSFFFF...
+        #    YYYYMMDDtHHMNSS
+        #    YYYYMMDDtHHMN
+        #    YYYYMMDDtHH
+        #    YY MMDDtHHMNSSFFFF...
+        #    YY MMDDtHHMNSS
+        #    YY MMDDtHHMN
+        #    YY MMDDtHH
+        # The t is an optional letter "t".
+        ($y,$m,$d,$h,$mn,$s,$tmp)=($1,$2,$3,$4,$5,$6,$7);
+        if ($h==24 && (! defined $mn || $mn==0) && (! defined $s || $s==0)) {
+          $h=0;
+          $midnight=1;
+        }
+        $z = ""    if (! defined $h);
+        return ""  if ($time  &&  defined $h);
+        last PARSE;
+
+      } elsif (/^$D4(?:\s*$DD(?:\s*$DD)?)?$/  ||
+               /^$DD(?:\s+$DD(?:\s*$DD)?)?$/) {
+        # ISO 8601 Dates
+        #    YYYYMMDD
+        #    YYYYMM
+        #    YYYY
+        #    YY MMDD
+        #    YY MM
+        #    YY
+        ($y,$m,$d)=($1,$2,$3);
+        last PARSE;
+
+      } elsif (/^$YY\s+$D\s+$D/) {
+        # YY-M-D
+        ($y,$m,$d)=($1,$2,$3);
+        last PARSE;
+
+      } elsif (/^$YY\s*W$DD\s*(\d)?$/i) {
+        # YY-W##-D
+        ($y,$wofm,$dofw)=($1,$2,$3);
+        ($y,$m,$d)=&Date_NthWeekOfYear($y,$wofm,$dofw);
+        last PARSE;
+
+      } elsif (/^$D4\s*(\d{3})$/ ||
+               /^$DD\s*(\d{3})$/) {
+        # YYDOY
+        ($y,$which)=($1,$2);
+        ($y,$m,$d)=&Date_NthDayOfYear($y,$which);
+        last PARSE;
+
+      } elsif ($iso<0) {
+        # We confused something like 1999/August12:00:00
+        # with a dateTtime format
+        $_=$orig;
+
+      } else {
+        return "";
+      }
+    }
+
+    # All deltas that are not ISO-8601 dates are NOT dates.
+    return ""  if ($Curr{"InCalc"}  &&  $delta);
+    if ($delta) {
+      &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+      return &DateCalc_DateDelta($Curr{"Now"},$delta);
+    }
+
+    # Check for some special types of dates (next, prev)
+    foreach $from (keys %{ $Lang{$L}{"Repl"} }) {
+      $to=$Lang{$L}{"Repl"}{$from};
+      s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i;
+    }
+    if (/$wom/i  ||  /$future/i  ||  /$later/i  ||  /$past/i  ||
+        /$next/i  ||  /$prev/i  ||  /^$week$/i  ||  /$wkabb/i) {
+      $tmp=0;
+
+      if (/^$wom\s*$week$of\s*$month\s*$YY?$/i) {
+        # last friday in October 95
+        ($wofm,$dofw,$m,$y)=($1,$2,$3,$4);
+        # fix $m, $y
+        return ""  if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
+        $dofw=$week{lc($dofw)};
+        $wofm=$wom{lc($wofm)};
+        # Get the first day of the month
+        $date=&Date_Join($y,$m,1,$h,$mn,$s);
+        if ($wofm==-1) {
+          $date=&DateCalc_DateDelta($date,"+0:1:0:0:0:0:0",\$err,0);
+          $date=&Date_GetPrev($date,$dofw,0);
+        } else {
+          for ($i=0; $i<$wofm; $i++) {
+            if ($i==0) {
+              $date=&Date_GetNext($date,$dofw,1);
+            } else {
+              $date=&Date_GetNext($date,$dofw,0);
+            }
+          }
+        }
+        last PARSE;
+
+      } elsif (/^$last$day$of\s*$month(?:$of?\s*$YY)?/i) {
+        # last day in month
+        ($m,$y)=($1,$2);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $y=&Date_FixYear($y)  if (! defined $y  or  length($y)<4);
+        $m=$month{lc($m)};
+        $d=&Date_DaysInMonth($m,$y);
+        last PARSE;
+
+      } elsif (/^$week$/i) {
+        # friday
+        ($dofw)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&Date_GetPrev($Curr{"Now"},$Cnf{"FirstDay"},1);
+        $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s);
+        last PARSE;
+
+      } elsif (/^$next\s*$week$/i) {
+        # next friday
+        ($dofw)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&Date_GetNext($Curr{"Now"},$dofw,0,$h,$mn,$s);
+        last PARSE;
+
+      } elsif (/^$prev\s*$week$/i) {
+        # last friday
+        ($dofw)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&Date_GetPrev($Curr{"Now"},$dofw,0,$h,$mn,$s);
+        last PARSE;
+
+      } elsif (/^$next$wkabb$/i) {
+        # next week
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:1:0:0:0:0",\$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+      } elsif (/^$prev$wkabb$/i) {
+        # last week
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:1:0:0:0:0",\$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+
+      } elsif (/^$next$mabb$/i) {
+        # next month
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"+0:1:0:0:0:0:0",\$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+      } elsif (/^$prev$mabb$/i) {
+        # last month
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"-0:1:0:0:0:0:0",\$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+
+      } elsif (/^$future\s*(\d+)$day$/i  ||
+               /^(\d+)$day$later$/i) {
+        # in 2 days
+        # 2 days later
+        ($num)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:0:$num:0:0:0",
+                                  \$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+      } elsif (/^(\d+)$day$past$/i) {
+        # 2 days ago
+        ($num)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:0:$num:0:0:0",
+                                 \$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+
+      } elsif (/^$future\s*(\d+)$wkabb$/i  ||
+               /^(\d+)$wkabb$later$/i) {
+        # in 2 weeks
+        # 2 weeks later
+        ($num)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:$num:0:0:0:0",
+                                  \$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+      } elsif (/^(\d+)$wkabb$past$/i) {
+        # 2 weeks ago
+        ($num)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:$num:0:0:0:0",
+                                 \$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+
+      } elsif (/^$future\s*(\d+)$mabb$/i  ||
+               /^(\d+)$mabb$later$/i) {
+        # in 2 months
+        # 2 months later
+        ($num)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"+0:$num:0:0:0:0:0",
+                                  \$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+      } elsif (/^(\d+)$mabb$past$/i) {
+        # 2 months ago
+        ($num)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},"-0:$num:0:0:0:0:0",
+                                  \$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
+        last PARSE;
+
+      } elsif (/^$week$future\s*(\d+)$wkabb$/i  ||
+               /^$week\s*(\d+)$wkabb$later$/i) {
+        # friday in 2 weeks
+        # friday 2 weeks later
+        ($dofw,$num)=($1,$2);
+        $tmp="+";
+      } elsif (/^$week\s*(\d+)$wkabb$past$/i) {
+        # friday 2 weeks ago
+        ($dofw,$num)=($1,$2);
+        $tmp="-";
+      } elsif (/^$future\s*(\d+)$wkabb$on$week$/i  ||
+               /^(\d+)$wkabb$later$on$week$/i) {
+        # in 2 weeks on friday
+        # 2 weeks later on friday
+        ($num,$dofw)=($1,$2);
+        $tmp="+"
+      } elsif (/^(\d+)$wkabb$past$on$week$/i) {
+        # 2 weeks ago on friday
+        ($num,$dofw)=($1,$2);
+        $tmp="-";
+      } elsif (/^$week\s*$wkabb$/i) {
+        # monday week    (British date: in 1 week on monday)
+        $dofw=$1;
+        $num=1;
+        $tmp="+";
+      } elsif ( (/^$now\s*$wkabb$/i   &&  ($tmp="Now"))  ||
+                (/^$today\s*$wkabb$/i &&  ($tmp="Today")) ) {
+        # now week     (British date: 1 week from now)
+        # today week   (British date: 1 week from today)
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{$tmp},"+0:0:1:0:0:0:0",\$err,0);
+        $date=&Date_SetTime($date,$h,$mn,$s)  if ($time);
+        last PARSE;
+      } elsif (/^$offset\s*$wkabb$/i) {
+        # tomorrow week  (British date: 1 week from tomorrow)
+        ($offset)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $offset=$Lang{$L}{"OffsetH"}{lc($offset)};
+        $date=&DateCalc_DateDelta($Curr{"Now"},$offset,\$err,0);
+        $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0);
+        if ($time) {
+          return ""
+            if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
+          $date=&Date_SetTime($date,$h,$mn,$s);
+        }
+        last PARSE;
+      }
+
+      if ($tmp) {
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=&DateCalc_DateDelta($Curr{"Now"},
+                                  $tmp . "0:0:$num:0:0:0:0",\$err,0);
+        $date=&Date_GetPrev($date,$Cnf{"FirstDay"},1);
+        $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s);
+        last PARSE;
+      }
+    }
+
+    # Change (2nd, second) to 2
+    $tmp=0;
+    if (/(^|[^a-z0-9])$dom($|[^a-z0-9])/i) {
+      if (/^\s*$dom\s*$/) {
+        ($d)=($1);
+        $d=$dom{lc($d)};
+        $m=$Curr{"M"};
+        last PARSE;
+      }
+      my $from = $2;
+      my $to   = $dom{ lc($from) };
+      s/(^|[^a-z])$from($|[^a-z])/$1 $to $2/i;
+      s/^\s+//;
+      s/\s+$//;
+    }
+
+    # Another set of special dates (Nth week)
+    if (/^$D\s*$week(?:$of?\s*$YY)?$/i) {
+      # 22nd sunday in 1996
+      ($which,$dofw,$y)=($1,$2,$3);
+      $y=$Curr{"Y"}  if (! $y);
+      $y--; # previous year
+      $tmp=&Date_GetNext("$y-12-31",$dofw,0);
+      if ($which>1) {
+        $tmp=&DateCalc_DateDelta($tmp,"+0:0:".($which-1).":0:0:0:0",\$err,0);
+      }
+      ($y,$m,$d)=(&Date_Split($tmp, 1))[0..2];
+      last PARSE;
+    } elsif (/^$week$wkabb\s*$D(?:$of?\s*$YY)?$/i  ||
+             /^$week\s*$D$wkabb(?:$of?\s*$YY)?$/i) {
+      # sunday week 22 in 1996
+      # sunday 22nd week in 1996
+      ($dofw,$which,$y)=($1,$2,$3);
+      ($y,$m,$d)=&Date_NthWeekOfYear($y,$which,$dofw);
+      last PARSE;
+    }
+
+    # Get rid of day of week
+    if (/(^|[^a-z])$week($|[^a-z])/i) {
+      $wk=$2;
+      (s/(^|[^a-z])$week,/$1 /i) ||
+        s/(^|[^a-z])$week($|[^a-z])/$1 $3/i;
+      s/^\s+//;
+      s/\s+$//;
+    }
+
+    {
+      # So that we can handle negative epoch times, let's convert
+      # things like "epoch -" to "epochNEGATIVE " before we strip out
+      # the $sep chars, which include '-'.
+      s,epoch\s*-,epochNEGATIVE ,g;
+
+      # Non-ISO8601 dates
+      s,\s*$sep\s*, ,g;     # change all non-ISO8601 seps to spaces
+      s,^\s*,,;             # remove leading/trailing space
+      s,\s*$,,;
+
+      if (/^$D\s+$D(?:\s+$YY)?$/) {
+        # MM DD YY (DD MM YY non-US)
+        ($m,$d,$y)=($1,$2,$3);
+        ($m,$d)=($d,$m)  if ($type ne "US");
+        last PARSE;
+
+      } elsif (/^$D4\s*$D\s*$D$/) {
+        # YYYY MM DD
+        ($y,$m,$d)=($1,$2,$3);
+        last PARSE;
+
+      } elsif (s/(^|[^a-z])$month($|[^a-z])/$1 $3/i) {
+        ($m)=($2);
+
+        if (/^\s*$D(?:\s+$YY)?\s*$/) {
+          # mmm DD YY
+          # DD mmm YY
+          # DD YY mmm
+          ($d,$y)=($1,$2);
+          last PARSE;
+
+        } elsif (/^\s*$D$D4\s*$/) {
+          # mmm DD YYYY
+          # DD mmm YYYY
+          # DD YYYY mmm
+          ($d,$y)=($1,$2);
+          last PARSE;
+
+        } elsif (/^\s*$D4\s*$D\s*$/) {
+          # mmm YYYY DD
+          # YYYY mmm DD
+          # YYYY DD mmm
+          ($y,$d)=($1,$2);
+          last PARSE;
+
+        } elsif (/^\s*$D4\s*$/) {
+          # mmm YYYY
+          # YYYY mmm
+          ($y,$d)=($1,1);
+          last PARSE;
+
+        } else {
+          return "";
+        }
+
+      } elsif (/^epochNEGATIVE (\d+)$/) {
+        $s=$1;
+        $date=&DateCalc("1970-01-01 00:00 GMT","-0:0:$s");
+      } elsif (/^epoch\s*(\d+)$/i) {
+        $s=$1;
+        $date=&DateCalc("1970-01-01 00:00 GMT","+0:0:$s");
+
+      } elsif ( (/^$now$/i   &&  ($tmp="Now"))  ||
+                (/^$today$/i &&  ($tmp="Today")) ) {
+        # now, today
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $date=$Curr{$tmp};
+        if ($time) {
+          return ""
+            if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
+          $date=&Date_SetTime($date,$h,$mn,$s);
+        }
+        last PARSE;
+
+      } elsif (/^$offset$/i) {
+        # yesterday, tomorrow
+        ($offset)=($1);
+        &Date_Init()  if (! $Cnf{"UpdateCurrTZ"});
+        $offset=$Lang{$L}{"OffsetH"}{lc($offset)};
+        $date=&DateCalc_DateDelta($Curr{"Now"},$offset,\$err,0);
+        if ($time) {
+          return ""
+            if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
+          $date=&Date_SetTime($date,$h,$mn,$s);
+        }
+        last PARSE;
+
+      } else {
+        return "";
+      }
+    }
+  }
+
+  if (! $date) {
+    return ""  if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
+    $date=&Date_Join($y,$m,$d,$h,$mn,$s);
+  }
+  $date=&Date_ConvTZ($date,$z);
+  if ($midnight) {
+    $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0");
+  }
+  return $date;
+}
+
+sub ParseDate {
+  print "DEBUG: ParseDate\n"  if ($Curr{"Debug"} =~ /trace/);
+  &Date_Init()  if (! $Curr{"InitDone"});
+  my($args,@args,@a,$ref,$date)=();
+  @a=@_;
+
+  # @a : is the list of args to ParseDate.  Currently, only one argument
+  #      is allowed and it must be a scalar (or a reference to a scalar)
+  #      or a reference to an array.
+
+  if ($#a!=0) {
+    print "ERROR:  Invalid number of arguments to ParseDate.\n";
+    return "";
+  }
+  $args=$a[0];
+  $ref=ref $args;
+  if (! $ref) {
+    return $args  if (&Date_Split($args));
+    @args=($args);
+  } elsif ($ref eq "ARRAY") {
+    @args=@$args;
+  } elsif ($ref eq "SCALAR") {
+    return $$args  if (&Date_Split($$args));
+    @args=($$args);
+  } else {
+    print "ERROR:  Invalid arguments to ParseDate.\n";
+    return "";
+  }
+  @a=@args;
+
+  # @args : a list containing all the arguments (dereferenced if appropriate)
+  # @a    : a list containing all the arguments currently being examined
+  # $ref  : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a
+  #         reference to a scalar, or a reference to an array was passed in
+  # $args : the scalar or refererence passed in
+
+ PARSE: while($#a>=0) {
+    $date=join(" ",@a);
+    $date=&ParseDateString($date);
+    last  if ($date);
+    pop(@a);
+  } # PARSE
+
+  splice(@args,0,$#a + 1);
+  @$args= @args  if (defined $ref  and  $ref eq "ARRAY");
+  $date;
+}
+
+sub Date_Cmp {
+  my($D1,$D2)=@_;
+  my($date1)=&ParseDateString($D1);
+  my($date2)=&ParseDateString($D2);
+  return $date1 cmp $date2;
+}
+
+# **NOTE**
+# The calc routines all call parse routines, so it is never necessary to
+# call Date_Init in the calc routines.
+sub DateCalc {
+  print "DEBUG: DateCalc\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($D1,$D2,@arg)=@_;
+  my($ref,$err,$errref,$mode)=();
+
+  ($errref,$mode) = (@arg);
+  $ref=0;
+
+  if (defined $errref) {
+    if (ref $errref) {
+      $ref=1;
+    } elsif (! defined $mode) {
+      $mode=$errref;
+      $errref="";
+    }
+  }
+
+  my(@date,@delta,$ret,$tmp,$oldincalc,$oldmode)=();
+
+  if (exists $Curr{"Mode"}) {
+    $oldmode = $Curr{"Mode"};
+  } else {
+    $oldmode = 0;
+  }
+
+  if (defined $mode  and  $mode>=0  and  $mode<=3) {
+    $Curr{"Mode"}=$mode;
+  } else {
+    $Curr{"Mode"}=0;
+  }
+
+  if (exists $Curr{"InCalc"}) {
+    $oldincalc = $Curr{"InCalc"};
+  } else {
+    $oldincalc = 0;
+  }
+  $Curr{"InCalc"}=1;
+
+  if ($tmp=&ParseDateString($D1)) {
+    # If we've already parsed the date, we don't want to do it a second
+    # time (so we don't convert timezones twice).
+    if (&Date_Split($D1)) {
+      push(@date,$D1);
+    } else {
+      push(@date,$tmp);
+    }
+  } elsif ($tmp=&ParseDateDelta($D1)) {
+    push(@delta,$tmp);
+  } else {
+    $$errref=1  if ($ref);
+    $Curr{"InCalc"} = $oldincalc;
+    $Curr{"Mode"}   = $oldmode;
+    return;
+  }
+
+  if ($tmp=&ParseDateString($D2)) {
+    if (&Date_Split($D2)) {
+      push(@date,$D2);
+    } else {
+      push(@date,$tmp);
+    }
+  } elsif ($tmp=&ParseDateDelta($D2)) {
+    push(@delta,$tmp);
+    $mode = $Curr{"Mode"};
+  } else {
+    $$errref=2  if ($ref);
+    $Curr{"InCalc"} = $oldincalc;
+    $Curr{"Mode"}   = $oldmode;
+    return;
+  }
+
+  $Curr{"InCalc"} = $oldincalc;
+  $Curr{"Mode"}   = $oldmode;
+
+  if ($#date==1) {
+    $ret=&DateCalc_DateDate(@date,$mode);
+  } elsif ($#date==0) {
+    $ret=&DateCalc_DateDelta(@date,@delta,\$err,$mode);
+    $$errref=$err  if ($ref);
+  } else {
+    $ret=&DateCalc_DeltaDelta(@delta,$mode);
+  }
+  $ret;
+}
+
+sub ParseDateDelta {
+  print "DEBUG: ParseDateDelta\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($args,@args,@a,$ref)=();
+  local($_)=();
+  @a=@_;
+
+  # @a : is the list of args to ParseDateDelta.  Currently, only one argument
+  #      is allowed and it must be a scalar (or a reference to a scalar)
+  #      or a reference to an array.
+
+  if ($#a!=0) {
+    print "ERROR:  Invalid number of arguments to ParseDateDelta.\n";
+    return "";
+  }
+  $args=$a[0];
+  $ref=ref $args;
+  if (! $ref) {
+    @args=($args);
+  } elsif ($ref eq "ARRAY") {
+    @args=@$args;
+  } elsif ($ref eq "SCALAR") {
+    @args=($$args);
+  } else {
+    print "ERROR:  Invalid arguments to ParseDateDelta.\n";
+    return "";
+  }
+  @a=@args;
+
+  # @args : a list containing all the arguments (dereferenced if appropriate)
+  # @a    : a list containing all the arguments currently being examined
+  # $ref  : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a
+  #         reference to a scalar, or a reference to an array was passed in
+  # $args : the scalar or refererence passed in
+
+  my(@colon,@delta,$delta,$dir,$colon,$sign,$val)=();
+  my($len,$tmp,$tmp2,$tmpl)=();
+  my($from,$to)=();
+  my($workweek)=$Cnf{"WorkWeekEnd"}-$Cnf{"WorkWeekBeg"}+1;
+
+  &Date_Init()  if (! $Curr{"InitDone"});
+  # A sign can be a sequence of zero or more + and - signs, this
+  # allows for deltas like '+ -2 days'.
+  my($signexp)='((?:[+-]\s*)*)';
+  my($numexp)='(\d+)';
+  my($exp1)="(?: \\s* $signexp \\s* $numexp \\s*)";
+  my($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp,$i)=();
+  $yexp=$mexp=$wexp=$dexp=$hexp=$mnexp=$sexp="()()";
+  $yexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Yabb"} .")?";
+  $mexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Mabb"} .")?";
+  $wexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Wabb"} .")?";
+  $dexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Dabb"} .")?";
+  $hexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Habb"} .")?";
+  $mnexp="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"MNabb"}.")?";
+  $sexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Sabb"} ."?)?";
+  my($future)=$Lang{$Cnf{"Language"}}{"Future"};
+  my($later)=$Lang{$Cnf{"Language"}}{"Later"};
+  my($past)=$Lang{$Cnf{"Language"}}{"Past"};
+
+  $delta="";
+ PARSE: while (@a) {
+    $_ = join(" ", grep {defined;} @a);
+    s/\s+$//;
+    last  if ($_ eq "");
+
+    # Mode is set in DateCalc.  ParseDateDelta only overrides it if the
+    # string contains a mode.
+    if      ($Lang{$Cnf{"Language"}}{"Exact"} &&
+             s/$Lang{$Cnf{"Language"}}{"Exact"}//) {
+      $Curr{"Mode"}=0;
+    } elsif ($Lang{$Cnf{"Language"}}{"Approx"} &&
+             s/$Lang{$Cnf{"Language"}}{"Approx"}//) {
+      $Curr{"Mode"}=1;
+    } elsif ($Lang{$Cnf{"Language"}}{"Business"} &&
+             s/$Lang{$Cnf{"Language"}}{"Business"}//) {
+      $Curr{"Mode"}=2;
+    } elsif (! exists $Curr{"Mode"}) {
+      $Curr{"Mode"}=0;
+    }
+    $workweek=7  if ($Curr{"Mode"} != 2);
+
+    foreach $from (keys %{ $Lang{$Cnf{"Language"}}{"Repl"} }) {
+      $to=$Lang{$Cnf{"Language"}}{"Repl"}{$from};
+      s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i;
+    }
+
+    # in or ago
+    #
+    # We need to make sure that $later, $future, and $past don't contain each
+    # other... Romanian pointed this out where $past is "in urma" and $future
+    # is "in".  When they do, we have to take this into account.
+    #   $len  length of best match (greatest wins)
+    #   $tmp  string after best match
+    #   $dir  direction (prior, after) of best match
+    #
+    #   $tmp2 string before/after current match
+    #   $tmpl length of current match
+
+    $len=0;
+    $tmp=$_;
+    $dir=1;
+
+    $tmp2=$_;
+    if ($tmp2 =~ s/(^|[^a-z])($future)($|[^a-z])/$1 $3/i) {
+      $tmpl=length($2);
+      if ($tmpl>$len) {
+        $tmp=$tmp2;
+        $dir=1;
+        $len=$tmpl;
+      }
+    }
+
+    $tmp2=$_;
+    if ($tmp2 =~ s/(^|[^a-z])($later)($|[^a-z])/$1 $3/i) {
+      $tmpl=length($2);
+      if ($tmpl>$len) {
+        $tmp=$tmp2;
+        $dir=1;
+        $len=$tmpl;
+      }
+    }
+
+    $tmp2=$_;
+    if ($tmp2 =~ s/(^|[^a-z])($past)($|[^a-z])/$1 $3/i) {
+      $tmpl=length($2);
+      if ($tmpl>$len) {
+        $tmp=$tmp2;
+        $dir=-1;
+        $len=$tmpl;
+      }
+    }
+
+    $_ = $tmp;
+    s/\s*$//;
+
+    # the colon part of the delta
+    $colon="";
+    if (s/($signexp?$numexp?(:($signexp?$numexp)?){1,6})$//) {
+      $colon=$1;
+      s/\s+$//;
+    }
+    @colon=split(/:/,$colon);
+
+    # the non-colon part of the delta
+    $sign="+";
+    @delta=();
+    $i=6;
+    foreach $exp1 ($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp) {
+      last  if ($#colon>=$i--);
+      $val=0;
+      if (s/^$exp1//ix) {
+        $val=$2   if ($2);
+        $sign=$1  if ($1);
+      }
+
+      # Collapse a sign like '+ -' into a single character like '-',
+      # by counting the occurrences of '-'.
+      #
+      $sign =~ s/\s+//g;
+      $sign =~ tr/+//d;
+      my $count = ($sign =~ tr/-//d);
+      die "bad characters in sign: $sign" if length $sign;
+      $sign = $count % 2 ? '-' : '+';
+
+      push(@delta,"$sign$val");
+    }
+    if (! /^\s*$/) {
+      pop(@a);
+      next PARSE;
+    }
+
+    # make sure that the colon part has a sign
+    for ($i=0; $i<=$#colon; $i++) {
+      $val=0;
+      if ($colon[$i] =~ /^$signexp$numexp?/) {
+        $val=$2   if ($2);
+        $sign=$1  if ($1);
+      }
+      $colon[$i] = "$sign$val";
+    }
+
+    # combine the two
+    push(@delta,@colon);
+    if ($dir<0) {
+      for ($i=0; $i<=$#delta; $i++) {
+        $delta[$i] =~ tr/-+/+-/;
+      }
+    }
+
+    # form the delta and shift off the valid part
+    $delta=join(":",@delta);
+    splice(@args,0,$#a+1);
+    @$args=@args  if (defined $ref  and  $ref eq "ARRAY");
+    last PARSE;
+  }
+
+  $delta=&Delta_Normalize($delta,$Curr{"Mode"});
+  return $delta;
+}
+
+sub UnixDate {
+  print "DEBUG: UnixDate\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,@format)=@_;
+  local($_)=();
+  my($format,%f,$out,@out,$c,$date1,$date2,$tmp)=();
+  my($scalar)=();
+  $date=&ParseDateString($date);
+  return  if (! $date);
+
+  my($y,$m,$d,$h,$mn,$s)=($f{"Y"},$f{"m"},$f{"d"},$f{"H"},$f{"M"},$f{"S"})=
+    &Date_Split($date, 1);
+  $f{"y"}=substr $f{"Y"},2;
+  &Date_Init()  if (! $Curr{"InitDone"});
+
+  if (! wantarray) {
+    $format=join(" ",@format);
+    @format=($format);
+    $scalar=1;
+  }
+
+  # month, week
+  $_=$m;
+  s/^0//;
+  $f{"b"}=$f{"h"}=$Lang{$Cnf{"Language"}}{"MonL"}[$_-1];
+  $f{"B"}=$Lang{$Cnf{"Language"}}{"MonthL"}[$_-1];
+  $_=$m;
+  s/^0/ /;
+  $f{"f"}=$_;
+  $f{"U"}=&Date_WeekOfYear($m,$d,$y,7);
+  $f{"W"}=&Date_WeekOfYear($m,$d,$y,1);
+
+  # check week 52,53 and 0
+  $f{"G"}=$f{"L"}=$y;
+  if ($f{"W"}>=52 || $f{"U"}>=52) {
+    my($dd,$mm,$yy)=($d,$m,$y);
+    $dd+=7;
+    if ($dd>31) {
+      $dd-=31;
+      $mm=1;
+      $yy++;
+      if (&Date_WeekOfYear($mm,$dd,$yy,1)==2) {
+        $f{"G"}=$yy;
+        $f{"W"}=1;
+      }
+      if (&Date_WeekOfYear($mm,$dd,$yy,7)==2) {
+        $f{"L"}=$yy;
+        $f{"U"}=1;
+      }
+    }
+  }
+  if ($f{"W"}==0) {
+    my($dd,$mm,$yy)=($d,$m,$y);
+    $dd-=7;
+    $dd+=31  if ($dd<1);
+    $yy = sprintf "%04d", $yy-1;
+    $mm=12;
+    $f{"G"}=$yy;
+    $f{"W"}=&Date_WeekOfYear($mm,$dd,$yy,1)+1;
+  }
+  if ($f{"U"}==0) {
+    my($dd,$mm,$yy)=($d,$m,$y);
+    $dd-=7;
+    $dd+=31  if ($dd<1);
+    $yy = sprintf "%04d", $yy-1;
+    $mm=12;
+    $f{"L"}=$yy;
+    $f{"U"}=&Date_WeekOfYear($mm,$dd,$yy,7)+1;
+  }
+
+  $f{"U"}="0".$f{"U"}  if (length $f{"U"} < 2);
+  $f{"W"}="0".$f{"W"}  if (length $f{"W"} < 2);
+
+  # day
+  $f{"j"}=&Date_DayOfYear($m,$d,$y);
+  $f{"j"} = "0" . $f{"j"}   while (length($f{"j"})<3);
+  $_=$d;
+  s/^0/ /;
+  $f{"e"}=$_;
+  $f{"w"}=&Date_DayOfWeek($m,$d,$y);
+  $f{"v"}=$Lang{$Cnf{"Language"}}{"WL"}[$f{"w"}-1];
+  $f{"v"}=" ".$f{"v"}  if (length $f{"v"} < 2);
+  $f{"a"}=$Lang{$Cnf{"Language"}}{"WkL"}[$f{"w"}-1];
+  $f{"A"}=$Lang{$Cnf{"Language"}}{"WeekL"}[$f{"w"}-1];
+  $f{"E"}=&Date_DaySuffix($f{"e"});
+
+  # hour
+  $_=$h;
+  s/^0/ /;
+  $f{"k"}=$_;
+  $f{"i"}=$f{"k"}+1;
+  $f{"i"}=$f{"k"};
+  $f{"i"}=12          if ($f{"k"}==0);
+  $f{"i"}=$f{"k"}-12  if ($f{"k"}>12);
+  $f{"i"}=$f{"i"}-12  if ($f{"i"}>12);
+  $f{"i"}=" ".$f{"i"} if (length($f{"i"})<2);
+  $f{"I"}=$f{"i"};
+  $f{"I"}=~ s/^ /0/;
+  $f{"p"}=$Lang{$Cnf{"Language"}}{"AMstr"};
+  $f{"p"}=$Lang{$Cnf{"Language"}}{"PMstr"}  if ($f{"k"}>11);
+
+  # minute, second, timezone
+  $f{"o"}=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s);
+  $f{"s"}=&Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s);
+  $f{"Z"}=($Cnf{"ConvTZ"} eq "IGNORE" or $Cnf{"ConvTZ"} eq "") ?
+           $Cnf{"TZ"} : $Cnf{"ConvTZ"};
+  $f{"z"}=($f{"Z"}=~/^[+-]\d{4}/) ? $f{"Z"} : ($Zone{"n2o"}{lc $f{"Z"}} || "");
+
+  # date, time
+  $f{"c"}=qq|$f{"a"} $f{"b"} $f{"e"} $h:$mn:$s $y|;
+  $f{"C"}=$f{"u"}=
+    qq|$f{"a"} $f{"b"} $f{"e"} $h:$mn:$s $f{"z"} $y|;
+  $f{"g"}=qq|$f{"a"}, $d $f{"b"} $y $h:$mn:$s $f{"z"}|;
+  $f{"D"}=$f{"x"}=qq|$m/$d/$f{"y"}|;
+  $f{"x"}=qq|$d/$m/$f{"y"}|  if ($Cnf{"DateFormat"} ne "US");
+  $f{"r"}=qq|$f{"I"}:$mn:$s $f{"p"}|;
+  $f{"R"}=qq|$h:$mn|;
+  $f{"T"}=$f{"X"}=qq|$h:$mn:$s|;
+  $f{"V"}=qq|$m$d$h$mn$f{"y"}|;
+  $f{"Q"}="$y$m$d";
+  $f{"q"}=qq|$y$m$d$h$mn$s|;
+  $f{"P"}=qq|$y$m$d$h:$mn:$s|;
+  $f{"O"}=qq|$y-$m-${d}T$h:$mn:$s|;
+  $f{"F"}=qq|$f{"A"}, $f{"B"} $f{"e"}, $f{"Y"}|;
+  if ($f{"W"}==0) {
+    $y--;
+    $tmp=&Date_WeekOfYear(12,31,$y,1);
+    $tmp="0$tmp"  if (length($tmp) < 2);
+    $f{"J"}=qq|$y-W$tmp-$f{"w"}|;
+  } else {
+    $f{"J"}=qq|$f{"G"}-W$f{"W"}-$f{"w"}|;
+  }
+  $f{"K"}=qq|$y-$f{"j"}|;
+  # %l is a special case.  Since it requires the use of the calculator
+  # which requires this routine, an infinite recursion results.  To get
+  # around this, %l is NOT determined every time this is called so the
+  # recursion breaks.
+
+  # other formats
+  $f{"n"}="\n";
+  $f{"t"}="\t";
+  $f{"%"}="%";
+  $f{"+"}="+";
+
+  foreach $format (@format) {
+    $format=reverse($format);
+    $out="";
+    while ($format ne "") {
+      $c=chop($format);
+      if ($c eq "%") {
+        $c=chop($format);
+        if ($c eq "l") {
+          &Date_Init();
+          $date1=&DateCalc_DateDelta($Curr{"Now"},"-0:6:0:0:0:0:0");
+          $date2=&DateCalc_DateDelta($Curr{"Now"},"+0:6:0:0:0:0:0");
+          if (&Date_Cmp($date,$date1)>=0  &&  &Date_Cmp($date,$date2)<=0) {
+            $f{"l"}=qq|$f{"b"} $f{"e"} $h:$mn|;
+          } else {
+            $f{"l"}=qq|$f{"b"} $f{"e"}  $f{"Y"}|;
+          }
+          $out .= $f{"$c"};
+        } elsif (exists $f{"$c"}) {
+          $out .= $f{"$c"};
+        } else {
+          $out .= $c;
+        }
+      } else {
+        $out .= $c;
+      }
+    }
+    push(@out,$out);
+  }
+  if ($scalar) {
+    return $out[0];
+  } else {
+    return (@out);
+  }
+}
+
+# Can't be in "use integer" because we're doing decimal arithmatic
+no integer;
+sub Delta_Format {
+  print "DEBUG: Delta_Format\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($delta,@arg)=@_;
+  my($mode);
+  if (lc($arg[0]) eq "approx") {
+    $mode = "approx";
+    shift(@arg);
+  } else {
+    $mode = "exact";
+  }
+  my($dec,@format) = @arg;
+
+  $delta=&ParseDateDelta($delta);
+  return ""  if (! $delta);
+  my(@out,%f,$out,$c1,$c2,$scalar,$format)=();
+  local($_)=$delta;
+  my($y,$M,$w,$d,$h,$m,$s)=&Delta_Split($delta);
+  # Get rid of positive signs.
+  ($y,$M,$w,$d,$h,$m,$s)=map { 1*$_; }($y,$M,$w,$d,$h,$m,$s);
+
+  if (defined $dec  &&  $dec>0) {
+    $dec="%." . ($dec*1) . "f";
+  } else {
+    $dec="%f";
+  }
+
+  if (! wantarray) {
+    $format=join(" ",@format);
+    @format=($format);
+    $scalar=1;
+  }
+
+  # Length of each unit in seconds
+  my($sl,$ml,$hl,$dl,$wl,$Ml,$yl)=();
+  $sl = 1;
+  $ml = $sl*60;
+  $hl = $ml*60;
+  $dl = $hl*24;
+  $wl = $dl*7;
+  $yl = $dl*365.25;
+  $Ml = $yl/12;
+
+  # The decimal amount of each unit contained in all smaller units
+  my($yd,$Md,$sd,$md,$hd,$dd,$wd)=();
+  if ($mode eq "exact") {
+    $yd = $M/12;
+    $Md = 0;
+  } else {
+    $yd = ($M*$Ml + $w*$wl + $d*$dl + $h*$hl + $m*$ml + $s*$sl)/$yl;
+    $Md =          ($w*$wl + $d*$dl + $h*$hl + $m*$ml + $s*$sl)/$Ml;
+  }
+
+  $wd = ($d*$dl + $h*$hl + $m*$ml + $s*$sl)/$wl;
+  $dd =          ($h*$hl + $m*$ml + $s*$sl)/$dl;
+  $hd =                   ($m*$ml + $s*$sl)/$hl;
+  $md =                            ($s*$sl)/$ml;
+  $sd = 0;
+
+  # The amount of each unit contained in higher units.
+  my($yh,$Mh,$sh,$mh,$hh,$dh,$wh)=();
+  $yh = 0;
+  $Mh = ($yh+$y)*12;
+
+  if ($mode eq "exact") {
+    $wh = 0;
+    $dh = ($wh+$w)*7;
+  } else {
+    $wh = ($yh+$y+$M/12)*365.25/7;
+    $dh = ($wh+$w)*7;
+  }
+
+  $hh = ($dh+$d)*24;
+  $mh = ($hh+$h)*60;
+  $sh = ($mh+$m)*60;
+
+  # Set up the formats
+
+  $f{"yv"} = $y;
+  $f{"Mv"} = $M;
+  $f{"wv"} = $w;
+  $f{"dv"} = $d;
+  $f{"hv"} = $h;
+  $f{"mv"} = $m;
+  $f{"sv"} = $s;
+
+  $f{"yh"} = $y+$yh;
+  $f{"Mh"} = $M+$Mh;
+  $f{"wh"} = $w+$wh;
+  $f{"dh"} = $d+$dh;
+  $f{"hh"} = $h+$hh;
+  $f{"mh"} = $m+$mh;
+  $f{"sh"} = $s+$sh;
+
+  $f{"yd"} = sprintf($dec,$y+$yd);
+  $f{"Md"} = sprintf($dec,$M+$Md);
+  $f{"wd"} = sprintf($dec,$w+$wd);
+  $f{"dd"} = sprintf($dec,$d+$dd);
+  $f{"hd"} = sprintf($dec,$h+$hd);
+  $f{"md"} = sprintf($dec,$m+$md);
+  $f{"sd"} = sprintf($dec,$s+$sd);
+
+  $f{"yt"} = sprintf($dec,$yh+$y+$yd);
+  $f{"Mt"} = sprintf($dec,$Mh+$M+$Md);
+  $f{"wt"} = sprintf($dec,$wh+$w+$wd);
+  $f{"dt"} = sprintf($dec,$dh+$d+$dd);
+  $f{"ht"} = sprintf($dec,$hh+$h+$hd);
+  $f{"mt"} = sprintf($dec,$mh+$m+$md);
+  $f{"st"} = sprintf($dec,$sh+$s+$sd);
+
+  $f{"%"}  = "%";
+
+  foreach $format (@format) {
+    $format=reverse($format);
+    $out="";
+  PARSE: while ($format) {
+      $c1=chop($format);
+      if ($c1 eq "%") {
+        $c1=chop($format);
+        if (exists($f{$c1})) {
+          $out .= $f{$c1};
+          next PARSE;
+        }
+        $c2=chop($format);
+        if (exists($f{"$c1$c2"})) {
+          $out .= $f{"$c1$c2"};
+          next PARSE;
+        }
+        $out .= $c1;
+        $format .= $c2;
+      } else {
+        $out .= $c1;
+      }
+    }
+    push(@out,$out);
+  }
+  if ($scalar) {
+    return $out[0];
+  } else {
+    return (@out);
+  }
+}
+use integer;
+
+sub ParseRecur {
+  print "DEBUG: ParseRecur\n"  if ($Curr{"Debug"} =~ /trace/);
+  &Date_Init()  if (! $Curr{"InitDone"});
+
+  my($recur,$dateb,$date0,$date1,$flag)=@_;
+  local($_)=$recur;
+
+  my($recur_0,$recur_1,@recur0,@recur1)=();
+  my(@tmp,$tmp,$each,$num,$y,$m,$d,$w,$h,$mn,$s,$delta,$y0,$y1,$yb)=();
+  my($yy,$n,$dd,@d,@tmp2,$date,@date,@w,@tmp3,@m,@y,$tmp2,$d2,@flags)=();
+
+  # $date0, $date1, $dateb, $flag : passed in (these are always the final say
+  #                                 in determining whether a date matches a
+  #                                 recurrence IF they are present.
+  # $date_b, $date_0, $date_1     : if a value can be determined from the
+  # $flag_t                         recurrence, they are stored here.
+  #
+  # If values can be determined from the recurrence AND are passed in, the
+  # following are used:
+  #    max($date0,$date_0)    i.e. the later of the two dates
+  #    min($date1,$date_1)    i.e. the earlier of the two dates
+  #
+  # The base date that is used is the first one defined from
+  #    $dateb $date_b
+  # The base date is only used if necessary (as determined by the recur).
+  # For example, "every other friday" requires a base date, but "2nd
+  # friday of every month" doesn't.
+
+  my($date_b,$date_0,$date_1,$flag_t);
+
+  #
+  # Check the arguments passed in.
+  #
+
+  $date0=""  if (! defined $date0);
+  $date1=""  if (! defined $date1);
+  $dateb=""  if (! defined $dateb);
+  $flag =""  if (! defined $flag);
+
+  if ($dateb) {
+    $dateb=&ParseDateString($dateb);
+    return ""  if (! $dateb);
+  }
+  if ($date0) {
+    $date0=&ParseDateString($date0);
+    return ""  if (! $date0);
+  }
+  if ($date1) {
+    $date1=&ParseDateString($date1);
+    return ""  if (! $date1);
+  }
+
+  #
+  # Parse the recur.  $date_b, $date_0, and $date_e are values obtained
+  # from the recur.
+  #
+
+  @tmp=&Recur_Split($_);
+
+  if (@tmp) {
+    ($recur_0,$recur_1,$flag_t,$date_b,$date_0,$date_1)=@tmp;
+    $recur_0 = ""  if (! defined $recur_0);
+    $recur_1 = ""  if (! defined $recur_1);
+    $flag_t  = ""  if (! defined $flag_t);
+    $date_b  = ""  if (! defined $date_b);
+    $date_0  = ""  if (! defined $date_0);
+    $date_1  = ""  if (! defined $date_1);
+
+    @recur0 = split(/:/,$recur_0);
+    @recur1 = split(/:/,$recur_1);
+    return ""  if ($#recur0 + $#recur1 + 2 != 7);
+
+    if ($date_b) {
+      $date_b=&ParseDateString($date_b);
+      return ""  if (! $date_b);
+    }
+    if ($date_0) {
+      $date_0=&ParseDateString($date_0);
+      return ""  if (! $date_0);
+    }
+    if ($date_1) {
+      $date_1=&ParseDateString($date_1);
+      return ""  if (! $date_1);
+    }
+
+  } else {
+
+    my($mmm)='\s*'.$Lang{$Cnf{"Language"}}{"Month"};  # \s*(jan|january|...)
+    my(%mmm)=%{ $Lang{$Cnf{"Language"}}{"MonthH"} };  # { jan=>1, ... }
+    my($wkexp)='\s*'.$Lang{$Cnf{"Language"}}{"Week"}; # \s*(mon|monday|...)
+    my(%week)=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };  # { monday=>1, ... }
+    my($day)='\s*'.$Lang{$Cnf{"Language"}}{"Dabb"};   # \s*(?:d|day|days)
+    my($month)='\s*'.$Lang{$Cnf{"Language"}}{"Mabb"}; # \s*(?:mon|month|months)
+    my($week)='\s*'.$Lang{$Cnf{"Language"}}{"Wabb"};  # \s*(?:w|wk|week|weeks)
+    my($daysexp)=$Lang{$Cnf{"Language"}}{"DoM"};      # (1st|first|...31st)
+    my(%dayshash)=%{ $Lang{$Cnf{"Language"}}{"DoMH"} };
+                                                      # { 1st=>1,first=>1,...}
+    my($of)='\s*'.$Lang{$Cnf{"Language"}}{"Of"};      # \s*(?:in|of)
+    my($lastexp)=$Lang{$Cnf{"Language"}}{"Last"};     # (?:last)
+    my($each)=$Lang{$Cnf{"Language"}}{"Each"};        # (?:each|every)
+
+    my($D)='\s*(\d+)';
+    my($Y)='\s*(\d{4}|\d{2})';
+
+    # Change 1st to 1
+    if (/(^|[^a-z])$daysexp($|[^a-z])/i) {
+      $tmp=lc($2);
+      $tmp=$dayshash{"$tmp"};
+      s/(^|[^a-z])$daysexp($|[^a-z])/$1 $tmp $3/i;
+    }
+    s/\s*$//;
+
+    # Get rid of "each"
+    if (/(^|[^a-z])$each($|[^a-z])/i) {
+      s/(^|[^a-z])$each($|[^a-z])/$1 $2/i;
+      $each=1;
+    } else {
+      $each=0;
+    }
+
+    if ($each) {
+
+      if (/^$D?$day(?:$of$mmm?$Y)?$/i ||
+          /^$D?$day(?:$of$mmm())?$/i) {
+        # every [2nd] day in [june] 1997
+        # every [2nd] day [in june]
+        ($num,$m,$y)=($1,$2,$3);
+        $num=1 if (! defined $num);
+        $m=""  if (! defined $m);
+        $y=""  if (! defined $y);
+
+        $y=$Curr{"Y"}  if (! $y);
+        if ($m) {
+          $m=$mmm{lc($m)};
+          $date_0=&Date_Join($y,$m,1,0,0,0);
+          $date_1=&DateCalc_DateDelta($date_0,"+0:1:0:0:0:0:0",0);
+        } else {
+          $date_0=&Date_Join($y,  1,1,0,0,0);
+          $date_1=&Date_Join($y+1,1,1,0,0,0);
+        }
+        $date_b=&DateCalc($date_0,"-0:0:0:1:0:0:0",0);
+        @recur0=(0,0,0,$num,0,0,0);
+        @recur1=();
+
+      } elsif (/^$D$day?$of$month(?:$of?$Y)?$/) {
+        # 2nd [day] of every month [in 1997]
+        ($num,$y)=($1,$2);
+        $y=$Curr{"Y"}  if (! $y);
+
+        $date_0=&Date_Join($y,  1,1,0,0,0);
+        $date_1=&Date_Join($y+1,1,1,0,0,0);
+        $date_b=$date_0;
+
+        @recur0=(0,1,0);
+        @recur1=($num,0,0,0);
+
+      } elsif (/^$D$wkexp$of$month(?:$of?$Y)?$/ ||
+               /^($lastexp)$wkexp$of$month(?:$of?$Y)?$/) {
+        # 2nd tuesday of every month [in 1997]
+        # last tuesday of every month [in 1997]
+        ($num,$d,$y)=($1,$2,$3);
+        $y=$Curr{"Y"}  if (! $y);
+        $d=$week{lc($d)};
+        $num=-1  if ($num !~ /^$D$/);
+
+        $date_0=&Date_Join($y,1,1,0,0,0);
+        $date_1=&Date_Join($y+1,1,1,0,0,0);
+        $date_b=$date_0;
+
+        @recur0=(0,1);
+        @recur1=($num,$d,0,0,0);
+
+      } elsif (/^$D?$wkexp(?:$of$mmm?$Y)?$/i ||
+               /^$D?$wkexp(?:$of$mmm())?$/i) {
+        # every tuesday in june 1997
+        # every 2nd tuesday in june 1997
+        ($num,$d,$m,$y)=($1,$2,$3,$4);
+        $y=$Curr{"Y"}  if (! $y);
+        $num=1 if (! defined $num);
+        $m=""  if (! defined $m);
+        $d=$week{lc($d)};
+
+        if ($m) {
+          $m=$mmm{lc($m)};
+          $date_0=&Date_Join($y,$m,1,0,0,0);
+          $date_1=&DateCalc_DateDelta($date_0,"+0:1:0:0:0:0:0",0);
+        } else {
+          $date_0=&Date_Join($y,1,1,0,0,0);
+          $date_1=&Date_Join($y+1,1,1,0,0,0);
+        }
+        $date_b=&DateCalc($date_0,"-0:0:0:1:0:0:0",0);
+
+        @recur0=(0,0,$num);
+        @recur1=($d,0,0,0);
+
+      } else {
+        return "";
+      }
+
+      $date_0=""  if ($date0);
+      $date_1=""  if ($date1);
+    } else {
+      return "";
+    }
+  }
+
+  #
+  # Override with any values passed in
+  #
+
+  $date0 = $date_0  if (! $date0);
+  $date1 = $date_1  if (! $date1);
+  $dateb = $date_b  if (! $dateb);
+  if ($flag =~ s/^\+//) {
+    $flag = "$flag_t,$flag"  if ($flag_t);
+  }
+  $flag = $flag_t  if (! $flag);
+  $flag = ""  if (! $flag);
+
+  if (! wantarray) {
+    $tmp  = join(":",@recur0);
+    $tmp .= "*" . join(":",@recur1)  if (@recur1);
+    $tmp .= "*$flag*$dateb*$date0*$date1";
+    return $tmp;
+  }
+  if (@recur0) {
+    return ()  if (! $date0  ||  ! $date1); # dateb is NOT required in all case
+  }
+
+  #
+  # Some flags affect parsing.
+  #
+
+  @flags   = split(/,/,$flag);
+  my($f);
+  foreach $f (@flags) {
+    if ($f =~ /^EASTER$/i) {
+      ($y,$m,$w,$d,$h,$mn,$s)=(@recur0,@recur1);
+      # We want something that will return Jan 1 for the given years.
+      if ($#recur0==-1) {
+        @recur1=($y,1,0,1,$h,$mn,$s);
+      } elsif ($#recur0<=3) {
+        @recur0=($y,0,0,0);
+        @recur1=($h,$mn,$s);
+      } elsif ($#recur0==4) {
+        @recur0=($y,0,0,0,0);
+        @recur1=($mn,$s);
+      } elsif ($#recur0==5) {
+        @recur0=($y,0,0,0,0,0);
+        @recur1=($s);
+      } else {
+        @recur0=($y,0,0,0,0,0,0);
+      }
+    }
+  }
+
+  #
+  # Determine the dates referenced by the recur.  Also, fix the base date
+  # as necessary for the recurrences which require it.
+  #
+
+  ($y,$m,$w,$d,$h,$mn,$s)=(@recur0,@recur1);
+  @y=@m=@w=@d=();
+  my(@time)=($h,$mn,$s);
+
+ RECUR: while (1) {
+
+    if ($#recur0==-1) {
+      # * 0-M-W-D-H-MN-S   => 0 * M-W-D-H-MN-S
+
+      if ($y eq "0") {
+        push(@recur0,1);
+        shift(@recur1);
+        next RECUR;
+      }
+
+      # Y-M-W-D-H-MN-S
+
+      @y=&ReturnList($y);
+      foreach $y (@y) {
+        $y=&Date_FixYear($y)  if (length($y)==2);
+        return ()  if (length($y)!=4  ||  ! &IsInt($y));
+      }
+
+      $date0=&ParseDate("0000-01-01")          if (! $date0);
+      $date1=&ParseDate("9999-12-31 23:59:59") if (! $date1);
+
+      if ($m eq "0"  and  $w eq "0") {
+
+        # * Y-0-0-0-H-MN-S
+        # * Y-0-0-DOY-H-MN-S
+
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+          return ()  if (! @d);
+          foreach $d (@d) {
+            return ()  if (! &IsInt($d,-366,366)  ||  $d==0);
+          }
+        }
+
+        @date=();
+        foreach $yy (@y) {
+          my $diy = &Date_DaysInYear($yy);
+          foreach $d (@d) {
+            my $tmpd = $d;
+            $tmpd += ($diy+1)  if ($tmpd < 0);
+            next  if (! &IsInt($tmpd,1,$diy));
+            ($y,$m,$dd)=&Date_NthDayOfYear($yy,$tmpd);
+            push(@date, &Date_Join($y,$m,$dd,0,0,0));
+          }
+        }
+        last RECUR;
+
+      } elsif ($w eq "0") {
+
+        # * Y-M-0-0-H-MN-S
+        # * Y-M-0-DOM-H-MN-S
+
+        @m=&ReturnList($m);
+        return ()  if (! @m);
+        foreach $m (@m) {
+          return ()  if (! &IsInt($m,1,12));
+        }
+
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+          return ()  if (! @d);
+          foreach $d (@d) {
+            return ()  if (! &IsInt($d,-31,31)  ||  $d==0);
+          }
+        }
+
+        @date=();
+        foreach $y (@y) {
+          foreach $m (@m) {
+            my $dim = &Date_DaysInMonth($m,$y);
+            foreach $d (@d) {
+              my $tmpd = $d;
+              $tmpd += ($dim+1)  if ($d<0);
+              next  if (! &IsInt($tmpd,1,$dim));
+              $date=&Date_Join($y,$m,$tmpd,0,0,0);
+              push(@date,$date);
+            }
+          }
+        }
+        last RECUR;
+
+      } elsif ($m eq "0") {
+
+        # * Y-0-WOY-DOW-H-MN-S
+        # * Y-0-WOY-0-H-MN-S
+
+        @w=&ReturnList($w);
+        return ()  if (! @w);
+        foreach $w (@w) {
+          return ()  if (! &IsInt($w,-53,53)  ||  $w==0);
+        }
+
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+          return ()  if (! @d);
+          foreach $d (@d) {
+            $d += 8  if ($d<0);
+            return ()  if (! &IsInt($d,1,7));
+          }
+        }
+
+        @date=();
+        foreach $y (@y) {
+          foreach $w (@w) {
+            foreach $d (@d) {
+              my($tmpw,$del);
+              if ($w<0) {
+                $date="$y-12-31-00:00:00";
+                $tmpw = (-$w)-1;
+                $del="-0:0:$tmpw:0:0:0:0";
+                $date=Date_GetPrev($date,$d,1);
+              } else {
+                $date="$y-01-01-00:00:00";
+                $tmpw = ($w)-1;
+                $del="0:0:$tmpw:0:0:0:0";
+                $date=Date_GetNext($date,$d,1);
+              }
+              $date=&DateCalc_DateDelta($date,$del);
+              push(@date,$date)  if ( (&Date_Split($date))[0] == $y);
+            }
+          }
+        }
+        last RECUR;
+
+      } else {
+
+        # * Y-M-WOM-DOW-H-MN-S
+        # * Y-M-WOM-0-H-MN-S
+
+        @m=&ReturnList($m);
+        return ()  if (! @m);
+        @w=&ReturnList($w);
+        return ()  if (! @w);
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+        }
+
+        @date=&Date_Recur_WoM(\@y,\@m,\@w,\@d);
+        last RECUR;
+      }
+    }
+
+    if ($#recur0==0) {
+
+      # Y * M-W-D-H-MN-S
+      $n=$y;
+      $n=1  if ($n==0);
+
+      if ($m eq "0") {
+
+        # Y * 0-W-D-H-MN-S    => Y-0 * W-D-H-MN-S
+        push(@recur0,0);
+        shift(@recur1);
+
+      } elsif ($w eq "0") {
+
+        # Y * M-0-DOM-H-MN-S
+        return ()  if (! $dateb  &&  $y != 1);
+
+        @m=&ReturnList($m);
+        return ()  if (! @m);
+        foreach $m (@m) {
+          return ()  if (! &IsInt($m,1,12));
+        }
+
+        if ($d eq "0") {
+          @d = (1);
+        } else {
+          @d=&ReturnList($d);
+          return ()  if (! @d);
+          foreach $d (@d) {
+            return ()  if (! &IsInt($d,-31,31)  ||  $d==0);
+          }
+        }
+
+        # We need to find years that are a multiple of $n from $y(base)
+        ($y0)=( &Date_Split($date0, 1) )[0];
+        ($y1)=( &Date_Split($date1, 1) )[0];
+        if ($dateb) {
+          ($yb)=( &Date_Split($dateb, 1) )[0];
+        } else {
+          # If $y=1, there is no base year
+          $yb=0;
+        }
+
+        @date=();
+        for ($yy=$y0; $yy<=$y1; $yy++) {
+          if (($yy-$yb)%$n == 0) {
+            foreach $m (@m) {
+              foreach $d (@d) {
+                my $dim  = &Date_DaysInMonth($m,$yy);
+                my $tmpd = $d;
+                if ($tmpd < 0) {
+                  $tmpd += ($dim+1);
+                }
+                next  if (! &IsInt($tmpd,1,$dim));
+                $date=&Date_Join($yy,$m,$tmpd,0,0,0);
+                push(@date,$date);
+              }
+            }
+          }
+        }
+        last RECUR;
+
+      } else {
+
+        # Y * M-WOM-DOW-H-MN-S
+        # Y * M-WOM-0-H-MN-S
+        return ()  if (! $dateb  &&  $y != 1);
+
+        @m=&ReturnList($m);
+        return ()  if (! @m);
+        @w=&ReturnList($w);
+        return ()  if (! @w);
+
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+        }
+
+        ($y0)=( &Date_Split($date0, 1) )[0];
+        ($y1)=( &Date_Split($date1, 1) )[0];
+        if ($dateb) {
+          ($yb)=( &Date_Split($dateb, 1) )[0];
+        } else {
+          # If $y=1, there is no base year
+          $yb=0;
+        }
+        @y=();
+        for ($yy=$y0; $yy<=$y1; $yy++) {
+          if (($yy-$yb)%$n == 0) {
+            push(@y,$yy);
+          }
+        }
+
+        @date=&Date_Recur_WoM(\@y,\@m,\@w,\@d);
+        last RECUR;
+      }
+    }
+
+    if ($#recur0==1) {
+
+      # Y-M * W-D-H-MN-S
+
+      if ($w eq "0") {
+        # Y-M * 0-D-H-MN-S   => Y-M-0 * D-H-MN-S
+        push(@recur0,0);
+        shift(@recur1);
+
+      } elsif ($m==0) {
+
+        # Y-0 * WOY-0-H-MN-S
+        # Y-0 * WOY-DOW-H-MN-S
+        return ()  if (! $dateb  &&  $y != 1);
+        $n=$y;
+        $n=1  if ($n==0);
+
+        @w=&ReturnList($w);
+        return ()  if (! @w);
+        foreach $w (@w) {
+          return ()  if ($w==0  ||  ! &IsInt($w,-53,53));
+        }
+
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+          return ()  if (! @d);
+          foreach $d (@d) {
+            $d += 8  if ($d<0);
+            return ()  if (! &IsInt($d,1,7));
+          }
+        }
+
+        # We need to find years that are a multiple of $n from $y(base)
+        ($y0)=( &Date_Split($date0, 1) )[0];
+        ($y1)=( &Date_Split($date1, 1) )[0];
+        if ($dateb) {
+          ($yb)=( &Date_Split($dateb, 1) )[0];
+        } else {
+          # If $y=1, there is no base year
+          $yb=0;
+        }
+
+        @date=();
+        for ($yy=$y0; $yy<=$y1; $yy++) {
+          if (($yy-$yb)%$n == 0) {
+            foreach $w (@w) {
+              foreach $d (@d) {
+                my($tmpw,$del);
+                if ($w<0) {
+                  $date="$yy-12-31-00:00:00";
+                  $tmpw = (-$w)-1;
+                  $del="-0:0:$tmpw:0:0:0:0";
+                  $date=Date_GetPrev($date,$d,1);
+                } else {
+                  $date="$yy-01-01-00:00:00";
+                  $tmpw = ($w)-1;
+                  $del="0:0:$tmpw:0:0:0:0";
+                  $date=Date_GetNext($date,$d,1);
+                }
+                $date=&DateCalc($date,$del);
+                next  if ((&Date_Split($date))[0] != $yy);
+                push(@date,$date);
+              }
+            }
+          }
+        }
+        last RECUR;
+
+      } else {
+
+        # Y-M * WOM-0-H-MN-S
+        # Y-M * WOM-DOW-H-MN-S
+        return ()  if (! $dateb);
+        @tmp=(@recur0);
+        push(@tmp,0)  while ($#tmp<6);
+        $delta=join(":",@tmp);
+        @tmp=&Date_Recur($date0,$date1,$dateb,$delta);
+
+        @w=&ReturnList($w);
+        @m=();
+        if ($d eq "0") {
+          @d=(1);
+        } else {
+          @d=&ReturnList($d);
+        }
+
+        @date=&Date_Recur_WoM(\@tmp,\@m,\@w,\@d);
+        last RECUR;
+      }
+    }
+
+    if ($#recur0==2) {
+      # Y-M-W * D-H-MN-S
+
+      if ($d eq "0") {
+
+        # Y-M-W * 0-H-MN-S
+        return ()  if (! $dateb);
+        $y=1  if ($y==0 && $m==0 && $w==0);
+        $delta="$y:$m:$w:0:0:0:0";
+        @date=&Date_Recur($date0,$date1,$dateb,$delta);
+        last RECUR;
+
+      } elsif ($m==0 && $w==0) {
+
+        # Y-0-0 * DOY-H-MN-S
+        $y=1  if ($y==0);
+        $n=$y;
+        return ()  if (! $dateb  &&  $y!=1);
+
+        @d=&ReturnList($d);
+        return ()  if (! @d);
+        foreach $d (@d) {
+          return ()  if (! &IsInt($d,-366,366)  ||  $d==0);
+        }
+
+        # We need to find years that are a multiple of $n from $y(base)
+        ($y0)=( &Date_Split($date0, 1) )[0];
+        ($y1)=( &Date_Split($date1, 1) )[0];
+        if ($dateb) {
+          ($yb)=( &Date_Split($dateb, 1) )[0];
+        } else {
+          # If $y=1, there is no base year
+          $yb=0;
+        }
+        @date=();
+        for ($yy=$y0; $yy<=$y1; $yy++) {
+          my $diy = &Date_DaysInYear($yy);
+          if (($yy-$yb)%$n == 0) {
+            foreach $d (@d) {
+              my $tmpd = $d;
+              $tmpd += ($diy+1)  if ($tmpd<0);
+              next  if (! &IsInt($tmpd,1,$diy));
+              ($y,$m,$dd)=&Date_NthDayOfYear($yy,$tmpd);
+              push(@date, &Date_Join($y,$m,$dd,0,0,0));
+            }
+          }
+        }
+        last RECUR;
+
+      } elsif ($w>0) {
+
+        # Y-M-W * DOW-H-MN-S
+        return ()  if (! $dateb);
+        @tmp=(@recur0);
+        push(@tmp,0)  while ($#tmp<6);
+        $delta=join(":",@tmp);
+
+        @d=&ReturnList($d);
+        return ()  if (! @d);
+        foreach $d (@d) {
+          $d += 8  if ($d<0);
+          return ()  if (! &IsInt($d,1,7));
+        }
+
+        # Find out what DofW the basedate is.
+        @tmp2=&Date_Split($dateb, 1);
+        $tmp=&Date_DayOfWeek($tmp2[1],$tmp2[2],$tmp2[0]);
+
+        @date=();
+        foreach $d (@d) {
+          $date_b=$dateb;
+          # Move basedate to DOW in the same week
+          if ($d != $tmp) {
+            if (($tmp>=$Cnf{"FirstDay"} && $d<$Cnf{"FirstDay"}) ||
+                ($tmp>=$Cnf{"FirstDay"} && $d>$tmp) ||
+                ($tmp<$d && $d<$Cnf{"FirstDay"})) {
+              $date_b=&Date_GetNext($date_b,$d);
+            } else {
+              $date_b=&Date_GetPrev($date_b,$d);
+            }
+          }
+          push(@date,&Date_Recur($date0,$date1,$date_b,$delta));
+        }
+        last RECUR;
+
+      } elsif ($m>0) {
+
+        # Y-M-0 * DOM-H-MN-S
+        return ()  if (! $dateb);
+        @tmp=(@recur0);
+        push(@tmp,0)  while ($#tmp<6);
+        $delta=join(":",@tmp);
+
+        @d=&ReturnList($d);
+        return ()  if (! @d);
+        foreach $d (@d) {
+          return ()  if ($d==0  ||  ! &IsInt($d,-31,31));
+        }
+
+        @tmp2=&Date_Recur($date0,$date1,$dateb,$delta);
+        @date=();
+        foreach $date (@tmp2) {
+          ($y,$m)=( &Date_Split($date, 1) )[0..1];
+          my $dim=&Date_DaysInMonth($m,$y);
+          foreach $d (@d) {
+            my $tmpd = $d;
+            $tmpd += ($dim+1)  if ($tmpd<0);
+            next  if (! &IsInt($tmpd,1,$dim));
+            push(@date,&Date_Join($y,$m,$tmpd,0,0,0));
+          }
+        }
+        last RECUR;
+
+      } else {
+        return ();
+      }
+    }
+
+    if ($#recur0>2) {
+
+      # Y-M-W-D * H-MN-S
+      # Y-M-W-D-H * MN-S
+      # Y-M-W-D-H-MN * S
+      # Y-M-W-D-H-S
+      return ()  if (! $dateb);
+      @tmp=(@recur0);
+      push(@tmp,0)  while ($#tmp<6);
+      $delta=join(":",@tmp);
+      return ()  if ($delta !~ /[1-9]/); # return if "0:0:0:0:0:0:0"
+      @date=&Date_Recur($date0,$date1,$dateb,$delta);
+      if (@recur1) {
+        unshift(@recur1,-1)  while ($#recur1<2);
+        @time=@recur1;
+      } else {
+        shift(@date);
+        pop(@date);
+        @time=();
+      }
+    }
+
+    last RECUR;
+  }
+  @date=&Date_RecurSetTime($date0,$date1,\@date,@time)  if (@time);
+
+  #
+  # We've got a list of dates.  Operate on them with the flags.
+  #
+
+  my($sign,$forw,$today,$df,$db,$work,$i);
+  if (@flags) {
+  FLAG: foreach $f (@flags) {
+      $f = uc($f);
+
+      if ($f =~ /^(P|N)(D|T)([1-7])$/) {
+        @tmp=($1,$2,$3);
+        $forw =($tmp[0] eq "P" ? 0 : 1);
+        $today=($tmp[1] eq "D" ? 0 : 1);
+        $d=$tmp[2];
+        @tmp=();
+        foreach $date (@date) {
+          if ($forw) {
+            push(@tmp, &Date_GetNext($date,$d,$today));
+          } else {
+            push(@tmp, &Date_GetPrev($date,$d,$today));
+          }
+        }
+        @date=@tmp;
+        next FLAG;
+      }
+
+      # We want to go forward exact amounts of time instead of
+      # business mode calculations so that we don't change the time
+      # (which may have been set in the recur).
+      if ($f =~ /^(F|B)(D|W)(\d+)$/) {
+        @tmp=($1,$2,$3);
+        $sign="+";
+        $sign="-"  if ($tmp[0] eq "B");
+        $work=0;
+        $work=1    if ($tmp[1] eq "W");
+        $n=$tmp[2];
+        @tmp=();
+        foreach $date (@date) {
+          for ($i=1; $i<=$n; $i++) {
+            while (1) {
+              $date=&DateCalc($date,"${sign}0:0:0:1:0:0:0");
+              last if (! $work  ||  &Date_IsWorkDay($date,0));
+            }
+          }
+          push(@tmp,$date);
+        }
+        @date=@tmp;
+        next FLAG;
+      }
+
+      if ($f =~ /^CW(N|P|D)$/ || $f =~ /^(N|P|D)W(D)$/) {
+        $tmp=$1;
+        my $noalt = $2 ? 1 : 0;
+        if ($tmp eq "N"  ||  ($tmp eq "D" && $Cnf{"TomorrowFirst"})) {
+          $forw=1;
+        } else {
+          $forw=0;
+        }
+
+        @tmp=();
+      DATE: foreach $date (@date) {
+          $df=$db=$date;
+          if (&Date_IsWorkDay($date)) {
+            push(@tmp,$date);
+            next DATE;
+          }
+          while (1) {
+            if ($forw) {
+              $d=$df=&DateCalc($df,"+0:0:0:1:0:0:0");
+            } else {
+              $d=$db=&DateCalc($db,"-0:0:0:1:0:0:0");
+            }
+            if (&Date_IsWorkDay($d)) {
+              push(@tmp,$d);
+              next DATE;
+            }
+            $forw=1-$forw  if (! $noalt);
+          }
+        }
+        @date=@tmp;
+        next FLAG;
+      }
+
+      if ($f eq "EASTER") {
+        @tmp=();
+        foreach $date (@date) {
+          ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
+          ($m,$d)=&Date_Easter($y);
+          $date=&Date_Join($y,$m,$d,$h,$mn,$s);
+          next  if (&Date_Cmp($date,$date0)<0  ||
+                    &Date_Cmp($date,$date1)>0);
+          push(@tmp,$date);
+        }
+        @date=@tmp;
+      }
+    }
+  }
+
+  @date = sort { Date_Cmp($a,$b) } @date;
+  return @date;
+}
+
+sub Date_GetPrev {
+  print "DEBUG: Date_GetPrev\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$dow,$today,$hr,$min,$sec)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  my($y,$m,$d,$h,$mn,$s,$err,$curr_dow,%dow,$num,$delta,$th,$tm,$ts,
+     $adjust,$curr)=();
+  $hr="00"   if (defined $hr   &&  $hr eq "0");
+  $min="00"  if (defined $min  &&  $min eq "0");
+  $sec="00"  if (defined $sec  &&  $sec eq "0");
+
+  if (! &Date_Split($date)) {
+    $date=&ParseDateString($date);
+    return ""  if (! $date);
+  }
+  $curr=$date;
+  ($y,$m,$d)=( &Date_Split($date, 1) )[0..2];
+
+  if ($dow) {
+    $curr_dow=&Date_DayOfWeek($m,$d,$y);
+    %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };
+    if (&IsInt($dow)) {
+      return ""  if ($dow<1  ||  $dow>7);
+    } else {
+      return ""  if (! exists $dow{lc($dow)});
+      $dow=$dow{lc($dow)};
+    }
+    if ($dow == $curr_dow) {
+      $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0)  if (! $today);
+      $adjust=1  if ($today==2);
+    } else {
+      $dow -= 7  if ($dow>$curr_dow); # make sure previous day is less
+      $num = $curr_dow - $dow;
+      $date=&DateCalc_DateDelta($date,"-0:0:0:$num:0:0:0",\$err,0);
+    }
+    $date=&Date_SetTime($date,$hr,$min,$sec)  if (defined $hr);
+    $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0)
+      if ($adjust  &&  &Date_Cmp($date,$curr)>0);
+
+  } else {
+    ($h,$mn,$s)=( &Date_Split($date, 1) )[3..5];
+    ($th,$tm,$ts)=&Date_ParseTime($hr,$min,$sec);
+    if ($hr) {
+      ($hr,$min,$sec)=($th,$tm,$ts);
+      $delta="-0:0:0:1:0:0:0";
+    } elsif ($min) {
+      ($hr,$min,$sec)=($h,$tm,$ts);
+      $delta="-0:0:0:0:1:0:0";
+    } elsif ($sec) {
+      ($hr,$min,$sec)=($h,$mn,$ts);
+      $delta="-0:0:0:0:0:1:0";
+    } else {
+      confess "ERROR: invalid arguments in Date_GetPrev.\n";
+    }
+
+    $d=&Date_SetTime($date,$hr,$min,$sec);
+    if ($today) {
+      $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if (&Date_Cmp($d,$date)>0);
+    } else {
+      $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if (&Date_Cmp($d,$date)>=0);
+    }
+    $date=$d;
+  }
+  return $date;
+}
+
+sub Date_GetNext {
+  print "DEBUG: Date_GetNext\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$dow,$today,$hr,$min,$sec)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  my($y,$m,$d,$h,$mn,$s,$err,$curr_dow,%dow,$num,$delta,$th,$tm,$ts,
+     $adjust,$curr)=();
+  $hr="00"   if (defined $hr   &&  $hr eq "0");
+  $min="00"  if (defined $min  &&  $min eq "0");
+  $sec="00"  if (defined $sec  &&  $sec eq "0");
+
+  if (! &Date_Split($date)) {
+    $date=&ParseDateString($date);
+    return ""  if (! $date);
+  }
+  $curr=$date;
+  ($y,$m,$d)=( &Date_Split($date, 1) )[0..2];
+
+  if ($dow) {
+    $curr_dow=&Date_DayOfWeek($m,$d,$y);
+    %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };
+    if (&IsInt($dow)) {
+      return ""  if ($dow<1  ||  $dow>7);
+    } else {
+      return ""  if (! exists $dow{lc($dow)});
+      $dow=$dow{lc($dow)};
+    }
+    if ($dow == $curr_dow) {
+      $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0)  if (! $today);
+      $adjust=1  if ($today==2);
+    } else {
+      $curr_dow -= 7  if ($curr_dow>$dow); # make sure next date is greater
+      $num = $dow - $curr_dow;
+      $date=&DateCalc_DateDelta($date,"+0:0:0:$num:0:0:0",\$err,0);
+    }
+    $date=&Date_SetTime($date,$hr,$min,$sec)  if (defined $hr);
+    $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0)
+      if ($adjust  &&  &Date_Cmp($date,$curr)<0);
+
+  } else {
+    ($h,$mn,$s)=( &Date_Split($date, 1) )[3..5];
+    ($th,$tm,$ts)=&Date_ParseTime($hr,$min,$sec);
+    if ($hr) {
+      ($hr,$min,$sec)=($th,$tm,$ts);
+      $delta="+0:0:0:1:0:0:0";
+    } elsif ($min) {
+      ($hr,$min,$sec)=($h,$tm,$ts);
+      $delta="+0:0:0:0:1:0:0";
+    } elsif ($sec) {
+      ($hr,$min,$sec)=($h,$mn,$ts);
+      $delta="+0:0:0:0:0:1:0";
+    } else {
+      confess "ERROR: invalid arguments in Date_GetNext.\n";
+    }
+
+    $d=&Date_SetTime($date,$hr,$min,$sec);
+    if ($today) {
+      $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if (&Date_Cmp($d,$date)<0);
+    } else {
+      $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if (&Date_Cmp($d,$date)<1);
+    }
+    $date=$d;
+  }
+
+  return $date;
+}
+
+sub Date_IsHoliday {
+  print "DEBUG: Date_IsHoliday\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $date=&ParseDateString($date);
+  return undef  if (! $date);
+  $date=&Date_SetTime($date,0,0,0);
+  my($y)=(&Date_Split($date, 1))[0];
+  &Date_UpdateHolidays($y)  if (! exists $Holiday{"dates"}{$y});
+  return undef  if (! exists $Holiday{"dates"}{$y}{$date});
+  my($name)=$Holiday{"dates"}{$y}{$date};
+  return ""   if (! $name);
+  $name;
+}
+
+sub Events_List {
+  print "DEBUG: Events_List\n"  if ($Curr{"Debug"} =~ /trace/);
+  my(@args)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  &Events_ParseRaw();
+
+  my($tmp,$date0,$date1,$flag);
+  $date0=&ParseDateString($args[0]);
+  warn "Invalid date $args[0]", return undef  if (! $date0);
+
+  if ($#args == 0) {
+    return &Events_Calc($date0);
+  }
+
+  if ($args[1]) {
+    $date1=&ParseDateString($args[1]);
+    warn "Invalid date $args[1]\n", return undef  if (! $date1);
+    if (&Date_Cmp($date0,$date1)>0) {
+      $tmp=$date1;
+      $date1=$date0;
+      $date0=$tmp;
+    }
+  } else {
+    $date0=&Date_SetTime($date0,"00:00:00");
+    $date1=&DateCalc_DateDelta($date0,"+0:0:0:1:0:0:0");
+  }
+
+  $tmp=&Events_Calc($date0,$date1);
+
+  $flag=$args[2];
+  return $tmp  if (! $flag);
+
+  my(@tmp,%ret,$delta)=();
+  @tmp=@$tmp;
+  push(@tmp,$date1);
+
+  if ($flag==1) {
+    while ($#tmp>0) {
+      ($date0,$tmp)=splice(@tmp,0,2);
+      $date1=$tmp[0];
+      $delta=&DateCalc_DateDate($date0,$date1);
+      foreach $flag (@$tmp) {
+        if (exists $ret{$flag}) {
+          $ret{$flag}=&DateCalc_DeltaDelta($ret{$flag},$delta);
+        } else {
+          $ret{$flag}=$delta;
+        }
+      }
+    }
+    return \%ret;
+
+  } elsif ($flag==2) {
+    while ($#tmp>0) {
+      ($date0,$tmp)=splice(@tmp,0,2);
+      $date1=$tmp[0];
+      $delta=&DateCalc_DateDate($date0,$date1);
+      $flag=join("+",sort { Date_Cmp($a,$b) } @$tmp);
+      next  if (! $flag);
+      if (exists $ret{$flag}) {
+        $ret{$flag}=&DateCalc_DeltaDelta($ret{$flag},$delta);
+      } else {
+        $ret{$flag}=$delta;
+      }
+    }
+    return \%ret;
+  }
+
+  warn "Invalid flag $flag\n";
+  return undef;
+}
+
+###
+# NOTE: The following routines may be called in the routines below with very
+#       little time penalty.
+###
+sub Date_SetTime {
+  print "DEBUG: Date_SetTime\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$h,$mn,$s)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  my($y,$m,$d)=();
+
+  if (! &Date_Split($date)) {
+    $date=&ParseDateString($date);
+    return ""  if (! $date);
+  }
+
+  ($y,$m,$d)=( &Date_Split($date, 1) )[0..2];
+  ($h,$mn,$s)=&Date_ParseTime($h,$mn,$s);
+
+  my($ampm,$wk);
+  return ""  if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
+  &Date_Join($y,$m,$d,$h,$mn,$s);
+}
+
+sub Date_SetDateField {
+  print "DEBUG: Date_SetDateField\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$field,$val,$nocheck)=@_;
+  my($y,$m,$d,$h,$mn,$s)=();
+  $nocheck=0  if (! defined $nocheck);
+
+  ($y,$m,$d,$h,$mn,$s)=&Date_Split($date);
+
+  if (! $y) {
+    $date=&ParseDateString($date);
+    return "" if (! $date);
+    ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
+  }
+
+  if      (lc($field) eq "y") {
+    $y=$val;
+  } elsif (lc($field) eq "m") {
+    $m=$val;
+  } elsif (lc($field) eq "d") {
+    $d=$val;
+  } elsif (lc($field) eq "h") {
+    $h=$val;
+  } elsif (lc($field) eq "mn") {
+    $mn=$val;
+  } elsif (lc($field) eq "s") {
+    $s=$val;
+  } else {
+    confess "ERROR: Date_SetDateField: invalid field: $field\n";
+  }
+
+  $date=&Date_Join($y,$m,$d,$h,$mn,$s);
+  return $date  if ($nocheck  ||  &Date_Split($date));
+  return "";
+}
+
+########################################################################
+# OTHER SUBROUTINES
+########################################################################
+# NOTE: These routines should not call any of the routines above as
+#       there will be a severe time penalty (and the possibility of
+#       infinite recursion).  The last couple routines above are
+#       exceptions.
+# NOTE: Date_Init is a special case.  It should be called (conditionally)
+#       in every routine that uses any variable from the Date::Manip
+#       namespace.
+########################################################################
+
+sub Date_DaysInMonth {
+  print "DEBUG: Date_DaysInMonth\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
+  $d_in_m[2]=29  if (&Date_LeapYear($y));
+  return $d_in_m[$m];
+}
+
+sub Date_DayOfWeek {
+  print "DEBUG: Date_DayOfWeek\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$d,$y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  my($dayofweek,$dec31)=();
+
+  $dec31=5;                     # Dec 31, 1BC was Friday
+  $dayofweek=(&Date_DaysSince1BC($m,$d,$y)+$dec31) % 7;
+  $dayofweek=7  if ($dayofweek==0);
+  return $dayofweek;
+}
+
+# Can't be in "use integer" because the numbers are too big.
+no integer;
+sub Date_SecsSince1970 {
+  print "DEBUG: Date_SecsSince1970\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$d,$y,$h,$mn,$s)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  my($sec_now,$sec_70)=();
+  $sec_now=(&Date_DaysSince1BC($m,$d,$y)-1)*24*3600 + $h*3600 + $mn*60 + $s;
+# $sec_70 =(&Date_DaysSince1BC(1,1,1970)-1)*24*3600;
+  $sec_70 =62167219200;
+  return ($sec_now-$sec_70);
+}
+
+sub Date_SecsSince1970GMT {
+  print "DEBUG: Date_SecsSince1970GMT\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$d,$y,$h,$mn,$s)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+
+  my($sec)=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s);
+  return $sec   if ($Cnf{"ConvTZ"} eq "IGNORE");
+
+  my($tz)=$Cnf{"ConvTZ"};
+  $tz=$Cnf{"TZ"}  if (! $tz);
+  $tz=$Zone{"n2o"}{lc($tz)}  if ($tz !~ /^[+-]\d{4}$/);
+
+  my($tzs)=1;
+  $tzs=-1 if ($tz<0);
+  $tz=~/.(..)(..)/;
+  my($tzh,$tzm)=($1,$2);
+  $sec - $tzs*($tzh*3600+$tzm*60);
+}
+use integer;
+
+sub Date_DaysSince1BC {
+  print "DEBUG: Date_DaysSince1BC\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$d,$y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  my($Ny,$N4,$N100,$N400,$dayofyear,$days)=();
+  my($cc,$yy)=();
+
+  $y=~ /(\d{2})(\d{2})/;
+  ($cc,$yy)=($1,$2);
+
+  # Number of full years since Dec 31, 1BC (counting the year 0000).
+  $Ny=$y;
+
+  # Number of full 4th years (incl. 0000) since Dec 31, 1BC
+  $N4=($Ny-1)/4 + 1;
+  $N4=0         if ($y==0);
+
+  # Number of full 100th years (incl. 0000)
+  $N100=$cc + 1;
+  $N100--       if ($yy==0);
+  $N100=0       if ($y==0);
+
+  # Number of full 400th years (incl. 0000)
+  $N400=($N100-1)/4 + 1;
+  $N400=0       if ($y==0);
+
+  $dayofyear=&Date_DayOfYear($m,$d,$y);
+  $days= $Ny*365 + $N4 - $N100 + $N400 + $dayofyear;
+
+  return $days;
+}
+
+sub Date_DayOfYear {
+  print "DEBUG: Date_DayOfYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$d,$y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  # DinM    = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+  my(@days) = ( 0, 31, 59, 90,120,151,181,212,243,273,304,334,365);
+  my($ly)=0;
+  $ly=1  if ($m>2 && &Date_LeapYear($y));
+  return ($days[$m-1]+$d+$ly);
+}
+
+sub Date_DaysInYear {
+  print "DEBUG: Date_DaysInYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  return 366  if (&Date_LeapYear($y));
+  return 365;
+}
+
+sub Date_WeekOfYear {
+  print "DEBUG: Date_WeekOfYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($m,$d,$y,$f)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+
+  my($day,$dow,$doy)=();
+  $doy=&Date_DayOfYear($m,$d,$y);
+
+  # The current DayOfYear and DayOfWeek
+  if ($Cnf{"Jan1Week1"}) {
+    $day=1;
+  } else {
+    $day=4;
+  }
+  $dow=&Date_DayOfWeek(1,$day,$y);
+
+  # Move back to the first day of week 1.
+  $f-=7  if ($f>$dow);
+  $day-= ($dow-$f);
+
+  return 0  if ($day>$doy);      # Day is in last week of previous year
+  return (($doy-$day)/7 + 1);
+}
+
+sub Date_LeapYear {
+  print "DEBUG: Date_LeapYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)!=4);
+  return 0 unless $y % 4 == 0;
+  return 1 unless $y % 100 == 0;
+  return 0 unless $y % 400 == 0;
+  return 1;
+}
+
+sub Date_DaySuffix {
+  print "DEBUG: Date_DaySuffix\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  return $Lang{$Cnf{"Language"}}{"DoML"}[$d-1];
+}
+
+sub Date_ConvTZ {
+  print "DEBUG: Date_ConvTZ\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$from,$to,$level)=@_;
+  if (not Date_Split($date)) {
+    my $err = "date passed in ('$date') is not a Date::Manip object";
+    if (! $level) {
+      croak $err;
+    } elsif ($level==1) {
+      carp $err;
+    }
+    return "";
+  }
+
+  &Date_Init()  if (! $Curr{"InitDone"});
+  my($gmt)=();
+
+  if (! $from) {
+
+    if (! $to) {
+      # TZ -> ConvTZ
+      return $date  if ($Cnf{"ConvTZ"} eq "IGNORE" or ! $Cnf{"ConvTZ"});
+      $from=$Cnf{"TZ"};
+      $to=$Cnf{"ConvTZ"};
+
+    } else {
+      # ConvTZ,TZ -> $to
+      $from=$Cnf{"ConvTZ"};
+      $from=$Cnf{"TZ"}  if (! $from);
+    }
+
+  } else {
+
+    if (! $to) {
+      # $from -> ConvTZ,TZ
+      return $date  if ($Cnf{"ConvTZ"} eq "IGNORE");
+      $to=$Cnf{"ConvTZ"};
+      $to=$Cnf{"TZ"}  if (! $to);
+
+    } else {
+      # $from -> $to
+    }
+  }
+
+  $to=$Zone{"n2o"}{lc($to)}
+    if (exists $Zone{"n2o"}{lc($to)});
+  $from=$Zone{"n2o"}{lc($from)}
+    if (exists $Zone{"n2o"}{lc($from)});
+  $gmt=$Zone{"n2o"}{"gmt"};
+
+  return $date  if ($from !~ /^[+-]\d{4}$/ or $to !~ /^[+-]\d{4}$/);
+  return $date  if ($from eq $to);
+
+  my($s1,$h1,$m1,$s2,$h2,$m2,$d,$h,$m,$sign,$delta,$err,$yr,$mon,$sec)=();
+  # We're going to try to do the calculation without calling DateCalc.
+  ($yr,$mon,$d,$h,$m,$sec)=&Date_Split($date, 1);
+
+  # Convert $date from $from to GMT
+  $from=~/([+-])(\d{2})(\d{2})/;
+  ($s1,$h1,$m1)=($1,$2,$3);
+  $s1= ($s1 eq "-" ? "+" : "-");   # switch sign
+  $sign=$s1 . "1";     # + or - 1
+
+  # and from GMT to $to
+  $to=~/([+-])(\d{2})(\d{2})/;
+  ($s2,$h2,$m2)=($1,$2,$3);
+
+  if ($s1 eq $s2) {
+    # Both the same sign
+    $m+= $sign*($m1+$m2);
+    $h+= $sign*($h1+$h2);
+  } else {
+    $sign=($s2 eq "-" ? +1 : -1)  if ($h1<$h2  ||  ($h1==$h2 && $m1<$m2));
+    $m+= $sign*($m1-$m2);
+    $h+= $sign*($h1-$h2);
+  }
+
+  if ($m>59) {
+    $h+= $m/60;
+    $m-= ($m/60)*60;
+  } elsif ($m<0) {
+    $h+= ($m/60 - 1);
+    $m-= ($m/60 - 1)*60;
+  }
+
+  if ($h>23) {
+    $delta=$h/24;
+    $h -= $delta*24;
+    if (($d + $delta) > 28) {
+      $date=&Date_Join($yr,$mon,$d,$h,$m,$sec);
+      return &DateCalc_DateDelta($date,"+0:0:0:$delta:0:0:0",\$err,0);
+    }
+    $d+= $delta;
+  } elsif ($h<0) {
+    $delta=-$h/24 + 1;
+    $h += $delta*24;
+    if (($d - $delta) < 1) {
+      $date=&Date_Join($yr,$mon,$d,$h,$m,$sec);
+      return &DateCalc_DateDelta($date,"-0:0:0:$delta:0:0:0",\$err,0);
+    }
+    $d-= $delta;
+  }
+  return &Date_Join($yr,$mon,$d,$h,$m,$sec);
+}
+
+sub Date_TimeZone {
+  print "DEBUG: Date_TimeZone\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($null,$tz,@tz,$std,$dst,$time,$isdst,$tmp,$in)=();
+  &Date_Init()  if (! $Curr{"InitDone"});
+
+  # Get timezones from all of the relevant places
+
+  push(@tz,$Cnf{"TZ"})  if (defined $Cnf{"TZ"});  # TZ config var
+  push(@tz,$ENV{"TZ"})  if (defined $ENV{"TZ"});  # TZ environ var
+  push(@tz,$ENV{'SYS$TIMEZONE_RULE'})
+    if defined $ENV{'SYS$TIMEZONE_RULE'};         # VMS TZ environ var
+  push(@tz,$ENV{'SYS$TIMEZONE_NAME'})
+    if defined $ENV{'SYS$TIMEZONE_NAME'};         # VMS TZ name environ var
+  push(@tz,$ENV{'UCX$TZ'})
+    if defined $ENV{'UCX$TZ'};                    # VMS TZ environ var
+  push(@tz,$ENV{'TCPIP$TZ'})
+    if defined $ENV{'TCPIP$TZ'};                  # VMS TZ environ var
+
+  # The `date` command... if we're doing taint checking, we need to
+  # always call it with a full path... otherwise, use the user's path.
+  #
+  # Microsoft operating systems don't have a date command built in.  Try
+  # to trap all the various ways of knowing we are on one of these systems.
+  #
+  # We'll try `date +%Z` first, and if that fails, we'll take just the
+  # `date` program and assume the output is of the format:
+  # Thu Aug 31 14:57:46 EDT 2000
+
+  unless (($^O ne 'cygwin' && $^X =~ /perl\.exe$/i) or
+          ($OS eq "Windows") or
+          ($OS eq "Netware") or
+          ($OS eq "VMS")) {
+    if ($Date::Manip::NoTaint) {
+      if ($OS eq "VMS") {
+        $tz=$ENV{'SYS$TIMEZONE_NAME'};
+        if (! $tz) {
+          $tz=$ENV{'MULTINET_TIMEZONE'};
+          if (! $tz) {
+            $tz=$ENV{'SYS$TIMEZONE_DIFFERENTIAL'}/3600.; # e.g. '-4' for EDT
+          }
+        }
+      } else {
+        $tz=`date +%Z 2> /dev/null`;
+        chomp($tz);
+        if (! $tz) {
+          $tz=`date 2> /dev/null`;
+          chomp($tz);
+          $tz=(split(/\s+/,$tz))[4];
+        }
+      }
+      push(@tz,$tz);
+    } else {
+      # We need to satisfy taint checking, but also look in all the
+      # directories in @DatePath.
+      #
+      local $ENV{PATH} = join(':', @Date::Manip::DatePath);
+      local $ENV{BASH_ENV} = '';
+      $tz=`date +%Z 2> /dev/null`;
+      chomp($tz);
+      if (! $tz) {
+	$tz=`date 2> /dev/null`;
+	chomp($tz);
+	$tz=(split(/\s+/,$tz))[4];
+      }
+      push(@tz,$tz);
+    }
+  }
+
+  push(@tz,$main::TZ)         if (defined $main::TZ);         # $main::TZ
+
+  if (-s "/etc/TIMEZONE") {                                   # /etc/TIMEZONE
+    $in=new IO::File;
+    $in->open("/etc/TIMEZONE","r");
+    while (! eof($in)) {
+      $tmp=<$in>;
+      if ($tmp =~ /^TZ\s*=\s*(.*?)\s*$/) {
+        push(@tz,$1);
+        last;
+      }
+    }
+    $in->close;
+  }
+
+  if (-s "/etc/timezone") {                                   # /etc/timezone
+    $in=new IO::File;
+    $in->open("/etc/timezone","r");
+    while (! eof($in)) {
+      $tmp=<$in>;
+      next  if ($tmp =~ /^\s*\043/);
+      chomp($tmp);
+      if ($tmp =~ /^\s*(.*?)\s*$/) {
+        push(@tz,$1);
+        last;
+      }
+    }
+    $in->close;
+  }
+
+  # Now parse each one to find the first valid one.
+  foreach $tz (@tz) {
+    $tz =~ s/\s*$//;
+    $tz =~ s/^\s*//;
+    $tz =~ s/^://;
+    next  if ($tz eq "");
+
+    return uc($tz)
+      if (defined $Zone{"n2o"}{lc($tz)});
+
+    if ($tz =~ /^[+-]\d{4}$/) {
+      return $tz;
+    } elsif ($tz =~ /^([+-]\d{2})(?::(\d{2}))?$/) {
+      my($h,$m)=($1,$2);
+      $m="00"  if (! $m);
+      return "$h$m";
+    }
+
+    # Handle US/Eastern format
+    if ($tz =~ /^$Zone{"tzones"}$/i) {
+      $tmp=lc $1;
+      $tz=$Zone{"tz2z"}{$tmp};
+    }
+
+    # Handle STD#DST# format (and STD-#DST-# formats)
+    if ($tz =~ /^([a-z]+)-?\d([a-z]+)-?\d?$/i) {
+      ($std,$dst)=($1,$2);
+      next  if (! defined $Zone{"n2o"}{lc($std)} or
+                ! defined $Zone{"n2o"}{lc($dst)});
+      $time = time();
+      ($null,$null,$null,$null,$null,$null,$null,$null,$isdst) =
+        localtime($time);
+      return uc($dst)  if ($isdst);
+      return uc($std);
+    }
+  }
+
+  confess "ERROR: Date::Manip unable to determine Time Zone.\n";
+}
+
+# Returns 1 if $date is a work day.  If $time is non-zero, the time is
+# also checked to see if it falls within work hours.  Returns "" if
+# an invalid date is passed in.
+sub Date_IsWorkDay {
+  print "DEBUG: Date_IsWorkDay\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$time)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $date=&ParseDateString($date);
+  return ""  if (! $date);
+  my($d)=$date;
+  $d=&Date_SetTime($date,$Cnf{"WorkDayBeg"})  if (! $time);
+
+  my($y,$mon,$day,$h,$m,$s,$dow)=();
+  ($y,$mon,$day,$h,$m,$s)=&Date_Split($d, 1);
+  $dow=&Date_DayOfWeek($mon,$day,$y);
+
+  return 0  if ($dow<$Cnf{"WorkWeekBeg"} or
+                $dow>$Cnf{"WorkWeekEnd"} or
+                "$h:$m" lt $Cnf{"WorkDayBeg"} or
+                "$h:$m" ge $Cnf{"WorkDayEnd"});
+
+  if (! exists $Holiday{"dates"}{$y}) {
+    # There will be recursion problems if we ever end up here twice.
+    $Holiday{"dates"}{$y}={};
+    &Date_UpdateHolidays($y)
+  }
+  $d=&Date_SetTime($date,"00:00:00");
+  return 0  if (exists $Holiday{"dates"}{$y}{$d});
+  1;
+}
+
+# Finds the day $off work days from now.  If $time is passed in, we must
+# also take into account the time of day.
+#
+# If $time is not passed in, day 0 is today (if today is a workday) or the
+# next work day if it isn't.  In any case, the time of day is unaffected.
+#
+# If $time is passed in, day 0 is now (if now is part of a workday) or the
+# start of the very next work day.
+sub Date_NextWorkDay {
+  print "DEBUG: Date_NextWorkDay\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$off,$time)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $date=&ParseDateString($date);
+  my($err)=();
+
+  if (! &Date_IsWorkDay($date,$time)) {
+    if ($time) {
+      while (1) {
+        $date=&Date_GetNext($date,undef,0,$Cnf{"WorkDayBeg"});
+        last  if (&Date_IsWorkDay($date,$time));
+      }
+    } else {
+      while (1) {
+        $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0",\$err,0);
+        last  if (&Date_IsWorkDay($date,$time));
+      }
+    }
+  }
+
+  while ($off>0) {
+    while (1) {
+      $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0",\$err,0);
+      last  if (&Date_IsWorkDay($date,$time));
+    }
+    $off--;
+  }
+
+  return $date;
+}
+
+# Finds the day $off work days before now.  If $time is passed in, we must
+# also take into account the time of day.
+#
+# If $time is not passed in, day 0 is today (if today is a workday) or the
+# previous work day if it isn't.  In any case, the time of day is unaffected.
+#
+# If $time is passed in, day 0 is now (if now is part of a workday) or the
+# end of the previous work period.  Note that since the end of a work day
+# will automatically be turned into the start of the next one, this time
+# may actually be treated as AFTER the current time.
+sub Date_PrevWorkDay {
+  print "DEBUG: Date_PrevWorkDay\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$off,$time)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $date=&ParseDateString($date);
+  my($err)=();
+
+  if (! &Date_IsWorkDay($date,$time)) {
+    if ($time) {
+      while (1) {
+        $date=&Date_GetPrev($date,undef,0,$Cnf{"WorkDayEnd"});
+        last  if (&Date_IsWorkDay($date,$time));
+      }
+      while (1) {
+        $date=&Date_GetNext($date,undef,0,$Cnf{"WorkDayBeg"});
+        last  if (&Date_IsWorkDay($date,$time));
+      }
+    } else {
+      while (1) {
+        $date=&DateCalc_DateDelta($date,"-0:0:0:1:0:0:0",\$err,0);
+        last  if (&Date_IsWorkDay($date,$time));
+      }
+    }
+  }
+
+  while ($off>0) {
+    while (1) {
+      $date=&DateCalc_DateDelta($date,"-0:0:0:1:0:0:0",\$err,0);
+      last  if (&Date_IsWorkDay($date,$time));
+    }
+    $off--;
+  }
+
+  return $date;
+}
+
+# This finds the nearest workday to $date.  If $date is a workday, it
+# is returned.
+sub Date_NearestWorkDay {
+  print "DEBUG: Date_NearestWorkDay\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date,$tomorrow)=@_;
+  &Date_Init()  if (! $Curr{"InitDone"});
+  $date=&ParseDateString($date);
+  my($a,$b,$dela,$delb,$err)=();
+  $tomorrow=$Cnf{"TomorrowFirst"}  if (! defined $tomorrow);
+
+  return $date  if (&Date_IsWorkDay($date));
+
+  # Find the nearest one.
+  if ($tomorrow) {
+    $dela="+0:0:0:1:0:0:0";
+    $delb="-0:0:0:1:0:0:0";
+  } else {
+    $dela="-0:0:0:1:0:0:0";
+    $delb="+0:0:0:1:0:0:0";
+  }
+  $a=$b=$date;
+
+  while (1) {
+    $a=&DateCalc_DateDelta($a,$dela,\$err);
+    return $a  if (&Date_IsWorkDay($a));
+    $b=&DateCalc_DateDelta($b,$delb,\$err);
+    return $b  if (&Date_IsWorkDay($b));
+  }
+}
+
+# &Date_NthDayOfYear($y,$n);
+#   Returns a list of (YYYY,MM,DD,HH,MM,SS) for the Nth day of the year.
+sub Date_NthDayOfYear {
+  no integer;
+  print "DEBUG: Date_NthDayOfYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($y,$n)=@_;
+  $y=$Curr{"Y"}  if (! $y);
+  $n=1       if (! defined $n  or  $n eq "");
+  $n+=0;     # to turn 023 into 23
+  $y=&Date_FixYear($y)  if (length($y)<4);
+  my $leap=&Date_LeapYear($y);
+  return ()  if ($n<1);
+  return ()  if ($n >= ($leap ? 367 : 366));
+
+  my(@d_in_m)=(31,28,31,30,31,30,31,31,30,31,30,31);
+  $d_in_m[1]=29  if ($leap);
+
+  # Calculate the hours, minutes, and seconds into the day.
+  my $remain=($n - int($n))*24;
+  my $h=int($remain);
+  $remain=($remain - $h)*60;
+  my $mn=int($remain);
+  $remain=($remain - $mn)*60;
+  my $s=$remain;
+
+  # Calculate the month and the day.
+  my($m,$d)=(0,0);
+  $n=int($n);
+  while ($n>0) {
+    $m++;
+    if ($n<=$d_in_m[0]) {
+      $d=int($n);
+      $n=0;
+    } else {
+      $n-= $d_in_m[0];
+      shift(@d_in_m);
+    }
+  }
+
+  ($y,$m,$d,$h,$mn,$s);
+}
+
+########################################################################
+# NOT FOR EXPORT
+########################################################################
+
+# This is used in Date_Init to fill in a hash based on international
+# data.  It takes a list of keys and values and returns both a hash
+# with these values and a regular expression of keys.
+#
+# IN:
+#   $data   = [ key1 val1 key2 val2 ... ]
+#   $opts   = lc     : lowercase the keys in the regexp
+#             sort   : sort (by length) the keys in the regexp
+#             back   : create a regexp with a back reference
+#             escape : escape all strings in the regexp
+#
+# OUT:
+#   $regexp = '(?:key1|key2|...)'
+#   $hash   = { key1=>val1 key2=>val2 ... }
+
+sub Date_InitHash {
+  print "DEBUG: Date_InitHash\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($data,$regexp,$opts,$hash)=@_;
+  my(@data)=@$data;
+  my($key,$val,@list)=();
+
+  # Parse the options
+  my($lc,$sort,$back,$escape)=(0,0,0,0);
+  $lc=1     if ($opts =~ /lc/i);
+  $sort=1   if ($opts =~ /sort/i);
+  $back=1   if ($opts =~ /back/i);
+  $escape=1 if ($opts =~ /escape/i);
+
+  # Create the hash
+  while (@data) {
+    ($key,$val,@data)=@data;
+    $key=lc($key)  if ($lc);
+    $$hash{$key}=$val;
+  }
+
+  # Create the regular expression
+  if ($regexp) {
+    @list=keys(%$hash);
+    @list=sort sortByLength(@list)  if ($sort);
+    if ($escape) {
+      foreach $val (@list) {
+        $val="\Q$val\E";
+      }
+    }
+    if ($back) {
+      $$regexp="(" . join("|",@list) . ")";
+    } else {
+      $$regexp="(?:" . join("|",@list) . ")";
+    }
+  }
+}
+
+# This is used in Date_Init to fill in regular expressions, lists, and
+# hashes based on international data.  It takes a list of lists which have
+# to be stored as regular expressions (to find any element in the list),
+# lists, and hashes (indicating the location in the lists).
+#
+# IN:
+#   $data   = [ [ [ valA1 valA2 ... ][ valA1' valA2' ... ] ... ]
+#               [ [ valB1 valB2 ... ][ valB1' valB2' ... ] ... ]
+#               ...
+#               [ [ valZ1 valZ2 ... ] [valZ1' valZ1' ... ] ... ] ]
+#   $lists  = [ \@listA \@listB ... \@listZ ]
+#   $opts   = lc     : lowercase the values in the regexp
+#             sort   : sort (by length) the values in the regexp
+#             back   : create a regexp with a back reference
+#             escape : escape all strings in the regexp
+#   $hash   = [ \%hash, TYPE ]
+#             TYPE 0 : $hash{ valBn=>n-1 }
+#             TYPE 1 : $hash{ valBn=>n }
+#
+# OUT:
+#   $regexp = '(?:valA1|valA2|...|valB1|...)'
+#   $lists  = [ [ valA1 valA2 ... ]         # only the 1st list (or
+#               [ valB1 valB2 ... ] ... ]   # 2nd for int. characters)
+#   $hash
+
+sub Date_InitLists {
+  print "DEBUG: Date_InitLists\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($data,$regexp,$opts,$lists,$hash)=@_;
+  my(@data)=@$data;
+  my(@lists)=@$lists;
+  my($i,@ele,$ele,@list,$j,$tmp)=();
+
+  # Parse the options
+  my($lc,$sort,$back,$escape)=(0,0,0,0);
+  $lc=1     if ($opts =~ /lc/i);
+  $sort=1   if ($opts =~ /sort/i);
+  $back=1   if ($opts =~ /back/i);
+  $escape=1 if ($opts =~ /escape/i);
+
+  # Set each of the lists
+  if (@lists) {
+    confess "ERROR: Date_InitLists: lists must be 1 per data\n"
+      if ($#lists != $#data);
+    for ($i=0; $i<=$#data; $i++) {
+      @ele=@{ $data[$i] };
+      if ($Cnf{"IntCharSet"} && $#ele>0) {
+        @{ $lists[$i] } = @{ $ele[1] };
+      } else {
+        @{ $lists[$i] } = @{ $ele[0] };
+      }
+    }
+  }
+
+  # Create the hash
+  my($hashtype,$hashsave,%hash)=();
+  if (@$hash) {
+    ($hash,$hashtype)=@$hash;
+    $hashsave=1;
+  } else {
+    $hashtype=0;
+    $hashsave=0;
+  }
+  for ($i=0; $i<=$#data; $i++) {
+    @ele=@{ $data[$i] };
+    foreach $ele (@ele) {
+      @list = @{ $ele };
+      for ($j=0; $j<=$#list; $j++) {
+        $tmp=$list[$j];
+        next  if (! $tmp);
+        $tmp=lc($tmp)  if ($lc);
+        $hash{$tmp}= $j+$hashtype;
+      }
+    }
+  }
+  %$hash = %hash  if ($hashsave);
+
+  # Create the regular expression
+  if ($regexp) {
+    @list=keys(%hash);
+    @list=sort sortByLength(@list)  if ($sort);
+    if ($escape) {
+      foreach $ele (@list) {
+        $ele="\Q$ele\E";
+      }
+    }
+    if ($back) {
+      $$regexp="(" . join("|",@list) . ")";
+    } else {
+      $$regexp="(?:" . join("|",@list) . ")";
+    }
+  }
+}
+
+# This is used in Date_Init to fill in regular expressions and lists based
+# on international data.  This takes a list of strings and returns a regular
+# expression (to find any one of them).
+#
+# IN:
+#   $data   = [ string1 string2 ... ]
+#   $opts   = lc     : lowercase the values in the regexp
+#             sort   : sort (by length) the values in the regexp
+#             back   : create a regexp with a back reference
+#             escape : escape all strings in the regexp
+#
+# OUT:
+#   $regexp = '(string1|string2|...)'
+
+sub Date_InitStrings {
+  print "DEBUG: Date_InitStrings\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($data,$regexp,$opts)=@_;
+  my(@list)=@{ $data };
+
+  # Parse the options
+  my($lc,$sort,$back,$escape)=(0,0,0,0);
+  $lc=1     if ($opts =~ /lc/i);
+  $sort=1   if ($opts =~ /sort/i);
+  $back=1   if ($opts =~ /back/i);
+  $escape=1 if ($opts =~ /escape/i);
+
+  # Create the regular expression
+  my($ele)=();
+  @list=sort sortByLength(@list)  if ($sort);
+  if ($escape) {
+    foreach $ele (@list) {
+      $ele="\Q$ele\E";
+    }
+  }
+  if ($back) {
+    $$regexp="(" . join("|",@list) . ")";
+  } else {
+    $$regexp="(?:" . join("|",@list) . ")";
+  }
+  $$regexp=lc($$regexp)  if ($lc);
+}
+
+# items is passed in (either as a space separated string, or a reference to
+# a list) and a regular expression which matches any one of the items is
+# prepared.  The regular expression will be of one of the forms:
+#   "(a|b)"       @list not empty, back option included
+#   "(?:a|b)"     @list not empty
+#   "()"          @list empty,     back option included
+#   ""            @list empty
+# $options is a string which contains any of the following strings:
+#   back     : the regular expression has a backreference
+#   opt      : the regular expression is optional and a "?" is appended in
+#              the first two forms
+#   optws    : the regular expression is optional and may be replaced by
+#              whitespace
+#   optWs    : the regular expression is optional, but if not present, must
+#              be replaced by whitespace
+#   sort     : the items in the list are sorted by length (longest first)
+#   lc       : the string is lowercased
+#   under    : any underscores are converted to spaces
+#   pre      : it may be preceded by whitespace
+#   Pre      : it must be preceded by whitespace
+#   PRE      : it must be preceded by whitespace or the start
+#   post     : it may be followed by whitespace
+#   Post     : it must be followed by whitespace
+#   POST     : it must be followed by whitespace or the end
+# Spaces due to pre/post options will not be included in the back reference.
+#
+# If $array is included, then the elements will also be returned as a list.
+# $array is a string which may contain any of the following:
+#   keys     : treat the list as a hash and only the keys go into the regexp
+#   key0     : treat the list as the values of a hash with keys 0 .. N-1
+#   key1     : treat the list as the values of a hash with keys 1 .. N
+#   val0     : treat the list as the keys of a hash with values 0 .. N-1
+#   val1     : treat the list as the keys of a hash with values 1 .. N
+
+#    &Date_InitLists([$lang{"month_name"},$lang{"month_abb"}],
+#             [\$Month,"lc,sort,back"],
+#             [\@Month,\@Mon],
+#             [\%Month,1]);
+
+# This is used in Date_Init to prepare regular expressions.  A list of
+# items is passed in (either as a space separated string, or a reference to
+# a list) and a regular expression which matches any one of the items is
+# prepared.  The regular expression will be of one of the forms:
+#   "(a|b)"       @list not empty, back option included
+#   "(?:a|b)"     @list not empty
+#   "()"          @list empty,     back option included
+#   ""            @list empty
+# $options is a string which contains any of the following strings:
+#   back     : the regular expression has a backreference
+#   opt      : the regular expression is optional and a "?" is appended in
+#              the first two forms
+#   optws    : the regular expression is optional and may be replaced by
+#              whitespace
+#   optWs    : the regular expression is optional, but if not present, must
+#              be replaced by whitespace
+#   sort     : the items in the list are sorted by length (longest first)
+#   lc       : the string is lowercased
+#   under    : any underscores are converted to spaces
+#   pre      : it may be preceded by whitespace
+#   Pre      : it must be preceded by whitespace
+#   PRE      : it must be preceded by whitespace or the start
+#   post     : it may be followed by whitespace
+#   Post     : it must be followed by whitespace
+#   POST     : it must be followed by whitespace or the end
+# Spaces due to pre/post options will not be included in the back reference.
+#
+# If $array is included, then the elements will also be returned as a list.
+# $array is a string which may contain any of the following:
+#   keys     : treat the list as a hash and only the keys go into the regexp
+#   key0     : treat the list as the values of a hash with keys 0 .. N-1
+#   key1     : treat the list as the values of a hash with keys 1 .. N
+#   val0     : treat the list as the keys of a hash with values 0 .. N-1
+#   val1     : treat the list as the keys of a hash with values 1 .. N
+sub Date_Regexp {
+  print "DEBUG: Date_Regexp\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($list,$options,$array)=@_;
+  my(@list,$ret,%hash,$i)=();
+  local($_)=();
+  $options=""  if (! defined $options);
+  $array=""    if (! defined $array);
+
+  my($sort,$lc,$under)=(0,0,0);
+  $sort =1  if ($options =~ /sort/i);
+  $lc   =1  if ($options =~ /lc/i);
+  $under=1  if ($options =~ /under/i);
+  my($back,$opt,$pre,$post,$ws)=("?:","","","","");
+  $back =""          if ($options =~ /back/i);
+  $opt  ="?"         if ($options =~ /opt/i);
+  $pre  ='\s*'       if ($options =~ /pre/);
+  $pre  ='\s+'       if ($options =~ /Pre/);
+  $pre  ='(?:\s+|^)' if ($options =~ /PRE/);
+  $post ='\s*'       if ($options =~ /post/);
+  $post ='\s+'       if ($options =~ /Post/);
+  $post ='(?:$|\s+)' if ($options =~ /POST/);
+  $ws   ='\s*'       if ($options =~ /optws/);
+  $ws   ='\s+'       if ($options =~ /optws/);
+
+  my($hash,$keys,$key0,$key1,$val0,$val1)=(0,0,0,0,0,0);
+  $keys =1     if ($array =~ /keys/i);
+  $key0 =1     if ($array =~ /key0/i);
+  $key1 =1     if ($array =~ /key1/i);
+  $val0 =1     if ($array =~ /val0/i);
+  $val1 =1     if ($array =~ /val1/i);
+  $hash =1     if ($keys or $key0 or $key1 or $val0 or $val1);
+
+  my($ref)=ref $list;
+  if (! $ref) {
+    $list =~ s/\s*$//;
+    $list =~ s/^\s*//;
+    $list =~ s/\s+/&&&/g;
+  } elsif ($ref eq "ARRAY") {
+    $list = join("&&&",@$list);
+  } else {
+    confess "ERROR: Date_Regexp.\n";
+  }
+
+  if (! $list) {
+    if ($back eq "") {
+      return "()";
+    } else {
+      return "";
+    }
+  }
+
+  $list=lc($list)  if ($lc);
+  $list=~ s/_/ /g  if ($under);
+  @list=split(/&&&/,$list);
+  if ($keys) {
+    %hash=@list;
+    @list=keys %hash;
+  } elsif ($key0 or $key1 or $val0 or $val1) {
+    $i=0;
+    $i=1  if ($key1 or $val1);
+    if ($key0 or $key1) {
+      %hash= map { $_,$i++ } @list;
+    } else {
+      %hash= map { $i++,$_ } @list;
+    }
+  }
+  @list=sort sortByLength(@list)  if ($sort);
+
+  $ret="($back" . join("|",@list) . ")";
+  $ret="(?:$pre$ret$post)"  if ($pre or $post);
+  $ret.=$opt;
+  $ret="(?:$ret|$ws)"  if ($ws);
+
+  if ($array and $hash) {
+    return ($ret,%hash);
+  } elsif ($array) {
+    return ($ret,@list);
+  } else {
+    return $ret;
+  }
+}
+
+# This will produce a delta with the correct number of signs.  At most two
+# signs will be in it normally (one before the year, and one in front of
+# the day), but if appropriate, signs will be in front of all elements.
+# Also, as many of the signs will be equivalent as possible.
+sub Delta_Normalize {
+  print "DEBUG: Delta_Normalize\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($delta,$mode)=@_;
+  return "" if (! $delta);
+  return "+0:+0:+0:+0:+0:+0:+0"
+    if ($delta =~ /^([+-]?0+:){6}[+-]?0+$/ and $Cnf{"DeltaSigns"});
+  return "+0:0:0:0:0:0:0" if ($delta =~ /^([+-]?0+:){6}[+-]?0+$/);
+
+  my($tmp,$sign1,$sign2,$len)=();
+
+  # Calculate the length of the day in minutes
+  $len=24*60;
+  $len=$Curr{"WDlen"}  if ($mode==2 || $mode==3);
+
+  # We have to get the sign of every component explicitely so that a "-0"
+  # or "+0" doesn't get lost by treating it numerically (i.e. "-0:0:2" must
+  # be a negative delta).
+
+  my($y,$mon,$w,$d,$h,$m,$s)=&Delta_Split($delta);
+
+  # We need to make sure that the signs of all parts of a delta are the
+  # same.  The easiest way to do this is to convert all of the large
+  # components to the smallest ones, then convert the smaller components
+  # back to the larger ones.
+
+  # Do the year/month part
+
+  $mon += $y*12;                         # convert y to m
+  $sign1="+";
+  if ($mon<0) {
+    $mon *= -1;
+    $sign1="-";
+  }
+
+  $y    = $mon/12;                       # convert m to y
+  $mon -= $y*12;
+
+  $y=0    if ($y eq "-0");               # get around silly -0 problem
+  $mon=0  if ($mon eq "-0");
+
+  # Do the wk/day/hour/min/sec part
+
+  {
+    # Unfortunately, $s is overflowing for dates more than ~70 years
+    # apart.
+    no integer;
+
+    if ($mode==3 || $mode==2) {
+      $s += $d*$len*60 + $h*3600 + $m*60;        # convert d/h/m to s
+    } else {
+      $s += ($d+7*$w)*$len*60 + $h*3600 + $m*60; # convert w/d/h/m to s
+    }
+    $sign2="+";
+    if ($s<0) {
+      $s*=-1;
+      $sign2="-";
+    }
+
+    $m  = int($s/60);                    # convert s to m
+    $s -= $m*60;
+    $d  = int($m/$len);                  # convert m to d
+    $m -= $d*$len;
+
+    # The rest should be fine.
+  }
+  $h  = $m/60;                           # convert m to h
+  $m -= $h*60;
+  if ($mode == 3 || $mode == 2) {
+    $w  = $w*1;                          # get around +0 problem
+  } else {
+    $w  = $d/7;                          # convert d to w
+    $d -= $w*7;
+  }
+
+  $w=0    if ($w eq "-0");               # get around silly -0 problem
+  $d=0    if ($d eq "-0");
+  $h=0    if ($h eq "-0");
+  $m=0    if ($m eq "-0");
+  $s=0    if ($s eq "-0");
+
+  # Only include two signs if necessary
+  $sign1=$sign2  if ($y==0 and $mon==0);
+  $sign2=$sign1  if ($w==0 and $d==0 and $h==0 and $m==0 and $s==0);
+  $sign2=""  if ($sign1 eq $sign2  and  ! $Cnf{"DeltaSigns"});
+
+  if ($Cnf{"DeltaSigns"}) {
+    return "$sign1$y:$sign1$mon:$sign2$w:$sign2$d:$sign2$h:$sign2$m:$sign2$s";
+  } else {
+    return "$sign1$y:$mon:$sign2$w:$d:$h:$m:$s";
+  }
+}
+
+# This checks a delta to make sure it is valid.  If it is, it splits
+# it and returns the elements with a sign on each.  The 2nd argument
+# specifies the default sign.  Blank elements are set to 0.  If the
+# third element is non-nil, exactly 7 elements must be included.
+sub Delta_Split {
+  print "DEBUG: Delta_Split\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($delta,$sign,$exact)=@_;
+  my(@delta)=split(/:/,$delta);
+  return ()  if ($exact  and $#delta != 6);
+  my($i)=();
+  $sign="+"  if (! defined $sign);
+  for ($i=0; $i<=$#delta; $i++) {
+    $delta[$i]="0"  if (! $delta[$i]);
+    return ()  if ($delta[$i] !~ /^[+-]?\d+$/);
+    $sign = ($delta[$i] =~ s/^([+-])// ? $1 : $sign);
+    $delta[$i] = $sign.$delta[$i];
+  }
+  @delta;
+}
+
+# Reads up to 3 arguments.  $h may contain the time in any international
+# format.  Any empty elements are set to 0.
+sub Date_ParseTime {
+  print "DEBUG: Date_ParseTime\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($h,$m,$s)=@_;
+  my($t)=&CheckTime("one");
+
+  if (defined $h  and  $h =~ /$t/) {
+    $h=$1;
+    $m=$2;
+    $s=$3   if (defined $3);
+  }
+  $h="00"  if (! defined $h);
+  $m="00"  if (! defined $m);
+  $s="00"  if (! defined $s);
+
+  ($h,$m,$s);
+}
+
+# Forms a date with the 6 elements passed in (all of which must be defined).
+# No check as to validity is made.
+sub Date_Join {
+  print "DEBUG: Date_Join\n"  if ($Curr{"Debug"} =~ /trace/);
+  foreach (0 .. $#_) {
+      croak "undefined arg $_ to Date_Join()" if not defined $_[$_];
+  }
+  my($y,$m,$d,$h,$mn,$s)=@_;
+  my($ym,$md,$dh,$hmn,$mns)=();
+
+  if      ($Cnf{"Internal"} == 0) {
+    $ym=$md=$dh="";
+    $hmn=$mns=":";
+
+  } elsif ($Cnf{"Internal"} == 1) {
+    $ym=$md=$dh=$hmn=$mns="";
+
+  } elsif ($Cnf{"Internal"} == 2) {
+    $ym=$md="-";
+    $dh=" ";
+    $hmn=$mns=":";
+
+  } else {
+    confess "ERROR: Invalid internal format in Date_Join.\n";
+  }
+  $m="0$m"    if (length($m)==1);
+  $d="0$d"    if (length($d)==1);
+  $h="0$h"    if (length($h)==1);
+  $mn="0$mn"  if (length($mn)==1);
+  $s="0$s"    if (length($s)==1);
+  "$y$ym$m$md$d$dh$h$hmn$mn$mns$s";
+}
+
+# This checks a time.  If it is valid, it splits it and returns 3 elements.
+# If "one" or "two" is passed in, a regexp with 1/2 or 2 digit hours is
+# returned.
+sub CheckTime {
+  print "DEBUG: CheckTime\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($time)=@_;
+  my($h)='(?:0?[0-9]|1[0-9]|2[0-3])';
+  my($h2)='(?:0[0-9]|1[0-9]|2[0-3])';
+  my($m)='[0-5][0-9]';
+  my($s)=$m;
+  my($hm)="(?:". $Lang{$Cnf{"Language"}}{"SepHM"} ."|:)";
+  my($ms)="(?:". $Lang{$Cnf{"Language"}}{"SepMS"} ."|:)";
+  my($ss)=$Lang{$Cnf{"Language"}}{"SepSS"};
+  my($t)="^($h)$hm($m)(?:$ms($s)(?:$ss\\d+)?)?\$";
+  if ($time eq "one") {
+    return $t;
+  } elsif ($time eq "two") {
+    $t="^($h2)$hm($m)(?:$ms($s)(?:$ss\\d+)?)?\$";
+    return $t;
+  }
+
+  if ($time =~ /$t/i) {
+    ($h,$m,$s)=($1,$2,$3);
+    $h="0$h" if (length($h)<2);
+    $m="0$m" if (length($m)<2);
+    $s="00"  if (! defined $s);
+    return ($h,$m,$s);
+  } else {
+    return ();
+  }
+}
+
+# This checks a recurrence.  If it is valid, it splits it and returns the
+# elements.  Otherwise, it returns an empty list.
+#    ($recur0,$recur1,$flags,$dateb,$date0,$date1)=&Recur_Split($recur);
+sub Recur_Split {
+  print "DEBUG: Recur_Split\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($recur)=@_;
+  my(@ret,@tmp);
+
+  my($R)  = '(\*?(?:[-,0-9]+[:\*]){6}[-,0-9]+)';
+  my($F)  = '(?:\*([^*]*))';
+  my($DB,$D0,$D1);
+  $DB=$D0=$D1=$F;
+
+  if ($recur =~ /^$R$F?$DB?$D0?$D1?$/) {
+    @ret=($1,$2,$3,$4,$5);
+    @tmp=split(/\*/,shift(@ret));
+    return ()  if ($#tmp>1);
+    return (@tmp,"",@ret)  if ($#tmp==0);
+    return (@tmp,@ret);
+  }
+  return ();
+}
+
+# This checks a date.  If it is valid, it splits it and returns the elements.
+#
+# The optional second argument says 'I really expect this to be a
+# valid Date::Manip object, please throw an exception if it is not'.
+# Otherwise, if the date passed in is undef or '', a regular
+# expression for the date is returned; if the string is nonempty but
+# still not valid, () is returned.
+#
+sub Date_Split {
+  print "DEBUG: Date_Split\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($date, $definitely_valid)=@_;
+  $definitely_valid = 0 if not defined $definitely_valid;
+  my($ym,$md,$dh,$hmn,$mns)=();
+  my($y)='(\d{4})';
+  my($m)='(0[1-9]|1[0-2])';
+  my($d)='(0[1-9]|[1-2][0-9]|3[0-1])';
+  my($h)='([0-1][0-9]|2[0-3])';
+  my($mn)='([0-5][0-9])';
+  my($s)=$mn;
+
+  if      ($Cnf{"Internal"} == 0) {
+    $ym=$md=$dh="";
+    $hmn=$mns=":";
+
+  } elsif ($Cnf{"Internal"} == 1) {
+    $ym=$md=$dh=$hmn=$mns="";
+
+  } elsif ($Cnf{"Internal"} == 2) {
+    $ym=$md="-";
+    $dh=" ";
+    $hmn=$mns=":";
+
+  } else {
+    confess "ERROR: Invalid internal format in Date_Split.\n";
+  }
+
+  my($t)="^$y$ym$m$md$d$dh$h$hmn$mn$mns$s\$";
+
+  if (not defined $date or $date eq '') {
+      if ($definitely_valid) {
+	  die "bad date '$date'";
+      } else {
+	  return $t;
+      }
+  }
+
+  if ($date =~ /$t/) {
+    ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6);
+    my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
+    $d_in_m[2]=29  if (&Date_LeapYear($y));
+    if ($d>$d_in_m[$m]) {
+	my $msg = "invalid date $date: day $d of month $m, but only $d_in_m[$m] days in that month";
+	if ($definitely_valid) {
+	    die $msg;
+	}
+	else {
+	    warn $msg;
+	    return ();
+	}
+    }
+    return ($y,$m,$d,$h,$mn,$s);
+  }
+
+  if ($definitely_valid) {
+      die "invalid date $date: doesn't match regexp $t";
+  }
+  return ();
+}
+
+# This returns the date easter occurs on for a given year as ($month,$day).
+# This is from the Calendar FAQ.
+sub Date_Easter {
+  my($y)=@_;
+  $y=&Date_FixYear($y)  if (length($y)==2);
+
+  my($c) = $y/100;
+  my($g) = $y % 19;
+  my($k) = ($c-17)/25;
+  my($i) = ($c - $c/4 - ($c-$k)/3 + 19*$g + 15) % 30;
+  $i     = $i - ($i/28)*(1 - ($i/28)*(29/($i+1))*((21-$g)/11));
+  my($j) = ($y + $y/4 + $i + 2 - $c + $c/4) % 7;
+  my($l) = $i-$j;
+  my($m) = 3 + ($l+40)/44;
+  my($d) = $l + 28 - 31*($m/4);
+  return ($m,$d);
+}
+
+# This takes a list of years, months, WeekOfMonth's, and DayOfWeek's, and
+# returns a list of dates.  Optionally, a list of dates can be passed in as
+# the 1st argument (with the 2nd argument the null list) and the year/month
+# of these will be used.
+sub Date_Recur_WoM {
+  my($y,$m,$w,$d)=@_;
+  my(@y)=@$y;
+  my(@m)=@$m;
+  my(@w)=@$w;
+  my(@d)=@$d;
+  my($date0,$date1,@tmp,@date,$d0,$d1,@tmp2)=();
+
+  if (@m) {
+    foreach $m (@m) {
+      return ()  if (! &IsInt($m,1,12));
+    }
+
+    @tmp=@tmp2=();
+    foreach $y (@y) {
+      foreach $m (@m) {
+        push(@tmp,$y);
+        push(@tmp2,$m);
+      }
+    }
+
+    @y=@tmp;
+    @m=@tmp2;
+
+  } else {
+    foreach $d0 (@y) {
+      @tmp=&Date_Split($d0);
+      return ()  if (! @tmp);
+      push(@tmp2,$tmp[0]);
+      push(@m,$tmp[1]);
+    }
+    @y=@tmp2;
+  }
+
+  return ()  if (! @w);
+  foreach $w (@w) {
+    return ()  if ($w==0  ||  ! &IsInt($w,-5,5));
+  }
+
+  if (@d) {
+    foreach $d (@d) {
+      return ()  if ($d==0  ||  ! &IsInt($d,-7,7));
+      $d += 8  if ($d < 0);
+    }
+  }
+
+  @date=();
+  foreach $y (@y) {
+    $m=shift(@m);
+
+    # Find 1st day of this month and next month
+    $date0=&Date_Join($y,$m,1,0,0,0);
+    $date1=&DateCalc_DateDelta($date0,"+0:1:0:0:0:0:0");
+
+    foreach $d (@d) {
+      # Find 1st occurence of DOW (in both months)
+      $d0=&Date_GetNext($date0,$d,1);
+      $d1=&Date_GetNext($date1,$d,1);
+
+      @tmp=();
+      while (&Date_Cmp($d0,$d1)<0) {
+        push(@tmp,$d0);
+        $d0=&DateCalc_DateDelta($d0,"+0:0:1:0:0:0:0");
+      }
+
+      @tmp2=();
+      foreach $w (@w) {
+        if ($w>0) {
+          next  if ($w > $#tmp+1);
+          push(@tmp2,$tmp[$w-1]);
+        } else {
+          next  if (-$w > $#tmp+1);
+          push(@tmp2,$tmp[$#tmp+1+$w]);
+        }
+      }
+      @tmp2=sort { Date_Cmp($a,$b) } @tmp2;
+      push(@date,@tmp2);
+    }
+  }
+
+  @date;
+}
+
+# This returns a sorted list of dates formed by adding/subtracting
+# $delta to $dateb in the range $date0<=$d<$dateb.  The first date in
+# the list is actually the first date<$date0 and the last date in the
+# list is the first date>=$date1 (because sometimes the set part will
+# move the date back into the range).
+sub Date_Recur {
+  my($date0,$date1,$dateb,$delta)=@_;
+  my(@ret,$d)=();
+
+  while (&Date_Cmp($dateb,$date0)<0) {
+    $dateb=&DateCalc_DateDelta($dateb,$delta);
+  }
+  while (&Date_Cmp($dateb,$date1)>=0) {
+    $dateb=&DateCalc_DateDelta($dateb,"-$delta");
+  }
+
+  # Add the dates $date0..$dateb
+  $d=$dateb;
+  while (&Date_Cmp($d,$date0)>=0) {
+    unshift(@ret,$d);
+    $d=&DateCalc_DateDelta($d,"-$delta");
+  }
+  # Add the first date earler than the range
+  unshift(@ret,$d);
+
+  # Add the dates $dateb..$date1
+  $d=&DateCalc_DateDelta($dateb,$delta);
+  while (&Date_Cmp($d,$date1)<0) {
+    push(@ret,$d);
+    $d=&DateCalc_DateDelta($d,$delta);
+  }
+  # Add the first date later than the range
+  push(@ret,$d);
+
+  @ret;
+}
+
+# This sets the values in each date of a recurrence.
+#
+# $h,$m,$s can each be values or lists "1-2,4".  If any are equal to "-1",
+# they are not set (and none of the larger elements are set).
+sub Date_RecurSetTime {
+  my($date0,$date1,$dates,$h,$m,$s)=@_;
+  my(@dates)=@$dates;
+  my(@h,@m,@s,$date,@tmp)=();
+
+  $m="-1"  if ($s eq "-1");
+  $h="-1"  if ($m eq "-1");
+
+  if ($h ne "-1") {
+    @h=&ReturnList($h);
+    return ()  if ! (@h);
+    @h=sort { $a<=>$b } (@h);
+
+    @tmp=();
+    foreach $date (@dates) {
+      foreach $h (@h) {
+        push(@tmp,&Date_SetDateField($date,"h",$h,1));
+      }
+    }
+    @dates=@tmp;
+  }
+
+  if ($m ne "-1") {
+    @m=&ReturnList($m);
+    return ()  if ! (@m);
+    @m=sort { $a<=>$b } (@m);
+
+    @tmp=();
+    foreach $date (@dates) {
+      foreach $m (@m) {
+        push(@tmp,&Date_SetDateField($date,"mn",$m,1));
+      }
+    }
+    @dates=@tmp;
+  }
+
+  if ($s ne "-1") {
+    @s=&ReturnList($s);
+    return ()  if ! (@s);
+    @s=sort { $a<=>$b } (@s);
+
+    @tmp=();
+    foreach $date (@dates) {
+      foreach $s (@s) {
+        push(@tmp,&Date_SetDateField($date,"s",$s,1));
+      }
+    }
+    @dates=@tmp;
+  }
+
+  @tmp=();
+  foreach $date (@dates) {
+    push(@tmp,$date)  if (&Date_Cmp($date,$date0)>=0  &&
+                          &Date_Cmp($date,$date1)<0  &&
+                          &Date_Split($date));
+  }
+
+  @tmp;
+}
+
+sub DateCalc_DateDate {
+  print "DEBUG: DateCalc_DateDate\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($D1,$D2,$mode)=@_;
+  my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
+  $mode=0  if (! defined $mode);
+
+  # Exact mode
+  if ($mode==0) {
+    my($y1,$m1,$d1,$h1,$mn1,$s1)=&Date_Split($D1, 1);
+    my($y2,$m2,$d2,$h2,$mn2,$s2)=&Date_Split($D2, 1);
+    my($i,@delta,$d,$delta,$y)=();
+
+    # form the delta for hour/min/sec
+    $delta[4]=$h2-$h1;
+    $delta[5]=$mn2-$mn1;
+    $delta[6]=$s2-$s1;
+
+    # form the delta for yr/mon/day
+    $delta[0]=$delta[1]=0;
+    $d=0;
+    if ($y2>$y1) {
+      $d=&Date_DaysInYear($y1) - &Date_DayOfYear($m1,$d1,$y1);
+      $d+=&Date_DayOfYear($m2,$d2,$y2);
+      for ($y=$y1+1; $y<$y2; $y++) {
+        $d+= &Date_DaysInYear($y);
+      }
+    } elsif ($y2<$y1) {
+      $d=&Date_DaysInYear($y2) - &Date_DayOfYear($m2,$d2,$y2);
+      $d+=&Date_DayOfYear($m1,$d1,$y1);
+      for ($y=$y2+1; $y<$y1; $y++) {
+        $d+= &Date_DaysInYear($y);
+      }
+      $d *= -1;
+    } else {
+      $d=&Date_DayOfYear($m2,$d2,$y2) - &Date_DayOfYear($m1,$d1,$y1);
+    }
+    $delta[2]=0;
+    $delta[3]=$d;
+
+    for ($i=0; $i<7; $i++) {
+      $delta[$i]="+".$delta[$i]  if ($delta[$i]>=0);
+    }
+
+    $delta=join(":",@delta);
+    $delta=&Delta_Normalize($delta,0);
+    return $delta;
+  }
+
+  my($date1,$date2)=($D1,$D2);
+  my($tmp,$sign,$err,@tmp)=();
+
+  # make sure both are work days
+  if ($mode==2 || $mode==3) {
+    $date1=&Date_NextWorkDay($date1,0,1);
+    $date2=&Date_NextWorkDay($date2,0,1);
+  }
+
+  # make sure date1 comes before date2
+  if (&Date_Cmp($date1,$date2)>0) {
+    $sign="-";
+    $tmp=$date1;
+    $date1=$date2;
+    $date2=$tmp;
+  } else {
+    $sign="+";
+  }
+  if (&Date_Cmp($date1,$date2)==0) {
+    return "+0:+0:+0:+0:+0:+0:+0"  if ($Cnf{"DeltaSigns"});
+    return "+0:0:0:0:0:0:0";
+  }
+
+  my($y1,$m1,$d1,$h1,$mn1,$s1)=&Date_Split($date1, 1);
+  my($y2,$m2,$d2,$h2,$mn2,$s2)=&Date_Split($date2, 1);
+  my($dy,$dm,$dw,$dd,$dh,$dmn,$ds,$ddd)=(0,0,0,0,0,0,0,0);
+
+  if ($mode != 3) {
+
+    # Do years
+    $dy=$y2-$y1;
+    $dm=0;
+    if ($dy>0) {
+      $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0:0",\$err,0);
+      if (&Date_Cmp($tmp,$date2)>0) {
+        $dy--;
+        $tmp=$date1;
+        $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0:0",\$err,0)
+          if ($dy>0);
+        $dm=12;
+      }
+      $date1=$tmp;
+    }
+
+    # Do months
+    $dm+=$m2-$m1;
+    if ($dm>0) {
+      $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0:0",\$err,0);
+      if (&Date_Cmp($tmp,$date2)>0) {
+        $dm--;
+        $tmp=$date1;
+        $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0:0",\$err,0)
+          if ($dm>0);
+      }
+      $date1=$tmp;
+    }
+
+    # At this point, check to see that we're on a business day again so that
+    # Aug 3 (Monday) -> Sep 3 (Sunday) -> Sep 4 (Monday)  = 1 month
+    if ($mode==2) {
+      if (! &Date_IsWorkDay($date1,0)) {
+        $date1=&Date_NextWorkDay($date1,0,1);
+      }
+    }
+  }
+
+  # Do days
+  if ($mode==2 || $mode==3) {
+    $dd=0;
+    while (1) {
+      $tmp=&Date_NextWorkDay($date1,1,1);
+      if (&Date_Cmp($tmp,$date2)<=0) {
+        $dd++;
+        $date1=$tmp;
+      } else {
+        last;
+      }
+    }
+
+  } else {
+    ($y1,$m1,$d1)=( &Date_Split($date1, 1) )[0..2];
+    $dd=0;
+    # If we're jumping across months, set $d1 to the first of the next month
+    # (or possibly the 0th of next month which is equivalent to the last day
+    # of this month)
+    if ($m1!=$m2) {
+      $d_in_m[2]=29  if (&Date_LeapYear($y1));
+      $dd=$d_in_m[$m1]-$d1+1;
+      $d1=1;
+      $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$dd:0:0:0",\$err,0);
+      if (&Date_Cmp($tmp,$date2)>0) {
+        $dd--;
+        $d1--;
+        $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$dd:0:0:0",\$err,0);
+      }
+      $date1=$tmp;
+    }
+
+    $ddd=0;
+    if ($d1<$d2) {
+      $ddd=$d2-$d1;
+      $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$ddd:0:0:0",\$err,0);
+      if (&Date_Cmp($tmp,$date2)>0) {
+        $ddd--;
+        $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$ddd:0:0:0",\$err,0);
+      }
+      $date1=$tmp;
+    }
+    $dd+=$ddd;
+  }
+
+  # in business mode, make sure h1 comes before h2 (if not find delta between
+  # now and end of day and move to start of next business day)
+  $d1=( &Date_Split($date1, 1) )[2];
+  $dh=$dmn=$ds=0;
+  if ($mode==2 || $mode==3  and  $d1 != $d2) {
+    $tmp=&Date_SetTime($date1,$Cnf{"WorkDayEnd"});
+    $tmp=&DateCalc_DateDelta($tmp,"+0:0:0:0:0:1:0")
+      if ($Cnf{"WorkDay24Hr"});
+    $tmp=&DateCalc_DateDate($date1,$tmp,0);
+    ($tmp,$tmp,$tmp,$tmp,$dh,$dmn,$ds)=&Delta_Split($tmp);
+    $date1=&Date_NextWorkDay($date1,1,0);
+    $date1=&Date_SetTime($date1,$Cnf{"WorkDayBeg"});
+    $d1=( &Date_Split($date1, 1) )[2];
+    confess "ERROR: DateCalc DateDate Business.\n"  if ($d1 != $d2);
+  }
+
+  # Hours, minutes, seconds
+  $tmp=&DateCalc_DateDate($date1,$date2,0);
+  @tmp=&Delta_Split($tmp);
+  $dh  += $tmp[4];
+  $dmn += $tmp[5];
+  $ds  += $tmp[6];
+
+  $tmp="$sign$dy:$dm:0:$dd:$dh:$dmn:$ds";
+  &Delta_Normalize($tmp,$mode);
+}
+
+sub DateCalc_DeltaDelta {
+  print "DEBUG: DateCalc_DeltaDelta\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($D1,$D2,$mode)=@_;
+  my(@delta1,@delta2,$i,$delta,@delta)=();
+  $mode=0  if (! defined $mode);
+
+  @delta1=&Delta_Split($D1);
+  @delta2=&Delta_Split($D2);
+  for ($i=0; $i<7; $i++) {
+    $delta[$i]=$delta1[$i]+$delta2[$i];
+    $delta[$i]="+".$delta[$i]  if ($delta[$i]>=0);
+  }
+
+  $delta=join(":",@delta);
+  $delta=&Delta_Normalize($delta,$mode);
+  return $delta;
+}
+
+sub DateCalc_DateDelta {
+  print "DEBUG: DateCalc_DateDelta\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($D1,$D2,$errref,$mode)=@_;
+  my($date)=();
+  my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
+  my($h1,$m1,$h2,$m2,$len,$hh,$mm)=();
+  $mode=0  if (! defined $mode);
+
+  if ($mode==2 || $mode==3) {
+    $h1=$Curr{"WDBh"};
+    $m1=$Curr{"WDBm"};
+    $h2=$Curr{"WDEh"};
+    $m2=$Curr{"WDEm"};
+    $hh=$h2-$h1;
+    $mm=$m2-$m1;
+    if ($mm<0) {
+      $hh--;
+      $mm+=60;
+    }
+  }
+
+  # Date, delta
+  my($y,$m,$d,$h,$mn,$s)=&Date_Split($D1, 1);
+  my($dy,$dm,$dw,$dd,$dh,$dmn,$ds)=&Delta_Split($D2);
+
+  # do the month/year part
+  $y+=$dy;
+  while (length($y)<4) {
+    $y = "0$y";
+  }
+  &ModuloAddition(-12,$dm,\$m,\$y);   # -12 means 1-12 instead of 0-11
+  $d_in_m[2]=29  if (&Date_LeapYear($y));
+
+  # if we have gone past the last day of a month, move the date back to
+  # the last day of the month
+  if ($d>$d_in_m[$m]) {
+    $d=$d_in_m[$m];
+  }
+
+  # do the week part
+  if ($mode==0  ||  $mode==1) {
+    $dd += $dw*7;
+  } else {
+    $date=&DateCalc_DateDelta(&Date_Join($y,$m,$d,$h,$mn,$s),
+                              "+0:0:$dw:0:0:0:0",0);
+    ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
+  }
+
+  # in business mode, set the day to a work day at this point so the h/mn/s
+  # stuff will work out
+  if ($mode==2 || $mode==3) {
+    $d=$d_in_m[$m] if ($d>$d_in_m[$m]);
+    $date=&Date_NextWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),0,1);
+    ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
+  }
+
+  # seconds, minutes, hours
+  &ModuloAddition(60,$ds,\$s,\$mn);
+  if ($mode==2 || $mode==3) {
+    while (1) {
+      &ModuloAddition(60,$dmn,\$mn,\$h);
+      $h+= $dh;
+
+      if ($h>$h2  or  $h==$h2 && $mn>$m2) {
+        $dh=$h-$h2;
+        $dmn=$mn-$m2;
+        $h=$h1;
+        $mn=$m1;
+        $dd++;
+
+      } elsif ($h<$h1  or  $h==$h1 && $mn<$m1) {
+        $dh=$h-$h1;
+        $dmn=$m1-$mn;
+        $h=$h2;
+        $mn=$m2;
+        $dd--;
+
+      } elsif ($h==$h2  &&  $mn==$m2) {
+        $dd++;
+        $dh=-$hh;
+        $dmn=-$mm;
+
+      } else {
+        last;
+      }
+    }
+
+  } else {
+    &ModuloAddition(60,$dmn,\$mn,\$h);
+    &ModuloAddition(24,$dh,\$h,\$d);
+  }
+
+  # If we have just gone past the last day of the month, we need to make
+  # up for this:
+  if ($d>$d_in_m[$m]) {
+    $dd+= $d-$d_in_m[$m];
+    $d=$d_in_m[$m];
+  }
+
+  # days
+  if ($mode==2 || $mode==3) {
+    if ($dd>=0) {
+      $date=&Date_NextWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),$dd,1);
+    } else {
+      $date=&Date_PrevWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),-$dd,1);
+    }
+    ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
+
+  } else {
+    $d_in_m[2]=29  if (&Date_LeapYear($y));
+    $d=$d_in_m[$m]  if ($d>$d_in_m[$m]);
+    $d += $dd;
+    while ($d<1) {
+      $m--;
+      if ($m==0) {
+        $m=12;
+        $y--;
+        if (&Date_LeapYear($y)) {
+          $d_in_m[2]=29;
+        } else {
+          $d_in_m[2]=28;
+        }
+      }
+      $d += $d_in_m[$m];
+    }
+    while ($d>$d_in_m[$m]) {
+      $d -= $d_in_m[$m];
+      $m++;
+      if ($m==13) {
+        $m=1;
+        $y++;
+        if (&Date_LeapYear($y)) {
+          $d_in_m[2]=29;
+        } else {
+          $d_in_m[2]=28;
+        }
+      }
+    }
+  }
+
+  if ($y<0 or $y>9999) {
+    $$errref=3;
+    return;
+  }
+  &Date_Join($y,$m,$d,$h,$mn,$s);
+}
+
+sub Date_UpdateHolidays {
+  print "DEBUG: Date_UpdateHolidays\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($year)=@_;
+  $Holiday{"year"}=$year;
+  $Holiday{"dates"}{$year}={};
+
+  my($date,$delta,$err)=();
+  my($key,@tmp,$tmp);
+
+  foreach $key (keys %{ $Holiday{"desc"} }) {
+    @tmp=&Recur_Split($key);
+    if (@tmp) {
+      $tmp=&ParseDateString("${year}010100:00:00");
+      ($date)=&ParseRecur($key,$tmp,$tmp,($year+1)."-01-01");
+      next  if (! $date);
+
+    } elsif ($key =~ /^(.*)([+-].*)$/) {
+      # Date +/- Delta
+      ($date,$delta)=($1,$2);
+      $tmp=&ParseDateString("$date $year");
+      if ($tmp) {
+        $date=$tmp;
+      } else {
+        $date=&ParseDateString($date);
+        next  if ($date !~ /^$year/);
+      }
+      $date=&DateCalc($date,$delta,\$err,0);
+
+    } else {
+      # Date
+      $date=$key;
+      $tmp=&ParseDateString("$date $year");
+      if ($tmp) {
+        $date=$tmp;
+      } else {
+        $date=&ParseDateString($date);
+        next  if ($date !~ /^$year/);
+      }
+    }
+    $Holiday{"dates"}{$year}{$date}=$Holiday{"desc"}{$key};
+  }
+}
+
+# This sets a Date::Manip config variable.
+sub Date_SetConfigVariable {
+  print "DEBUG: Date_SetConfigVariable\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($var,$val)=@_;
+
+  # These are most appropriate for command line options instead of in files.
+  $Cnf{"PathSep"}=$val,          return  if ($var =~ /^PathSep$/i);
+  $Cnf{"PersonalCnf"}=$val,      return  if ($var =~ /^PersonalCnf$/i);
+  $Cnf{"PersonalCnfPath"}=$val,  return  if ($var =~ /^PersonalCnfPath$/i);
+  &EraseHolidays(),              return  if ($var =~ /^EraseHolidays$/i);
+  $Cnf{"IgnoreGlobalCnf"}=1,     return  if ($var =~ /^IgnoreGlobalCnf$/i);
+  $Cnf{"GlobalCnf"}=$val,        return  if ($var =~ /^GlobalCnf$/i);
+
+  $Curr{"InitLang"}=1,
+  $Cnf{"Language"}=$val,         return  if ($var =~ /^Language$/i);
+  $Cnf{"DateFormat"}=$val,       return  if ($var =~ /^DateFormat$/i);
+  $Cnf{"TZ"}=$val,               return  if ($var =~ /^TZ$/i);
+  $Cnf{"ConvTZ"}=$val,           return  if ($var =~ /^ConvTZ$/i);
+  $Cnf{"Internal"}=$val,         return  if ($var =~ /^Internal$/i);
+  $Cnf{"FirstDay"}=$val,         return  if ($var =~ /^FirstDay$/i);
+  $Cnf{"WorkWeekBeg"}=$val,      return  if ($var =~ /^WorkWeekBeg$/i);
+  $Cnf{"WorkWeekEnd"}=$val,      return  if ($var =~ /^WorkWeekEnd$/i);
+  $Cnf{"WorkDayBeg"}=$val,
+  $Curr{"ResetWorkDay"}=1,       return  if ($var =~ /^WorkDayBeg$/i);
+  $Cnf{"WorkDayEnd"}=$val,
+  $Curr{"ResetWorkDay"}=1,       return  if ($var =~ /^WorkDayEnd$/i);
+  $Cnf{"WorkDay24Hr"}=$val,
+  $Curr{"ResetWorkDay"}=1,       return  if ($var =~ /^WorkDay24Hr$/i);
+  $Cnf{"DeltaSigns"}=$val,       return  if ($var =~ /^DeltaSigns$/i);
+  $Cnf{"Jan1Week1"}=$val,        return  if ($var =~ /^Jan1Week1$/i);
+  $Cnf{"YYtoYYYY"}=$val,         return  if ($var =~ /^YYtoYYYY$/i);
+  $Cnf{"UpdateCurrTZ"}=$val,     return  if ($var =~ /^UpdateCurrTZ$/i);
+  $Cnf{"IntCharSet"}=$val,       return  if ($var =~ /^IntCharSet$/i);
+  $Curr{"DebugVal"}=$val,        return  if ($var =~ /^Debug$/i);
+  $Cnf{"TomorrowFirst"}=$val,    return  if ($var =~ /^TomorrowFirst$/i);
+  $Cnf{"ForceDate"}=$val,        return  if ($var =~ /^ForceDate$/i);
+  $Cnf{"TodayIsMidnight"}=$val,  return  if ($var =~ /^TodayIsMidnight$/i);
+
+  confess "ERROR: Unknown configuration variable $var in Date::Manip.\n";
+}
+
+sub EraseHolidays {
+  print "DEBUG: EraseHolidays\n"  if ($Curr{"Debug"} =~ /trace/);
+
+  $Cnf{"EraseHolidays"}=0;
+  delete $Holiday{"list"};
+  $Holiday{"list"}={};
+  delete $Holiday{"desc"};
+  $Holiday{"desc"}={};
+  $Holiday{"dates"}={};
+}
+
+# This returns a pointer to a list of times and events in the format
+#    [ date [ events ], date, [ events ], ... ]
+# where each list of events are events that are in effect at the date
+# immediately preceding the list.
+#
+# This takes either one date or two dates as arguments.
+sub Events_Calc {
+  print "DEBUG: Events_Calc\n"  if ($Curr{"Debug"} =~ /trace/);
+
+  my($date0,$date1)=@_;
+
+  my($tmp);
+  $date0=&ParseDateString($date0);
+  return undef  if (! $date0);
+  if ($date1) {
+    $date1=&ParseDateString($date1);
+    if (&Date_Cmp($date0,$date1)>0) {
+      $tmp=$date1;
+      $date1=$date0;
+      $date0=$tmp;
+    }
+  } else {
+    $date1=&DateCalc_DateDelta($date0,"+0:0:0:0:0:0:1");
+  }
+
+  #
+  #   [ d0,d1,del,name ]     => [ d0, d1+del )
+  #   [ d0,0,del,name ]      => [ d0, d0+del )
+  #
+  my(%ret,$d0,$d1,$del,$name,$c0,$c1);
+  my(@tmp)=@{ $Events{"dates"} };
+ DATE: while (@tmp) {
+    ($d0,$d1,$del,$name)=splice(@tmp,0,4);
+    $d0=&ParseDateString($d0);
+    $d1=&ParseDateString($d1)   if ($d1);
+    $del=&ParseDateDelta($del)  if ($del);
+    if ($d1) {
+      if ($del) {
+        $d1=&DateCalc_DateDelta($d1,$del);
+      }
+    } else {
+      $d1=&DateCalc_DateDelta($d0,$del);
+    }
+    if (&Date_Cmp($d0,$d1)>0) {
+      $tmp=$d1;
+      $d1=$d0;
+      $d0=$tmp;
+    }
+    #         [ date0,date1 )
+    # [ d0,d1 )      OR     [ d0,d1 )
+    next DATE  if (&Date_Cmp($d1,$date0)<=0  ||
+                   &Date_Cmp($d0,$date1)>=0);
+    #      [ date0,date1 )
+    # [ d0,d1 )
+    # [ d0,                  d1 )
+    if (&Date_Cmp($d0,$date0)<=0) {
+      push @{ $ret{$date0} },$name;
+      push @{ $ret{$d1} },"!$name"  if (&Date_Cmp($d1,$date1)<0);
+      next DATE;
+    }
+    #      [ date0,date1 )
+    #                 [ d0,d1 )
+    if (&Date_Cmp($d1,$date1)>=0) {
+      push @{ $ret{$d0} },$name;
+      next DATE;
+    }
+    #      [ date0,date1 )
+    #         [ d0,d1 )
+    push @{ $ret{$d0} },$name;
+    push @{ $ret{$d1} },"!$name";
+  }
+
+  #
+  #   [ recur,delta0,delta1,name ]   => [ {date-delta0},{date+delta1} )
+  #
+  my($rec,$del0,$del1,@d);
+  @tmp=@{ $Events{"recur"} };
+ RECUR: while (@tmp) {
+    ($rec,$del0,$del1,$name)=splice(@tmp,0,4);
+    @d=();
+
+  }
+
+  # Sort them AND take into account the "!$name" entries.
+  my(%tmp,$date,@tmp2,@ret);
+  @d=sort { &Date_Cmp($a,$b) } keys %ret;
+  foreach $date (@d) {
+    @tmp=@{ $ret{$date} };
+    @tmp2=();
+    foreach $tmp (@tmp) {
+      push(@tmp2,$tmp), next  if ($tmp =~ /^!/);
+      $tmp{$tmp}=1;
+    }
+    foreach $tmp (@tmp2) {
+      $tmp =~ s/^!//;
+      delete $tmp{$tmp};
+    }
+    push(@ret,$date,[ keys %tmp ]);
+  }
+
+  %tmp = @ret;
+  @ret = ();
+  foreach my $d (sort { Date_Cmp($a,$b) } keys %tmp) {
+    my $e = $tmp{$d};
+    push @ret,($d,[ sort @$e ]);
+  }
+  return \@ret;
+}
+
+# This parses the raw events list
+sub Events_ParseRaw {
+  print "DEBUG: Events_ParseRaw\n"  if ($Curr{"Debug"} =~ /trace/);
+
+  # Only need to be parsed once
+  my($force)=@_;
+  $Events{"parsed"}=0  if ($force);
+  return  if ($Events{"parsed"});
+  $Events{"parsed"}=1;
+
+  my(@events)=@{ $Events{"raw"} };
+  my($event,$name,@event,$date0,$date1,$tmp,$delta,$recur0,$recur1,@recur,$r,
+     $recur);
+ EVENT: while (@events) {
+    ($event,$name)=splice(@events,0,2);
+    @event=split(/\s*;\s*/,$event);
+
+    if ($#event == 0) {
+
+      if ($date0=&ParseDateString($event[0])) {
+        #
+        # date = event
+        #
+        $tmp=&ParseDateString("$event[0] 00:00:00");
+        if ($tmp  &&  $tmp eq $date0) {
+          $delta="+0:0:0:1:0:0:0";
+        } else {
+          $delta="+0:0:0:0:1:0:0";
+        }
+        push @{ $Events{"dates"} },($date0,0,$delta,$name);
+
+      } elsif ($recur=&ParseRecur($event[0])) {
+        #
+        # recur = event
+        #
+        ($recur0,$recur1)=&Recur_Split($recur);
+        if ($recur0) {
+          if ($recur1) {
+            $r="$recur0:$recur1";
+          } else {
+            $r=$recur0;
+          }
+        } else {
+          $r=$recur1;
+        }
+        (@recur)=split(/:/,$r);
+        if (pop(@recur)==0  &&  pop(@recur)==0  &&  pop(@recur)==0) {
+          $delta="+0:0:0:1:0:0:0";
+        } else {
+          $delta="+0:0:0:0:1:0:0";
+        }
+        push @{ $Events{"recur"} },($recur,0,$delta,$name);
+
+      } else {
+        # ??? = event
+        warn "WARNING: illegal event ignored [ @event ]\n";
+        next EVENT;
+      }
+
+    } elsif ($#event == 1) {
+
+      if ($date0=&ParseDateString($event[0])) {
+
+        if ($date1=&ParseDateString($event[1])) {
+          #
+          # date ; date = event
+          #
+          $tmp=&ParseDateString("$event[1] 00:00:00");
+          if ($tmp  &&  $tmp eq $date1) {
+            $date1=&DateCalc_DateDelta($date1,"+0:0:0:1:0:0:0");
+          }
+          push @{ $Events{"dates"} },($date0,$date1,0,$name);
+
+        } elsif ($delta=&ParseDateDelta($event[1])) {
+          #
+          # date ; delta = event
+          #
+          push @{ $Events{"dates"} },($date0,0,$delta,$name);
+
+        } else {
+          # date ; ??? = event
+          warn "WARNING: illegal event ignored [ @event ]\n";
+          next EVENT;
+        }
+
+      } elsif ($recur=&ParseRecur($event[0])) {
+
+        if ($delta=&ParseDateDelta($event[1])) {
+          #
+          # recur ; delta = event
+          #
+          push @{ $Events{"recur"} },($recur,0,$delta,$name);
+
+        } else {
+          # recur ; ??? = event
+          warn "WARNING: illegal event ignored [ @event ]\n";
+          next EVENT;
+        }
+
+      } else {
+        # ??? ; ??? = event
+        warn "WARNING: illegal event ignored [ @event ]\n";
+        next EVENT;
+      }
+
+    } else {
+      # date ; delta0 ; delta1 = event
+      # recur ; delta0 ; delta1 = event
+      # ??? ; ??? ; ??? ... = event
+      warn "WARNING: illegal event ignored [ @event ]\n";
+      next EVENT;
+    }
+  }
+}
+
+# This reads an init file.
+sub Date_InitFile {
+  print "DEBUG: Date_InitFile\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($file)=@_;
+  my($in)=new IO::File;
+  local($_)=();
+  my($section)="vars";
+  my($var,$val,$recur,$name)=();
+
+  $in->open($file)  ||  return;
+  while(defined ($_=<$in>)) {
+    chomp;
+    s/^\s+//;
+    s/\s+$//;
+    next  if (! $_  or  /^\#/);
+
+    if (/^\*holiday/i) {
+      $section="holiday";
+      &EraseHolidays()  if ($section =~ /holiday/i  &&  $Cnf{"EraseHolidays"});
+      next;
+    } elsif (/^\*events/i) {
+      $section="events";
+      next;
+    }
+
+    if ($section =~ /var/i) {
+      confess "ERROR: invalid Date::Manip config file line.\n  $_\n"
+        if (! /(.*\S)\s*=\s*(.*)$/);
+      ($var,$val)=($1,$2);
+      &Date_SetConfigVariable($var,$val);
+
+    } elsif ($section =~ /holiday/i) {
+      confess "ERROR: invalid Date::Manip config file line.\n  $_\n"
+        if (! /(.*\S)\s*=\s*(.*)$/);
+      ($recur,$name)=($1,$2);
+      $name=""  if (! defined $name);
+      $Holiday{"desc"}{$recur}=$name;
+
+    } elsif ($section =~ /events/i) {
+      confess "ERROR: invalid Date::Manip config file line.\n  $_\n"
+        if (! /(.*\S)\s*=\s*(.*)$/);
+      ($val,$var)=($1,$2);
+      push @{ $Events{"raw"} },($val,$var);
+
+    } else {
+      # A section not currently used by Date::Manip (but may be
+      # used by some extension to it).
+      next;
+    }
+  }
+  close($in);
+}
+
+# $flag=&Date_TimeCheck(\$h,\$mn,\$s,\$ampm);
+#   Returns 1 if any of the fields are bad.  All fields are optional, and
+#   all possible checks are done on the data.  If a field is not passed in,
+#   it is set to default values.  If data is missing, appropriate defaults
+#   are supplied.
+sub Date_TimeCheck {
+  print "DEBUG: Date_TimeCheck\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($h,$mn,$s,$ampm)=@_;
+  my($tmp1,$tmp2,$tmp3)=();
+
+  $$h=""     if (! defined $$h);
+  $$mn=""    if (! defined $$mn);
+  $$s=""     if (! defined $$s);
+  $$ampm=""  if (! defined $$ampm);
+  $$ampm=uc($$ampm)  if ($$ampm);
+
+  # Check hour
+  $tmp1=$Lang{$Cnf{"Language"}}{"AmPm"};
+  $tmp2="";
+  if ($$ampm =~ /^$tmp1$/i) {
+    $tmp3=$Lang{$Cnf{"Language"}}{"AM"};
+    $tmp2="AM"  if ($$ampm =~ /^$tmp3$/i);
+    $tmp3=$Lang{$Cnf{"Language"}}{"PM"};
+    $tmp2="PM"  if ($$ampm =~ /^$tmp3$/i);
+  } elsif ($$ampm) {
+    return 1;
+  }
+  if ($tmp2 eq "AM" || $tmp2 eq "PM") {
+    $$h="0$$h"    if (length($$h)==1);
+    return 1      if ($$h<1 || $$h>12);
+    $$h="00"      if ($tmp2 eq "AM"  and  $$h==12);
+    $$h += 12     if ($tmp2 eq "PM"  and  $$h!=12);
+  } else {
+    $$h="00"      if ($$h eq "");
+    $$h="0$$h"    if (length($$h)==1);
+    return 1      if (! &IsInt($$h,0,23));
+    $tmp2="AM"    if ($$h<12);
+    $tmp2="PM"    if ($$h>=12);
+  }
+  $$ampm=$Lang{$Cnf{"Language"}}{"AMstr"};
+  $$ampm=$Lang{$Cnf{"Language"}}{"PMstr"}  if ($tmp2 eq "PM");
+
+  # Check minutes
+  $$mn="00"       if ($$mn eq "");
+  $$mn="0$$mn"    if (length($$mn)==1);
+  return 1        if (! &IsInt($$mn,0,59));
+
+  # Check seconds
+  $$s="00"        if ($$s eq "");
+  $$s="0$$s"      if (length($$s)==1);
+  return 1        if (! &IsInt($$s,0,59));
+
+  return 0;
+}
+
+# $flag=&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
+#   Returns 1 if any of the fields are bad.  All fields are optional, and
+#   all possible checks are done on the data.  If a field is not passed in,
+#   it is set to default values.  If data is missing, appropriate defaults
+#   are supplied.
+#
+#   If the flag UpdateHolidays is set, the year is set to
+#   CurrHolidayYear.
+sub Date_DateCheck {
+  print "DEBUG: Date_DateCheck\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($y,$m,$d,$h,$mn,$s,$ampm,$wk)=@_;
+  my($tmp1,$tmp2,$tmp3)=();
+
+  my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
+  my($curr_y)=$Curr{"Y"};
+  my($curr_m)=$Curr{"M"};
+  my($curr_d)=$Curr{"D"};
+  $$m=1, $$d=1  if (defined $$y and ! defined $$m and ! defined $$d);
+  $$y=""     if (! defined $$y);
+  $$m=""     if (! defined $$m);
+  $$d=""     if (! defined $$d);
+  $$wk=""    if (! defined $$wk);
+  $$d=$curr_d  if ($$y eq "" and $$m eq "" and $$d eq "");
+
+  # Check year.
+  $$y=$curr_y             if ($$y eq "");
+  $$y=&Date_FixYear($$y)  if (length($$y)<4);
+  return 1                if (! &IsInt($$y,0,9999));
+  $d_in_m[2]=29           if (&Date_LeapYear($$y));
+
+  # Check month
+  $$m=$curr_m             if ($$m eq "");
+  $$m=$Lang{$Cnf{"Language"}}{"MonthH"}{lc($$m)}
+    if (exists $Lang{$Cnf{"Language"}}{"MonthH"}{lc($$m)});
+  $$m="0$$m"              if (length($$m)==1);
+  return 1                if (! &IsInt($$m,1,12));
+
+  # Check day
+  $$d="01"                if ($$d eq "");
+  $$d="0$$d"              if (length($$d)==1);
+  return 1                if (! &IsInt($$d,1,$d_in_m[$$m]));
+  if ($$wk) {
+    $tmp1=&Date_DayOfWeek($$m,$$d,$$y);
+    $tmp2=$Lang{$Cnf{"Language"}}{"WeekH"}{lc($$wk)}
+      if (exists $Lang{$Cnf{"Language"}}{"WeekH"}{lc($$wk)});
+    return 1      if ($tmp1 != $tmp2);
+  }
+
+  return &Date_TimeCheck($h,$mn,$s,$ampm);
+}
+
+# Takes a year in 2 digit form and returns it in 4 digit form
+sub Date_FixYear {
+  print "DEBUG: Date_FixYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($y)=@_;
+  my($curr_y)=$Curr{"Y"};
+  $y=$curr_y  if (! defined $y  or  ! $y);
+  return $y  if (length($y)==4);
+  confess "ERROR: Invalid year ($y)\n"  if (length($y)!=2);
+  my($y1,$y2)=();
+
+  if (lc($Cnf{"YYtoYYYY"}) eq "c") {
+    $y1=substring($y,0,2);
+    $y="$y1$y";
+
+  } elsif ($Cnf{"YYtoYYYY"} =~ /^c(\d{2})$/i) {
+    $y1=$1;
+    $y="$y1$y";
+
+  } elsif ($Cnf{"YYtoYYYY"} =~ /^c(\d{2})(\d{2})$/i) {
+    $y1="$1$2";
+    $y ="$1$y";
+    $y += 100  if ($y<$y1);
+
+  } else {
+    $y1=$curr_y-$Cnf{"YYtoYYYY"};
+    $y2=$y1+99;
+    $y="19$y";
+    while ($y<$y1) {
+      $y+=100;
+    }
+    while ($y>$y2) {
+      $y-=100;
+    }
+  }
+  $y;
+}
+
+# &Date_NthWeekOfYear($y,$n);
+#   Returns a list of (YYYY,MM,DD) for the 1st day of the Nth week of the
+#   year.
+# &Date_NthWeekOfYear($y,$n,$dow,$flag);
+#   Returns a list of (YYYY,MM,DD) for the Nth DoW of the year.  If flag
+#   is nil, the first DoW of the year may actually be in the previous
+#   year (since the 1st week may include days from the previous year).
+#   If flag is non-nil, the 1st DoW of the year refers to the 1st one
+#   actually in the year
+sub Date_NthWeekOfYear {
+  print "DEBUG: Date_NthWeekOfYear\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($y,$n,$dow,$flag)=@_;
+  my($m,$d,$err,$tmp,$date,%dow)=();
+  $y=$Curr{"Y"}  if (! defined $y  or  ! $y);
+  $n=1       if (! defined $n  or  $n eq "");
+  return ()  if ($n<0  ||  $n>53);
+  if (defined $dow) {
+    $dow=lc($dow);
+    %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };
+    $dow=$dow{$dow}  if (exists $dow{$dow});
+    return ()  if ($dow<1 || $dow>7);
+    $flag=""   if (! defined $flag);
+  } else {
+    $dow="";
+    $flag="";
+  }
+
+  $y=&Date_FixYear($y)  if (length($y)<4);
+  if ($Cnf{"Jan1Week1"}) {
+    $date=&Date_Join($y,1,1,0,0,0);
+  } else {
+    $date=&Date_Join($y,1,4,0,0,0);
+  }
+  $date=&Date_GetPrev($date,$Cnf{"FirstDay"},1);
+  $date=&Date_GetNext($date,$dow,1)  if ($dow ne "");
+
+  if ($flag) {
+    ($tmp)=&Date_Split($date, 1);
+    $n++  if ($tmp != $y);
+  }
+
+  if ($n>1) {
+    $date=&DateCalc_DateDelta($date,"+0:0:". ($n-1) . ":0:0:0:0",\$err,0);
+  } elsif ($n==0) {
+    $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0);
+  }
+  ($y,$m,$d)=&Date_Split($date, 1);
+  ($y,$m,$d);
+}
+
+########################################################################
+# LANGUAGE INITIALIZATION
+########################################################################
+
+# 8-bit international characters can be gotten by "\xXX".  I don't know
+# how to get 16-bit characters.  I've got to read up on perllocale.
+sub Char_8Bit {
+  my($hash)=@_;
+
+  #   grave `
+  #     A`    00c0     a`    00e0
+  #     E`    00c8     e`    00e8
+  #     I`    00cc     i`    00ec
+  #     O`    00d2     o`    00f2
+  #     U`    00d9     u`    00f9
+  #     W`    1e80     w`    1e81
+  #     Y`    1ef2     y`    1ef3
+
+  $$hash{"A`"} = "\xc0";   #   LATIN CAPITAL LETTER A WITH GRAVE
+  $$hash{"E`"} = "\xc8";   #   LATIN CAPITAL LETTER E WITH GRAVE
+  $$hash{"I`"} = "\xcc";   #   LATIN CAPITAL LETTER I WITH GRAVE
+  $$hash{"O`"} = "\xd2";   #   LATIN CAPITAL LETTER O WITH GRAVE
+  $$hash{"U`"} = "\xd9";   #   LATIN CAPITAL LETTER U WITH GRAVE
+  $$hash{"a`"} = "\xe0";   #   LATIN SMALL LETTER A WITH GRAVE
+  $$hash{"e`"} = "\xe8";   #   LATIN SMALL LETTER E WITH GRAVE
+  $$hash{"i`"} = "\xec";   #   LATIN SMALL LETTER I WITH GRAVE
+  $$hash{"o`"} = "\xf2";   #   LATIN SMALL LETTER O WITH GRAVE
+  $$hash{"u`"} = "\xf9";   #   LATIN SMALL LETTER U WITH GRAVE
+
+  #   acute '
+  #     A'    00c1     a'    00e1
+  #     C'    0106     c'    0107
+  #     E'    00c9     e'    00e9
+  #     I'    00cd     i'    00ed
+  #     L'    0139     l'    013a
+  #     N'    0143     n'    0144
+  #     O'    00d3     o'    00f3
+  #     R'    0154     r'    0155
+  #     S'    015a     s'    015b
+  #     U'    00da     u'    00fa
+  #     W'    1e82     w'    1e83
+  #     Y'    00dd     y'    00fd
+  #     Z'    0179     z'    017a
+
+  $$hash{"A'"} = "\xc1";   #   LATIN CAPITAL LETTER A WITH ACUTE
+  $$hash{"E'"} = "\xc9";   #   LATIN CAPITAL LETTER E WITH ACUTE
+  $$hash{"I'"} = "\xcd";   #   LATIN CAPITAL LETTER I WITH ACUTE
+  $$hash{"O'"} = "\xd3";   #   LATIN CAPITAL LETTER O WITH ACUTE
+  $$hash{"U'"} = "\xda";   #   LATIN CAPITAL LETTER U WITH ACUTE
+  $$hash{"Y'"} = "\xdd";   #   LATIN CAPITAL LETTER Y WITH ACUTE
+  $$hash{"a'"} = "\xe1";   #   LATIN SMALL LETTER A WITH ACUTE
+  $$hash{"e'"} = "\xe9";   #   LATIN SMALL LETTER E WITH ACUTE
+  $$hash{"i'"} = "\xed";   #   LATIN SMALL LETTER I WITH ACUTE
+  $$hash{"o'"} = "\xf3";   #   LATIN SMALL LETTER O WITH ACUTE
+  $$hash{"u'"} = "\xfa";   #   LATIN SMALL LETTER U WITH ACUTE
+  $$hash{"y'"} = "\xfd";   #   LATIN SMALL LETTER Y WITH ACUTE
+
+  #   double acute "         "
+  #     O"    0150     o"    0151
+  #     U"    0170     u"    0171
+
+  #   circumflex ^
+  #     A^    00c2     a^    00e2
+  #     C^    0108     c^    0109
+  #     E^    00ca     e^    00ea
+  #     G^    011c     g^    011d
+  #     H^    0124     h^    0125
+  #     I^    00ce     i^    00ee
+  #     J^    0134     j^    0135
+  #     O^    00d4     o^    00f4
+  #     S^    015c     s^    015d
+  #     U^    00db     u^    00fb
+  #     W^    0174     w^    0175
+  #     Y^    0176     y^    0177
+
+  $$hash{"A^"} = "\xc2";   #   LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+  $$hash{"E^"} = "\xca";   #   LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+  $$hash{"I^"} = "\xce";   #   LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+  $$hash{"O^"} = "\xd4";   #   LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+  $$hash{"U^"} = "\xdb";   #   LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+  $$hash{"a^"} = "\xe2";   #   LATIN SMALL LETTER A WITH CIRCUMFLEX
+  $$hash{"e^"} = "\xea";   #   LATIN SMALL LETTER E WITH CIRCUMFLEX
+  $$hash{"i^"} = "\xee";   #   LATIN SMALL LETTER I WITH CIRCUMFLEX
+  $$hash{"o^"} = "\xf4";   #   LATIN SMALL LETTER O WITH CIRCUMFLEX
+  $$hash{"u^"} = "\xfb";   #   LATIN SMALL LETTER U WITH CIRCUMFLEX
+
+  #   tilde ~
+  #     A~    00c3    a~    00e3
+  #     I~    0128    i~    0129
+  #     N~    00d1    n~    00f1
+  #     O~    00d5    o~    00f5
+  #     U~    0168    u~    0169
+
+  $$hash{"A~"} = "\xc3";   #   LATIN CAPITAL LETTER A WITH TILDE
+  $$hash{"N~"} = "\xd1";   #   LATIN CAPITAL LETTER N WITH TILDE
+  $$hash{"O~"} = "\xd5";   #   LATIN CAPITAL LETTER O WITH TILDE
+  $$hash{"a~"} = "\xe3";   #   LATIN SMALL LETTER A WITH TILDE
+  $$hash{"n~"} = "\xf1";   #   LATIN SMALL LETTER N WITH TILDE
+  $$hash{"o~"} = "\xf5";   #   LATIN SMALL LETTER O WITH TILDE
+
+  #   macron -
+  #     A-    0100    a-    0101
+  #     E-    0112    e-    0113
+  #     I-    012a    i-    012b
+  #     O-    014c    o-    014d
+  #     U-    016a    u-    016b
+
+  #   breve ( [half circle up]
+  #     A(    0102    a(    0103
+  #     G(    011e    g(    011f
+  #     U(    016c    u(    016d
+
+  #   dot .
+  #     C.    010a    c.    010b
+  #     E.    0116    e.    0117
+  #     G.    0120    g.    0121
+  #     I.    0130
+  #     Z.    017b    z.    017c
+
+  #   diaeresis :  [side by side dots]
+  #     A:    00c4    a:    00e4
+  #     E:    00cb    e:    00eb
+  #     I:    00cf    i:    00ef
+  #     O:    00d6    o:    00f6
+  #     U:    00dc    u:    00fc
+  #     W:    1e84    w:    1e85
+  #     Y:    0178    y:    00ff
+
+  $$hash{"A:"} = "\xc4";   #   LATIN CAPITAL LETTER A WITH DIAERESIS
+  $$hash{"E:"} = "\xcb";   #   LATIN CAPITAL LETTER E WITH DIAERESIS
+  $$hash{"I:"} = "\xcf";   #   LATIN CAPITAL LETTER I WITH DIAERESIS
+  $$hash{"O:"} = "\xd6";   #   LATIN CAPITAL LETTER O WITH DIAERESIS
+  $$hash{"U:"} = "\xdc";   #   LATIN CAPITAL LETTER U WITH DIAERESIS
+  $$hash{"a:"} = "\xe4";   #   LATIN SMALL LETTER A WITH DIAERESIS
+  $$hash{"e:"} = "\xeb";   #   LATIN SMALL LETTER E WITH DIAERESIS
+  $$hash{"i:"} = "\xef";   #   LATIN SMALL LETTER I WITH DIAERESIS
+  $$hash{"o:"} = "\xf6";   #   LATIN SMALL LETTER O WITH DIAERESIS
+  $$hash{"u:"} = "\xfc";   #   LATIN SMALL LETTER U WITH DIAERESIS
+  $$hash{"y:"} = "\xff";   #   LATIN SMALL LETTER Y WITH DIAERESIS
+
+  #   ring o
+  #     U0    016e    u0    016f
+
+  #   cedilla ,  [squiggle down and left below the letter]
+  #     ,C    00c7    ,c    00e7
+  #     ,G    0122    ,g    0123
+  #     ,K    0136    ,k    0137
+  #     ,L    013b    ,l    013c
+  #     ,N    0145    ,n    0146
+  #     ,R    0156    ,r    0157
+  #     ,S    015e    ,s    015f
+  #     ,T    0162    ,t    0163
+
+  $$hash{",C"} = "\xc7";   #   LATIN CAPITAL LETTER C WITH CEDILLA
+  $$hash{",c"} = "\xe7";   #   LATIN SMALL LETTER C WITH CEDILLA
+
+  #   ogonek ;  [squiggle down and right below the letter]
+  #     A;    0104    a;    0105
+  #     E;    0118    e;    0119
+  #     I;    012e    i;    012f
+  #     U;    0172    u;    0173
+
+  #   caron <  [little v on top]
+  #     A<    01cd    a<    01ce
+  #     C<    010c    c<    010d
+  #     D<    010e    d<    010f
+  #     E<    011a    e<    011b
+  #     L<    013d    l<    013e
+  #     N<    0147    n<    0148
+  #     R<    0158    r<    0159
+  #     S<    0160    s<    0161
+  #     T<    0164    t<    0165
+  #     Z<    017d    z<    017e
+
+
+  # Other characters
+
+  # First character is below, 2nd character is above
+  $$hash{"||"} = "\xa6";   #   BROKEN BAR
+  $$hash{" :"} = "\xa8";   #   DIAERESIS
+  $$hash{"-a"} = "\xaa";   #   FEMININE ORDINAL INDICATOR
+  #$$hash{" -"}= "\xaf";   #   MACRON   (narrow bar)
+  $$hash{" -"} = "\xad";   #   HYPHEN   (wide bar)
+  $$hash{" o"} = "\xb0";   #   DEGREE SIGN
+  $$hash{"-+"} = "\xb1";   #   PLUS\342\200\220MINUS SIGN
+  $$hash{" 1"} = "\xb9";   #   SUPERSCRIPT ONE
+  $$hash{" 2"} = "\xb2";   #   SUPERSCRIPT TWO
+  $$hash{" 3"} = "\xb3";   #   SUPERSCRIPT THREE
+  $$hash{" '"} = "\xb4";   #   ACUTE ACCENT
+  $$hash{"-o"} = "\xba";   #   MASCULINE ORDINAL INDICATOR
+  $$hash{" ."} = "\xb7";   #   MIDDLE DOT
+  $$hash{", "} = "\xb8";   #   CEDILLA
+  $$hash{"Ao"} = "\xc5";   #   LATIN CAPITAL LETTER A WITH RING ABOVE
+  $$hash{"ao"} = "\xe5";   #   LATIN SMALL LETTER A WITH RING ABOVE
+  $$hash{"ox"} = "\xf0";   #   LATIN SMALL LETTER ETH
+
+  # upside down characters
+
+  $$hash{"ud!"} = "\xa1";  #   INVERTED EXCLAMATION MARK
+  $$hash{"ud?"} = "\xbf";  #   INVERTED QUESTION MARK
+
+  # overlay characters
+
+  $$hash{"X o"} = "\xa4";  #   CURRENCY SIGN
+  $$hash{"Y ="} = "\xa5";  #   YEN SIGN
+  $$hash{"S o"} = "\xa7";  #   SECTION SIGN
+  $$hash{"O c"} = "\xa9";  #   COPYRIGHT SIGN    Copyright
+  $$hash{"O R"} = "\xae";  #   REGISTERED SIGN
+  $$hash{"D -"} = "\xd0";  #   LATIN CAPITAL LETTER ETH
+  $$hash{"O /"} = "\xd8";  #   LATIN CAPITAL LETTER O WITH STROKE
+  $$hash{"o /"} = "\xf8";  #   LATIN SMALL LETTER O WITH STROKE
+
+  # special names
+
+  $$hash{"1/4"} = "\xbc";  #   VULGAR FRACTION ONE QUARTER
+  $$hash{"1/2"} = "\xbd";  #   VULGAR FRACTION ONE HALF
+  $$hash{"3/4"} = "\xbe";  #   VULGAR FRACTION THREE QUARTERS
+  $$hash{"<<"}  = "\xab";  #   LEFT POINTING DOUBLE ANGLE QUOTATION MARK
+  $$hash{">>"}  = "\xbb";  #   RIGHT POINTING DOUBLE ANGLE QUOTATION MARK
+  $$hash{"cent"}= "\xa2";  #   CENT SIGN
+  $$hash{"lb"}  = "\xa3";  #   POUND SIGN
+  $$hash{"mu"}  = "\xb5";  #   MICRO SIGN
+  $$hash{"beta"}= "\xdf";  #   LATIN SMALL LETTER SHARP S
+  $$hash{"para"}= "\xb6";  #   PILCROW SIGN
+  $$hash{"-|"}  = "\xac";  #   NOT SIGN
+  $$hash{"AE"}  = "\xc6";  #   LATIN CAPITAL LETTER AE
+  $$hash{"ae"}  = "\xe6";  #   LATIN SMALL LETTER AE
+  $$hash{"x"}   = "\xd7";  #   MULTIPLICATION SIGN
+  $$hash{"P"}   = "\xde";  #   LATIN CAPITAL LETTER THORN
+  $$hash{"/"}   = "\xf7";  #   DIVISION SIGN
+  $$hash{"p"}   = "\xfe";  #   LATIN SMALL LETTER THORN
+}
+
+# $hashref = &Date_Init_LANGUAGE;
+#   This returns a hash containing all of the initialization for a
+#   specific language.  The hash elements are:
+#
+#   @ month_name      full month names          January February ...
+#   @ month_abb       month abbreviations       Jan Feb ...
+#   @ day_name        day names                 Monday Tuesday ...
+#   @ day_abb         day abbreviations         Mon Tue ...
+#   @ day_char        day character abbrevs     M T ...
+#   @ am              AM notations
+#   @ pm              PM notations
+#
+#   @ num_suff        number with suffix        1st 2nd ...
+#   @ num_word        numbers spelled out       first second ...
+#
+#   $ now             words which mean now      now ...
+#   $ today           words which mean today    today ...
+#   $ last            words which mean last     last final ...
+#   $ each            words which mean each     each every ...
+#   $ of              of (as in a member of)    in of ...
+#                     ex.  4th day OF June
+#   $ at              at 4:00                   at
+#   $ on              on Sunday                 on
+#   $ future          in the future             in
+#   $ past            in the past               ago
+#   $ next            next item                 next
+#   $ prev            previous item             last previous
+#   $ later           2 hours later
+#
+#   % offset          a hash of special dates   { tomorrow->0:0:0:1:0:0:0 }
+#   % times           a hash of times           { noon->12:00:00 ... }
+#
+#   $ years           words for year            y yr year ...
+#   $ months          words for month
+#   $ weeks           words for week
+#   $ days            words for day
+#   $ hours           words for hour
+#   $ minutes         words for minute
+#   $ seconds         words for second
+#   % replace
+#       The replace element is quite important, but a bit tricky.  In
+#       English (and probably other languages), one of the abbreviations
+#       for the word month that would be nice is "m".  The problem is that
+#       "m" matches the "m" in "minute" which causes the string to be
+#       improperly matched in some cases.  Hence, the list of abbreviations
+#       for month is given as:
+#         "mon month months"
+#       In order to allow you to enter "m", replacements can be done.
+#       $replace is a list of pairs of words which are matched and replaced
+#       AS ENTIRE WORDS.  Having $replace equal to "m"->"month" means that
+#       the entire word "m" will be replaced with "month".  This allows the
+#       desired abbreviation to be used.  Make sure that replace contains
+#       an even number of words (i.e. all must be pairs).  Any time a
+#       desired abbreviation matches the start of any other, it has to go
+#       here.
+#
+#   $ exact           exact mode                exactly
+#   $ approx          approximate mode          approximately
+#   $ business        business mode             business
+#
+#   r sephm           hour/minute separator     (?::)
+#   r sepms           minute/second separator   (?::)
+#   r sepss           second/fraction separator (?:[.:])
+#
+#   Elements marked with an asterix (@) are returned as a set of lists.
+#   Each list contains the strings for each element.  The first set is used
+#   when the 7-bit ASCII (US) character set is wanted.  The 2nd set is used
+#   when an international character set is available.  Both of the 1st two
+#   sets should be complete (but the 2nd list can be left empty to force the
+#   first set to be used always).  The 3rd set and later can be partial sets
+#   if desired.
+#
+#   Elements marked with a dollar ($) are returned as a simple list of words.
+#
+#   Elements marked with a percent (%) are returned as a hash list.
+#
+#   Elements marked with (r) are regular expression elements which must not
+#   create a back reference.
+#
+# ***NOTE*** Every hash element (unless otherwise noted) MUST be defined in
+# every language.
+
+sub Date_Init_English {
+  print "DEBUG: Date_Init_English\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+
+  $$d{"month_name"}=
+    [["January","February","March","April","May","June",
+      "July","August","September","October","November","December"]];
+
+  $$d{"month_abb"}=
+    [["Jan","Feb","Mar","Apr","May","Jun",
+      "Jul","Aug","Sep","Oct","Nov","Dec"],
+     [],
+     ["","","","","","","","","Sept"]];
+
+  $$d{"day_name"}=
+    [["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]];
+  $$d{"day_abb"}=
+    [["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
+     ["",   "Tues","",  "Thur","",  "",   ""]];
+  $$d{"day_char"}=
+    [["M","T","W","Th","F","Sa","S"]];
+
+  $$d{"num_suff"}=
+    [["1st","2nd","3rd","4th","5th","6th","7th","8th","9th","10th",
+      "11th","12th","13th","14th","15th","16th","17th","18th","19th","20th",
+      "21st","22nd","23rd","24th","25th","26th","27th","28th","29th","30th",
+      "31st"]];
+  $$d{"num_word"}=
+    [["first","second","third","fourth","fifth","sixth","seventh","eighth",
+      "ninth","tenth","eleventh","twelfth","thirteenth","fourteenth",
+      "fifteenth","sixteenth","seventeenth","eighteenth","nineteenth",
+      "twentieth","twenty-first","twenty-second","twenty-third",
+      "twenty-fourth","twenty-fifth","twenty-sixth","twenty-seventh",
+      "twenty-eighth","twenty-ninth","thirtieth","thirty-first"]];
+
+  $$d{"now"}     =["now"];
+  $$d{"today"}   =["today"];
+  $$d{"last"}    =["last","final"];
+  $$d{"each"}    =["each","every"];
+  $$d{"of"}      =["in","of"];
+  $$d{"at"}      =["at"];
+  $$d{"on"}      =["on"];
+  $$d{"future"}  =["in"];
+  $$d{"past"}    =["ago"];
+  $$d{"next"}    =["next"];
+  $$d{"prev"}    =["previous","last"];
+  $$d{"later"}   =["later"];
+
+  $$d{"exact"}   =["exactly"];
+  $$d{"approx"}  =["approximately"];
+  $$d{"business"}=["business"];
+
+  $$d{"offset"}  =["yesterday","-0:0:0:1:0:0:0","tomorrow","+0:0:0:1:0:0:0","overmorrow","+0:0:0:2:0:0:0"];
+  $$d{"times"}   =["noon","12:00:00","midnight","00:00:00"];
+
+  $$d{"years"}   =["y","yr","year","yrs","years"];
+  $$d{"months"}  =["mon","month","months"];
+  $$d{"weeks"}   =["w","wk","wks","week","weeks"];
+  $$d{"days"}    =["d","day","days"];
+  $$d{"hours"}   =["h","hr","hrs","hour","hours"];
+  $$d{"minutes"} =["mn","min","minute","minutes"];
+  $$d{"seconds"} =["s","sec","second","seconds"];
+  $$d{"replace"} =["m","month"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["AM","A.M."];
+  $$d{"pm"}      = ["PM","P.M."];
+}
+
+sub Date_Init_Italian {
+  print "DEBUG: Date_Init_Italian\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($i)=$h{"i`"};
+
+  $$d{"month_name"}=
+    [[qw(Gennaio Febbraio Marzo Aprile Maggio Giugno
+         Luglio Agosto Settembre Ottobre Novembre Dicembre)]];
+
+  $$d{"month_abb"}=
+    [[qw(Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic)]];
+
+  $$d{"day_name"}=
+    [[qw(Lunedi Martedi Mercoledi Giovedi Venerdi Sabato Domenica)],
+     [qw(Luned${i} Marted${i} Mercoled${i} Gioved${i} Venerd${i})]];
+  $$d{"day_abb"}=
+    [[qw(Lun Mar Mer Gio Ven Sab Dom)]];
+  $$d{"day_char"}=
+    [[qw(L Ma Me G V S D)]];
+
+  $$d{"num_suff"}=
+    [[qw(1mo 2do 3zo 4to 5to 6to 7mo 8vo 9no 10mo 11mo 12mo 13mo 14mo 15mo
+         16mo 17mo 18mo 19mo 20mo 21mo 22mo 23mo 24mo 25mo 26mo 27mo 28mo
+         29mo 3mo 31mo)]];
+  $$d{"num_word"}=
+    [[qw(primo secondo terzo quarto quinto sesto settimo ottavo nono decimo
+         undicesimo dodicesimo tredicesimo quattordicesimo quindicesimo
+         sedicesimo diciassettesimo diciottesimo diciannovesimo ventesimo
+         ventunesimo ventiduesimo ventitreesimo ventiquattresimo
+         venticinquesimo ventiseiesimo ventisettesimo ventottesimo
+         ventinovesimo trentesimo trentunesimo)]];
+
+  $$d{"now"}     =[qw(adesso)];
+  $$d{"today"}   =[qw(oggi)];
+  $$d{"last"}    =[qw(ultimo)];
+  $$d{"each"}    =[qw(ogni)];
+  $$d{"of"}      =[qw(della del)];
+  $$d{"at"}      =[qw(alle)];
+  $$d{"on"}      =[qw(di)];
+  $$d{"future"}  =[qw(fra)];
+  $$d{"past"}    =[qw(fa)];
+  $$d{"next"}    =[qw(prossimo)];
+  $$d{"prev"}    =[qw(ultimo)];
+  $$d{"later"}   =[qw(dopo)];
+
+  $$d{"exact"}   =[qw(esattamente)];
+  $$d{"approx"}  =[qw(circa)];
+  $$d{"business"}=[qw(lavorativi lavorativo)];
+
+  $$d{"offset"}  =[qw(ieri -0:0:0:1:0:0:0 domani +0:0:0:1:0:0:0)];
+  $$d{"times"}   =[qw(mezzogiorno 12:00:00 mezzanotte 00:00:00)];
+
+  $$d{"years"}   =[qw(anni anno a)];
+  $$d{"months"}  =[qw(mesi mese mes)];
+  $$d{"weeks"}   =[qw(settimane settimana sett)];
+  $$d{"days"}    =[qw(giorni giorno g)];
+  $$d{"hours"}   =[qw(ore ora h)];
+  $$d{"minutes"} =[qw(minuti minuto min)];
+  $$d{"seconds"} =[qw(secondi secondo sec)];
+  $$d{"replace"} =[qw(s sec m mes)];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = [qw(AM)];
+  $$d{"pm"}      = [qw(PM)];
+}
+
+sub Date_Init_French {
+  print "DEBUG: Date_Init_French\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($e)=$h{"e'"};
+  my($u)=$h{"u^"};
+  my($a)=$h{"a'"};
+
+  $$d{"month_name"}=
+    [["janvier","fevrier","mars","avril","mai","juin",
+      "juillet","aout","septembre","octobre","novembre","decembre"],
+     ["janvier","f${e}vrier","mars","avril","mai","juin",
+      "juillet","ao${u}t","septembre","octobre","novembre","d${e}cembre"]];
+  $$d{"month_abb"}=
+    [["jan","fev","mar","avr","mai","juin",
+      "juil","aout","sept","oct","nov","dec"],
+     ["jan","f${e}v","mar","avr","mai","juin",
+      "juil","ao${u}t","sept","oct","nov","d${e}c"]];
+
+  $$d{"day_name"}=
+    [["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"]];
+  $$d{"day_abb"}=
+    [["lun","mar","mer","jeu","ven","sam","dim"]];
+  $$d{"day_char"}=
+    [["l","ma","me","j","v","s","d"]];
+
+  $$d{"num_suff"}=
+    [["1er","2e","3e","4e","5e","6e","7e","8e","9e","10e",
+      "11e","12e","13e","14e","15e","16e","17e","18e","19e","20e",
+      "21e","22e","23e","24e","25e","26e","27e","28e","29e","30e",
+      "31e"]];
+  $$d{"num_word"}=
+    [["premier","deux","trois","quatre","cinq","six","sept","huit","neuf",
+      "dix","onze","douze","treize","quatorze","quinze","seize","dix-sept",
+      "dix-huit","dix-neuf","vingt","vingt et un","vingt-deux","vingt-trois",
+      "vingt-quatre","vingt-cinq","vingt-six","vingt-sept","vingt-huit",
+      "vingt-neuf","trente","trente et un"],
+     ["1re"]];
+
+  $$d{"now"}     =["maintenant"];
+  $$d{"today"}   =["aujourd'hui"];
+  $$d{"last"}    =["dernier"];
+  $$d{"each"}    =["chaque","tous les","toutes les"];
+  $$d{"of"}      =["en","de"];
+  $$d{"at"}      =["a","${a}0"];
+  $$d{"on"}      =["sur"];
+  $$d{"future"}  =["en"];
+  $$d{"past"}    =["il y a"];
+  $$d{"next"}    =["suivant"];
+  $$d{"prev"}    =["precedent","pr${e}c${e}dent"];
+  $$d{"later"}   =["plus tard"];
+
+  $$d{"exact"}   =["exactement"];
+  $$d{"approx"}  =["approximativement"];
+  $$d{"business"}=["professionel"];
+
+  $$d{"offset"}  =["hier","-0:0:0:1:0:0:0","demain","+0:0:0:1:0:0:0"];
+  $$d{"times"}   =["midi","12:00:00","minuit","00:00:00"];
+
+  $$d{"years"}   =["an","annee","ans","annees","ann${e}e","ann${e}es"];
+  $$d{"months"}  =["mois"];
+  $$d{"weeks"}   =["sem","semaine"];
+  $$d{"days"}    =["j","jour","jours"];
+  $$d{"hours"}   =["h","heure","heures"];
+  $$d{"minutes"} =["mn","min","minute","minutes"];
+  $$d{"seconds"} =["s","sec","seconde","secondes"];
+  $$d{"replace"} =["m","mois"];
+
+  $$d{"sephm"}   ='[h:]';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:,]';
+
+  $$d{"am"}      = ["du matin"];
+  $$d{"pm"}      = ["du soir"];
+}
+
+sub Date_Init_Romanian {
+  print "DEBUG: Date_Init_Romanian\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($p)=$h{"p"};
+  my($i)=$h{"i^"};
+  my($a)=$h{"a~"};
+  my($o)=$h{"-o"};
+
+  $$d{"month_name"}=
+    [["ianuarie","februarie","martie","aprilie","mai","iunie",
+      "iulie","august","septembrie","octombrie","noiembrie","decembrie"]];
+  $$d{"month_abb"}=
+    [["ian","febr","mart","apr","mai","iun",
+      "iul","aug","sept","oct","nov","dec"],
+     ["","feb"]];
+
+  $$d{"day_name"}=
+    [["luni","marti","miercuri","joi","vineri","simbata","duminica"],
+     ["luni","mar${p}i","miercuri","joi","vineri","s${i}mb${a}t${a}",
+      "duminic${a}"]];
+  $$d{"day_abb"}=
+    [["lun","mar","mie","joi","vin","sim","dum"],
+     ["lun","mar","mie","joi","vin","s${i}m","dum"]];
+  $$d{"day_char"}=
+    [["L","Ma","Mi","J","V","S","D"]];
+
+  $$d{"num_suff"}=
+    [["prima","a doua","a 3-a","a 4-a","a 5-a","a 6-a","a 7-a","a 8-a",
+      "a 9-a","a 10-a","a 11-a","a 12-a","a 13-a","a 14-a","a 15-a",
+      "a 16-a","a 17-a","a 18-a","a 19-a","a 20-a","a 21-a","a 22-a",
+      "a 23-a","a 24-a","a 25-a","a 26-a","a 27-a","a 28-a","a 29-a",
+      "a 30-a","a 31-a"]];
+
+  $$d{"num_word"}=
+    [["prima","a doua","a treia","a patra","a cincea","a sasea","a saptea",
+      "a opta","a noua","a zecea","a unsprezecea","a doisprezecea",
+      "a treisprezecea","a patrusprezecea","a cincisprezecea","a saiprezecea",
+      "a saptesprezecea","a optsprezecea","a nouasprezecea","a douazecea",
+      "a douazecisiuna","a douazecisidoua","a douazecisitreia",
+      "a douazecisipatra","a douazecisicincea","a douazecisisasea",
+      "a douazecisisaptea","a douazecisiopta","a douazecisinoua","a treizecea",
+      "a treizecisiuna"],
+     ["prima","a doua","a treia","a patra","a cincea","a ${o}asea",
+      "a ${o}aptea","a opta","a noua","a zecea","a unsprezecea",
+      "a doisprezecea","a treisprezecea","a patrusprezecea","a cincisprezecea",
+      "a ${o}aiprezecea","a ${o}aptesprezecea","a optsprezecea",
+      "a nou${a}sprezecea","a dou${a}zecea","a dou${a}zeci${o}iuna",
+      "a dou${a}zeci${o}idoua","a dou${a}zeci${o}itreia",
+      "a dou${a}zeci${o}ipatra","a dou${a}zeci${o}icincea",
+      "a dou${a}zeci${o}i${o}asea","a dou${a}zeci${o}i${o}aptea",
+      "a dou${a}zeci${o}iopta","a dou${a}zeci${o}inoua","a treizecea",
+      "a treizeci${o}iuna"],
+     ["intii", "doi", "trei", "patru", "cinci", "sase", "sapte",
+      "opt","noua","zece","unsprezece","doisprezece",
+      "treisprezece","patrusprezece","cincisprezece","saiprezece",
+      "saptesprezece","optsprezece","nouasprezece","douazeci",
+      "douazecisiunu","douazecisidoi","douazecisitrei",
+      "douazecisipatru","douazecisicinci","douazecisisase","douazecisisapte",
+      "douazecisiopt","douazecisinoua","treizeci","treizecisiunu"],
+     ["${i}nt${i}i", "doi", "trei", "patru", "cinci", "${o}ase", "${o}apte",
+      "opt","nou${a}","zece","unsprezece","doisprezece",
+      "treisprezece","patrusprezece","cincisprezece","${o}aiprezece",
+      "${o}aptesprezece","optsprezece","nou${a}sprezece","dou${a}zeci",
+      "dou${a}zeci${o}iunu","dou${a}zeci${o}idoi","dou${a}zeci${o}itrei",
+      "dou${a}zecisipatru","dou${a}zeci${o}icinci","dou${a}zeci${o}i${o}ase",
+      "dou${a}zeci${o}i${o}apte","dou${a}zeci${o}iopt",
+      "dou${a}zeci${o}inou${a}","treizeci","treizeci${o}iunu"]];
+
+  $$d{"now"}     =["acum"];
+  $$d{"today"}   =["azi","astazi","ast${a}zi"];
+  $$d{"last"}    =["ultima"];
+  $$d{"each"}    =["fiecare"];
+  $$d{"of"}      =["din","in","n"];
+  $$d{"at"}      =["la"];
+  $$d{"on"}      =["on"];
+  $$d{"future"}  =["in","${i}n"];
+  $$d{"past"}    =["in urma", "${i}n urm${a}"];
+  $$d{"next"}    =["urmatoarea","urm${a}toarea"];
+  $$d{"prev"}    =["precedenta","ultima"];
+  $$d{"later"}   =["mai tirziu", "mai t${i}rziu"];
+
+  $$d{"exact"}   =["exact"];
+  $$d{"approx"}  =["aproximativ"];
+  $$d{"business"}=["de lucru","lucratoare","lucr${a}toare"];
+
+  $$d{"offset"}  =["ieri","-0:0:0:1:0:0:0",
+                   "alaltaieri", "-0:0:0:2:0:0:0",
+                   "alalt${a}ieri","-0:0:0:2:0:0:0",
+                   "miine","+0:0:0:1:0:0:0",
+                   "m${i}ine","+0:0:0:1:0:0:0",
+                   "poimiine","+0:0:0:2:0:0:0",
+                   "poim${i}ine","+0:0:0:2:0:0:0"];
+  $$d{"times"}   =["amiaza","12:00:00",
+                   "amiaz${a}","12:00:00",
+                   "miezul noptii","00:00:00",
+                   "miezul nop${p}ii","00:00:00"];
+
+  $$d{"years"}   =["ani","an","a"];
+  $$d{"months"}  =["luni","luna","lun${a}","l"];
+  $$d{"weeks"}   =["saptamini","s${a}pt${a}m${i}ni","saptamina",
+                   "s${a}pt${a}m${i}na","sapt","s${a}pt"];
+  $$d{"days"}    =["zile","zi","z"];
+  $$d{"hours"}   =["ore", "ora", "or${a}", "h"];
+  $$d{"minutes"} =["minute","min","m"];
+  $$d{"seconds"} =["secunde","sec",];
+  $$d{"replace"} =["s","secunde"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:,]';
+
+  $$d{"am"}      = ["AM","A.M."];
+  $$d{"pm"}      = ["PM","P.M."];
+}
+
+sub Date_Init_Swedish {
+  print "DEBUG: Date_Init_Swedish\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($ao)=$h{"ao"};
+  my($o) =$h{"o:"};
+  my($a) =$h{"a:"};
+
+  $$d{"month_name"}=
+    [["Januari","Februari","Mars","April","Maj","Juni",
+      "Juli","Augusti","September","Oktober","November","December"]];
+  $$d{"month_abb"}=
+    [["Jan","Feb","Mar","Apr","Maj","Jun",
+      "Jul","Aug","Sep","Okt","Nov","Dec"]];
+
+  $$d{"day_name"}=
+    [["Mandag","Tisdag","Onsdag","Torsdag","Fredag","Lordag","Sondag"],
+     ["M${ao}ndag","Tisdag","Onsdag","Torsdag","Fredag","L${o}rdag",
+      "S${o}ndag"]];
+  $$d{"day_abb"}=
+    [["Man","Tis","Ons","Tor","Fre","Lor","Son"],
+     ["M${ao}n","Tis","Ons","Tor","Fre","L${o}r","S${o}n"]];
+  $$d{"day_char"}=
+    [["M","Ti","O","To","F","L","S"]];
+
+  $$d{"num_suff"}=
+    [["1:a","2:a","3:e","4:e","5:e","6:e","7:e","8:e","9:e","10:e",
+      "11:e","12:e","13:e","14:e","15:e","16:e","17:e","18:e","19:e","20:e",
+      "21:a","22:a","23:e","24:e","25:e","26:e","27:e","28:e","29:e","30:e",
+      "31:a"]];
+  $$d{"num_word"}=
+    [["forsta","andra","tredje","fjarde","femte","sjatte","sjunde",
+      "attonde","nionde","tionde","elfte","tolfte","trettonde","fjortonde",
+      "femtonde","sextonde","sjuttonde","artonde","nittonde","tjugonde",
+      "tjugoforsta","tjugoandra","tjugotredje","tjugofjarde","tjugofemte",
+      "tjugosjatte","tjugosjunde","tjugoattonde","tjugonionde",
+      "trettionde","trettioforsta"],
+     ["f${o}rsta","andra","tredje","fj${a}rde","femte","sj${a}tte","sjunde",
+      "${ao}ttonde","nionde","tionde","elfte","tolfte","trettonde","fjortonde",
+      "femtonde","sextonde","sjuttonde","artonde","nittonde","tjugonde",
+      "tjugof${o}rsta","tjugoandra","tjugotredje","tjugofj${a}rde","tjugofemte",
+      "tjugosj${a}tte","tjugosjunde","tjugo${ao}ttonde","tjugonionde",
+      "trettionde","trettiof${o}rsta"]];
+
+  $$d{"now"}     =["nu"];
+  $$d{"today"}   =["idag"];
+  $$d{"last"}    =["forra","f${o}rra","senaste"];
+  $$d{"each"}    =["varje"];
+  $$d{"of"}      =["om"];
+  $$d{"at"}      =["kl","kl.","klockan"];
+  $$d{"on"}      =["pa","p${ao}"];
+  $$d{"future"}  =["om"];
+  $$d{"past"}    =["sedan"];
+  $$d{"next"}    =["nasta","n${a}sta"];
+  $$d{"prev"}    =["forra","f${o}rra"];
+  $$d{"later"}   =["senare"];
+
+  $$d{"exact"}   =["exakt"];
+  $$d{"approx"}  =["ungefar","ungef${a}r"];
+  $$d{"business"}=["arbetsdag","arbetsdagar"];
+
+  $$d{"offset"}  =["ig${ao}r","-0:0:0:1:0:0:0","igar","-0:0:0:1:0:0:0",
+                   "imorgon","+0:0:0:1:0:0:0"];
+  $$d{"times"}   =["mitt pa dagen","12:00:00","mitt p${ao} dagen","12:00:00",
+                   "midnatt","00:00:00"];
+
+  $$d{"years"}   =["ar","${ao}r"];
+  $$d{"months"}  =["man","manad","manader","m${ao}n","m${ao}nad","m${ao}nader"];
+  $$d{"weeks"}   =["v","vecka","veckor"];
+  $$d{"days"}    =["d","dag","dagar"];
+  $$d{"hours"}   =["t","tim","timme","timmar"];
+  $$d{"minutes"} =["min","minut","minuter"];
+  $$d{"seconds"} =["s","sek","sekund","sekunder"];
+  $$d{"replace"} =["m","minut"];
+
+  $$d{"sephm"}   ='[.:]';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["FM"];
+  $$d{"pm"}      = ["EM"];
+}
+
+sub Date_Init_German {
+  print "DEBUG: Date_Init_German\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($a)=$h{"a:"};
+  my($u)=$h{"u:"};
+  my($o)=$h{"o:"};
+  my($b)=$h{"beta"};
+
+  $$d{"month_name"}=
+    [["Januar","Februar","Maerz","April","Mai","Juni",
+      "Juli","August","September","Oktober","November","Dezember"],
+    ["J${a}nner","Februar","M${a}rz","April","Mai","Juni",
+      "Juli","August","September","Oktober","November","Dezember"]];
+  $$d{"month_abb"}=
+    [["Jan","Feb","Mar","Apr","Mai","Jun",
+      "Jul","Aug","Sep","Okt","Nov","Dez"],
+     ["J${a}n","Feb","M${a}r","Apr","Mai","Jun",
+      "Jul","Aug","Sep","Okt","Nov","Dez"]];
+
+  $$d{"day_name"}=
+    [["Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag",
+      "Sonntag"]];
+  $$d{"day_abb"}=
+    [["Mo","Di","Mi","Do","Fr","Sa","So"]];
+  $$d{"day_char"}=
+    [["M","Di","Mi","Do","F","Sa","So"]];
+
+  $$d{"num_suff"}=
+    [["1.","2.","3.","4.","5.","6.","7.","8.","9.","10.",
+      "11.","12.","13.","14.","15.","16.","17.","18.","19.","20.",
+      "21.","22.","23.","24.","25.","26.","27.","28.","29.","30.",
+      "31."]];
+  $$d{"num_word"}=
+    [
+     ["erste","zweite","dritte","vierte","funfte","sechste","siebente",
+      "achte","neunte","zehnte","elfte","zwolfte","dreizehnte","vierzehnte",
+      "funfzehnte","sechzehnte","siebzehnte","achtzehnte","neunzehnte",
+      "zwanzigste","einundzwanzigste","zweiundzwanzigste","dreiundzwanzigste",
+      "vierundzwanzigste","funfundzwanzigste","sechundzwanzigste",
+      "siebundzwanzigste","achtundzwanzigste","neunundzwanzigste",
+      "dreibigste","einunddreibigste"],
+     ["erste","zweite","dritte","vierte","f${u}nfte","sechste","siebente",
+      "achte","neunte","zehnte","elfte","zw${o}lfte","dreizehnte",
+      "vierzehnte","f${u}nfzehnte","sechzehnte","siebzehnte","achtzehnte",
+      "neunzehnte","zwanzigste","einundzwanzigste","zweiundzwanzigste",
+      "dreiundzwanzigste","vierundzwanzigste","f${u}nfundzwanzigste",
+      "sechundzwanzigste","siebundzwanzigste","achtundzwanzigste",
+      "neunundzwanzigste","drei${b}igste","einunddrei${b}igste"],
+    ["erster"]];
+
+  $$d{"now"}     =["jetzt"];
+  $$d{"today"}   =["heute"];
+  $$d{"last"}    =["letzte","letzten"];
+  $$d{"each"}    =["jeden"];
+  $$d{"of"}      =["der","im","des"];
+  $$d{"at"}      =["um"];
+  $$d{"on"}      =["am"];
+  $$d{"future"}  =["in"];
+  $$d{"past"}    =["vor"];
+  $$d{"next"}    =["nachste","n${a}chste","nachsten","n${a}chsten"];
+  $$d{"prev"}    =["vorherigen","vorherige","letzte","letzten"];
+  $$d{"later"}   =["spater","sp${a}ter"];
+
+  $$d{"exact"}   =["genau"];
+  $$d{"approx"}  =["ungefahr","ungef${a}hr"];
+  $$d{"business"}=["Arbeitstag"];
+
+  $$d{"offset"}  =["gestern","-0:0:0:1:0:0:0","morgen","+0:0:0:1:0:0:0","${u}bermorgen","+0:0:0:2:0:0:0"];
+  $$d{"times"}   =["mittag","12:00:00","mitternacht","00:00:00"];
+
+  $$d{"years"}   =["j","Jahr","Jahre","Jahren"];
+  $$d{"months"}  =["Monat","Monate","Monaten"];
+  $$d{"weeks"}   =["w","Woche","Wochen"];
+  $$d{"days"}    =["t","Tag","Tage","Tagen"];
+  $$d{"hours"}   =["h","std","Stunde","Stunden"];
+  $$d{"minutes"} =["min","Minute","Minuten"];
+  $$d{"seconds"} =["s","sek","Sekunde","Sekunden"];
+  $$d{"replace"} =["m","Monat"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   ='[: ]';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["FM"];
+  $$d{"pm"}      = ["EM"];
+}
+
+sub Date_Init_Dutch {
+  print "DEBUG: Date_Init_Dutch\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+
+  $$d{"month_name"}=
+    [["januari","februari","maart","april","mei","juni","juli","augustus",
+      "september","october","november","december"],
+     ["","","","","","","","","","oktober"]];
+
+  $$d{"month_abb"}=
+    [["jan","feb","maa","apr","mei","jun","jul",
+      "aug","sep","oct","nov","dec"],
+     ["","","mrt","","","","","","","okt"]];
+  $$d{"day_name"}=
+    [["maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag",
+      "zondag"]];
+  $$d{"day_abb"}=
+    [["ma","di","wo","do","vr","zat","zon"],
+     ["","","","","","za","zo"]];
+  $$d{"day_char"}=
+    [["M","D","W","D","V","Za","Zo"]];
+
+  $$d{"num_suff"}=
+    [["1ste","2de","3de","4de","5de","6de","7de","8ste","9de","10de",
+      "11de","12de","13de","14de","15de","16de","17de","18de","19de","20ste",
+      "21ste","22ste","23ste","24ste","25ste","26ste","27ste","28ste","29ste",
+      "30ste","31ste"]];
+  $$d{"num_word"}=
+    [["eerste","tweede","derde","vierde","vijfde","zesde","zevende","achtste",
+      "negende","tiende","elfde","twaalfde",
+      map {"${_}tiende";} qw (der veer vijf zes zeven acht negen),
+      "twintigste",
+      map {"${_}entwintigste";} qw (een twee drie vier vijf zes zeven acht
+                                    negen),
+      "dertigste","eenendertigste"],
+     ["","","","","","","","","","","","","","","","","","","","",
+      map {"${_}-en-twintigste";} qw (een twee drie vier vijf zes zeven acht
+                                      negen),
+      "dertigste","een-en-dertigste"],
+     ["een","twee","drie","vier","vijf","zes","zeven","acht","negen","tien",
+      "elf","twaalf",
+      map {"${_}tien"} qw (der veer vijf zes zeven acht negen),
+      "twintig",
+      map {"${_}entwintig"} qw (een twee drie vier vijf zes zeven acht negen),
+      "dertig","eenendertig"],
+     ["","","","","","","","","","","","","","","","","","","","",
+      map {"${_}-en-twintig"} qw (een twee drie vier vijf zes zeven acht
+                                  negen),
+      "dertig","een-en-dertig"]];
+
+  $$d{"now"}     =["nu","nou"];
+  $$d{"today"}   =["vandaag"];
+  $$d{"last"}    =["laatste"];
+  $$d{"each"}    =["elke","elk"];
+  $$d{"of"}      =["in","van"];
+  $$d{"at"}      =["om"];
+  $$d{"on"}      =["op"];
+  $$d{"future"}  =["over"];
+  $$d{"past"}    =["geleden","vroeger","eerder"];
+  $$d{"next"}    =["volgende","volgend"];
+  $$d{"prev"}    =["voorgaande","voorgaand"];
+  $$d{"later"}   =["later"];
+
+  $$d{"exact"}   =["exact","precies","nauwkeurig"];
+  $$d{"approx"}  =["ongeveer","ong",'ong\.',"circa","ca",'ca\.'];
+  $$d{"business"}=["werk","zakelijke","zakelijk"];
+
+  $$d{"offset"}  =["morgen","+0:0:0:1:0:0:0","overmorgen","+0:0:0:2:0:0:0",
+                   "gisteren","-0:0:0:1:0:0:0","eergisteren","-0::00:2:0:0:0"];
+  $$d{"times"}   =["noen","12:00:00","middernacht","00:00:00"];
+
+  $$d{"years"}   =["jaar","jaren","ja","j"];
+  $$d{"months"}  =["maand","maanden","mnd"];
+  $$d{"weeks"}   =["week","weken","w"];
+  $$d{"days"}    =["dag","dagen","d"];
+  $$d{"hours"}   =["uur","uren","u","h"];
+  $$d{"minutes"} =["minuut","minuten","min"];
+  $$d{"seconds"} =["seconde","seconden","sec","s"];
+  $$d{"replace"} =["m","minuten"];
+
+  $$d{"sephm"}   ='[:.uh]';
+  $$d{"sepms"}   ='[:.m]';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["am","a.m.","vm","v.m.","voormiddag","'s_ochtends",
+                    "ochtend","'s_nachts","nacht"];
+  $$d{"pm"}      = ["pm","p.m.","nm","n.m.","namiddag","'s_middags","middag",
+                    "'s_avonds","avond"];
+}
+
+sub Date_Init_Polish {
+  print "DEBUG: Date_Init_Polish\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+
+  $$d{"month_name"}=
+    [["stycznia","luty","marca","kwietnia","maja","czerwca",
+      "lipca","sierpnia","wrzesnia","pazdziernika","listopada","grudnia"],
+     ["stycznia","luty","marca","kwietnia","maja","czerwca","lipca",
+      "sierpnia","wrze\x9cnia","pa\x9fdziernika","listopada","grudnia"]];
+  $$d{"month_abb"}=
+    [["sty.","lut.","mar.","kwi.","maj","cze.",
+      "lip.","sie.","wrz.","paz.","lis.","gru."],
+     ["sty.","lut.","mar.","kwi.","maj","cze.",
+      "lip.","sie.","wrz.","pa\x9f.","lis.","gru."]];
+
+  $$d{"day_name"}=
+    [["poniedzialek","wtorek","sroda","czwartek","piatek","sobota",
+      "niedziela"],
+     ["poniedzia\x81\xb3ek","wtorek","\x9croda","czwartek","pi\x81\xb9tek",
+      "sobota","niedziela"]];
+  $$d{"day_abb"}=
+    [["po.","wt.","sr.","cz.","pi.","so.","ni."],
+     ["po.","wt.","\x9cr.","cz.","pi.","so.","ni."]];
+  $$d{"day_char"}=
+    [["p","w","e","c","p","s","n"],
+     ["p","w","\x9c.","c","p","s","n"]];
+
+  $$d{"num_suff"}=
+    [["1.","2.","3.","4.","5.","6.","7.","8.","9.","10.",
+      "11.","12.","13.","14.","15.","16.","17.","18.","19.","20.",
+      "21.","22.","23.","24.","25.","26.","27.","28.","29.","30.",
+      "31."]];
+  $$d{"num_word"}=
+    [["pierwszego","drugiego","trzeczego","czwartego","piatego","szostego",
+      "siodmego","osmego","dziewiatego","dziesiatego",
+      "jedenastego","dwunastego","trzynastego","czternastego","pietnastego",
+      "szestnastego","siedemnastego","osiemnastego","dziewietnastego",
+      "dwudziestego",
+      "dwudziestego pierwszego","dwudziestego drugiego",
+      "dwudziestego trzeczego","dwudziestego czwartego",
+      "dwudziestego piatego","dwudziestego szostego",
+      "dwudziestego siodmego","dwudziestego osmego",
+      "dwudziestego dziewiatego","trzydziestego","trzydziestego pierwszego"],
+     ["pierwszego","drugiego","trzeczego","czwartego","pi\x81\xb9tego",
+      "sz\x81\xf3stego","si\x81\xf3dmego","\x81\xf3smego","dziewi\x81\xb9tego",
+      "dziesi\x81\xb9tego","jedenastego","dwunastego","trzynastego",
+      "czternastego","pi\x81\xeatnastego","szestnastego","siedemnastego",
+      "osiemnastego","dziewietnastego","dwudziestego",
+      "dwudziestego pierwszego","dwudziestego drugiego",
+      "dwudziestego trzeczego","dwudziestego czwartego",
+      "dwudziestego pi\x81\xb9tego","dwudziestego sz\x81\xf3stego",
+      "dwudziestego si\x81\xf3dmego","dwudziestego \x81\xf3smego",
+      "dwudziestego dziewi\x81\xb9tego","trzydziestego",
+      "trzydziestego pierwszego"]];
+
+  $$d{"now"}     =["teraz"];
+  $$d{"today"}   =["dzisaj"];
+  $$d{"last"}    =["ostatni","ostatna"];
+  $$d{"each"}    =["kazdy","ka\x81\xbfdy", "kazdym","ka\x81\xbfdym"];
+  $$d{"of"}      =["w","z"];
+  $$d{"at"}      =["o","u"];
+  $$d{"on"}      =["na"];
+  $$d{"future"}  =["za"];
+  $$d{"past"}    =["temu"];
+  $$d{"next"}    =["nastepny","nast\x81\xeapny","nastepnym","nast\x81\xeapnym",
+                   "przyszly","przysz\x81\xb3y","przyszlym",
+                   "przysz\x81\xb3ym"];
+  $$d{"prev"}    =["zeszly","zesz\x81\xb3y","zeszlym","zesz\x81\xb3ym"];
+  $$d{"later"}   =["later"];
+
+  $$d{"exact"}   =["doklandnie","dok\x81\xb3andnie"];
+  $$d{"approx"}  =["w przyblizeniu","w przybli\x81\xbfeniu","mniej wiecej",
+                   "mniej wi\x81\xeacej","okolo","oko\x81\xb3o"];
+  $$d{"business"}=["sluzbowy","s\x81\xb3u\x81\xbfbowy","sluzbowym",
+                   "s\x81\xb3u\x81\xbfbowym"];
+
+  $$d{"times"}   =["po\x81\xb3udnie","12:00:00",
+                   "p\x81\xf3\x81\xb3noc","00:00:00",
+                   "poludnie","12:00:00","polnoc","00:00:00"];
+  $$d{"offset"}  =["wczoraj","-0:0:1:0:0:0","jutro","+0:0:1:0:0:0"];
+
+  $$d{"years"}   =["rok","lat","lata","latach"];
+  $$d{"months"}  =["m.","miesiac","miesi\x81\xb9c","miesiecy",
+                   "miesi\x81\xeacy","miesiacu","miesi\x81\xb9cu"];
+  $$d{"weeks"}   =["ty.","tydzien","tydzie\x81\xf1","tygodniu"];
+  $$d{"days"}    =["d.","dzien","dzie\x81\xf1","dni"];
+  $$d{"hours"}   =["g.","godzina","godziny","godzinie"];
+  $$d{"minutes"} =["mn.","min.","minut","minuty"];
+  $$d{"seconds"} =["s.","sekund","sekundy"];
+  $$d{"replace"} =["m.","miesiac"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["AM","A.M."];
+  $$d{"pm"}      = ["PM","P.M."];
+}
+
+sub Date_Init_Spanish {
+  print "DEBUG: Date_Init_Spanish\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+
+  $$d{"month_name"}=
+    [["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto",
+      "Septiembre","Octubre","Noviembre","Diciembre"]];
+
+  $$d{"month_abb"}=
+    [["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct",
+      "Nov","Dic"]];
+
+  $$d{"day_name"}=
+    [["Lunes","Martes","Miercoles","Jueves","Viernes","Sabado","Domingo"]];
+  $$d{"day_abb"}=
+    [["Lun","Mar","Mie","Jue","Vie","Sab","Dom"]];
+  $$d{"day_char"}=
+    [["L","Ma","Mi","J","V","S","D"]];
+
+  $$d{"num_suff"}=
+    [["1o","2o","3o","4o","5o","6o","7o","8o","9o","10o",
+      "11o","12o","13o","14o","15o","16o","17o","18o","19o","20o",
+      "21o","22o","23o","24o","25o","26o","27o","28o","29o","30o","31o"],
+     ["1a","2a","3a","4a","5a","6a","7a","8a","9a","10a",
+      "11a","12a","13a","14a","15a","16a","17a","18a","19a","20a",
+      "21a","22a","23a","24a","25a","26a","27a","28a","29a","30a","31a"]];
+  $$d{"num_word"}=
+    [["Primero","Segundo","Tercero","Cuarto","Quinto","Sexto","Septimo",
+      "Octavo","Noveno","Decimo","Decimo Primero","Decimo Segundo",
+      "Decimo Tercero","Decimo Cuarto","Decimo Quinto","Decimo Sexto",
+      "Decimo Septimo","Decimo Octavo","Decimo Noveno","Vigesimo",
+      "Vigesimo Primero","Vigesimo Segundo","Vigesimo Tercero",
+      "Vigesimo Cuarto","Vigesimo Quinto","Vigesimo Sexto",
+      "Vigesimo Septimo","Vigesimo Octavo","Vigesimo Noveno","Trigesimo",
+      "Trigesimo Primero"],
+     ["Primera","Segunda","Tercera","Cuarta","Quinta","Sexta","Septima",
+      "Octava","Novena","Decima","Decimo Primera","Decimo Segunda",
+      "Decimo Tercera","Decimo Cuarta","Decimo Quinta","Decimo Sexta",
+      "Decimo Septima","Decimo Octava","Decimo Novena","Vigesima",
+      "Vigesimo Primera","Vigesimo Segunda","Vigesimo Tercera",
+      "Vigesimo Cuarta","Vigesimo Quinta","Vigesimo Sexta",
+      "Vigesimo Septima","Vigesimo Octava","Vigesimo Novena","Trigesima",
+      "Trigesimo Primera"]];
+
+  $$d{"now"}     =["Ahora"];
+  $$d{"today"}   =["Hoy"];
+  $$d{"last"}    =["ultimo"];
+  $$d{"each"}    =["cada"];
+  $$d{"of"}      =["en","de"];
+  $$d{"at"}      =["a"];
+  $$d{"on"}      =["el"];
+  $$d{"future"}  =["en"];
+  $$d{"past"}    =["hace"];
+  $$d{"next"}    =["siguiente"];
+  $$d{"prev"}    =["anterior"];
+  $$d{"later"}   =["later"];
+
+  $$d{"exact"}   =["exactamente"];
+  $$d{"approx"}  =["aproximadamente"];
+  $$d{"business"}=["laborales"];
+
+  $$d{"offset"}  =["ayer","-0:0:0:1:0:0:0","manana","+0:0:0:1:0:0:0"];
+  $$d{"times"}   =["mediodia","12:00:00","medianoche","00:00:00"];
+
+  $$d{"years"}   =["a","ano","ano","anos","anos"];
+  $$d{"months"}  =["m","mes","mes","meses"];
+  $$d{"weeks"}   =["sem","semana","semana","semanas"];
+  $$d{"days"}    =["d","dia","dias"];
+  $$d{"hours"}   =["hr","hrs","hora","horas"];
+  $$d{"minutes"} =["min","min","minuto","minutos"];
+  $$d{"seconds"} =["s","seg","segundo","segundos"];
+  $$d{"replace"} =["m","mes"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["AM","A.M."];
+  $$d{"pm"}      = ["PM","P.M."];
+}
+
+sub Date_Init_Portuguese {
+  print "DEBUG: Date_Init_Portuguese\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($o) = $h{"-o"};
+  my($c) = $h{",c"};
+  my($a) = $h{"a'"};
+  my($e) = $h{"e'"};
+  my($u) = $h{"u'"};
+  my($o2)= $h{"o'"};
+  my($a2)= $h{"a`"};
+  my($a3)= $h{"a~"};
+  my($e2)= $h{"e^"};
+
+  $$d{"month_name"}=
+    [["Janeiro","Fevereiro","Marco","Abril","Maio","Junho",
+      "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],
+     ["Janeiro","Fevereiro","Mar${c}o","Abril","Maio","Junho",
+      "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"]];
+
+  $$d{"month_abb"}=
+    [["Jan","Fev","Mar","Abr","Mai","Jun",
+      "Jul","Ago","Set","Out","Nov","Dez"]];
+
+  $$d{"day_name"}=
+    [["Segunda","Terca","Quarta","Quinta","Sexta","Sabado","Domingo"],
+     ["Segunda","Ter${c}a","Quarta","Quinta","Sexta","S${a}bado","Domingo"]];
+  $$d{"day_abb"}=
+    [["Seg","Ter","Qua","Qui","Sex","Sab","Dom"],
+     ["Seg","Ter","Qua","Qui","Sex","S${a}b","Dom"]];
+  $$d{"day_char"}=
+    [["Sg","T","Qa","Qi","Sx","Sb","D"]];
+
+  $$d{"num_suff"}=
+    [["1${o}","2${o}","3${o}","4${o}","5${o}","6${o}","7${o}","8${o}",
+      "9${o}","10${o}","11${o}","12${o}","13${o}","14${o}","15${o}",
+      "16${o}","17${o}","18${o}","19${o}","20${o}","21${o}","22${o}",
+      "23${o}","24${o}","25${o}","26${o}","27${o}","28${o}","29${o}",
+      "30${o}","31${o}"]];
+  $$d{"num_word"}=
+    [["primeiro","segundo","terceiro","quarto","quinto","sexto","setimo",
+      "oitavo","nono","decimo","decimo primeiro","decimo segundo",
+      "decimo terceiro","decimo quarto","decimo quinto","decimo sexto",
+      "decimo setimo","decimo oitavo","decimo nono","vigesimo",
+      "vigesimo primeiro","vigesimo segundo","vigesimo terceiro",
+      "vigesimo quarto","vigesimo quinto","vigesimo sexto","vigesimo setimo",
+      "vigesimo oitavo","vigesimo nono","trigesimo","trigesimo primeiro"],
+     ["primeiro","segundo","terceiro","quarto","quinto","sexto","s${e}timo",
+      "oitavo","nono","d${e}cimo","d${e}cimo primeiro","d${e}cimo segundo",
+      "d${e}cimo terceiro","d${e}cimo quarto","d${e}cimo quinto",
+      "d${e}cimo sexto","d${e}cimo s${e}timo","d${e}cimo oitavo",
+      "d${e}cimo nono","vig${e}simo","vig${e}simo primeiro",
+      "vig${e}simo segundo","vig${e}simo terceiro","vig${e}simo quarto",
+      "vig${e}simo quinto","vig${e}simo sexto","vig${e}simo s${e}timo",
+      "vig${e}simo oitavo","vig${e}simo nono","trig${e}simo",
+      "trig${e}simo primeiro"]];
+
+  $$d{"now"}     =["agora"];
+  $$d{"today"}   =["hoje"];
+  $$d{"last"}    =["${u}ltimo","ultimo"];
+  $$d{"each"}    =["cada"];
+  $$d{"of"}      =["da","do"];
+  $$d{"at"}      =["as","${a2}s"];
+  $$d{"on"}      =["na","no"];
+  $$d{"future"}  =["em"];
+  $$d{"past"}    =["a","${a2}"];
+  $$d{"next"}    =["proxima","proximo","pr${o2}xima","pr${o2}ximo"];
+  $$d{"prev"}    =["ultima","ultimo","${u}ltima","${u}ltimo"];
+  $$d{"later"}   =["passadas","passados"];
+
+  $$d{"exact"}   =["exactamente"];
+  $$d{"approx"}  =["aproximadamente"];
+  $$d{"business"}=["util","uteis"];
+
+  $$d{"offset"}  =["ontem","-0:0:0:1:0:0:0",
+                   "amanha","+0:0:0:1:0:0:0","amanh${a3}","+0:0:0:1:0:0:0"];
+  $$d{"times"}   =["meio-dia","12:00:00","meia-noite","00:00:00"];
+
+  $$d{"years"}   =["anos","ano","ans","an","a"];
+  $$d{"months"}  =["meses","m${e2}s","mes","m"];
+  $$d{"weeks"}   =["semanas","semana","sem","sems","s"];
+  $$d{"days"}    =["dias","dia","d"];
+  $$d{"hours"}   =["horas","hora","hr","hrs"];
+  $$d{"minutes"} =["minutos","minuto","min","mn"];
+  $$d{"seconds"} =["segundos","segundo","seg","sg"];
+  $$d{"replace"} =["m","mes","s","sems"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[,]';
+
+  $$d{"am"}      = ["AM","A.M."];
+  $$d{"pm"}      = ["PM","P.M."];
+}
+
+sub Date_Init_Russian {
+  print "DEBUG: Date_Init_Russian\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+  my(%h)=();
+  &Char_8Bit(\%h);
+  my($a) =$h{"a:"};
+
+  $$d{"month_name"}=
+    [
+     ["\xd1\xce\xd7\xc1\xd2\xd1","\xc6\xc5\xd7\xd2\xc1\xcc\xd1",
+      "\xcd\xc1\xd2\xd4\xc1","\xc1\xd0\xd2\xc5\xcc\xd1","\xcd\xc1\xd1",
+      "\xc9\xc0\xce\xd1",
+      "\xc9\xc0\xcc\xd1","\xc1\xd7\xc7\xd5\xd3\xd4\xc1",
+      "\xd3\xc5\xce\xd4\xd1\xc2\xd2\xd1","\xcf\xcb\xd4\xd1\xc2\xd2\xd1",
+      "\xce\xcf\xd1\xc2\xd2\xd1","\xc4\xc5\xcb\xc1\xc2\xd2\xd1"],
+     ["\xd1\xce\xd7\xc1\xd2\xd8","\xc6\xc5\xd7\xd2\xc1\xcc\xd8",
+      "\xcd\xc1\xd2\xd4","\xc1\xd0\xd2\xc5\xcc\xd8","\xcd\xc1\xca",
+      "\xc9\xc0\xce\xd8",
+      "\xc9\xc0\xcc\xd8","\xc1\xd7\xc7\xd5\xd3\xd4",
+      "\xd3\xc5\xce\xd4\xd1\xc2\xd2\xd8","\xcf\xcb\xd4\xd1\xc2\xd2\xd8",
+      "\xce\xcf\xd1\xc2\xd2\xd8","\xc4\xc5\xcb\xc1\xc2\xd2\xd8"]
+    ];
+
+  $$d{"month_abb"}=
+    [["\xd1\xce\xd7","\xc6\xc5\xd7","\xcd\xd2\xd4","\xc1\xd0\xd2",
+      "\xcd\xc1\xca","\xc9\xc0\xce",
+      "\xc9\xc0\xcc","\xc1\xd7\xc7","\xd3\xce\xd4","\xcf\xcb\xd4",
+      "\xce\xcf\xd1\xc2","\xc4\xc5\xcb"],
+     ["","\xc6\xd7\xd2","","","\xcd\xc1\xd1","",
+      "","","\xd3\xc5\xce","\xcf\xcb\xd4","\xce\xcf\xd1",""]];
+
+  $$d{"day_name"}=
+    [["\xd0\xcf\xce\xc5\xc4\xc5\xcc\xd8\xce\xc9\xcb",
+      "\xd7\xd4\xcf\xd2\xce\xc9\xcb","\xd3\xd2\xc5\xc4\xc1",
+      "\xde\xc5\xd4\xd7\xc5\xd2\xc7","\xd0\xd1\xd4\xce\xc9\xc3\xc1",
+      "\xd3\xd5\xc2\xc2\xcf\xd4\xc1",
+      "\xd7\xcf\xd3\xcb\xd2\xc5\xd3\xc5\xce\xd8\xc5"]];
+  $$d{"day_abb"}=
+    [["\xd0\xce\xc4","\xd7\xd4\xd2","\xd3\xd2\xc4","\xde\xd4\xd7",
+      "\xd0\xd4\xce","\xd3\xd5\xc2","\xd7\xd3\xcb"],
+     ["\xd0\xcf\xce","\xd7\xd4\xcf","\xd3\xd2e","\xde\xc5\xd4",
+      "\xd0\xd1\xd4","\xd3\xd5\xc2","\xd7\xcf\xd3\xcb"]];
+  $$d{"day_char"}=
+    [["\xd0\xce","\xd7\xd4","\xd3\xd2","\xde\xd4","\xd0\xd4","\xd3\xc2",
+      "\xd7\xd3"]];
+
+  $$d{"num_suff"}=
+    [["1 ","2 ","3 ","4 ","5 ","6 ","7 ","8 ","9 ","10 ",
+      "11 ","12 ","13 ","14 ","15 ","16 ","17 ","18 ","19 ","20 ",
+      "21 ","22 ","23 ","24 ","25 ","26 ","27 ","28 ","29 ","30 ",
+      "31 "]];
+  $$d{"num_word"}=
+    [["\xd0\xc5\xd2\xd7\xd9\xca","\xd7\xd4\xcf\xd2\xcf\xca",
+      "\xd4\xd2\xc5\xd4\xc9\xca","\xde\xc5\xd4\xd7\xc5\xd2\xd4\xd9\xca",
+      "\xd0\xd1\xd4\xd9\xca","\xdb\xc5\xd3\xd4\xcf\xca",
+      "\xd3\xc5\xc4\xd8\xcd\xcf\xca","\xd7\xcf\xd3\xd8\xcd\xcf\xca",
+      "\xc4\xc5\xd7\xd1\xd4\xd9\xca","\xc4\xc5\xd3\xd1\xd4\xd9\xca",
+      "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xc4\xd7\xc5\xce\xc1\xc4\xde\xc1\xd4\xd9\xca",
+      "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xd9\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xc9\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xd9\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xd9\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xca",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xd9\xca",
+      "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd9\xca",
+      "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xd9\xca"],
+
+     ["\xd0\xc5\xd2\xd7\xcf\xc5","\xd7\xd4\xcf\xd2\xcf\xc5",
+      "\xd4\xd2\xc5\xd4\xd8\xc5","\xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc5",
+      "\xd0\xd1\xd4\xcf\xc5","\xdb\xc5\xd3\xd4\xcf\xc5",
+      "\xd3\xc5\xc4\xd8\xcd\xcf\xc5","\xd7\xcf\xd3\xd8\xcd\xcf\xc5",
+      "\xc4\xc5\xd7\xd1\xd4\xcf\xc5","\xc4\xc5\xd3\xd1\xd4\xcf\xc5",
+      "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xc4\xd7\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xd8\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xcf\xc5",
+      "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xcf\xc5",
+      "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc5"],
+
+     ["\xd0\xc5\xd2\xd7\xcf\xc7\xcf","\xd7\xd4\xcf\xd2\xcf\xc7\xcf",
+      "\xd4\xd2\xc5\xd4\xd8\xc5\xc7\xcf",
+      "\xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc7\xcf","\xd0\xd1\xd4\xcf\xc7\xcf",
+      "\xdb\xc5\xd3\xd4\xcf\xc7\xcf","\xd3\xc5\xc4\xd8\xcd\xcf\xc7\xcf",
+      "\xd7\xcf\xd3\xd8\xcd\xcf\xc7\xcf",
+      "\xc4\xc5\xd7\xd1\xd4\xcf\xc7\xcf","\xc4\xc5\xd3\xd1\xd4\xcf\xc7\xcf",
+      "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xc4\xd7\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xc5",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xd8\xc5\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xc7\xcf",
+      "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xcf\xc7\xcf",
+      "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
+      "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc7\xcf"]];
+
+  $$d{"now"}     =["\xd3\xc5\xca\xde\xc1\xd3"];
+  $$d{"today"}   =["\xd3\xc5\xc7\xcf\xc4\xce\xd1"];
+  $$d{"last"}    =["\xd0\xcf\xd3\xcc\xc5\xc4\xce\xc9\xca"];
+  $$d{"each"}    =["\xcb\xc1\xd6\xc4\xd9\xca"];
+  $$d{"of"}      =[" "];
+  $$d{"at"}      =["\xd7"];
+  $$d{"on"}      =["\xd7"];
+  $$d{"future"}  =["\xd7\xd0\xc5\xd2\xc5\xc4 \xce\xc1"];
+  $$d{"past"}    =["\xce\xc1\xda\xc1\xc4 \xce\xc1 "];
+  $$d{"next"}    =["\xd3\xcc\xc5\xc4\xd5\xc0\xdd\xc9\xca"];
+  $$d{"prev"}    =["\xd0\xd2\xc5\xc4\xd9\xc4\xd5\xdd\xc9\xca"];
+  $$d{"later"}   =["\xd0\xcf\xda\xd6\xc5"];
+
+  $$d{"exact"}   =["\xd4\xcf\xde\xce\xcf"];
+  $$d{"approx"}  =["\xd0\xd2\xc9\xcd\xc5\xd2\xce\xcf"];
+  $$d{"business"}=["\xd2\xc1\xc2\xcf\xde\xc9\xc8"];
+
+  $$d{"offset"}  =["\xd0\xcf\xda\xc1\xd7\xde\xc5\xd2\xc1","-0:0:0:2:0:0:0",
+                   "\xd7\xde\xc5\xd2\xc1","-0:0:0:1:0:0:0",
+                   "\xda\xc1\xd7\xd4\xd2\xc1","+0:0:0:1:0:0:0",
+                   "\xd0\xcf\xd3\xcc\xc5\xda\xc1\xd7\xd4\xd2\xc1",
+                   "+0:0:0:2:0:0:0"];
+  $$d{"times"}   =["\xd0\xcf\xcc\xc4\xc5\xce\xd8","12:00:00",
+                   "\xd0\xcf\xcc\xce\xcf\xde\xd8","00:00:00"];
+
+  $$d{"years"}   =["\xc7","\xc7\xc4","\xc7\xcf\xc4","\xcc\xc5\xd4",
+                   "\xcc\xc5\xd4","\xc7\xcf\xc4\xc1"];
+  $$d{"months"}  =["\xcd\xc5\xd3","\xcd\xc5\xd3\xd1\xc3",
+                   "\xcd\xc5\xd3\xd1\xc3\xc5\xd7"];
+  $$d{"weeks"}   =["\xce\xc5\xc4\xc5\xcc\xd1","\xce\xc5\xc4\xc5\xcc\xd8",
+                   "\xce\xc5\xc4\xc5\xcc\xc9","\xce\xc5\xc4\xc5\xcc\xc0"];
+  $$d{"days"}    =["\xc4","\xc4\xc5\xce\xd8","\xc4\xce\xc5\xca",
+                   "\xc4\xce\xd1"];
+  $$d{"hours"}   =["\xde","\xde.","\xde\xd3","\xde\xd3\xd7","\xde\xc1\xd3",
+                   "\xde\xc1\xd3\xcf\xd7","\xde\xc1\xd3\xc1"];
+  $$d{"minutes"} =["\xcd\xce","\xcd\xc9\xce","\xcd\xc9\xce\xd5\xd4\xc1",
+                   "\xcd\xc9\xce\xd5\xd4"];
+  $$d{"seconds"} =["\xd3","\xd3\xc5\xcb","\xd3\xc5\xcb\xd5\xce\xc4\xc1",
+                   "\xd3\xc5\xcb\xd5\xce\xc4"];
+  $$d{"replace"} =[];
+
+  $$d{"sephm"}   ="[:\xde]";
+  $$d{"sepms"}   ="[:\xcd]";
+  $$d{"sepss"}   ="[:.\xd3]";
+
+  $$d{"am"}      = ["\xc4\xd0","${a}\xf0","${a}.\xf0.","\xce\xcf\xde\xc9",
+                    "\xd5\xd4\xd2\xc1",
+                    "\xc4\xcf \xd0\xcf\xcc\xd5\xc4\xce\xd1"];
+  $$d{"pm"}      = ["\xd0\xd0","\xf0\xf0","\xf0.\xf0.","\xc4\xce\xd1",
+                    "\xd7\xc5\xde\xc5\xd2\xc1",
+                    "\xd0\xcf\xd3\xcc\xc5 \xd0\xcf\xcc\xd5\xc4\xce\xd1",
+                    "\xd0\xcf \xd0\xcf\xcc\xd5\xc4\xce\xc0"];
+}
+
+sub Date_Init_Turkish {
+  print "DEBUG: Date_Init_Turkish\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+
+  $$d{"month_name"}=
+    [
+     ["ocak","subat","mart","nisan","mayis","haziran",
+      "temmuz","agustos","eylul","ekim","kasim","aralik"],
+     ["ocak","\xfeubat","mart","nisan","may\xfds","haziran",
+      "temmuz","a\xf0ustos","eyl\xfcl","ekim","kas\xfdm","aral\xfdk"]
+     ];
+
+  $$d{"month_abb"}=
+    [
+     ["oca","sub","mar","nis","may","haz",
+      "tem","agu","eyl","eki","kas","ara"],
+     ["oca","\xfeub","mar","nis","may","haz",
+      "tem","a\xf0u","eyl","eki","kas","ara"]
+     ];
+
+  $$d{"day_name"}=
+    [
+     ["pazartesi","sali","carsamba","persembe","cuma","cumartesi","pazar"],
+     ["pazartesi","sal\xfd","\xe7ar\xfeamba","per\xfeembe","cuma",
+      "cumartesi","pazar"],
+     ];
+
+  $$d{"day_abb"}=
+    [
+     ["pzt","sal","car","per","cum","cts","paz"],
+     ["pzt","sal","\xe7ar","per","cum","cts","paz"],
+     ];
+
+  $$d{"day_char"}=
+    [["Pt","S","Cr","Pr","C","Ct","P"],
+     ["Pt","S","\xc7","Pr","C","Ct","P"]];
+
+  $$d{"num_suff"}=
+    [[ "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10.",
+       "11.", "12.", "13.", "14.", "15.", "16.", "17.", "18.", "19.", "20.",
+       "21.", "22.", "23.", "24.", "25.", "26.", "27.", "28.", "29.", "30.",
+       "31."]];
+
+  $$d{"num_word"}=
+    [
+     ["birinci","ikinci","ucuncu","dorduncu",
+      "besinci","altinci","yedinci","sekizinci",
+      "dokuzuncu","onuncu","onbirinci","onikinci",
+      "onucuncu","ondordoncu",
+      "onbesinci","onaltinci","onyedinci","onsekizinci",
+      "ondokuzuncu","yirminci","yirmibirinci","yirmikinci",
+      "yirmiucuncu","yirmidorduncu",
+      "yirmibesinci","yirmialtinci","yirmiyedinci","yirmisekizinci",
+      "yirmidokuzuncu","otuzuncu","otuzbirinci"],
+     ["birinci","ikinci","\xfc\xe7\xfcnc\xfc","d\xf6rd\xfcnc\xfc",
+      "be\xfeinci","alt\xfdnc\xfd","yedinci","sekizinci",
+      "dokuzuncu","onuncu","onbirinci","onikinci",
+      "on\xfc\xe7\xfcnc\xfc","ond\xf6rd\xfcnc\xfc",
+      "onbe\xfeinci","onalt\xfdnc\xfd","onyedinci","onsekizinci",
+      "ondokuzuncu","yirminci","yirmibirinci","yirmikinci",
+      "yirmi\xfc\xe7\xfcnc\xfc","yirmid\xf6rd\xfcnc\xfc",
+      "yirmibe\xfeinci","yirmialt\xfdnc\xfd","yirmiyedinci","yirmisekizinci",
+      "yirmidokuzuncu","otuzuncu","otuzbirinci"]
+     ];
+
+  $$d{"now"}     =["\xfeimdi", "simdi"];
+  $$d{"today"}   =["bugun", "bug\xfcn"];
+  $$d{"last"}    =["son", "sonuncu"];
+  $$d{"each"}    =["her"];
+  $$d{"of"}      =["of"];
+  $$d{"at"}      =["saat"];
+  $$d{"on"}      =["on"];
+  $$d{"future"}  =["gelecek"];
+  $$d{"past"}    =["ge\xe7mi\xfe", "gecmis","gecen", "ge\xe7en"];
+  $$d{"next"}    =["gelecek","sonraki"];
+  $$d{"prev"}    =["onceki","\xf6nceki"];
+  $$d{"later"}   =["sonra"];
+
+  $$d{"exact"}   =["tam"];
+  $$d{"approx"}  =["yakla\xfe\xfdk", "yaklasik"];
+  $$d{"business"}=["i\xfe","\xe7al\xfd\xfema","is", "calisma"];
+
+  $$d{"offset"}  =["d\xfcn","-0:0:0:1:0:0:0",
+                   "dun", "-0:0:0:1:0:0:0",
+                   "yar\xfdn","+0:0:0:1:0:0:0",
+                   "yarin","+0:0:0:1:0:0:0"];
+
+  $$d{"times"}   =["\xf6\xf0len","12:00:00",
+                   "oglen","12:00:00",
+                   "yarim","12:300:00",
+                   "yar\xfdm","12:30:00",
+                   "gece yar\xfds\xfd","00:00:00",
+                   "gece yarisi","00:00:00"];
+
+  $$d{"years"}   =["yil","y"];
+  $$d{"months"}  =["ay","a"];
+  $$d{"weeks"}   =["hafta", "h"];
+  $$d{"days"}    =["gun","g"];
+  $$d{"hours"}   =["saat"];
+  $$d{"minutes"} =["dakika","dak","d"];
+  $$d{"seconds"} =["saniye","sn",];
+  $$d{"replace"} =["s","saat"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:,]';
+
+  $$d{"am"}      = ["\xf6gleden \xf6nce","ogleden once"];
+  $$d{"pm"}      = ["\xf6\xf0leden sonra","ogleden sonra"];
+}
+
+sub Date_Init_Danish {
+  print "DEBUG: Date_Init_Danish\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+
+  $$d{"month_name"}=
+    [["Januar","Februar","Marts","April","Maj","Juni",
+      "Juli","August","September","Oktober","November","December"]];
+  $$d{"month_abb"}=
+    [["Jan","Feb","Mar","Apr","Maj","Jun",
+      "Jul","Aug","Sep","Okt","Nov","Dec"]];
+
+  $$d{"day_name"}=
+    [["Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lordag","Sondag"],
+     ["Mandag","Tirsdag","Onsdag","Torsdag","Fredag","L\xf8rdag","S\xf8ndag"]];
+
+  $$d{"day_abb"}=
+    [["Man","Tir","Ons","Tor","Fre","Lor","Son"],
+     ["Man","Tir","Ons","Tor","Fre","L\xf8r","S\xf8n"]];
+  $$d{"day_char"}=
+    [["M","Ti","O","To","F","L","S"]];
+
+  $$d{"num_suff"}=
+    [["1:e","2:e","3:e","4:e","5:e","6:e","7:e","8:e","9:e","10:e",
+      "11:e","12:e","13:e","14:e","15:e","16:e","17:e","18:e","19:e","20:e",
+      "21:e","22:e","23:e","24:e","25:e","26:e","27:e","28:e","29:e","30:e",
+      "31:e"]];
+  $$d{"num_word"}=
+    [["forste","anden","tredie","fjerde","femte","sjette","syvende",
+      "ottende","niende","tiende","elfte","tolvte","trettende","fjortende",
+      "femtende","sekstende","syttende","attende","nittende","tyvende",
+      "enogtyvende","toogtyvende","treogtyvende","fireogtyvende","femogtyvende",
+      "seksogtyvende","syvogtyvende","otteogtyvende","niogtyvende",
+      "tredivte","enogtredivte"],
+     ["f\xf8rste","anden","tredie","fjerde","femte","sjette","syvende",
+      "ottende","niende","tiende","elfte","tolvte","trettende","fjortende",
+      "femtende","sekstende","syttende","attende","nittende","tyvende",
+      "enogtyvende","toogtyvende","treogtyvende","fireogtyvende","femogtyvende",
+      "seksogtyvende","syvogtyvende","otteogtyvende","niogtyvende",
+      "tredivte","enogtredivte"]];
+
+  $$d{"now"}     =["nu"];
+  $$d{"today"}   =["idag"];
+  $$d{"last"}    =["forrige","sidste","nyeste"];
+  $$d{"each"}    =["hver"];
+  $$d{"of"}      =["om"];
+  $$d{"at"}      =["kl","kl.","klokken"];
+  $$d{"on"}      =["pa","p\xe5"];
+  $$d{"future"}  =["om"];
+  $$d{"past"}    =["siden"];
+  $$d{"next"}    =["nasta","n\xe6ste"];
+  $$d{"prev"}    =["forrige"];
+  $$d{"later"}   =["senere"];
+
+  $$d{"exact"}   =["pracist","pr\xe6cist"];
+  $$d{"approx"}  =["circa"];
+  $$d{"business"}=["arbejdsdag","arbejdsdage"];
+
+  $$d{"offset"}  =["ig\xe5r","-0:0:0:1:0:0:0","igar","-0:0:0:1:0:0:0",
+                   "imorgen","+0:0:0:1:0:0:0"];
+  $$d{"times"}   =["midt pa dagen","12:00:00","midt p\xe5 dagen","12:00:00",
+                   "midnat","00:00:00"];
+
+  $$d{"years"}   =["ar","\xe5r"];
+  $$d{"months"}  =["man","maned","maneder","m\xe5n","m\xe5ned","m\xe5neder"];
+  $$d{"weeks"}   =["u","uge","uger"];
+  $$d{"days"}    =["d","dag","dage"];
+  $$d{"hours"}   =["t","tim","time","timer"];
+  $$d{"minutes"} =["min","minut","minutter"];
+  $$d{"seconds"} =["s","sek","sekund","sekunder"];
+  $$d{"replace"} =["m","minut"];
+
+  $$d{"sephm"}   ='[.:]';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["FM"];
+  $$d{"pm"}      = ["EM"];
+}
+
+sub Date_Init_Catalan {
+  print "DEBUG: Date_Init_Catalan\n"  if ($Curr{"Debug"} =~ /trace/);
+  my($d)=@_;
+
+  $$d{"month_name"}=
+    [["Gener","Febrer","Marc","Abril","Maig","Juny",
+      "Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],
+     ["Gener","Febrer","Març","Abril","Maig","Juny",
+      "Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],
+     ["Gener","Febrer","Marc,","Abril","Maig","Juny",
+      "Juliol","Agost","Setembre","Octubre","Novembre","Desembre"]];
+
+  $$d{"month_abb"}=
+    [["Gen","Feb","Mar","Abr","Mai","Jun",
+      "Jul","Ago","Set","Oct","Nov","Des"],
+     [],
+     ["","","","","","",
+      "","","","","","Dec"] #common mistake
+    ];
+
+  $$d{"day_name"}=
+    [["Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte","Diumenge"]];
+  $$d{"day_abb"}=
+    [["Dll","Dmt","Dmc","Dij","Div","Dis","Diu"],
+     ["","Dim","","","","",""],
+     ["","","Dic","","","",""]
+     ];
+  $$d{"day_char"}=
+    [["Dl","Dm","Dc","Dj","Dv","Ds","Du"] ,
+    ["L","M","X","J","V","S","U"]];
+
+  $$d{"num_suff"}=
+    [["1er","2n","3r","4t","5e","6e","7e","8e","9e","10e",
+      "11e","12e","13e","14e","15e","16e","17e","18e","19e","20e",
+      "21e","22e","23e","24e","25e","26e","27e","28e","29e","30e",
+      "31e"],
+     ["1er","2n","3r","4t","5è","6è","7è","8è","9è","10è",
+      "11è","12è","13è","14è","15è","16è","17è","18è","19è","20è",
+      "21è","22è","23è","24è","25è","26è","27è","28è","29è","30è",
+      "31è"]];
+  $$d{"num_word"}=
+    [["primer","segon","tercer","quart","cinque","sise","sete","vuite",
+      "nove","dese","onze","dotze","tretze","catorze",
+      "quinze","setze","dissete","divuite","dinove",
+      "vinte","vint-i-une","vint-i-dose","vint-i-trese",
+      "vint-i-quatre","vint-i-cinque","vint-i-sise","vint-i-sete",
+      "vint-i-vuite","vint-i-nove","trente","trenta-une"],
+     ["primer","segon","tercer","quart","cinquè","sisè","setè","vuitè",
+      "novè","desè","onzè","dotzè","tretzè","catorzè",
+      "quinzè","setzè","dissetè","divuitè","dinovè",
+      "vintè","vint-i-unè","vint-i-dosè","vint-i-tresè",
+      "vint-i-quatrè","vint-i-cinquè","vint-i-sisè","vint-i-setè",
+      "vint-i-vuitè","vint-i-novè","trentè","trenta-unè"]];
+
+  $$d{"now"}     =["avui","ara"];
+  $$d{"last"}    =["darrer","últim","darrera","última"];
+  $$d{"each"}    =["cada","cadascun","cadascuna"];
+  $$d{"of"}      =["de","d'"];
+  $$d{"at"}      =["a les","a","al"];
+  $$d{"on"}      =["el"];
+  $$d{"future"}  =["d'aquí a"];
+  $$d{"past"}    =["fa"];
+  $$d{"next"}    =["proper"];
+  $$d{"prev"}    =["passat","proppassat","anterior"];
+  $$d{"later"}   =["més tard"];
+
+  $$d{"exact"}   =["exactament"];
+  $$d{"approx"}  =["approximadament"];
+  $$d{"business"}=["empresa"];
+
+  $$d{"offset"}  =["ahir","-0:0:0:1:0:0:0","demà","+0:0:0:1:0:0:0","abans d'ahir","-0:0:0:2:0:0:0","demà passat","+0:0:0:2:0:0:0",];
+  $$d{"times"}   =["migdia","12:00:00","mitjanit","00:00:00"];
+
+  $$d{"years"}   =["a","an","any","anys"];
+  $$d{"months"}  =["mes","me","ms"];
+  $$d{"weeks"}   =["se","set","setm","setmana","setmanes"];
+  $$d{"days"}    =["d","dia","dies"];
+  $$d{"hours"}   =["h","ho","hores","hora"];
+  $$d{"minutes"} =["mn","min","minut","minuts"];
+  $$d{"seconds"} =["s","seg","segon","segons"];
+  $$d{"replace"} =["m","mes","s","setmana"];
+
+  $$d{"sephm"}   =':';
+  $$d{"sepms"}   =':';
+  $$d{"sepss"}   ='[.:]';
+
+  $$d{"am"}      = ["AM","A.M."];
+  $$d{"pm"}      = ["PM","P.M."];
+}
+
+########################################################################
+# FROM MY PERSONAL LIBRARIES
+########################################################################
+
+no integer;
+
+# &ModuloAddition($N,$add,\$val,\$rem);
+#   This calculates $val=$val+$add and forces $val to be in a certain range.
+#   This is useful for adding numbers for which only a certain range is
+#   allowed (for example, minutes can be between 0 and 59 or months can be
+#   between 1 and 12).  The absolute value of $N determines the range and
+#   the sign of $N determines whether the range is 0 to N-1 (if N>0) or
+#   1 to N (N<0).  The remainder (as modulo N) is added to $rem.
+#   Example:
+#     To add 2 hours together (with the excess returned in days) use:
+#       &ModuloAddition(60,$s1,\$s,\$day);
+sub ModuloAddition {
+  my($N,$add,$val,$rem)=@_;
+  return  if ($N==0);
+  $$val+=$add;
+  if ($N<0) {
+    # 1 to N
+    $N = -$N;
+    if ($$val>$N) {
+      $$rem+= int(($$val-1)/$N);
+      $$val = ($$val-1)%$N +1;
+    } elsif ($$val<1) {
+      $$rem-= int(-$$val/$N)+1;
+      $$val = $N-(-$$val % $N);
+    }
+
+  } else {
+    # 0 to N-1
+    if ($$val>($N-1)) {
+      $$rem+= int($$val/$N);
+      $$val = $$val%$N;
+    } elsif ($$val<0) {
+      $$rem-= int(-($$val+1)/$N)+1;
+      $$val = ($N-1)-(-($$val+1)%$N);
+    }
+  }
+}
+
+# $Flag=&IsInt($String [,$low, $high]);
+#    Returns 1 if $String is a valid integer, 0 otherwise.  If $low is
+#    entered, $String must be >= $low.  If $high is entered, $String must
+#    be <= $high.  It is valid to check only one of the bounds.
+sub IsInt {
+  my($N,$low,$high)=@_;
+  return 0  if (! defined $N  or
+                $N !~ /^\s*[-+]?\d+\s*$/  or
+                defined $low   &&  $N<$low  or
+                defined $high  &&  $N>$high);
+  return 1;
+}
+
+# $Pos=&SinLindex(\@List,$Str [,$offset [,$CaseInsensitive]]);
+#    Searches for an exact string in a list.
+#
+#    This is similar to RinLindex except that it searches for elements
+#    which are exactly equal to $Str (possibly case insensitive).
+sub SinLindex {
+  my($listref,$Str,$offset,$Insensitive)=@_;
+  my($i,$len,$tmp)=();
+  $len=$#$listref;
+  return -2  if ($len<0 or ! $Str);
+  return -1  if (&Index_First(\$offset,$len));
+  $Str=uc($Str)  if ($Insensitive);
+  for ($i=$offset; $i<=$len; $i++) {
+    $tmp=$$listref[$i];
+    $tmp=uc($tmp)  if ($Insensitive);
+    return $i  if ($tmp eq $Str);
+  }
+  return -1;
+}
+
+sub Index_First {
+  my($offsetref,$max)=@_;
+  $$offsetref=0  if (! $$offsetref);
+  if ($$offsetref < 0) {
+    $$offsetref += $max + 1;
+    $$offsetref=0  if ($$offsetref < 0);
+  }
+  return -1 if ($$offsetref > $max);
+  return 0;
+}
+
+# $File=&CleanFile($file);
+#   This cleans up a path to remove the following things:
+#     double slash       /a//b  -> /a/b
+#     trailing dot       /a/.   -> /a
+#     leading dot        ./a    -> a
+#     trailing slash     a/     -> a
+sub CleanFile {
+  my($file)=@_;
+  $file =~ s/\s*$//;
+  $file =~ s/^\s*//;
+  $file =~ s|//+|/|g;  # multiple slash
+  $file =~ s|/\.$|/|;  # trailing /. (leaves trailing slash)
+  $file =~ s|^\./||    # leading ./
+    if ($file ne "./");
+  $file =~ s|/$||      # trailing slash
+    if ($file ne "/");
+  return $file;
+}
+
+# $File=&ExpandTilde($file);
+#   This checks to see if a "~" appears as the first character in a path.
+#   If it does, the "~" expansion is interpreted (if possible) and the full
+#   path is returned.  If a "~" expansion is used but cannot be
+#   interpreted, an empty string is returned.
+#
+#   This is Windows/Mac friendly.
+#   This is efficient.
+sub ExpandTilde {
+  my($file)=shift;
+  my($user,$home)=();
+  # ~aaa/bbb=      ~  aaa      /bbb
+  if ($file =~ s|^~([^/]*)||) {
+    $user=$1;
+    # Single user operating systems (Mac, MSWindows) don't have the getpwnam
+    # and getpwuid routines defined.  Try to catch various different ways
+    # of knowing we are on one of these systems:
+    return ""  if ($OS eq "Windows"  or
+                   $OS eq "Mac"  or
+                   $OS eq "Netware"  or
+                   $OS eq "MPE");
+    $user=""  if (! defined $user);
+
+    if ($user) {
+      $home= (getpwnam($user))[7];
+    } else {
+      $home= (getpwuid($<))[7];
+    }
+    $home = VMS::Filespec::unixpath($home)  if ($OS eq "VMS");
+    return ""  if (! $home);
+    $file="$home/$file";
+  }
+  $file;
+}
+
+# $File=&FullFilePath($file);
+#   Returns the full or relative path to $file (expanding "~" if necessary).
+#   Returns an empty string if a "~" expansion cannot be interpreted.  The
+#   path does not need to exist.  CleanFile is called.
+sub FullFilePath {
+  my($file)=shift;
+  my($rootpat) = '^/'; #default pattern to match absolute path
+  $rootpat = '^(\\|/|([A-Za-z]:[\\/]))' if ($OS eq 'Windows');
+  $file=&ExpandTilde($file);
+  return ""  if (! $file);
+  return &CleanFile($file);
+}
+
+# $Flag=&CheckFilePath($file [,$mode]);
+#   Checks to see if $file exists, to see what type it is, and whether
+#   the script can access it.  If it exists and has the correct mode, 1
+#   is returned.
+#
+#   $mode is a string which may contain any of the valid file test operator
+#   characters except t, M, A, C.  The appropriate test is run for each
+#   character.  For example, if $mode is "re" the -r and -e tests are both
+#   run.
+#
+#   An empty string is returned if the file doesn't exist.  A 0 is returned
+#   if the file exists but any test fails.
+#
+#   All characters in $mode which do not correspond to valid tests are
+#   ignored.
+sub CheckFilePath {
+  my($file,$mode)=@_;
+  my($test)=();
+  $file=&FullFilePath($file);
+  $mode = ""  if (! defined $mode);
+
+  # Run tests
+  return 0  if (! defined $file or ! $file);
+  return 0  if ((                  ! -e $file) or
+                ($mode =~ /r/  &&  ! -r $file) or
+                ($mode =~ /w/  &&  ! -w $file) or
+                ($mode =~ /x/  &&  ! -x $file) or
+                ($mode =~ /R/  &&  ! -R $file) or
+                ($mode =~ /W/  &&  ! -W $file) or
+                ($mode =~ /X/  &&  ! -X $file) or
+                ($mode =~ /o/  &&  ! -o $file) or
+                ($mode =~ /O/  &&  ! -O $file) or
+                ($mode =~ /z/  &&  ! -z $file) or
+                ($mode =~ /s/  &&  ! -s $file) or
+                ($mode =~ /f/  &&  ! -f $file) or
+                ($mode =~ /d/  &&  ! -d $file) or
+                ($mode =~ /l/  &&  ! -l $file) or
+                ($mode =~ /s/  &&  ! -s $file) or
+                ($mode =~ /p/  &&  ! -p $file) or
+                ($mode =~ /b/  &&  ! -b $file) or
+                ($mode =~ /c/  &&  ! -c $file) or
+                ($mode =~ /u/  &&  ! -u $file) or
+                ($mode =~ /g/  &&  ! -g $file) or
+                ($mode =~ /k/  &&  ! -k $file) or
+                ($mode =~ /T/  &&  ! -T $file) or
+                ($mode =~ /B/  &&  ! -B $file));
+  return 1;
+}
+#&&
+
+# $Path=&FixPath($path [,$full] [,$mode] [,$error]);
+#   Makes sure that every directory in $path (a colon separated list of
+#   directories) appears as a full path or relative path.  All "~"
+#   expansions are removed.  All trailing slashes are removed also.  If
+#   $full is non-nil, relative paths are expanded to full paths as well.
+#
+#   If $mode is given, it may be either "e", "r", or "w".  In this case,
+#   additional checking is done to each directory.  If $mode is "e", it
+#   need ony exist to pass the check.  If $mode is "r", it must have have
+#   read and execute permission.  If $mode is "w", it must have read,
+#   write, and execute permission.
+#
+#   The value of $error determines what happens if the directory does not
+#   pass the test.  If it is non-nil, if any directory does not pass the
+#   test, the subroutine returns the empty string.  Otherwise, it is simply
+#   removed from $path.
+#
+#   The corrected path is returned.
+sub FixPath {
+  my($path,$full,$mode,$err)=@_;
+  local($_)="";
+  my(@dir)=split(/$Cnf{"PathSep"}/,$path);
+  $full=0  if (! defined $full);
+  $mode="" if (! defined $mode);
+  $err=0   if (! defined $err);
+  $path="";
+  if ($mode eq "e") {
+    $mode="de";
+  } elsif ($mode eq "r") {
+    $mode="derx";
+  } elsif ($mode eq "w") {
+    $mode="derwx";
+  }
+
+  foreach (@dir) {
+
+    # Expand path
+    if ($full) {
+      $_=&FullFilePath($_);
+    } else {
+      $_=&ExpandTilde($_);
+    }
+    if (! $_) {
+      return ""  if ($err);
+      next;
+    }
+
+    # Check mode
+    if (! $mode  or  &CheckFilePath($_,$mode)) {
+      $path .= $Cnf{"PathSep"} . $_;
+    } else {
+      return "" if ($err);
+    }
+  }
+  $path =~ s/^$Cnf{"PathSep"}//;
+  return $path;
+}
+#&&
+
+# $File=&SearchPath($file,$path [,$mode] [,@suffixes]);
+#   Searches through directories in $path for a file named $file.  The
+#   full path is returned if one is found, or an empty string otherwise.
+#   The file may exist with one of the @suffixes.  The mode is checked
+#   similar to &CheckFilePath.
+#
+#   The first full path that matches the name and mode is returned.  If none
+#   is found, an empty string is returned.
+sub SearchPath {
+  my($file,$path,$mode,@suff)=@_;
+  my($f,$s,$d,@dir,$fs)=();
+  $path=&FixPath($path,1,"r");
+  @dir=split(/$Cnf{"PathSep"}/,$path);
+  foreach $d (@dir) {
+    $f="$d/$file";
+    $f=~ s|//|/|g;
+    return $f if (&CheckFilePath($f,$mode));
+    foreach $s (@suff) {
+      $fs="$f.$s";
+      return $fs if (&CheckFilePath($fs,$mode));
+    }
+  }
+  return "";
+}
+
+# @list=&ReturnList($str);
+#    This takes a string which should be a comma separated list of integers
+#    or ranges (5-7).  It returns a sorted list of all integers referred to
+#    by the string, or () if there is an invalid element.
+#
+#    Negative integers are also handled.  "-2--1" is equivalent to "-2,-1".
+sub ReturnList {
+  my($str)=@_;
+  my(@ret,@str,$from,$to,$tmp)=();
+  @str=split(/,/,$str);
+  foreach $str (@str) {
+    if ($str =~ /^[-+]?\d+$/) {
+      push(@ret,$str);
+    } elsif ($str =~ /^([-+]?\d+)-([-+]?\d+)$/) {
+      ($from,$to)=($1,$2);
+      if ($from>$to) {
+        $tmp=$from;
+        $from=$to;
+        $to=$tmp;
+      }
+      push(@ret,$from..$to);
+    } else {
+      return ();
+    }
+  }
+  @ret;
+}
+
+1;
Index: releases/Pester/1.1b6/Source/NJRCenteringMovieView.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRCenteringMovieView.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRCenteringMovieView.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NJRCenteringMovieView.h
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Jan 03 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRCenteringMovieView : NSMovieView {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRCenteringMovieView.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRCenteringMovieView.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRCenteringMovieView.m	(revision 365)
@@ -0,0 +1,57 @@
+//
+//  NJRCenteringMovieView.m
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Jan 03 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRCenteringMovieView.h"
+#import <QuickTime/Movies.h>
+
+@implementation NJRCenteringMovieView
+
+// from _MacTech_ December 2002, p. 76
+
+- (NSRect)movieRect;
+{
+    NSRect viewRect = [super movieRect];
+    Movie qtMovie = [[self movie] QTMovie];
+    Rect movieRect = {0, 0, 0, 0};
+
+    GetMovieNaturalBoundsRect(qtMovie, &movieRect);
+
+    float movieWidth = movieRect.right - movieRect.left;
+    float movieHeight = movieRect.bottom - movieRect.top;
+
+    if ( (movieWidth <= viewRect.size.width) &&
+         (movieHeight <= viewRect.size.height) ) {
+        // Movie is smaller than or equal to the view size; just center the movie
+        viewRect.origin.y += (int)((viewRect.size.height - movieHeight) / 2.);
+        viewRect.size.height = movieHeight;
+
+        viewRect.origin.x += (int)((viewRect.size.width - movieWidth) / 2.);
+        viewRect.size.width = movieWidth;
+    } else {
+        // We need to scale down movie, centering horizontally and vertically
+        float movieRatio = movieWidth / (float)movieHeight;
+        float viewRatio = viewRect.size.width / viewRect.size.height;
+
+        if (movieRatio > viewRatio) {
+            // Movie is wider than will fit; rescale.
+            float newHeight = viewRect.size.width / movieRatio;
+
+            viewRect.origin.y += (int)((viewRect.size.height - newHeight) / 2.);
+            viewRect.size.height = newHeight;
+        } else {
+            // Movie is taller than will fit (or has the ideal aspect ratio); rescale.
+            float newWidth = viewRect.size.height * movieRatio;
+
+            viewRect.origin.x += (int) ((viewRect.size.width - newWidth) / 2.);
+            viewRect.size.width = newWidth;
+        }
+    }
+    return viewRect;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRDateFormatter.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRDateFormatter.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRDateFormatter.h	(revision 365)
@@ -0,0 +1,20 @@
+//
+//  NJRDateFormatter.h
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Oct 09 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NJRDateFormatter : NSDateFormatter {
+    NSArray *tryFormatters;
+}
+
++ (BOOL)naturalLanguageParsingAvailable;
++ (NJRDateFormatter *)dateFormatter;
++ (NJRDateFormatter *)timeFormatter;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRDateFormatter.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRDateFormatter.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRDateFormatter.m	(revision 365)
@@ -0,0 +1,215 @@
+//
+//  NJRDateFormatter.m
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Oct 09 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRDateFormatter.h"
+
+// generated by perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c
+#include <EXTERN.h>
+#include <perl.h>
+
+EXTERN_C void xs_init (pTHX);
+
+EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
+
+EXTERN_C void
+xs_init(pTHX)
+{
+    char *file = __FILE__;
+    dXSUB_SYS;
+    
+    /* DynaLoader is a special case */
+    newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+}
+// end generated code
+
+static PerlInterpreter *my_perl;
+static NSDateFormatter *dateManipFormatter;
+
+static NSDate *parse_natural_language_date(NSString *input) {
+    if (my_perl == NULL) return nil;
+    
+    if ([input rangeOfString: @"|"].length > 0) {
+	NSMutableString *sanitized = [[input mutableCopy] autorelease];
+	[sanitized replaceOccurrencesOfString: @"|" withString: @""
+				      options: NSLiteralSearch
+					range: NSMakeRange(0, [sanitized length])];
+	input = sanitized;
+    }
+    
+    NSString *temp = [[NSString alloc] initWithFormat: @"UnixDate(q|%@|, '%%q')", input];
+    // NSLog(@"%@", temp);
+    SV *d = eval_pv([temp UTF8String], TRUE);
+    [temp release];
+    if (d == NULL) return nil;
+    
+    STRLEN s_len;
+    char *s = SvPV(d, s_len);
+    if (s == NULL || s_len == 0) return nil;
+    
+    NSDate *date = [dateManipFormatter dateFromString: [NSString stringWithUTF8String: s]];
+    // NSLog(@"%@", date);
+    
+    return date;
+}
+
+static void init_perl(void) {
+    const char *argv[] = {"", "-CSD", "-I", "", "-MDate::Manip", "-e", "0"};
+    argv[3] = [[[NSBundle mainBundle] resourcePath] fileSystemRepresentation];
+    PERL_SYS_INIT(0, NULL);
+    my_perl = perl_alloc();
+    if (my_perl == NULL) return;
+    
+    perl_construct(my_perl);
+    if (perl_parse(my_perl, xs_init, 7, (char **)argv, NULL) != 0) goto fail;
+    
+    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+    if (perl_run(my_perl) != 0) goto fail;
+    
+    // XXX detect localization changes
+    eval_pv("Date_Init(\"Language=English\", \"DateFormat=non-US\", \"Internal=1\"", TRUE);
+
+    if (parse_natural_language_date(@"tomorrow") == nil) goto fail;
+    
+    return;
+    
+fail:
+    perl_destruct(my_perl);
+    perl_free(my_perl);
+    PERL_SYS_TERM();
+    my_perl = NULL;
+}
+
+// workaround for bug in Jaguar (and earlier?) NSCalendarDate dateWithNaturalLanguageString:
+NSString *stringByRemovingSurroundingWhitespace(NSString *string) {
+    static NSCharacterSet *nonWhitespace = nil;
+    NSRange firstValidCharacter, lastValidCharacter;
+    
+    if (!nonWhitespace) {
+        nonWhitespace = [[[NSCharacterSet characterSetWithCharactersInString:
+			   @" \t\r\n"] invertedSet] retain];
+    }
+    
+    firstValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace];
+    if (firstValidCharacter.length == 0)
+        return @"";
+    lastValidCharacter = [string rangeOfCharacterFromSet:nonWhitespace options: NSBackwardsSearch];
+    
+    if (firstValidCharacter.location == 0 && lastValidCharacter.location == [string length] - 1)
+        return string;
+    else
+        return [string substringWithRange: NSUnionRange(firstValidCharacter, lastValidCharacter)];
+}
+
+static const NSDateFormatterStyle formatterStyles[] = {
+    NSDateFormatterShortStyle,
+    NSDateFormatterMediumStyle,
+    NSDateFormatterLongStyle,
+    NSDateFormatterFullStyle,
+    NSDateFormatterNoStyle
+};
+
+@implementation NJRDateFormatter
+
+#pragma mark initialize-release
+
++ (void)initialize;
+{
+    dateManipFormatter = [[NSDateFormatter alloc] init];
+    [dateManipFormatter setDateFormat: @"yyyyMMddHHmmss"]; // Date::Manip's "%q"
+    init_perl();
+}
+
++ (NJRDateFormatter *)dateFormatter;
+{
+    NJRDateFormatter *formatter = [[self alloc] init];
+    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
+    
+    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
+	NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
+	[tryFormatter setLenient: YES];
+	[tryFormatter setTimeStyle: NSDateFormatterNoStyle];
+	[tryFormatter setDateStyle: *s];
+	[tryFormatters addObject: tryFormatter];
+	[tryFormatter release];
+    }
+    // XXX do this in init
+    formatter->tryFormatters = tryFormatters;
+
+    return [formatter autorelease];
+}
+
++ (NJRDateFormatter *)timeFormatter;
+{
+    NJRDateFormatter *formatter = [[self alloc] init];
+    NSMutableArray *tryFormatters = [[NSMutableArray alloc] init];
+    
+    for (NSDateFormatterStyle *s = formatterStyles ; *s < NSDateFormatterNoStyle ; *s++) {
+	NSDateFormatter *tryFormatter = [[NSDateFormatter alloc] init];
+	[tryFormatter setLenient: YES];
+	[tryFormatter setTimeStyle: *s];
+	[tryFormatter setDateStyle: NSDateFormatterNoStyle];
+	[tryFormatters addObject: tryFormatter];
+	[tryFormatter release];
+    }
+    formatter->tryFormatters = tryFormatters;
+    
+    return [formatter autorelease];
+}
+
+- (void)dealloc;
+{
+    [tryFormatters release]; tryFormatters = nil;
+    [super dealloc];
+}
+
+#pragma mark primitive formatter 
+
+- (NSString *)stringForObjectValue:(id)obj;
+{
+    return [super stringForObjectValue: obj];
+}
+
+- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs;
+{
+    return [super attributedStringForObjectValue: obj
+			   withDefaultAttributes: attrs];
+}
+
+- (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
+{
+    if ([super getObjectValue: anObject forString: string errorDescription: error])
+	return YES;
+	
+    NSDate *date;
+    NSEnumerator *e = [tryFormatters objectEnumerator];
+    NSDateFormatter *tryFormatter;
+    
+    // XXX untested; does this work?
+    while ( (tryFormatter = [e nextObject]) != nil) {
+	date = [tryFormatter dateFromString: string];
+	if (date != nil) goto success;
+    }
+    
+    date = parse_natural_language_date(string);
+    if (date != nil) goto success;
+    
+    return NO;
+
+success:
+    *anObject = date;
+    if (error != NULL) *error = nil;
+    return YES;
+}
+
+#pragma mark miscellaneous
+
++ (BOOL)naturalLanguageParsingAvailable;
+{
+    return (my_perl != NULL);
+}
+@end
Index: releases/Pester/1.1b6/Source/NJRFSObjectSelector.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRFSObjectSelector.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRFSObjectSelector.h	(revision 365)
@@ -0,0 +1,27 @@
+/* NJRFSObjectSelector */
+
+#import <Cocoa/Cocoa.h>
+#import "BDAlias.h"
+
+@interface NJRFSObjectSelector : NSButton
+{
+    BDAlias *selectedAlias;
+    NSArray *fileTypes;
+    BOOL canChooseDirectories;
+    BOOL canChooseFiles;
+    BOOL dragAccepted;
+    BOOL isEnabled;
+}
+- (IBAction)select:(id)sender;
+- (void)setAlias:(BDAlias *)alias;
+- (BDAlias *)alias;
+- (BOOL)acceptsPath:(NSString *)path;
+- (void)setPath:(NSString *)path; // does not validate
+- (BOOL)canChooseDirectories;
+- (BOOL)canChooseFiles;
+- (void)setCanChooseDirectories:(BOOL)flag;
+- (void)setCanChooseFiles:(BOOL)flag;
+- (NSArray *)fileTypes;
+- (void)setFileTypes:(NSArray *)types;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRFSObjectSelector.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRFSObjectSelector.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRFSObjectSelector.m	(revision 365)
@@ -0,0 +1,323 @@
+#import "NJRFSObjectSelector.h"
+#import "NSMenuItem-NJRExtensions.h"
+#import "NSImage-NJRExtensions.h"
+#import "NSString-NJRExtensions.h"
+#include <Carbon/Carbon.h>
+
+static NSImage *PopupTriangleImage = nil;
+static NSSize PopupTriangleSize;
+
+@implementation NJRFSObjectSelector
+
+- (void)_initSelector;
+{
+    if (PopupTriangleImage == nil) {
+        PopupTriangleImage = [[NSImage imageNamed: @"Popup triangle"] retain];
+        PopupTriangleSize = [PopupTriangleImage size];
+    }
+    canChooseFiles = YES; canChooseDirectories = NO;
+    [self setAlias: nil];
+    [[self cell] setHighlightsBy: NSChangeBackgroundCell];
+    [[self cell] setGradientType: NSGradientNone];
+    [self registerForDraggedTypes:
+        [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
+}
+
+- (void)dealloc;
+{
+    [selectedAlias release];
+    [fileTypes release];
+    [super dealloc];
+}
+
+- (id)initWithCoder:(NSCoder *)coder;
+{
+    if ( (self = [super initWithCoder: coder]) != nil) {
+        [self _initSelector];
+    }
+    return self;
+}
+
+- (id)initWithFrame:(NSRect)frame;
+{
+    if ( (self = [super initWithFrame: frame]) != nil) {
+        [self _initSelector];
+    }
+    return self;
+}
+
+- (void)drawRect:(NSRect)rect;
+{
+    NSRect boundsRect = [self bounds];
+    [super drawRect: rect];
+    if (dragAccepted) {
+        [[NSColor selectedControlColor] set];
+        [NSBezierPath setDefaultLineWidth: 2];
+        [NSBezierPath strokeRect: NSInsetRect(boundsRect, 2, 2)];
+    } else if (selectedAlias != nil && [self isEnabled]) {
+        // equivalent to popup triangle location for large bezel in Carbon
+        [PopupTriangleImage compositeToPoint: NSMakePoint(NSMaxX(boundsRect) - PopupTriangleSize.width - 5, NSMaxY(boundsRect) - 5) operation: NSCompositeSourceOver];
+    }
+}
+
+- (BOOL)acceptsPath:(NSString *)path;
+{
+    NSFileManager *fm = [NSFileManager defaultManager];
+    BOOL isDir;
+
+    if (![fm fileExistsAtPath: path isDirectory: &isDir]) return NO;
+
+    if (isDir) return canChooseDirectories;
+    if (canChooseFiles) {
+        NSEnumerator *e = [fileTypes objectEnumerator];
+        NSString *extension = [path pathExtension];
+        NSString *hfsType = NSHFSTypeOfFile(path);
+        NSString *fileType;
+        
+        while ( (fileType = [e nextObject]) != nil) {
+            if ([fileType isEqualToString: extension] || [fileType isEqualToString: hfsType])
+                return YES;
+        }
+    }
+    return NO;
+}
+
+- (void)setImage:(NSImage *)image;
+{
+    // XXX change to use IconRef; see ICeCoffEELabeledIconCell and Google
+    [super setImage: [image bestFitImageForSize: [[self cell] cellSize]]];
+}
+
+- (IBAction)select:(id)sender;
+{
+    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+    NSString *path = [selectedAlias fullPath];
+    [openPanel setAllowsMultipleSelection: NO];
+    [openPanel setCanChooseDirectories: canChooseDirectories];
+    [openPanel setCanChooseFiles: canChooseFiles];
+    [openPanel beginSheetForDirectory: [path stringByDeletingLastPathComponent]
+                                 file: [path lastPathComponent]
+                                types: fileTypes
+                       modalForWindow: [self window]
+                        modalDelegate: self
+                       didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
+                          contextInfo: nil];
+}
+
+- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
+{
+    [sheet close];
+
+    if (returnCode == NSOKButton) {
+        NSArray *files = [sheet filenames];
+        NSAssert1([files count] == 1, @"%d items returned, only one expected", [files count]);
+        [self setPath: [files objectAtIndex: 0]];
+		if ([self target] != nil && [[self target] respondsToSelector:[self action]])
+			[[self target] performSelector: [self action] withObject: self];
+    }
+}
+
+- (void)revealInFinder:(id<NSMenuItem>)sender;
+{
+    NSString *path = [sender representedObject];
+    if (path == nil) return;
+    [[NSWorkspace sharedWorkspace] selectFile: path inFileViewerRootedAtPath: @""];
+}
+
+- (void)setAlias:(BDAlias *)alias;
+{
+    if (selectedAlias != alias) {
+        [selectedAlias release];
+        selectedAlias = [alias retain];
+    }
+
+    if (alias != nil) { // alias is set
+        NSString *path = [alias fullPath];
+        NSString *revealPath = nil;
+        NSMenu *menu = [[NSMenu alloc] initWithTitle: @""];
+        NSFileManager *fmgr = [NSFileManager defaultManager];
+        NSMenuItem *item;
+        if (path != nil) { // can resolve alias
+            [self setImage: [[NSWorkspace sharedWorkspace] iconForFile: path]];
+            {	// set image first so titleRectForBounds: returns the correct value
+                NSMutableString *title = [[fmgr displayNameAtPath: path] mutableCopy];
+                NSDictionary *fontAttributes = [[self attributedTitle] fontAttributesInRange: NSMakeRange(0, 0)];
+                [title truncateToWidth: [[self cell] titleRectForBounds: [self bounds]].size.width - PopupTriangleSize.width by: NSLineBreakByTruncatingMiddle withAttributes: fontAttributes];
+                [self setTitle: title];
+                [title release];
+            }
+            do {
+                NSAssert1(![path isEqualToString: revealPath], @"Stuck on path |%@|", [alias fullPath]);
+                item = [menu addItemWithTitle: [fmgr displayNameAtPath: path]
+                                       action: @selector(revealInFinder:)
+                                keyEquivalent: @""];
+                [item setTarget: self];
+                [item setRepresentedObject: revealPath];
+                [item setImageFromPath: path];
+                revealPath = path;
+                path = [path stringByDeletingLastPathComponent];
+            } while (![revealPath isEqualToString: @"/"] && ![path isEqualToString: @"/Volumes"]);
+            [[self cell] setMenu: menu];
+        } else {
+            [self setImage: nil];
+            [self setTitle: @"(not available)"];
+            [[self cell] setMenu: nil];
+        }
+    } else {
+        [self setImage: nil];
+        [self setTitle: @"(none selected)"];
+        [[self cell] setMenu: nil];
+    }
+    [self setEnabled: isEnabled];
+}
+
+- (BOOL)isEnabled;
+{
+    return isEnabled;
+}
+
+- (void)setEnabled:(BOOL)enabled;
+{
+    isEnabled = enabled;
+    [super setEnabled: enabled ? selectedAlias != nil : NO];
+}
+
+- (void)rightMouseDown:(NSEvent *)theEvent;
+{
+    [self mouseDown: theEvent];
+}
+
+- (void)otherMouseDown:(NSEvent *)theEvent;
+{
+    [self mouseDown: theEvent];
+}
+
+extern MenuRef _NSGetCarbonMenu(NSMenu *menu);
+
+- (void)mouseDown:(NSEvent *)theEvent;
+{
+    if (![self isEnabled]) return;
+    
+    NSMenu *menu = [[self cell] menu];
+    MenuRef mRef = _NSGetCarbonMenu(menu);
+
+    if (mRef == NULL) {
+        NSMenu *appMenu = [[[NSApp mainMenu] itemWithTitle: @""] submenu];
+        if (appMenu != nil) {
+            NSMenuItem *item = [appMenu addItemWithTitle: @"" action: NULL keyEquivalent: @""];
+            [appMenu setSubmenu: menu forItem: item];
+            [appMenu removeItem: item];
+        }
+        mRef = _NSGetCarbonMenu(menu);
+    }
+
+    ChangeMenuAttributes(mRef, kMenuAttrExcludesMarkColumn, 0);
+    theEvent = [NSEvent mouseEventWithType: [theEvent type]
+                                  location: [self convertPoint: NSMakePoint(-1, 1) toView: nil]
+                             modifierFlags: [theEvent modifierFlags]
+                                 timestamp: [theEvent timestamp]
+                              windowNumber: [theEvent windowNumber]
+                                   context: [theEvent context]
+                               eventNumber: [theEvent eventNumber]
+                                clickCount: [theEvent clickCount]
+                                  pressure: [theEvent pressure]];
+
+    // XXX otherwise Cocoa thoughtfully doesn't give me the font I want
+    NSFont *font = [[self cell] font];
+    [NSMenu popUpContextMenu: menu withEvent: theEvent forView: self withFont: 
+     [NSFont fontWithName: [font fontName] size: [font pointSize] - 0.001]];
+}
+
+- (BDAlias *)alias;
+{
+    return selectedAlias;
+}
+
+- (void)setPath:(NSString *)path;
+{
+    [self setAlias: [BDAlias aliasWithPath: path]];
+}
+
+- (BOOL)canChooseDirectories;
+{
+    return canChooseDirectories;
+}
+
+- (BOOL)canChooseFiles;
+{
+    return canChooseFiles;
+}
+
+- (void)setCanChooseDirectories:(BOOL)flag;
+{
+    canChooseDirectories = flag;
+}
+
+- (void)setCanChooseFiles:(BOOL)flag;
+{
+    canChooseFiles = flag;
+}
+
+- (NSArray *)fileTypes;
+{
+    return fileTypes;
+}
+
+- (void)setFileTypes:(NSArray *)types;
+{
+    if (fileTypes == types) return;
+    
+    [fileTypes release];
+    fileTypes = [types retain];
+}
+
+@end
+
+
+@implementation NJRFSObjectSelector (NSDraggingDestination)
+
+- (BOOL)acceptsDragFrom:(id <NSDraggingInfo>)sender;
+{
+    NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
+
+    if (url == nil || ![url isFileURL]) return NO;
+    return [self acceptsPath: [url path]];
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
+{
+    if ([self acceptsDragFrom: sender] && [sender draggingSourceOperationMask] &
+        (NSDragOperationCopy | NSDragOperationLink)) {
+        dragAccepted = YES;
+        [self setNeedsDisplay: YES];
+        return NSDragOperationLink;
+    }
+    return NSDragOperationNone;
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender;
+{
+    dragAccepted = NO;
+    [self setNeedsDisplay: YES];
+}
+
+- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
+{
+    dragAccepted = NO;
+    [self setNeedsDisplay: YES];
+    return [self acceptsDragFrom: sender];
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
+{
+    if ([sender draggingSource] != self) {
+        NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
+        if (url == nil) return NO;
+        [self setPath: [url path]];
+		if ([self target] != nil && [[self target] respondsToSelector:[self action]])
+			[[self target] performSelector: [self action] withObject: self];
+    }
+    return YES;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHistoryTrackingComboBox.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRHistoryTrackingComboBox.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHistoryTrackingComboBox.h	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  NJRHistoryTrackingComboBox.h
+//  DockCam
+//
+//  Created by Nicholas Riley on Fri Jun 28 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface NJRHistoryTrackingComboBox : NSComboBox {
+
+}
+
+- (IBAction)removeEntry:(id)sender;
+- (IBAction)clearAllEntries:(id)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHistoryTrackingComboBox.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRHistoryTrackingComboBox.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHistoryTrackingComboBox.m	(revision 365)
@@ -0,0 +1,74 @@
+//
+//  NJRHistoryTrackingComboBox.m
+//  DockCam
+//
+//  Created by Nicholas Riley on Fri Jun 28 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRHistoryTrackingComboBox.h"
+
+#define NJRHistoryTrackingComboBoxMaxItems 10
+
+@implementation NJRHistoryTrackingComboBox
+
+- (NSString *)_defaultKey;
+{
+    NSAssert([self tag] != 0, NSLocalizedString(@"Can't track history for combo box with tag 0: please set a tag", "Assertion for history tracking combo box if tag is 0"));
+    return [NSString stringWithFormat: @"NJRHistoryTrackingComboBox tag %d", [self tag]];
+}
+
+- (void)awakeFromNib;
+{
+    [[NSNotificationCenter defaultCenter] addObserver: self
+                                             selector: @selector(textDidEndEditing:)
+                                                 name: NSTextDidEndEditingNotification
+                                               object: self];
+    [self removeAllItems];
+    [self addItemsWithObjectValues: [[NSUserDefaults standardUserDefaults] stringArrayForKey: [self _defaultKey]]];
+}
+
+- (void)_writeHistory;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    [defaults setObject: [self objectValues] forKey: [self _defaultKey]];
+    [defaults synchronize];
+}
+
+- (IBAction)removeEntry:(id)sender;
+{
+    int idx = [self indexOfSelectedItem];
+    if (idx == -1) {
+        [self selectItemWithObjectValue: [self stringValue]];
+        idx = [self indexOfSelectedItem];
+    }
+    if (idx != -1) [self removeItemAtIndex: idx];
+    [self setStringValue: @""];
+    [self _writeHistory];
+}
+
+- (IBAction)clearAllEntries:(id)sender;
+{
+    [self removeAllItems];
+    [self setStringValue: @""];
+    [self _writeHistory];
+}
+
+- (BOOL)textShouldEndEditing:(NSText *)textObject;
+{
+    NSString *newValue = [self stringValue];
+    int oldIndex = [self indexOfItemWithObjectValue: newValue];
+    if ([newValue length] == 0) return YES; // donÕt save empty entries
+    [self removeItemWithObjectValue: newValue];
+    [self insertItemWithObjectValue: newValue atIndex: 0];
+    if (oldIndex == NSNotFound) {
+        int numItems = [self numberOfItems];
+        while (numItems-- > NJRHistoryTrackingComboBoxMaxItems) {
+            [self removeItemAtIndex: numItems - 1];
+        }
+    }
+    [self _writeHistory];
+    return YES;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKey.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKey.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKey.h	(revision 365)
@@ -0,0 +1,32 @@
+//
+//  NJRHotKey.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 01 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NJRHotKey : NSObject {
+    NSString *hotKeyCharacters;
+    unsigned hotKeyModifierFlags;
+    unsigned short hotKeyCode;
+}
+
++ (NJRHotKey *)hotKeyWithCharacters:(NSString *)characters modifierFlags:(unsigned)modifierFlags keyCode:(unsigned short)keyCode;
+
+- (id)initWithCharacters:(NSString *)characters modifierFlags:(unsigned)modifierFlags keyCode:(unsigned short)keyCode;
+
+- (NSString *)characters;
+- (unsigned)modifierFlags; // Cocoa
+- (long)modifiers; // Carbon
+- (unsigned short)keyCode;
+
+- (NSDictionary *)propertyListRepresentation;
+- (id)initWithPropertyList:(NSDictionary *)dict;
+
+- (NSString *)keyGlyphs;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKey.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKey.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKey.m	(revision 365)
@@ -0,0 +1,115 @@
+//
+//  NJRHotKey.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 01 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRHotKey.h"
+#import "NSString-NJRExtensions.h"
+
+#include <Carbon/Carbon.h>
+
+// property list keys
+static NSString * const PLCharacters = @"characters"; // NSString
+static NSString * const PLModifierFlags = @"modifierFlags"; // NSNumber
+static NSString * const PLKeyCode = @"keyCode"; // NSNumber
+
+@implementation NJRHotKey
+
+#pragma mark initialize-release
+
++ (NJRHotKey *)hotKeyWithCharacters:(NSString *)characters modifierFlags:(unsigned)modifierFlags keyCode:(unsigned short)keyCode;
+{
+    return [[[self alloc] initWithCharacters: characters modifierFlags: modifierFlags keyCode: keyCode] autorelease];
+}
+
+- (id)initWithCharacters:(NSString *)characters modifierFlags:(unsigned)modifierFlags keyCode:(unsigned short)keyCode;
+{
+    if ( (self = [self init]) != nil) {
+        hotKeyCharacters = [characters retain];
+        hotKeyModifierFlags = modifierFlags;
+        hotKeyCode = keyCode;
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [hotKeyCharacters release];
+    [super dealloc];
+}
+
+#pragma mark accessing
+
+- (NSString *)characters;
+{
+    return hotKeyCharacters;
+}
+
+- (unsigned)modifierFlags;
+{
+    return hotKeyModifierFlags;
+}
+
+- (long)modifiers;
+{
+    static long modifierMap[5][2] = {
+       { NSCommandKeyMask, cmdKey },
+       { NSAlternateKeyMask, optionKey },
+       { NSControlKeyMask, controlKey },
+       { NSShiftKeyMask, shiftKey },
+       { 0, 0 }
+    };
+
+    long modifiers = 0;
+    int i;
+
+    for (i = 0 ; modifierMap[i][0] != 0 ; i++)
+        if (hotKeyModifierFlags & modifierMap[i][0])
+            modifiers |= modifierMap[i][1];
+
+    return modifiers;
+}
+
+- (unsigned short)keyCode;
+{
+    return hotKeyCode;
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    return [NSDictionary dictionaryWithObjectsAndKeys:
+        hotKeyCharacters, PLCharacters,
+        [NSNumber numberWithUnsignedInt: hotKeyModifierFlags], PLModifierFlags,
+        [NSNumber numberWithUnsignedShort: hotKeyCode], PLKeyCode,
+        nil];
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [self init]) != nil) {
+	@try {
+            hotKeyCharacters = [[dict objectForKey: PLCharacters] retain];
+            hotKeyModifierFlags = [[dict objectForKey: PLModifierFlags] unsignedIntValue];
+            hotKeyCode = [[dict objectForKey: PLKeyCode] unsignedShortValue];
+	}
+	@catch (NSException *exception) {
+	}
+        if (hotKeyCharacters == nil || hotKeyCode == 0) {
+            [self release];
+            self = nil;
+        }
+    }
+    return self;
+}
+
+- (NSString *)keyGlyphs;
+{
+    return [[hotKeyCharacters keyEquivalentAttributedStringWithModifierFlags: hotKeyModifierFlags] string];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKeyField.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKeyField.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKeyField.h	(revision 365)
@@ -0,0 +1,28 @@
+//
+//  NJRHotKeyField.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Mar 29 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@class NJRHotKey;
+
+@interface NJRHotKeyField : NSTextField {
+    NJRHotKey *hotKey;
+}
+
+- (NJRHotKey *)hotKey;
+- (void)setHotKey:(NJRHotKey *)aKey;
+
+- (IBAction)clear:(id)sender;
+
+@end
+
+@interface NSObject (NJRHotKeyFieldDelegate)
+
+- (BOOL)hotKeyField:(NJRHotKeyField *)hotKeyField shouldAcceptCharacter:(unichar)keyChar modifierFlags:(unsigned)modifierFlags rejectionMessage:(NSString **)message;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKeyField.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKeyField.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKeyField.m	(revision 365)
@@ -0,0 +1,180 @@
+//
+//  NJRHotKeyField.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Mar 29 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRHotKeyField.h"
+#import "NJRHotKeyFieldCell.h"
+#import "NJRHotKey.h"
+#import "NSString-NJRExtensions.h"
+
+static const NSRange zeroRange = {0, 0};
+static const unsigned int capturedModifierMask = (NSShiftKeyMask |
+                                                  NSControlKeyMask |
+                                                  NSAlternateKeyMask |
+                                                  NSCommandKeyMask);
+
+static NSParagraphStyle *leftAlignStyle = nil, *centerAlignStyle = nil;
+static NSDictionary *statusAttributes = nil;
+
+@implementation NJRHotKeyField
+
++ (void)initialize;
+{
+    NSMutableParagraphStyle *paraStyle = [[NSMutableParagraphStyle alloc] init];
+    [paraStyle setAlignment: NSLeftTextAlignment];
+    leftAlignStyle = [paraStyle copy];
+    [paraStyle setAlignment: NSCenterTextAlignment];
+    centerAlignStyle = [paraStyle copy];
+    [paraStyle release];
+
+    statusAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
+        [NSFont systemFontOfSize: [NSFont labelFontSize]], NSFontAttributeName,
+        centerAlignStyle, NSParagraphStyleAttributeName, nil];
+}
+
+- (void)_setUp;
+{
+    [self setAllowsEditingTextAttributes: YES];
+    [self setImportsGraphics: NO];
+    [self cell]->isa = [NJRHotKeyFieldCell class];
+}
+
+#pragma mark initialize-release
+
+- (id)initWithCoder:(NSCoder *)coder;
+{
+    if ( (self = [super initWithCoder: coder]) != nil) {
+        [self _setUp];
+    }
+    return self;
+}
+
+- (id)initWithFrame:(NSRect)frameRect;
+{
+    if ( (self = [super initWithFrame: frameRect]) != nil) {
+        [self _setUp];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [hotKey release];
+    [super dealloc];
+}
+
+#pragma mark interface updating
+
+- (void)showStatus:(NSString *)error;
+{
+    [self setAttributedStringValue:
+        [[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"(%@)", error]
+                                        attributes: statusAttributes]];
+    [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(clearStatus) object: nil];
+    [self performSelector: @selector(clearStatus) withObject: nil afterDelay: 0.5];
+    [[self currentEditor] setSelectedRange: zeroRange];
+}
+
+- (void)previewKeyEquivalentAttributedString:(NSAttributedString *)equivString;
+{
+    NSMutableAttributedString *previewString = [equivString mutableCopy];
+    [previewString addAttribute: NSParagraphStyleAttributeName value: leftAlignStyle range: NSMakeRange(0, [previewString length])];
+    [self setAttributedStringValue: previewString];
+    [[self currentEditor] setSelectedRange: zeroRange];
+    [previewString release];
+}
+
+- (void)showKeyEquivalentAttributedStringFinalized:(BOOL)finalized;
+{
+    if ([hotKey characters] == nil) {
+        [self showStatus: @"none assigned"];
+        return;
+    }
+    NSMutableAttributedString *equivString = [[[hotKey characters] keyEquivalentAttributedStringWithModifierFlags: [hotKey modifierFlags]] mutableCopy];
+    [equivString addAttribute: NSParagraphStyleAttributeName
+                        value: (finalized ? centerAlignStyle : leftAlignStyle)
+                        range: NSMakeRange(0, [equivString length])];
+    [self setAttributedStringValue: equivString];
+    [[self currentEditor] setSelectedRange: zeroRange];
+    [equivString release];
+}
+
+- (void)clearStatus;
+{
+    if ([[[self attributedStringValue] attributesAtIndex: 0 effectiveRange: NULL] isEqualToDictionary: statusAttributes]) {
+        [self showKeyEquivalentAttributedStringFinalized: ([[NSApp currentEvent] modifierFlags] & capturedModifierMask) == 0];
+    }
+}
+
+#pragma mark event handling
+
+- (void)keyUp:(NSEvent *)theEvent;
+{
+    NSString *characters = [theEvent charactersIgnoringModifiers];
+    int length = [characters length];
+    if (length > 1) {
+        [self showStatus: @"please press only one non-modifier key"];
+        return;
+    }
+    if (length == 1) {
+        unsigned modifierFlags = ([theEvent modifierFlags] & capturedModifierMask);
+        id delegate = [self delegate];
+        NSString *message = nil;
+        if (delegate != nil && ![delegate hotKeyField: self shouldAcceptCharacter: [characters characterAtIndex: 0] modifierFlags: modifierFlags rejectionMessage: &message]) {
+            [self showStatus: message != nil ? message : @"key is unavailable for use"];
+        } else {
+            [self setHotKey: [NJRHotKey hotKeyWithCharacters: characters modifierFlags: modifierFlags keyCode: [theEvent keyCode]]];
+            [NSApp sendAction: [self action] to: [self target] from: self];
+            [self showKeyEquivalentAttributedStringFinalized: ([theEvent modifierFlags] & capturedModifierMask) == 0];
+        }
+    }
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent *)theEvent;
+{
+    [self keyUp: theEvent];
+    return [super performKeyEquivalent: theEvent];
+}
+
+- (void)flagsChanged:(NSEvent *)theEvent;
+{
+    unsigned modifierFlags = [theEvent modifierFlags];
+
+    if ((modifierFlags & capturedModifierMask) == 0) {
+        [self showKeyEquivalentAttributedStringFinalized: YES];
+    } else {
+        [self previewKeyEquivalentAttributedString:
+            [@"" keyEquivalentAttributedStringWithModifierFlags: modifierFlags]];
+    }
+}
+
+#pragma mark acccessing
+
+- (NJRHotKey *)hotKey;
+{
+    return hotKey;
+}
+
+- (void)setHotKey:(NJRHotKey *)aKey;
+{
+    if (aKey != hotKey) {
+        [hotKey release];
+        hotKey = [aKey retain];
+        [self showKeyEquivalentAttributedStringFinalized: ([[NSApp currentEvent] modifierFlags] & capturedModifierMask) == 0];
+    }
+}
+
+#pragma mark actions
+
+- (IBAction)clear:(id)sender;
+{
+    [self setHotKey: nil];
+    [NSApp sendAction: [self action] to: [self target] from: self];
+    [self showKeyEquivalentAttributedStringFinalized: YES];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKeyFieldCell.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKeyFieldCell.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKeyFieldCell.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NJRHotKeyFieldCell.h
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Mar 31 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface NJRHotKeyFieldCell : NSTextFieldCell {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKeyFieldCell.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKeyFieldCell.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKeyFieldCell.m	(revision 365)
@@ -0,0 +1,21 @@
+//
+//  NJRHotKeyFieldCell.m
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Mar 31 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRHotKeyFieldCell.h"
+#import "NJRHotKeyField.h"
+
+@implementation NJRHotKeyFieldCell
+
+- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(int)selStart length:(int)selLength
+{
+    [super selectWithFrame: aRect inView: controlView editor: textObj delegate: anObject start: 0 length: 0];
+    [textObj setSelectable: NO];
+    [textObj setEditable: NO];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKeyManager.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKeyManager.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKeyManager.h	(revision 365)
@@ -0,0 +1,30 @@
+//
+//  NJRHotKeyManager.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 01 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class NJRHotKey;
+
+@interface NJRHotKeyManager : NSObject {
+    BOOL shortcutsEnabled;
+    NSMutableDictionary *shortcuts;
+}
+
++ (NJRHotKeyManager *)sharedManager;
+
+- (BOOL)addShortcutWithIdentifier:(NSString *)identifier hotKey:(NJRHotKey *)hotKey target:(id)target action:(SEL)action;
+- (void)removeShortcutWithIdentifier:(NSString *)identifier;
+
+- (NSArray *)shortcutIdentifiers;
+- (NJRHotKey *)hotKeyForShortcutWithIdentifier:(NSString *)identifier;
+
+- (void)setShortcutsEnabled:(BOOL)enabled;
+- (BOOL)shortcutsEnabled;
+
+
+@end
Index: releases/Pester/1.1b6/Source/NJRHotKeyManager.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRHotKeyManager.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRHotKeyManager.m	(revision 365)
@@ -0,0 +1,261 @@
+//
+//  NJRHotKeyManager.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 01 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+// based on HotKeyCenter, by Quentin Carnicelli
+// renamed, reorganized, cleaned up, pre-10.2 support removed
+
+#import "NJRHotKeyManager.h"
+#import "NJRHotKey.h"
+#import <Carbon/Carbon.h>
+
+const OSType kHotKeyManagerSignature = 'NHKM';
+
+@interface _NJRHotKeyShortcut : NSObject {
+    @public
+    BOOL isRegistered;
+    EventHotKeyRef hotKeyRef;
+    NJRHotKey *hotKey;
+    id target;
+    SEL action;
+}
+@end
+
+@implementation _NJRHotKeyShortcut
+
+- (void)dealloc;
+{
+    [hotKey release];
+    [target release];
+    [super dealloc];
+}
+
+@end
+
+pascal OSErr keyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *refCon);
+
+@interface NJRHotKeyManager (Private)
+- (OSStatus)_handleHotKeyEvent:(EventRef)inEvent;
+- (BOOL)_registerHotKeyIfNeeded:(_NJRHotKeyShortcut *)shortcut;
+- (void)_unregisterHotKeyIfNeeded:(_NJRHotKeyShortcut *)shortcut;
+- (void)_hotKeyDown:(_NJRHotKeyShortcut *)hotKey;
+- (void)_hotKeyUp:(_NJRHotKeyShortcut *)hotKey;
+- (void)_hotKeyDownWithRef:(EventHotKeyRef)ref;
+- (void)_hotKeyUpWithRef:(EventHotKeyRef)ref;
+- (_NJRHotKeyShortcut *)_findShortcutWithRef:(EventHotKeyRef)ref;
+@end
+
+@implementation NJRHotKeyManager
+
++ (NJRHotKeyManager *)sharedManager;
+{
+    static NJRHotKeyManager *manager = nil;
+
+    if (manager == nil) {
+        manager = [[self alloc] init];
+
+        EventTypeSpec eventSpec[2] = {
+           { kEventClassKeyboard, kEventHotKeyPressed },
+           { kEventClassKeyboard, kEventHotKeyReleased }
+        };
+
+        InstallEventHandler(GetEventDispatcherTarget(),
+                            NewEventHandlerUPP((EventHandlerProcPtr) keyEventHandler),
+                            2, eventSpec, nil, nil);
+    }
+    return manager;
+}
+
+- (id)init;
+{
+    if ( (self = [super init]) != nil) {
+        shortcutsEnabled = YES;
+        shortcuts = [[NSMutableDictionary alloc] init];
+    }
+
+    return self;
+}
+
+- (void)dealloc;
+{
+    [shortcuts release];
+    [super dealloc];
+}
+
+#pragma mark -
+
+- (BOOL)addShortcutWithIdentifier:(NSString *)identifier hotKey:(NJRHotKey *)hotKey target:(id)target action:(SEL)action;
+{
+    NSParameterAssert(identifier != nil);
+    NSParameterAssert(hotKey != nil);
+    NSParameterAssert(target != nil);
+    NSParameterAssert(action != nil);
+
+    if ([shortcuts objectForKey: identifier] != nil)
+        [self removeShortcutWithIdentifier: identifier];
+
+    _NJRHotKeyShortcut *newShortcut = [[_NJRHotKeyShortcut alloc] init];
+    newShortcut->isRegistered = NO;
+    newShortcut->hotKeyRef = nil;
+    newShortcut->hotKey = [hotKey retain];
+    newShortcut->target = [target retain];
+    newShortcut->action = action;
+
+    [shortcuts setObject: newShortcut forKey: identifier];
+    [newShortcut release];
+    
+    return [self _registerHotKeyIfNeeded: newShortcut];
+}
+
+- (void)removeShortcutWithIdentifier:(NSString *)identifier;
+{
+    _NJRHotKeyShortcut *hotKey = [shortcuts objectForKey: identifier];
+
+    if (hotKey == nil) return;
+    [self _unregisterHotKeyIfNeeded: hotKey];
+    [shortcuts removeObjectForKey: identifier];
+}
+
+- (NSArray *)shortcutIdentifiers;
+{
+    return [shortcuts allKeys];
+}
+
+- (NJRHotKey *)hotKeyForShortcutWithIdentifier:(NSString *)identifier;
+{
+    _NJRHotKeyShortcut *hotKey = [shortcuts objectForKey: identifier];
+
+    return (hotKey == nil ? nil : [[hotKey->hotKey retain] autorelease]);
+}
+
+- (void)setShortcutsEnabled:(BOOL)enabled;
+{
+    NSEnumerator *enumerator = [shortcuts objectEnumerator];
+    _NJRHotKeyShortcut *hotKey;
+
+    while ( (hotKey = [enumerator nextObject]) != nil) {
+        if (enabled)
+            [self _registerHotKeyIfNeeded: hotKey];
+        else
+            [self _unregisterHotKeyIfNeeded: hotKey];
+    }
+    shortcutsEnabled = enabled;
+}
+
+- (BOOL)shortcutsEnabled;
+{
+    return shortcutsEnabled;
+}
+
+#pragma mark -
+
+- (OSStatus)_handleHotKeyEvent:(EventRef)inEvent;
+{
+    OSStatus err;
+    EventHotKeyID hotKeyID;
+    _NJRHotKeyShortcut *shortcut;
+
+    NSAssert(GetEventClass(inEvent) == kEventClassKeyboard, @"Unhandled event class");
+
+    if ( (err = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, nil,
+                                  sizeof(EventHotKeyID), nil, &hotKeyID)) != noErr)
+        return err;
+
+    NSAssert(hotKeyID.signature == kHotKeyManagerSignature, @"Unknown hot key");
+
+    shortcut = (_NJRHotKeyShortcut *)hotKeyID.id;
+    NSAssert(shortcut != nil, @"Got bad hot key");
+
+    switch (GetEventKind(inEvent)) {
+        case kEventHotKeyPressed:
+            [self _hotKeyDown: shortcut];
+            break;
+        case kEventHotKeyReleased:
+            [self _hotKeyUp: shortcut];
+            break;
+        default:
+            break;
+    }
+
+    return noErr;
+}
+
+#pragma mark -
+
+- (BOOL)_registerHotKeyIfNeeded:(_NJRHotKeyShortcut *)shortcut;
+{
+    NJRHotKey *hotKey;
+
+    NSParameterAssert(shortcut != nil);
+
+    hotKey = shortcut->hotKey;
+
+    if (shortcutsEnabled && !(shortcut->isRegistered)) {
+        EventHotKeyID keyID;
+        OSStatus err;
+
+        keyID.signature = kHotKeyManagerSignature;
+        keyID.id = (unsigned long)shortcut;
+        if ( (err = RegisterEventHotKey([hotKey keyCode], [hotKey modifiers], keyID, GetEventDispatcherTarget(), 0, &shortcut->hotKeyRef)) != noErr)
+            return NO;
+
+        shortcut->isRegistered = YES;
+    }
+
+    return YES;
+}
+
+- (void)_unregisterHotKeyIfNeeded:(_NJRHotKeyShortcut *)shortcut;
+{
+    NSParameterAssert(shortcut != nil);
+ 
+    if (shortcut->isRegistered && shortcut->hotKeyRef != nil)
+        UnregisterEventHotKey(shortcut->hotKeyRef);
+}
+
+- (void)_hotKeyDown:(_NJRHotKeyShortcut *)hotKey;
+{
+    id target = hotKey->target;
+    SEL action = hotKey->action;
+
+    [target performSelector: action withObject: self];
+}
+
+- (void)_hotKeyUp:(_NJRHotKeyShortcut *)hotKey;
+{
+}
+
+- (void)_hotKeyDownWithRef:(EventHotKeyRef)ref;
+{
+    _NJRHotKeyShortcut *hotKey = [self _findShortcutWithRef: ref];
+
+    if (hotKey != nil)
+        [self _hotKeyDown: hotKey];
+}
+
+- (void)_hotKeyUpWithRef:(EventHotKeyRef)ref;
+{
+}
+
+- (_NJRHotKeyShortcut *)_findShortcutWithRef:(EventHotKeyRef)ref;
+{
+    NSEnumerator *enumerator = [shortcuts objectEnumerator];
+    _NJRHotKeyShortcut *hotKey;
+
+    while ( (hotKey = [enumerator nextObject]) != nil) {
+        if (hotKey->hotKeyRef == ref)
+            return hotKey;
+    }
+    return nil;
+}
+
+@end
+
+pascal OSErr keyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *refCon)
+{
+    return [[NJRHotKeyManager sharedManager] _handleHotKeyEvent: inEvent];
+}
Index: releases/Pester/1.1b6/Source/NJRIntegerFilter.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRIntegerFilter.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRIntegerFilter.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NJRIntegerFilter.h
+//  HostLauncher
+//
+//  Created by Nicholas Riley on Tue Dec 18 2001.
+//  Copyright (c) 2001 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NJRIntegerFilter : NSNumberFormatter {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRIntegerFilter.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRIntegerFilter.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRIntegerFilter.m	(revision 365)
@@ -0,0 +1,29 @@
+//
+//  NJRIntegerFilter.m
+//  HostLauncher
+//
+//  Created by Nicholas Riley on Tue Dec 18 2001.
+//  Copyright (c) 2001 Nicholas Riley. All rights reserved.
+//
+
+// based on MyIntegerFilter, later VRIntegerNumberFilter
+// Copyright © 2001-2002 Bill Cheeseman. All rights reserved.
+
+#import "NJRIntegerFilter.h"
+
+@implementation NJRIntegerFilter
+
+// Input validation
+
+- (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error;
+{
+    // Override method to enable NSControl delegate method control:didFailToValidatePartialString:errorDescription: to reject invalid keypresses. Filters out keyboard input characters other than 0..9.
+    if ([[*partialStringPtr substringWithRange:NSMakeRange(origSelRange.location, (*proposedSelRangePtr).location - origSelRange.location)] rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet] options:NSLiteralSearch].location != NSNotFound) {
+        *error = NSLocalizedString(@"Input is not an integer.", @"Presented when user value not a numeric digit");
+        return NO; // Reject *partialStringPtr as typed, invoke delegate method for error handling
+    }
+    *error = nil;
+    return YES; // Accept *partialStringPtr as typed
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRIntervalField.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRIntervalField.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRIntervalField.h	(revision 365)
@@ -0,0 +1,22 @@
+//
+//  NJRIntervalField.h
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Dec 25 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+#import "NJRValidatingField.h"
+
+@interface NJRIntervalField : NJRValidatingField {
+    IBOutlet NSPopUpButton *intervalUnits;
+}
+
+- (NSTimeInterval)interval;
+- (BOOL)setInterval:(NSTimeInterval)interval; // returns false if out of range
+
+
+@end
Index: releases/Pester/1.1b6/Source/NJRIntervalField.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRIntervalField.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRIntervalField.m	(revision 365)
@@ -0,0 +1,82 @@
+//
+//  NJRIntervalField.m
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Dec 25 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRIntervalField.h"
+
+// XXX much implementation borrowed from DockCam, then factored; should replace DockCam interval selector with a NJRIntervalField at some point
+
+@implementation NJRIntervalField
+
+- (NSTimeInterval)interval;
+{
+    NSText *editor = [self currentEditor];
+    id obj = nil;
+    
+    if (editor != nil) {
+        NSString *stringValue = [editor string];
+        if (![[self formatter] getObjectValue: &obj forString: stringValue errorDescription: NULL])
+            return 0;
+    } else {
+        obj = self;
+    }
+    
+    return [obj intValue] * [[intervalUnits selectedItem] tag];
+}
+
+- (BOOL)setInterval:(NSTimeInterval)interval;
+{
+    // we assume that the tags are in ascending order in the array
+    NSEnumerator *e = [[intervalUnits itemArray] reverseObjectEnumerator];
+    NSMenuItem *i;
+    int multiplierTag;
+
+    while ( (i = [e nextObject]) != nil) {
+        multiplierTag = [i tag];
+        if (multiplierTag <= 0) continue;
+        if (((int)interval % multiplierTag) == 0) {
+            NSFormatter *formatter = [self formatter];
+            int intervalValue = interval / multiplierTag;
+            if (formatter != nil) {
+                id ignored;
+                if (![formatter getObjectValue: &ignored forString: [formatter stringForObjectValue: [NSNumber numberWithInt: intervalValue]] errorDescription: NULL]) return NO;
+            }
+            [self setIntValue: intervalValue];
+            [intervalUnits selectItem: i];
+            return YES;
+        }
+    }
+    return NO;
+}
+
+- (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)range replacementString:(NSString *)string;
+{
+    unsigned length = [string length];
+    if (length != 0) {
+        unichar c;
+        int tag = -1;
+        c = [string characterAtIndex: length - 1];
+        switch (c) {
+            case 's': case 'S': tag = 1; break;
+            case 'm': case 'M': tag = 60; break;
+            case 'h': case 'H': tag = 60 * 60; break;
+            case 'u': case 'U': tag = -2; break;
+            default: break;
+        }
+        if (tag != -1) {
+            int itemIndex = [intervalUnits indexOfItemWithTag: tag];
+            if (itemIndex != -1) {
+                [intervalUnits selectItemAtIndex: itemIndex];
+                [[intervalUnits menu] performActionForItemAtIndex: itemIndex];
+            }
+            if (tag < 0) return NO; // don't send update
+        }
+    }
+    return [super textView: textView shouldChangeTextInRange: range replacementString: string];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRNonCenteringWindow.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRNonCenteringWindow.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRNonCenteringWindow.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NJRNonCenteringWindow.h
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Apr 09 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRNonCenteringWindow : NSWindow {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRNonCenteringWindow.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRNonCenteringWindow.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRNonCenteringWindow.m	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  NJRNonCenteringWindow.m
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Apr 09 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRNonCenteringWindow.h"
+
+
+@implementation NJRNonCenteringWindow
+
+- (void)center;
+{
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJROffCenterPanel.h
===================================================================
--- releases/Pester/1.1b6/Source/NJROffCenterPanel.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJROffCenterPanel.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NJROffCenterPanel.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Feb 15 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJROffCenterPanel : NSPanel {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJROffCenterPanel.m
===================================================================
--- releases/Pester/1.1b6/Source/NJROffCenterPanel.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJROffCenterPanel.m	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  NJROffCenterPanel.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Feb 15 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJROffCenterPanel.h"
+
+
+@implementation NJROffCenterPanel
+
+- (void)center;
+{
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRQTMediaPopUpButton.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRQTMediaPopUpButton.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRQTMediaPopUpButton.h	(revision 365)
@@ -0,0 +1,35 @@
+//
+//  NJRQTMediaPopUpButton.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "BDAlias.h"
+
+extern NSString * const NJRQTMediaPopUpButtonMovieChangedNotification;
+
+@interface NJRQTMediaPopUpButton : NSPopUpButton {
+    IBOutlet NSMovieView *preview;
+    BOOL movieCanRepeat, movieHasAudio;
+    NSMenuItem *otherItem;
+    BDAlias *selectedAlias, *previousAlias;
+    NSMutableArray *recentMediaAliasData;
+    BOOL dragAccepted;
+    BOOL savedVolume;
+    float outputVolume;
+}
+
+- (BDAlias *)selectedAlias;
+- (void)setAlias:(BDAlias *)alias;
+- (float)outputVolume;
+- (void)setOutputVolume:(float)volume withPreview:(BOOL)doPreview;
+
+- (BOOL)canRepeat;
+- (BOOL)hasAudio;
+
+- (IBAction)stopSoundPreview:(id)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRQTMediaPopUpButton.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRQTMediaPopUpButton.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRQTMediaPopUpButton.m	(revision 365)
@@ -0,0 +1,611 @@
+//
+//  NJRQTMediaPopUpButton.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRQTMediaPopUpButton.h"
+#import "NJRSoundManager.h"
+#import "NSMovie-NJRExtensions.h"
+#import "NSMenuItem-NJRExtensions.h"
+
+#import <QTKit/QTKit.h>
+
+#include <QuickTime/Movies.h>
+#include <limits.h>
+
+static const int NJRQTMediaPopUpButtonMaxRecentItems = 10;
+
+NSString * const NJRQTMediaPopUpButtonMovieChangedNotification = @"NJRQTMediaPopUpButtonMovieChangedNotification";
+
+@interface NJRQTMediaPopUpButton (Private)
+- (void)_setPath:(NSString *)path;
+- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
+- (BOOL)_validateWithPreview:(BOOL)doPreview;
+- (void)_updateOutputVolume;
+- (void)_startSoundPreview;
+@end
+
+@implementation NJRQTMediaPopUpButton
+
+// XXX handle refreshing sound list on resume
+// XXX don't add icons on Puma, they look like ass
+// XXX launch preview on a separate thread (if movies take too long to load, they inhibit the interface responsiveness)
+
+// Recent media layout:
+// Most recent media are at TOP of menu (smaller item numbers, starting at [self indexOfItem: otherItem] + 1)
+// Most recent media are at END of array (larger indices)
+
+#pragma mark recently selected media tracking
+
+- (NSString *)_defaultKey;
+{
+    NSAssert([self tag] != 0, NSLocalizedString(@"Can't track recently selected media for popup with tag 0: please set a tag", "Assertion for QuickTime media popup button if tag is 0"));
+    return [NSString stringWithFormat: @"NJRQTMediaPopUpButtonMaxRecentItems tag %d", [self tag]];
+}
+
+- (void)_writeRecentMedia;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    [defaults setObject: recentMediaAliasData forKey: [self _defaultKey]];
+    [defaults synchronize];
+}
+
+- (NSMenuItem *)_addRecentMediaAtPath:(NSString *)path withAlias:(BDAlias *)alias;
+{
+    NSString *title = [[NSFileManager defaultManager] displayNameAtPath: path];
+    NSMenu *menu = [self menu];
+    NSMenuItem *item;
+    if (title == nil || path == nil) return nil;
+    item = [menu insertItemWithTitle: title action: @selector(_aliasSelected:) keyEquivalent: @"" atIndex: [menu indexOfItem: otherItem] + 1];
+    [item setTarget: self];
+    [item setRepresentedObject: alias];
+    [item setImageFromPath: path];
+    [recentMediaAliasData addObject: [alias aliasData]];
+    if ([recentMediaAliasData count] > NJRQTMediaPopUpButtonMaxRecentItems) {
+        [menu removeItemAtIndex: [menu numberOfItems] - 1];
+        [recentMediaAliasData removeObjectAtIndex: 0];
+    }
+    return item;
+}
+
+- (void)_addRecentMediaFromAliasesData:(NSArray *)aliasesData;
+{
+    NSEnumerator *e = [aliasesData objectEnumerator];
+    NSData *aliasData;
+    BDAlias *alias;
+    while ( (aliasData = [e nextObject]) != nil) {
+        if ( (alias = [[BDAlias alloc] initWithData: aliasData]) != nil) {
+            [self _addRecentMediaAtPath: [alias fullPath] withAlias: alias];
+            [alias release];
+        }
+    }
+}
+
+- (void)_validateRecentMedia;
+{
+    NSEnumerator *e = [recentMediaAliasData reverseObjectEnumerator];
+    NSData *aliasData;
+    NSMenuItem *item;
+    BDAlias *itemAlias;
+    int otherIndex = [self indexOfItem: otherItem];
+    int aliasDataCount = [recentMediaAliasData count];
+    int lastItemIndex = [self numberOfItems] - 1;
+    int recentItemCount = lastItemIndex - otherIndex;
+    int recentItemIndex = otherIndex;
+    NSAssert2(recentItemCount == aliasDataCount, @"Counted %d recent menu items, %d of alias data", recentItemCount, aliasDataCount);
+    while ( (aliasData = [e nextObject]) != nil) { // go BACKWARD through array while going DOWN menu
+        recentItemIndex++;
+        item = [self itemAtIndex: recentItemIndex];
+        itemAlias = [item representedObject];
+        if ([itemAlias aliasDataIsEqual: aliasData])
+            NSLog(@"item %d %@: %@", recentItemIndex, [item title], [itemAlias fullPath]);
+        else
+            NSLog(@"ITEM %d %@: %@ != aliasData %@", recentItemIndex, [item title], [itemAlias fullPath], [[BDAlias aliasWithData: aliasData] fullPath]);
+    }
+}
+
+#pragma mark initialize-release
+
+- (void)_setUp;
+{
+    NSMenu *menu = [self menu];
+    [self removeAllItems];
+    [menu setAutoenablesItems: NO];
+
+    NSMenuItem *item = [menu addItemWithTitle: @"Alert sound" action: @selector(_beepSelected:) keyEquivalent: @""];
+    [item setTarget: self];
+    [menu addItem: [NSMenuItem separatorItem]];
+
+    NSMutableArray *soundFolderPaths = [[NSMutableArray alloc] initWithCapacity: kLastDomainConstant - kSystemDomain + 1];
+    for (FSVolumeRefNum domain = kSystemDomain ; domain <= kLastDomainConstant ; domain++) {
+	OSStatus err;
+	FSRef fsr;
+	err = FSFindFolder(domain, kSystemSoundsFolderType, false, &fsr);
+	if (err != noErr) continue;
+
+	UInt8 path[PATH_MAX];
+	err = FSRefMakePath(&fsr, path, PATH_MAX);
+	if (err != noErr) continue;
+
+	CFStringRef pathString = CFStringCreateWithFileSystemRepresentation(NULL, (const char *)path);
+	if (pathString == NULL) continue;
+
+	[soundFolderPaths addObject: (NSString *)pathString];
+	CFRelease(pathString);
+    }
+    NSFileManager *fm = [NSFileManager defaultManager];
+    NSEnumerator *e = [soundFolderPaths objectEnumerator];
+    NSString *folderPath;
+    while ( (folderPath = [e nextObject]) != nil) {
+	if (![fm changeCurrentDirectoryPath: folderPath]) continue;
+
+	NSDirectoryEnumerator *de = [fm enumeratorAtPath: folderPath];
+	NSString *path;
+	while ( (path = [de nextObject]) != nil) {
+	    BOOL isDir;
+	    if (![fm fileExistsAtPath: path isDirectory: &isDir] || isDir) {
+		[de skipDescendents];
+		continue;
+	    }
+
+	    if (![QTMovie canInitWithFile: path]) continue;
+	    
+	    item = [menu addItemWithTitle: [fm displayNameAtPath: path]
+				   action: @selector(_systemSoundSelected:)
+			    keyEquivalent: @""];
+            [item setTarget: self];
+            [item setImageFromPath: path];
+	    path = [folderPath stringByAppendingPathComponent: path];
+            [item setRepresentedObject: path];
+	    [item setToolTip: path];
+        }
+    }
+    [soundFolderPaths release];
+    
+    if ([menu numberOfItems] == 2) {
+        item = [menu addItemWithTitle: NSLocalizedString(@"Can't locate alert sounds", "QuickTime media popup menu item surrogate for alert sound list if no sounds are found") action: nil keyEquivalent: @""];
+        [item setEnabled: NO];
+    }
+	 
+    [menu addItem: [NSMenuItem separatorItem]];
+    item = [menu addItemWithTitle: NSLocalizedString(@"Other...", "Media popup item to select another sound/movie/image") action: @selector(select:) keyEquivalent: @""];
+    [item setTarget: self];
+    otherItem = [item retain];
+
+    [self _validateWithPreview: NO];
+
+    recentMediaAliasData = [[NSMutableArray alloc] initWithCapacity: NJRQTMediaPopUpButtonMaxRecentItems + 1];
+    [self _addRecentMediaFromAliasesData: [[NSUserDefaults standardUserDefaults] arrayForKey: [self _defaultKey]]];
+    // [self _validateRecentMedia];
+
+    [self registerForDraggedTypes:
+        [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
+}
+
+- (id)initWithFrame:(NSRect)frame;
+{
+    if ( (self = [super initWithFrame: frame]) != nil) {
+        [self _setUp];
+    }
+    return self;
+}
+
+- (id)initWithCoder:(NSCoder *)coder;
+{
+    if ( (self = [super initWithCoder: coder]) != nil) {
+        [self _setUp];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [recentMediaAliasData release]; recentMediaAliasData = nil;
+    [otherItem release];
+    [selectedAlias release]; [previousAlias release];
+    [super dealloc];
+}
+
+#pragma mark accessing
+
+- (BDAlias *)selectedAlias;
+{
+    return selectedAlias;
+}
+
+- (void)_setAlias:(BDAlias *)alias;
+{
+    BDAlias *oldAlias = [selectedAlias retain];
+    [previousAlias release];
+    previousAlias = oldAlias;
+    if (selectedAlias != alias) {
+        [selectedAlias release];
+        selectedAlias = [alias retain];
+    }
+}
+
+- (void)setAlias:(BDAlias *)alias;
+{
+    [self _setAlias: alias];
+    if ([self _validateWithPreview: NO]) {
+        [self selectItem: [self _itemForAlias: selectedAlias]];
+    }
+}
+
+- (void)_setPath:(NSString *)path;
+{
+    [self _setAlias: [BDAlias aliasWithPath: path]];
+}
+
+- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
+{
+    if (alias == nil) return [self itemAtIndex: 0];
+
+    // [self _validateRecentMedia];
+    NSString *path = [alias fullPath];
+
+    // selected a system sound?
+    int itemIndex = [[self menu] indexOfItemWithRepresentedObject: path];
+    if (itemIndex != -1) {
+        // NSLog(@"_itemForAlias: selected system sound");
+        return [self itemAtIndex: itemIndex];
+    } else {
+        NSEnumerator *e = [recentMediaAliasData reverseObjectEnumerator];
+        NSData *aliasData;
+        NSMenuItem *item;
+        int recentIndex = 1;
+
+        while ( (aliasData = [e nextObject]) != nil) {
+            // selected a recently selected, non-system sound?
+            if ([alias aliasDataIsEqual: aliasData]) {
+                int otherIndex = [self indexOfItem: otherItem];
+                int menuIndex = recentIndex + otherIndex;
+                if (menuIndex == otherIndex + 1) return [self itemAtIndex: menuIndex]; // already at top
+                // remove item, add (at top) later
+                // NSLog(@"_itemForAlias removing item: idx %d + otherItemIdx %d + 1 = %d [%@]", recentIndex, otherIndex, menuIndex, [self itemAtIndex: menuIndex]);
+                [self removeItemAtIndex: menuIndex];
+                [recentMediaAliasData removeObjectAtIndex: [recentMediaAliasData count] - recentIndex];
+                break;
+            }
+            recentIndex++;
+        }
+
+        // create the item
+        item = [self _addRecentMediaAtPath: path withAlias: alias];
+        [self _writeRecentMedia];
+        return item;
+    }
+}
+
+- (BOOL)canRepeat;
+{
+    return movieCanRepeat;
+}
+
+- (BOOL)hasAudio;
+{
+    return movieHasAudio;
+}
+
+- (float)outputVolume;
+{
+    return outputVolume;
+}
+
+- (void)setOutputVolume:(float)volume withPreview:(BOOL)doPreview;
+{
+    if (![NJRSoundManager volumeIsNotMutedOrInvalid: volume]) return;
+    outputVolume = volume;
+    if (!doPreview) return;
+    // NSLog(@"setting volume to %f, preview movie %@", volume, [preview movie]);
+    if ([preview movie] == nil) {
+        [self _validateWithPreview: YES];
+    } else { // don't restart preview if already playing
+        [self _updateOutputVolume];
+    }
+}
+
+#pragma mark selected media validation
+
+- (void)_invalidateSelection;
+{
+    [self _setAlias: previousAlias];
+    [self selectItem: [self _itemForAlias: [self selectedAlias]]];
+    [[NSNotificationCenter defaultCenter] postNotificationName: NJRQTMediaPopUpButtonMovieChangedNotification object: self];
+}
+
+- (void)_updateOutputVolume;
+{
+    if ([preview movie] != nil && outputVolume != kNoVolume) {
+        if (!savedVolume && ![NJRSoundManager saveDefaultOutputVolume])
+            return;
+        savedVolume = YES;
+        [NJRSoundManager setDefaultOutputVolume: outputVolume];
+        if (![preview isPlaying]) [self _startSoundPreview];
+    }
+}
+
+- (void)_resetOutputVolume;
+{
+    [NJRSoundManager restoreSavedDefaultOutputVolumeIfCurrently: outputVolume];
+    savedVolume = NO;
+}
+
+- (void)_resetPreview;
+{
+    // if we donÕt do this after the runloop has finished, then we crash in MCIdle because itÕs expecting a movie and doesnÕt have one any more
+    [preview setMovie: nil]; // otherwise we get an extra runloop timer which uses a lot of CPU from +[NSMovieView _idleMovies]
+    // need to wait for runloop to stop movie, otherwise we're still playing at the time the volume changes
+    [self performSelector: @selector(_resetOutputVolume) withObject: nil afterDelay: 0];
+}
+
+void
+MovieStoppedCB(QTCallBack cb, long refCon)
+{
+    NJRQTMediaPopUpButton *self = (NJRQTMediaPopUpButton *)refCon;
+    // avoid multiple messages from multiple movie playback cycles in the same runloop
+    [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_resetPreview) object: nil];
+    [self performSelector: @selector(_resetPreview) withObject: nil afterDelay: 0];
+    DisposeCallBack(cb);
+}
+
+- (void)_startSoundPreview;
+{
+    Movie qtMovie = [[preview movie] QTMovie];
+    QTCallBack cbStop = NewCallBack(GetMovieTimeBase(qtMovie), callBackAtExtremes);
+    QTCallBackUPP cbStopUPP = NewQTCallBackUPP(MovieStoppedCB);
+    OSErr err = CallMeWhen(cbStop, cbStopUPP, (long)self, triggerAtStop, 0, 0);
+    if (err != noErr) {
+        NSLog(@"Can't register QuickTime stop timebase callback for preview: %ld", err);
+        DisposeCallBack(cbStop);
+    }
+    [preview start: self];
+}
+
+- (BOOL)_validateWithPreview:(BOOL)doPreview;
+{
+    [preview stop: self];
+    if (selectedAlias == nil) {
+        [preview setMovie: nil];
+        movieCanRepeat = YES;
+        movieHasAudio = NO; // XXX should be YES - this is broken, NSBeep() is asynchronous
+        if (doPreview) {
+            // XXX [self _updateOutputVolume];
+            NSBeep();
+            // XXX [self _resetOutputVolume];
+        }
+    } else {
+        NSMovie *movie = [[NSMovie alloc] initWithURL: [NSURL fileURLWithPath: [selectedAlias fullPath]] byReference: YES];
+        movieCanRepeat = ![movie isStatic];
+        if (movieHasAudio = [movie hasAudio]) {
+            [preview setMovie: doPreview ? movie : nil];
+            [self _updateOutputVolume];
+        } else {
+            [self _resetPreview];
+            doPreview = NO;
+            if (movie == nil) {
+                NSBeginAlertSheet(@"Format not recognized", nil, nil, nil, [self window], nil, nil, nil, nil, NSLocalizedString(@"The item you selected isn't a sound or movie recognized by QuickTime.  Please select a different item.", "Message displayed in alert sheet when media document is not recognized by QuickTime"));
+                [self _invalidateSelection];
+                return NO;
+            }
+            if (![movie hasAudio] && ![movie hasVideo]) {
+                NSBeginAlertSheet(@"No video or audio", nil, nil, nil, [self window], nil, nil, nil, nil, NSLocalizedString(@"'%@' contains neither audio nor video content playable by QuickTime.  Please select a different item.", "Message displayed in alert sheet when media document is readable, but has neither audio nor video tracks"), [[NSFileManager defaultManager] displayNameAtPath: [selectedAlias fullPath]]);
+                [self _invalidateSelection];
+                [movie release];
+                return NO;
+            }
+        }
+        if (doPreview) {
+            [self _startSoundPreview];
+        }
+        [movie release];
+    }
+    [[NSNotificationCenter defaultCenter] postNotificationName: NJRQTMediaPopUpButtonMovieChangedNotification object: self];
+    return YES;
+}
+
+#pragma mark actions
+
+- (IBAction)stopSoundPreview:(id)sender;
+{
+    [preview stop: self];
+    [self _resetPreview];
+}
+
+- (void)_beepSelected:(NSMenuItem *)sender;
+{
+    [self _setAlias: nil];
+    [self _validateWithPreview: YES];
+}
+
+- (void)_systemSoundSelected:(NSMenuItem *)sender;
+{
+    [self _setPath: [sender representedObject]];
+    if (![self _validateWithPreview: YES]) {
+        [[self menu] removeItem: sender];
+    }
+}
+
+- (void)_aliasSelected:(NSMenuItem *)sender;
+{
+    BDAlias *alias = [sender representedObject];
+    int index = [self indexOfItem: sender], otherIndex = [self indexOfItem: otherItem];
+    [self _setAlias: alias];
+    if (![self _validateWithPreview: YES]) {
+        [[self menu] removeItem: sender];
+    } else if (index > otherIndex + 1) { // move "other" item to top of list
+        int recentIndex = [recentMediaAliasData count] - index + otherIndex;
+        NSMenuItem *item = [[self itemAtIndex: index] retain];
+        NSData *data = [[recentMediaAliasData objectAtIndex: recentIndex] retain];
+        // [self _validateRecentMedia];
+        [self removeItemAtIndex: index];
+        [[self menu] insertItem: item atIndex: otherIndex + 1];
+        [self selectItem: item];
+        [item release];
+        NSAssert(recentIndex >= 0, @"Recent media index invalid");
+        // NSLog(@"_aliasSelected removing item %d - %d + %d = %d of recentMediaAliasData", [recentMediaAliasData count], index, otherIndex, recentIndex);
+        [recentMediaAliasData removeObjectAtIndex: recentIndex];
+        [recentMediaAliasData addObject: data];
+        [self _validateRecentMedia];
+        [data release];
+    } // else NSLog(@"_aliasSelected ...already at top");
+}
+
+- (IBAction)select:(id)sender;
+{
+    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+    NSString *path = [selectedAlias fullPath];
+    [openPanel setAllowsMultipleSelection: NO];
+    [openPanel setCanChooseDirectories: NO];
+    [openPanel setCanChooseFiles: YES];
+    [openPanel beginSheetForDirectory: [path stringByDeletingLastPathComponent]
+                                 file: [path lastPathComponent]
+                                types: nil // XXX fix for QuickTime!
+                       modalForWindow: [self window]
+                        modalDelegate: self
+                       didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
+                          contextInfo: nil];
+}
+
+- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
+{
+    [sheet close];
+
+    if (returnCode == NSOKButton) {
+        NSArray *files = [sheet filenames];
+        NSAssert1([files count] == 1, @"%d items returned, only one expected", [files count]);
+        [self _setPath: [files objectAtIndex: 0]];
+        if ([self _validateWithPreview: YES]) {
+            [self selectItem: [self _itemForAlias: selectedAlias]];
+        }
+    } else {
+        // "Other..." item is still selected, revert to previously selected item
+        // XXX issue with cancelling, top item in recent menu is sometimes duplicated!?
+        [self selectItem: [self _itemForAlias: selectedAlias]];
+    }
+    // [self _validateRecentMedia];
+}
+
+- (void)setEnabled:(BOOL)flag;
+{
+    [super setEnabled: flag];
+    if (flag) ; // XXX [self startSoundPreview: self]; // need to prohibit at startup
+    else [self stopSoundPreview: self];
+}
+
+#pragma mark drag feedback
+
+- (void)drawRect:(NSRect)rect;
+{
+    if (dragAccepted) {
+        NSWindow *window = [self window];
+        NSRect boundsRect = [self bounds];
+        BOOL isFirstResponder = ([window firstResponder] == self);
+        // focus ring and drag feedback interfere with one another
+        if (isFirstResponder) [window makeFirstResponder: window];
+        [super drawRect: rect];
+        [[NSColor selectedControlColor] set];
+        NSFrameRectWithWidthUsingOperation(NSInsetRect(boundsRect, 2, 2), 3, NSCompositeSourceIn);
+        if (isFirstResponder) [window makeFirstResponder: self];
+    } else {
+        [super drawRect: rect];
+    }
+}
+
+@end
+
+@implementation NJRQTMediaPopUpButton (NSDraggingDestination)
+
+- (BOOL)acceptsDragFrom:(id <NSDraggingInfo>)sender;
+{
+    NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
+    NSFileManager *fm = [NSFileManager defaultManager];
+    BOOL isDir;
+
+    if (url == nil || ![url isFileURL]) return NO;
+
+    if (![fm fileExistsAtPath: [url path] isDirectory: &isDir]) return NO;
+
+    if (isDir) return NO;
+    
+    return YES;
+}
+
+- (NSString *)_descriptionForDraggingInfo:(id <NSDraggingInfo>)sender;
+{
+    NSDragOperation mask = [sender draggingSourceOperationMask];
+    NSMutableString *s = [NSMutableString stringWithFormat: @"Drag seq %d source: %@",
+        [sender draggingSequenceNumber], [sender draggingSource]];
+    NSPasteboard *draggingPasteboard = [sender draggingPasteboard];
+    NSArray *types = [draggingPasteboard types];
+    NSEnumerator *e = [types objectEnumerator];
+    NSString *type;
+    [s appendString: @"\nDrag operations:"];
+    if (mask & NSDragOperationCopy) [s appendString: @" copy"];
+    if (mask & NSDragOperationLink) [s appendString: @" link"];
+    if (mask & NSDragOperationGeneric) [s appendString: @" generic"];
+    if (mask & NSDragOperationPrivate) [s appendString: @" private"];
+    if (mask & NSDragOperationMove) [s appendString: @" move"];
+    if (mask & NSDragOperationDelete) [s appendString: @" delete"];
+    if (mask & NSDragOperationEvery) [s appendString: @" every"];
+    if (mask & NSDragOperationNone) [s appendString: @" none"];
+    [s appendFormat: @"\nImage: %@ at %@", [sender draggedImage],
+        NSStringFromPoint([sender draggedImageLocation])];
+    [s appendFormat: @"\nDestination: %@ at %@", [sender draggingDestinationWindow],
+        NSStringFromPoint([sender draggingLocation])];
+    [s appendFormat: @"\nPasteboard: %@ types:", draggingPasteboard];
+    while ( (type = [e nextObject]) != nil) {
+        if ([type hasPrefix: @"CorePasteboardFlavorType 0x"]) {
+            const char *osTypeHex = [[type substringFromIndex: [type rangeOfString: @"0x" options: NSBackwardsSearch].location] lossyCString];
+            OSType osType;
+            sscanf(osTypeHex, "%lx", &osType);
+            [s appendFormat: @" '%4s'", &osType];
+        } else {
+            [s appendFormat: @" '%@'", type];
+        }
+    }
+    return s;
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
+{
+    if ([self acceptsDragFrom: sender] && [sender draggingSourceOperationMask] &
+        (NSDragOperationCopy | NSDragOperationLink)) {
+        dragAccepted = YES;
+        [self setNeedsDisplay: YES];
+        // NSLog(@"draggingEntered accept:\n%@", [self _descriptionForDraggingInfo: sender]);
+        return NSDragOperationLink;
+    }
+    return NSDragOperationNone;
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender;
+{
+    dragAccepted = NO;
+    [self setNeedsDisplay: YES];
+}
+
+- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
+{
+    dragAccepted = NO;
+    [self setNeedsDisplay: YES];
+    return [self acceptsDragFrom: sender];
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
+{
+    if ([sender draggingSource] != self) {
+        NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
+        if (url == nil) return NO;
+        [self _setPath: [url path]];
+        if ([self _validateWithPreview: YES]) {
+            [self selectItem: [self _itemForAlias: selectedAlias]];
+        }
+    }
+    return YES;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRReadMeController.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRReadMeController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRReadMeController.h	(revision 365)
@@ -0,0 +1,29 @@
+//
+//  NJRReadMeController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Feb 18 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRReadMeController : NSWindowController {
+    IBOutlet NSSplitView *splitter;
+    IBOutlet NSTableView *contents;
+    IBOutlet NSTextView *body;
+    IBOutlet NSBox *bodyBox;
+    IBOutlet NSTabView *progressTabs;
+    IBOutlet NSProgressIndicator *progress;
+    NSMutableArray *headings;
+    float maxHeadingWidth;
+    NSDictionary *headingAttributes;
+}
+
++ (NJRReadMeController *)readMeControllerWithRTFDocument:(NSString *)aPath;
+- (id)initWithRTFDocument:(NSString *)aPath;
+
+- (IBAction)contentsClicked:(NSTableView *)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRReadMeController.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRReadMeController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRReadMeController.m	(revision 365)
@@ -0,0 +1,319 @@
+//
+//  NJRReadMeController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Feb 18 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRReadMeController.h"
+#import "NSString-NJRExtensions.h"
+
+@interface NJRHelpContentsEntry : NSObject {
+    unsigned level;
+    NSString *description;
+    NSRange range;
+}
+
++ (NJRHelpContentsEntry *)headingLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
+- (id)initWithLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
+
+- (NSString *)description;
+- (NSRange)range;
+- (NSMutableString *)displayString;
+
+@end
+
+@implementation NJRHelpContentsEntry
+
++ (NJRHelpContentsEntry *)headingLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
+{
+    return [[[self alloc] initWithLevel: aLevel description: aDescription range: aRange] autorelease];
+}
+
+- (id)initWithLevel:(int)aLevel description:(NSString *)aDescription range:(NSRange)aRange;
+{
+    if ( (self = [super init]) != nil) {
+        level = aLevel;
+        description = [[aDescription stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] retain];
+        range = aRange;
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [description release];
+    [super dealloc];
+}
+
+- (NSString *)description;
+{
+    return [NSString stringWithFormat: @"%u > %@ %@", level, description, NSStringFromRange(range)];
+}
+
+- (NSRange)range;
+{
+    return range;
+}
+
+- (NSMutableString *)displayString;
+{
+    NSMutableString *s = [[NSMutableString alloc] init];
+
+    unsigned i;
+    for (i = 0 ; i < level ; i++) {
+        [s appendString: @"  "];
+    }
+    [s appendString: description];
+    return [s autorelease];
+}
+
+@end
+
+@interface NJRReadMeController (Private)
+- (void)_saveSplit;
+- (void)_restoreSplit;
+@end
+
+@implementation NJRReadMeController
+
+static NJRReadMeController *sharedController = nil;
+
++ (NJRReadMeController *)readMeControllerWithRTFDocument:(NSString *)aPath;
+{
+    return [[self alloc] initWithRTFDocument: aPath];
+}
+
+- (id)initWithRTFDocument:(NSString *)aPath;
+{
+    if (sharedController != nil) {
+        [self release];
+        [[sharedController window] makeKeyAndOrderFront: sharedController];
+        return sharedController;
+    }
+    if ( (self = [super initWithWindowNibName: @"Read Me"]) != nil) {
+        NSWindow *window = [self window];
+        [progress setIndeterminate: YES];
+        [progressTabs selectTabViewItemWithIdentifier: @"progress"];
+
+        [NSThread detachNewThreadSelector: @selector(readRTF:) toTarget: self withObject: aPath];
+        NSString *frameAutosaveName;
+        if ( (frameAutosaveName = [window frameAutosaveName]) == nil) {
+            // XXX workaround for bug in 10.1.5–10.2.4 (at least): autosave name set in IB doesn't show up
+            [self setWindowFrameAutosaveName: @"Read Me"];
+            frameAutosaveName = [window frameAutosaveName];
+        }
+        if (frameAutosaveName == nil || ![window setFrameUsingName: frameAutosaveName])
+            [window center];
+        [self _restoreSplit];
+
+        [window makeKeyAndOrderFront: self];
+        sharedController = self;
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [headings release];
+    [headingAttributes release];
+    if (sharedController == self) sharedController = nil;
+    [super dealloc];
+}
+
+- (void)readRTF:(NSString *)aPath;
+{
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSAttributedString *readMe = [[NSAttributedString alloc] initWithPath: aPath documentAttributes: nil];
+    if (readMe == nil) {
+    [body insertText: [NSString stringWithFormat: NSLocalizedString(@"Can't read document '%@'", "Message displayed in in place of read me contents when read me file could not be read; %@ replaced by last path component of filename, e.g. 'Read Me.rtfd'"), [aPath lastPathComponent]]];
+    } else {
+        NSTextStorage *storage = [body textStorage];
+        [storage setAttributedString: readMe];
+        [readMe release]; readMe = nil;
+
+        unsigned int length = [storage length];
+        [progress setIndeterminate: NO];
+        [progress setMaxValue: length];
+        NSRange effectiveRange = NSMakeRange(0, 0);
+        unsigned int chunkLength = 0;
+        NSFont *fontAttr = nil;
+        NSString *fontName = nil; float fontSize = 0;
+        NSString *heading = nil;
+
+        headings = [[NSMutableArray alloc] init];
+        
+        // XXX need this instead? (id)attribute:(NSString *)attrName atIndex:(unsigned int)location longestEffectiveRange:(NSRangePointer)range inRange:(NSRange)rangeLimit;
+        while (NSMaxRange(effectiveRange) < length) {
+            fontAttr = (NSFont *)[storage attribute: NSFontAttributeName
+                                            atIndex: NSMaxRange(effectiveRange) effectiveRange: &effectiveRange];
+            if (effectiveRange.length < 3) continue;
+            fontName = [fontAttr fontName]; fontSize = [fontAttr pointSize];
+            chunkLength = effectiveRange.length;
+            if ([fontName isEqualToString: @"GillSans-Bold"]) {
+                heading = [[storage attributedSubstringFromRange: effectiveRange] string];
+                if (fontSize == 24)
+                    [headings addObject: [NJRHelpContentsEntry headingLevel: 0 description: [[storage attributedSubstringFromRange: effectiveRange] string] range: effectiveRange]];
+                else
+                    [headings addObject:
+                        [NJRHelpContentsEntry headingLevel: (fontSize == 14) ? 1: 2 description: heading range: effectiveRange]];
+            }
+            if (fontSize != 14) continue;
+            if ([fontName isEqualToString: @"HoeflerText-Black"]) {
+                heading = [[storage attributedSubstringFromRange: NSMakeRange(effectiveRange.location, chunkLength + 1)] string];
+                switch ([heading characterAtIndex: chunkLength]) {
+                    case ':':
+                    case ',':
+                        break;
+                    case ' ':
+                        switch ([heading characterAtIndex: chunkLength - 1]) {
+                            case ':':
+                            case ',':
+                                chunkLength--;
+                                break;
+                            default:
+                                continue;
+                        }
+                        break;
+                    default:
+                        continue;
+                }
+                [headings addObject: [NJRHelpContentsEntry headingLevel: 2 description: [heading substringToIndex: chunkLength] range: NSMakeRange(effectiveRange.location, chunkLength)]];
+            }
+            [progress setDoubleValue: NSMaxRange(effectiveRange)];
+        }
+    }
+    headingAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: [[[contents tableColumnWithIdentifier: @"heading"] dataCell] font], NSFontAttributeName, nil];
+    NSEnumerator *e = [headings objectEnumerator];
+    NSString *s;
+    float width;
+    maxHeadingWidth = 0;
+    while ( (s = [(NJRHelpContentsEntry *)[e nextObject] displayString]) != nil) {
+        width = [s sizeWithAttributes: headingAttributes].width;
+        if (width > maxHeadingWidth) maxHeadingWidth = width;
+    }
+    maxHeadingWidth += 25; // account for scroll bar and frame
+    [self _saveSplit];
+    [self _restoreSplit];
+    
+    [contents reloadData];
+    [progressTabs selectTabViewItemWithIdentifier: @"completed"];
+    [pool release];
+}
+
+- (void)_saveSplit;
+{
+    NSString *frameAutosaveName;
+    if ( (frameAutosaveName = [[self window] frameAutosaveName]) != nil) {
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        [defaults setFloat: maxHeadingWidth forKey:
+            [frameAutosaveName stringByAppendingString: @" maximum contents heading width"]];
+        NSBox *contentsBox = [[splitter subviews] objectAtIndex: 0];
+        [defaults setBool: [splitter isSubviewCollapsed: contentsBox] forKey:
+                [frameAutosaveName stringByAppendingString: @" contents are collapsed"]];
+        [defaults synchronize];
+    }
+}
+
+- (void)_restoreSplit;
+{
+    NSString *frameAutosaveName;
+    BOOL contentsCollapsed = NO;
+    if ( (frameAutosaveName = [[self window] frameAutosaveName]) != nil) {
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        contentsCollapsed = [defaults boolForKey:
+            [frameAutosaveName stringByAppendingString: @" contents are collapsed"]];
+        if (maxHeadingWidth == 0) { // don't want to restore 0 if we can't write to defaults
+            maxHeadingWidth = [defaults floatForKey:
+                [frameAutosaveName stringByAppendingString: @" maximum contents heading width"]];
+        }
+    }
+    NSBox *contentsBox = [[splitter subviews] objectAtIndex: 0];
+    if ([splitter isSubviewCollapsed: contentsBox] ||
+        (maxHeadingWidth == 0 && !contentsCollapsed)) return;
+    if (contentsCollapsed) {
+        [splitter performSelectorOnMainThread: @selector(collapseSubview:) withObject: contentsBox waitUntilDone: YES];
+        return;
+    }
+    NSSize contentsSize = [contentsBox frame].size;
+    float widthDiff = contentsSize.width - maxHeadingWidth;
+    if (widthDiff < 1) return;
+    NSSize bodySize = [bodyBox frame].size;
+    bodySize.width += widthDiff;
+    contentsSize.width -= widthDiff;
+    [contentsBox setFrameSize: contentsSize];
+    [bodyBox setFrameSize: bodySize];
+    [splitter performSelectorOnMainThread: @selector(adjustSubviews) withObject: nil waitUntilDone: NO];
+}
+
+- (IBAction)contentsClicked:(NSTableView *)sender;
+{
+    int row = [sender clickedRow];
+    if (row == -1) return;
+    NSRange range = [[headings objectAtIndex: row] range];
+    [body setSelectedRange: range];
+    [body scrollRangeToVisible: range];
+}
+
+@end
+
+@implementation NJRReadMeController (NSTableDataSource)
+
+- (int)numberOfRowsInTableView:(NSTableView *)tableView;
+{
+    return [headings count];
+}
+
+// need to enable column resizing for this to work, otherwise we never get called again
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
+{
+    NSMutableString *s = [[headings objectAtIndex: row] displayString];
+    [s truncateToWidth: [tableView frameOfCellAtColumn: 0 row: row].size.width by: NSLineBreakByTruncatingTail withAttributes: headingAttributes];
+    return s;
+}
+
+@end
+
+@implementation NJRReadMeController (NSSplitViewDelegate)
+
+- (float)splitView:(NSSplitView *)sender constrainMinCoordinate:(float)proposedCoord ofSubviewAt:(int)offset;
+{
+    return MAX(proposedCoord, 80);
+}
+
+- (float)splitView:(NSSplitView *)sender constrainMaxCoordinate:(float)proposedCoord ofSubviewAt:(int)offset;
+{
+    return MIN(proposedCoord, maxHeadingWidth);
+}
+
+- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize;
+{
+    NSSize newSize = [sender frame].size;
+    NSBox *contentsBox = [[splitter subviews] objectAtIndex: 0];
+    NSSize contentsSize = [contentsBox frame].size;
+    NSSize bodySize = [bodyBox frame].size;
+    contentsSize.height += newSize.height - oldSize.height;
+    [contentsBox setFrameSize: contentsSize];
+    bodySize.width += newSize.width - oldSize.width;
+    bodySize.height += newSize.height - oldSize.height;
+    [bodyBox setFrameSize: bodySize];
+    [sender adjustSubviews];
+}
+
+- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview;
+{
+    return [contents isDescendantOf: subview];
+}
+
+@end
+
+@implementation NJRReadMeController (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    [self _saveSplit];
+    [self autorelease];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRScrollView.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRScrollView.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRScrollView.h	(revision 365)
@@ -0,0 +1,17 @@
+//
+//  NJRScrollView.h
+//  HostLauncher
+//
+//  Created by nicholas on Tue Oct 30 2001.
+//  Copyright (c) 2001 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRScrollView : NSScrollView {
+    BOOL shouldDrawFocusRing;
+    NSResponder *lastResp;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRScrollView.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRScrollView.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRScrollView.m	(revision 365)
@@ -0,0 +1,38 @@
+//
+//  NJRScrollView.m
+//  HostLauncher
+//
+//  Created by nicholas on Tue Oct 30 2001.
+//  Copyright (c) 2001 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRScrollView.h"
+
+
+@implementation NJRScrollView
+
+- (BOOL)needsDisplay;
+{
+    NSResponder *resp = nil;
+    if ([[self window] isKeyWindow]) {
+        resp = [[self window] firstResponder];
+        if (resp == lastResp) return [super needsDisplay];
+    } else if (lastResp == nil) {
+        return [super needsDisplay];
+    }
+    shouldDrawFocusRing = (resp != nil && [resp isKindOfClass: [NSView class]] &&
+                           [(NSView *)resp isDescendantOf: self]); // [sic]
+    lastResp = resp;
+    [self setKeyboardFocusRingNeedsDisplayInRect: [self bounds]];
+    return YES;
+}
+
+- (void)drawRect:(NSRect)rect {
+    [super drawRect: rect];
+    if (shouldDrawFocusRing) {
+        NSSetFocusRingStyle(NSFocusRingOnly);
+        NSRectFill([self bounds]);
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRSoundManager.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRSoundManager.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRSoundManager.h	(revision 365)
@@ -0,0 +1,24 @@
+//
+//  NJRSoundManager.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 08 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NJRSoundManager : NSObject {
+
+}
+
++ (BOOL)getDefaultOutputVolume:(float *)volume;
++ (void)setDefaultOutputVolume:(float)volume;
++ (BOOL)volumeIsNotMutedOrInvalid:(float)volume;
+
++ (BOOL)saveDefaultOutputVolume;
++ (void)restoreSavedDefaultOutputVolume;
++ (void)restoreSavedDefaultOutputVolumeIfCurrently:(float)volume;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRSoundManager.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRSoundManager.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRSoundManager.m	(revision 365)
@@ -0,0 +1,103 @@
+//
+//  NJRSoundManager.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 08 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRSoundManager.h"
+#import <CoreAudio/CoreAudio.h>
+
+@implementation NJRSoundManager
+
+static const UInt32 kLeftChannel = 0, kRightChannel = 1;
+
+static AudioDeviceID deviceID;
+static UInt32 stereoChannels[2];
+static float channelVolume[2];
+static float savedChannelVolume[2] = {-1, -1};
+
++ (BOOL)volumeIsNotMutedOrInvalid:(float)volume;
+{
+    return (volume > 0 && volume <= 1);
+}
+
++ (BOOL)_getDefaultOutputDevice;
+{
+    UInt32 propertySize;
+    OSStatus err;
+
+    propertySize = sizeof(deviceID);
+    err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultSystemOutputDevice, &propertySize, &deviceID);
+    if (err != noErr) return NO;
+
+    propertySize = sizeof(stereoChannels);
+    err = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyPreferredChannelsForStereo, &propertySize, &stereoChannels);
+    if (err != noErr) return NO;
+    return YES;
+}
+
++ (BOOL)getDefaultOutputVolume:(float *)volume;
+{
+    UInt32 propertySize;
+    OSStatus err;
+
+    if (![self _getDefaultOutputDevice]) return NO;
+    
+    // read the current volume scalar settings [0...1]
+    propertySize = sizeof(float);
+    err = AudioDeviceGetProperty(deviceID, stereoChannels[kLeftChannel], false, kAudioDevicePropertyVolumeScalar, &propertySize, &channelVolume[kLeftChannel]);
+    if (err != noErr) return NO;
+    err = AudioDeviceGetProperty(deviceID, stereoChannels[kRightChannel], false, kAudioDevicePropertyVolumeScalar, &propertySize, &channelVolume[kRightChannel]);
+    if (err != noErr) return NO;
+    if (volume != NULL) *volume = MAX(channelVolume[kLeftChannel], channelVolume[kRightChannel]);
+    return YES;
+}
+
++ (void)_updateChannelVolume;
+{
+    UInt32 propertySize = sizeof(channelVolume[kLeftChannel]);
+    // ignore errors
+    AudioDeviceSetProperty(deviceID, NULL, stereoChannels[kLeftChannel], false, kAudioDevicePropertyVolumeScalar, propertySize, &channelVolume[kLeftChannel]);
+    AudioDeviceSetProperty(deviceID, NULL, stereoChannels[kRightChannel], false, kAudioDevicePropertyVolumeScalar, propertySize, &channelVolume[kRightChannel]);
+}
+
++ (BOOL)saveDefaultOutputVolume;
+{
+    if (![self getDefaultOutputVolume: NULL]) return NO;
+    savedChannelVolume[kLeftChannel] = channelVolume[kLeftChannel];
+    savedChannelVolume[kRightChannel] = channelVolume[kRightChannel];
+    // NSLog(@"saving channel volume {%f, %f}", channelVolume[kLeftChannel],channelVolume[kRightChannel]);
+    return YES;
+}
+
++ (void)setDefaultOutputVolume:(float)volume;
+{
+    if (![self _getDefaultOutputDevice]) return;
+
+    channelVolume[kLeftChannel] = volume;
+    channelVolume[kRightChannel] = volume;
+    [self _updateChannelVolume];
+}
+
++ (void)restoreSavedDefaultOutputVolume;
+{
+    if (savedChannelVolume[kLeftChannel] < 0) return;
+    // NSLog(@"restoring saved channel volume");
+    channelVolume[kLeftChannel] = savedChannelVolume[kLeftChannel];
+    channelVolume[kRightChannel] = savedChannelVolume[kRightChannel];
+    savedChannelVolume[kLeftChannel] = -1;
+    savedChannelVolume[kRightChannel] = -1;
+    [self _updateChannelVolume];
+}
+
++ (void)restoreSavedDefaultOutputVolumeIfCurrently:(float)volume;
+{
+    float currentVolume;
+    if ([self getDefaultOutputVolume: &currentVolume] && abs(volume - currentVolume) < 0.05) {
+        [self restoreSavedDefaultOutputVolume];
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRSplitView.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRSplitView.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRSplitView.h	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  NJRSplitView.h
+//  Pester
+//
+//  Created by Nicholas Riley on Thu Feb 20 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRSplitView : NSSplitView {
+    float expandedWidth;
+}
+
+- (void)collapseSubview:(NSView *)subview;
+- (void)expandSubview:(NSView *)subview;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRSplitView.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRSplitView.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRSplitView.m	(revision 365)
@@ -0,0 +1,97 @@
+//
+//  NJRSplitView.m
+//  Pester
+//
+//  Created by Nicholas Riley on Thu Feb 20 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRSplitView.h"
+
+
+@implementation NJRSplitView
+
+// inspiration (but very little code remaining) from <http://cocoa.mamasam.com/COCOADEV/2003/02/1/55911.php>
+// XXX need to generalize: assumes the split is vertical, and you want to collapse a previous view
+// XXX shouldn't set width to 0 as it can destroy layouts, but we have no other choice
+
+- (void)collapseSubview:(NSView *)subview;
+{
+    NSSize size = [subview frame].size;
+    NSAssert([self isVertical] && size.width > 1, @"unsupported configuration");
+    expandedWidth = size.width;
+    size.width = 0;
+    [subview setFrameSize: size];
+    [self setNeedsDisplay: YES];
+}
+
+// XXX this is a horrendous hack to work around NSSplitView bugs, and leaks memory
+- (void)expandSubview:(NSView *)subview;
+{
+    NSArray *subviews = [self subviews];
+    NSAssert([subviews count] == 2, @"unsupported configuration");
+    NSBox *leftView = [subviews objectAtIndex: 0];
+    NSView *rightView = [subviews objectAtIndex: 1];
+    NSRect leftFrame = [leftView frame], rightFrame = [rightView frame];
+    NSAssert([self isVertical] && subview == leftView, @"unsupported configuration");
+    if (expandedWidth == 0) {
+        expandedWidth = (rightFrame.size.width - [self dividerThickness]) / 2;
+    }
+    if ([[self delegate] respondsToSelector: @selector(splitView:constrainMaxCoordinate:ofSubviewAt:)]) {
+        expandedWidth = [[self delegate] splitView: self constrainMaxCoordinate: expandedWidth ofSubviewAt: 0];
+    }
+    // begin hack
+    NSBox *newLeftView = [[NSBox alloc] initWithFrame: leftFrame];
+    [newLeftView setBorderType: [leftView borderType]];
+    [newLeftView setTitlePosition: [leftView titlePosition]];
+    [newLeftView setBoxType: [leftView boxType]];
+    [newLeftView setAutoresizingMask: [leftView autoresizingMask]];
+    [newLeftView setContentViewMargins: [leftView contentViewMargins]];
+    NSView *leftContentView = [leftView contentView];
+    [leftContentView retain];
+    [leftContentView removeFromSuperviewWithoutNeedingDisplay];
+    [newLeftView setContentView: leftContentView];
+    [leftContentView release];
+    leftFrame.origin = NSZeroPoint;
+    rightFrame.origin.x += expandedWidth;
+    leftFrame.size.width = expandedWidth;
+    rightFrame.size.width -= expandedWidth;
+    [leftView removeFromSuperviewWithoutNeedingDisplay];
+    [rightView retain];
+    [rightView removeFromSuperviewWithoutNeedingDisplay];
+    [newLeftView setFrame: leftFrame];
+    [rightView setFrame: rightFrame];
+    [self addSubview: newLeftView];
+    [self addSubview: rightView];
+    [newLeftView release];
+    [rightView release];
+    // end hack
+    [self setNeedsDisplay: YES];
+}
+
+- (void)mouseDown:(NSEvent *)event;
+{
+    if ([event clickCount] == 2) {
+        NSPoint location = [self convertPoint: [event locationInWindow] fromView: nil];
+        NSArray *subviews = [self subviews];
+        NSView *leftView = [subviews objectAtIndex: 0];
+        if (NSPointInRect(location, [leftView frame]) ||
+            NSPointInRect(location, [[subviews objectAtIndex: 1] frame])) return;
+        if ([self isSubviewCollapsed: leftView]) {
+            [self expandSubview: leftView];
+        } else {
+            [self collapseSubview: leftView];
+        }
+    }
+    [super mouseDown: event];
+}
+
+- (BOOL)isSubviewCollapsed:(NSView *)subview;
+{
+    NSRect frame = [subview frame];
+    if (frame.origin.x == 1e6) return YES;
+    if (frame.size.width <= 1) return YES;
+    return NO;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRTableDelegate.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRTableDelegate.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRTableDelegate.h	(revision 365)
@@ -0,0 +1,27 @@
+//
+//  NJRTableDelegate.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Oct 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "NJRTableView.h"
+
+
+@interface NJRTableDelegate : NSObject {
+    IBOutlet NJRTableView *tableView;
+    NSTableColumn *sortingColumn;
+    NSString *sortingKey;
+    BOOL sortDescending;
+
+    NSMutableArray *reorderedData;
+}
+
+- (NSMutableArray *)reorderedDataForData:(NSArray *)data;
+
+- (NSSet *)selectedItems;
+- (void)selectItems:(NSSet *)inSelectedItems;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRTableDelegate.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRTableDelegate.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRTableDelegate.m	(revision 365)
@@ -0,0 +1,286 @@
+//
+//  NJRTableDelegate.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Oct 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRTableDelegate.h"
+#import "NSTableView-NJRExtensions.h"
+
+#pragma mark sorting support
+
+typedef struct { NSString *key; BOOL descending; } SortContext;
+
+// Sort array of itemNums, by looking up the itemNum in the dictionary of objects.
+// based on code of Ondra Cada <ocs@ocs.cz> on cocoa-dev list
+
+int ORDER_BY_CONTEXT(id left, id right, void *ctxt) {
+    SortContext *context = (SortContext *)ctxt;
+    int order = 0;
+    id key = context->key;
+    if (0 != key) {
+        id first, second;	// the actual objects to compare
+
+        if (context->descending) {
+            first  = [right valueForKey: key];
+            second = [left  valueForKey: key];
+        } else {
+            first  = [left  valueForKey: key];
+            second = [right valueForKey: key];
+        }
+
+        if ([first respondsToSelector: @selector(caseInsensitiveCompare:)]) {
+            order = [first caseInsensitiveCompare:second];
+        } else { // sort numbers or dates
+            order = [(NSNumber *)first compare:second];
+        }
+    }
+    return order;
+}
+
+@interface NJRTableDelegate (Private)
+
+- (void)_positionTypeSelectDisplay;
+- (void)_sortByColumn:(NSTableColumn *)inTableColumn;
+
+@end
+
+@implementation NJRTableDelegate
+
+#pragma mark initialize-release
+
+- (void)awakeFromNib;
+{
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_positionTypeSelectDisplay) name: NSViewFrameDidChangeNotification object: tableView];
+}
+
+- (void)dealloc
+{
+    [[NSNotificationCenter defaultCenter] removeObserver: self];
+    [sortingColumn release];
+    [sortingKey release];
+    [reorderedData release];
+    [super dealloc];
+}
+
+#pragma mark accessing
+
+- (void)setSortingColumn:(NSTableColumn *)inNewValue;
+{
+    [inNewValue retain];
+    [sortingColumn release];
+    sortingColumn = inNewValue;
+}
+
+- (void)setSortingKey:(NSString *)inNewValue;
+{
+    [inNewValue retain];
+    [sortingKey release];
+    sortingKey = inNewValue;
+}
+
+#pragma mark sorting
+
+- (NSString *)_sortContextDefaultKey;
+{
+    NSString *autosaveName = [tableView autosaveName];
+    if (autosaveName != nil)
+        return [NSString stringWithFormat: @"NJRTableDelegate SortContext %@", autosaveName];
+    else
+        return nil;
+}
+
+- (void)_sortData;
+{
+    SortContext ctxt = { sortingKey, sortDescending };
+    NSString *sortContextKey = [self _sortContextDefaultKey];
+
+    if (sortContextKey != nil) {
+        [[NSUserDefaults standardUserDefaults] setObject:
+            [NSDictionary dictionaryWithObjectsAndKeys: sortingKey, @"sortingKey", [NSNumber numberWithBool: sortDescending], @"sortDescending", nil]
+                                                    forKey: [self _sortContextDefaultKey]];
+    }
+    
+    // sort the NSMutableArray
+    [reorderedData sortUsingFunction: ORDER_BY_CONTEXT context: &ctxt];
+    [tableView reloadData];
+}
+
+- (void)_sortByColumn:(NSTableColumn *)inTableColumn;
+{
+    NSSet *oldSelection = [self selectedItems];
+    if (sortingColumn == inTableColumn) {
+        // User clicked same column, change sort order
+        sortDescending = !sortDescending;
+        // Possible optimization: Don't actually re-sort if you just change the sorting direction; instead, just display either the nth item or the (count-1-n)th item depending on ascending/descending.)
+    } else {
+        // User clicked new column, change old/new column headers, save new sorting column, and re-sort the array.
+        if (sortingColumn != nil) {
+            [tableView setIndicatorImage: nil inTableColumn: sortingColumn];
+            sortDescending = NO; // on initial sort, preserve previous sort order
+        }
+        [self setSortingKey: [inTableColumn identifier]];
+        [self setSortingColumn: inTableColumn];
+        [tableView setHighlightedTableColumn: inTableColumn];
+    }
+    [tableView setIndicatorImage: (sortDescending ? [NSTableView descendingSortIndicator] : [NSTableView ascendingSortIndicator]) inTableColumn: inTableColumn];
+    [self _positionTypeSelectDisplay];
+    // Actually sort the data
+    [self _sortData];
+    [self selectItems: oldSelection];
+}
+
+- (void)_initialSortData;
+{
+    NSString *sortContextKey = [self _sortContextDefaultKey];
+    NSDictionary *sortContext;
+    NSString *key;
+    NSTableColumn *column;
+
+    if (sortContextKey == nil) goto noContext;
+    if ( (sortContext = [[NSUserDefaults standardUserDefaults] dictionaryForKey: sortContextKey]) == nil) goto noContext;
+    if ( (key = [sortContext objectForKey: @"sortingKey"]) == nil) goto noContext;
+    if ( (column = [tableView tableColumnWithIdentifier: key]) == nil) goto noContext;
+    sortDescending = [[sortContext objectForKey: @"sortDescending"] boolValue];
+    [self _sortByColumn: column];
+    return;
+    
+noContext:
+    sortDescending = NO;
+    [self _sortByColumn: [[tableView tableColumns] objectAtIndex: 0]];
+}
+
+- (NSMutableArray *)reorderedDataForData:(NSArray *)data;
+{
+    if (reorderedData == nil) {
+        reorderedData = [data mutableCopy];
+        [self _initialSortData];
+    } else {
+        NSSet *oldSelection = [self selectedItems];
+        [reorderedData release]; reorderedData = nil;
+        reorderedData = [data mutableCopy];
+        [self _sortData];
+        [self selectItems: oldSelection];
+    }
+    return reorderedData;
+}
+
+#pragma mark type selection
+
+- (void)_positionTypeSelectDisplay;
+{
+    [tableView resetTypeSelect]; // avoid extraneous matching
+    if ([tableView typeSelectDisplay] != nil && sortingColumn != nil) {
+        NSControl *typeSelectControl = [tableView typeSelectDisplay];
+        if ([typeSelectControl isKindOfClass: [NSControl class]]) {
+            NSView *superview = [typeSelectControl superview];
+            NSRect columnRect = [superview convertRect: [tableView rectOfColumn: [tableView columnWithIdentifier: sortingKey]] fromView: tableView];
+            // XXX support horizontal scroll bar/clipping (not for Pester, but eventually)
+            // NSRect tableScrollFrame = [[tableView enclosingScrollView] frame];
+            NSRect selectFrame = [typeSelectControl frame];
+            [superview setNeedsDisplayInRect: selectFrame]; // fix artifacts caused by moving view
+            selectFrame.origin.x = columnRect.origin.x;
+            selectFrame.size.width = columnRect.size.width;
+            [typeSelectControl setAlignment: [[sortingColumn dataCell] alignment]];
+            [typeSelectControl setFrame: selectFrame];
+        }
+    }
+}
+
+#pragma mark saving/restoring selection
+
+- (NSSet *)selectedItems;
+{
+    NSMutableSet *result = [NSMutableSet set];
+    NSEnumerator *e = [tableView selectedRowEnumerator];
+    NSNumber *rowNum;
+
+    while ( (rowNum = [e nextObject]) != nil) {
+        id item = [reorderedData objectAtIndex: [rowNum intValue]];
+        [result addObject: item];
+    }
+    return result;
+}
+
+- (void)selectItems:(NSSet *)inSelectedItems;
+{
+    NSEnumerator *e = [inSelectedItems objectEnumerator];
+    id item;
+    int savedLastRow = 0;
+
+    [tableView deselectAll: nil];
+
+    while ( (item = [e nextObject]) != nil ) {
+        int row = [reorderedData indexOfObjectIdenticalTo: item];
+        if (row != NSNotFound) {
+            [tableView selectRow: row byExtendingSelection: YES];
+            savedLastRow = row;
+        }
+    }
+    [tableView scrollRowToVisible: savedLastRow];
+}
+
+@end
+
+@implementation NJRTableDelegate (NSTableViewDelegate)
+
+- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)inTableColumn
+{
+    [[tableView window] makeFirstResponder: aTableView];
+    [self _sortByColumn: inTableColumn];
+}
+
+- (void)tableViewColumnDidResize:(NSNotification *)notification;
+{
+    [self _positionTypeSelectDisplay];
+}
+
+- (void)tableViewColumnDidMove:(NSNotification *)notification;
+{
+    [self _positionTypeSelectDisplay];
+}
+
+- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc row:(int)rowIndex mouseLocation:(NSPoint)mouseLocation;
+{
+    id dataSource = [aTableView dataSource];
+    
+    if ([dataSource respondsToSelector: @selector(toolTipForRow:)])
+	return [dataSource toolTipForRow: rowIndex];
+    
+    return nil;
+}
+
+@end
+
+@implementation NJRTableDelegate (NJRTableViewDelegate)
+
+- (void)tableView:(NSTableView *)aTableView selectRowMatchingString:(NSString *)matchString;
+{
+    // Look for a highlighted column, presuming we are sorted by that column, and search its values.
+    NSTableColumn *col = [aTableView highlightedTableColumn];
+    id dataSource = [aTableView dataSource];
+    int i, rowCount = [reorderedData count];
+    if (nil == col) return;
+    if (sortDescending) {
+        for ( i = rowCount - 1 ; i >= 0 ; i-- ) {
+            NSComparisonResult order = [matchString caseInsensitiveCompare:
+                [dataSource tableView: aTableView objectValueForTableColumn: col row: i]];
+            if (order != NSOrderedDescending) break;
+        }
+        if (i < 0) i = 0;
+    } else {
+        for ( i = 0 ; i < rowCount ; i++ ) {
+            NSComparisonResult order = [matchString caseInsensitiveCompare:
+                [dataSource tableView: aTableView objectValueForTableColumn: col row: i]];
+            if (order != NSOrderedDescending) break;
+        }
+        if (i >= rowCount) i = rowCount - 1;
+    }
+    // Now select row i -- either the one we found, or the first/last row if not found.
+    [aTableView selectRow: i byExtendingSelection: NO];
+    [aTableView scrollRowToVisible: i];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRTableView.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRTableView.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRTableView.h	(revision 365)
@@ -0,0 +1,33 @@
+//
+//  NJRTableView.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Nov 17 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRTableView : NSTableView {
+    NSMutableString *typed;
+    IBOutlet id typeSelectDisplay;
+}
+
+- (id)typeSelectDisplay;
+- (void)resetTypeSelect;
+
+@end
+
+@interface NSObject (NJRTableViewDelegate)
+
+- (void)tableView:(NSTableView *)aTableView selectRowMatchingString:(NSString *)matchString;
+
+@end
+
+@interface NSObject (NJRTableViewDataSource)
+
+- (void)removeSelectedRowsFromTableView:(NSTableView *)tableView;
+- (NSString *)toolTipForRow:(int)rowIndex;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRTableView.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRTableView.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRTableView.m	(revision 365)
@@ -0,0 +1,105 @@
+//
+//  NJRTableView.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Nov 17 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRTableView.h"
+#import "NSTableView-NJRExtensions.h"
+#import "NSCharacterSet-NJRExtensions.h"
+
+/* only defined in 10.2, but we want to be able to compile without warnings on 10.1.x */
+@interface NSColor (JaguarExtras)
++ (NSColor *)alternateSelectedControlColor;
+@end
+
+@implementation NJRTableView
+
+#pragma mark type selection
+
+- (id)typeSelectDisplay;
+{
+    return typeSelectDisplay;
+}
+
+- (void)moveToEndOfDocument:(id)sender {
+    [self scrollRowToVisible: [self numberOfRows] - 1];
+}
+
+- (void)moveToBeginningOfDocument:(id)sender {
+    [self scrollRowToVisible: 0];
+}
+
+- (void)keyDown:(NSEvent *)theEvent;
+{
+    NSString *characters;
+    unichar firstCharacter;
+    characters = [theEvent characters];
+    firstCharacter = [characters characterAtIndex: 0];
+    switch (firstCharacter) {
+        case 0177: // delete key
+        case NSDeleteFunctionKey:
+        case NSDeleteCharFunctionKey:
+            if ([self selectedRow] >= 0 && [[self dataSource] respondsToSelector: @selector(removeSelectedRowsFromTableView:)]) {
+                [[self dataSource] removeSelectedRowsFromTableView: self];
+            }
+            return;
+        case NSHomeFunctionKey:
+            [self moveToBeginningOfDocument: nil];
+            return;
+        case NSEndFunctionKey:
+            [self moveToEndOfDocument: nil];
+            return;
+    }
+    if ([[NSCharacterSet typeSelectSet] characterIsMember: firstCharacter]) {
+        // invoking -[NSResponder interpretKeyEvents:] will cause insertText: to be invoked, and allows function keys to still work.
+        [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]];
+    } else {
+        [super keyDown: theEvent];
+    }
+}
+
+- (void)insertText:(id)inString;
+{
+    if (![[self delegate] respondsToSelector:@selector(selectString:inTableView:)]) {
+        // For consistency with List Manager as documented, reset the typeahead buffer after twice the delay until key repeat (in ticks).
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        int keyRepeatTicks = [defaults integerForKey: @"InitialKeyRepeat"];
+        NSTimeInterval resetDelay;
+
+        if (keyRepeatTicks == 0) keyRepeatTicks = 35; // default may be missing; if so, set default
+
+        resetDelay = MIN(2.0 / 60.0 * keyRepeatTicks, 2.0);
+
+        if (typed == nil) typed = [[NSMutableString alloc] init];
+        [typed appendString: inString];
+
+        // Cancel any previously queued future invocations of _resetTypeSelect
+        [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_resetTypeSelect) object: nil];
+
+        // queue an invocation of clearAccumulatingTypeahead for the near future.
+        [self performSelector: @selector(_resetTypeSelect) withObject: nil afterDelay: resetDelay];
+
+        // Use stringWithString to make an autoreleased copy, since we may clear out the original string below before it can be used.
+        [[self delegate] tableView: self selectRowMatchingString: [NSString stringWithString: typed]];
+
+        // Show the current typeahead string in the optional display field, like CodeWarrior does (well, not really, CW is much more elegant because it doesn't select anything until you stop typing)
+        [typeSelectDisplay setObjectValue: typed];
+    }
+}
+
+- (void)_resetTypeSelect;
+{
+    [typed setString: @""];
+    [typeSelectDisplay setObjectValue: nil];
+}
+
+- (void)resetTypeSelect;
+{
+    [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_resetTypeSelect) object: nil];
+    [self _resetTypeSelect];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRUnfocusableMovieView.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRUnfocusableMovieView.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRUnfocusableMovieView.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NJRUnfocusableMovieView.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Dec 15 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRUnfocusableMovieView : NSMovieView {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRUnfocusableMovieView.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRUnfocusableMovieView.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRUnfocusableMovieView.m	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  NJRUnfocusableMovieView.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Dec 15 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRUnfocusableMovieView.h"
+
+
+@implementation NJRUnfocusableMovieView
+
+- (BOOL)acceptsFirstResponder;
+{
+    return NO;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NJRValidatingField.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRValidatingField.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRValidatingField.h	(revision 365)
@@ -0,0 +1,18 @@
+//
+//  NJRValidatingField.h
+//  Pester
+//
+//  Created by Nicholas Riley on 11/27/07.
+//  Copyright 2007 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NJRValidatingField : NSTextField {
+
+}
+
+- (void)handleDidFailToFormatString:(NSString *)string errorDescription:(NSString *)error label:(NSString *)label;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRValidatingField.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRValidatingField.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRValidatingField.m	(revision 365)
@@ -0,0 +1,94 @@
+//
+//  NJRValidatingField.m
+//  Pester
+//
+//  Created by Nicholas Riley on 11/27/07.
+//  Copyright 2007 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRValidatingField.h"
+
+
+@implementation NJRValidatingField
+
+- (void)_propagateValidationChange;
+{
+    NSText *fieldEditor = [self currentEditor];
+    NSDictionary *userInfo = nil;
+    if (fieldEditor != nil) userInfo = [NSDictionary dictionaryWithObject: fieldEditor forKey: @"NSFieldEditor"];
+    [[NSNotificationCenter defaultCenter] postNotificationName: NSControlTextDidChangeNotification object: self userInfo: userInfo];
+    [self selectText: self];
+}
+
+- (void)validationFailedSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
+    // modal delegate callback method for NSBeginAlertSheet() function; called in the above method
+    if (returnCode == NSAlertOtherReturn) { // cancel
+        [self abortEditing]; // abort edit session and reinstate original value
+        [self _propagateValidationChange];
+    } else if (returnCode == NSAlertAlternateReturn) { // set to min/max/default value
+        [self setObjectValue: [(NSDictionary *)contextInfo objectForKey: @"proposedValue"]];
+        [self validateEditing];
+        [self _propagateValidationChange];
+    }
+    [(NSDictionary *)contextInfo release];
+}
+
+- (void)handleDidFailToFormatString:(NSString *)string errorDescription:(NSString *)error label:(NSString *)label;
+{
+    NSString *alertMessage = nil;
+    NSString *alternateButtonString = nil;
+    NSNumber *proposedValue = nil;
+    NSDictionary *contextInfo;
+
+    NSString *alertInformation = [NSString localizedStringWithFormat:
+        NSLocalizedString(@"The %@ field must be set to a value between %@ and %@.",
+                          @"Informative text for alert posed by text field when invalid value entered"),
+        label, [[self formatter] minimum], [[self formatter] maximum]];
+    NSString *defaultButtonString = NSLocalizedString(@"Edit", @"Name of Edit button");
+    NSString *otherButtonString = NSLocalizedString(@"Cancel", @"Name of Cancel button");
+
+    if ([error isEqualToString:
+        NSLocalizedStringFromTableInBundle(@"Fell short of minimum", @"Formatter",
+                                           [NSBundle bundleForClass:[NSFormatter class]],
+                                           @"Presented when user value smaller than minimum")]) {
+        proposedValue = [[self formatter] minimum];
+        alertMessage = [NSString stringWithFormat:
+            NSLocalizedString(@"%@ is too small for the %@ field.",
+                              @"Message text for alert posed by numeric field when too-small value entered"),
+            string, label];
+        alternateButtonString = [NSString localizedStringWithFormat:
+            NSLocalizedString(@"Set to %@",
+                              @"Name of alternate button for alert posed by numeric field when too-small value entered"),
+            proposedValue];
+    } else if ([error isEqualToString:
+        NSLocalizedStringFromTableInBundle(@"Maximum exceeded", @"Formatter",
+                                           [NSBundle bundleForClass:[NSFormatter class]],
+                                           @"Presented when user value larger than maximum")]) {
+        proposedValue = [[self formatter] maximum];
+        alertMessage = [NSString stringWithFormat:
+            NSLocalizedString(@"%@ is too large for the %@ field.",
+                              @"Message text for alert posed by numeric field when too-large value entered"),
+            string, label];
+        alternateButtonString = [NSString localizedStringWithFormat:
+            NSLocalizedString(@"Set to %@",
+                              @"Name of alternate button for alert posed by numeric field when too-large value entered"),
+            proposedValue];
+    } else if ([error isEqualToString:
+        NSLocalizedStringFromTableInBundle(@"Invalid number", @"Formatter",
+                                           [NSBundle bundleForClass:[NSFormatter class]],
+                                           @"Presented when user typed illegal characters: no valid object")]) {
+        alertMessage = [NSString stringWithFormat:
+            NSLocalizedString(@"%@ is not a valid entry for the %@ field.",
+                              @"Message text for alert posed by text field when invalid value entered"),
+            string, label];
+        alternateButtonString = nil;
+    }
+
+    contextInfo = [NSDictionary dictionaryWithObject: proposedValue forKey: @"proposedValue"];
+    [contextInfo retain];
+    NSBeep();
+    NSBeginAlertSheet(alertMessage, defaultButtonString, alternateButtonString, otherButtonString, [self window],
+                      self, @selector(validationFailedSheetDidEnd:returnCode:contextInfo:), NULL, contextInfo,
+                      alertInformation);
+}
+@end
Index: releases/Pester/1.1b6/Source/NJRVoicePopUpButton.h
===================================================================
--- releases/Pester/1.1b6/Source/NJRVoicePopUpButton.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRVoicePopUpButton.h	(revision 365)
@@ -0,0 +1,29 @@
+//
+//  NJRVoicePopUpButton.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@interface NJRVoicePopUpButton : NSPopUpButton {
+    id _delegate;
+    NSSpeechSynthesizer *_speaker;
+}
+
+- (void)setVoice:(NSString *)voice;
+
+- (id)delegate;
+- (void)setDelegate:(id)delegate;
+
+- (IBAction)stopVoicePreview:(id)sender;
+
+@end
+
+@interface NSObject (NJRVoicePopUpButtonDelegate)
+
+- (NSString *)voicePopUpButton:(NJRVoicePopUpButton *)sender previewStringForVoice:(NSString *)voice;
+
+@end
Index: releases/Pester/1.1b6/Source/NJRVoicePopUpButton.m
===================================================================
--- releases/Pester/1.1b6/Source/NJRVoicePopUpButton.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NJRVoicePopUpButton.m	(revision 365)
@@ -0,0 +1,134 @@
+//
+//  NJRVoicePopUpButton.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRVoicePopUpButton.h"
+
+@implementation NJRVoicePopUpButton
+
+- (void)_refreshVoiceList;
+{
+    NSMenu *menu;
+    NSMenuItem *item;
+    NSArray *voices = [NSSpeechSynthesizer availableVoices];
+
+    [self removeAllItems];
+    menu = [self menu];
+    [menu setAutoenablesItems: NO];
+    // XXX would be more elegant with surrogate support like my font popup menu
+    item = [menu addItemWithTitle: @"«unknown»" action: nil keyEquivalent: @""];
+    [item setEnabled: NO];
+    [menu addItem: [NSMenuItem separatorItem]];
+    if (voices == nil || [voices count] == 0) {
+        item = [menu addItemWithTitle: NSLocalizedString(@"Can't locate voices", "Voice popup menu item surrogate for voice list if no voices are found") action: nil keyEquivalent: @""];
+        [item setEnabled: NO];
+    } else {
+        NSEnumerator *e = [voices objectEnumerator];
+        NSString *voice;
+        while ( (voice = [e nextObject]) != nil) {
+            item = [menu addItemWithTitle:
+		    [[NSSpeechSynthesizer attributesForVoice: voice] objectForKey: NSVoiceName]
+				   action: @selector(_previewVoice) keyEquivalent: @""];
+	    [item setRepresentedObject: voice];
+            [item setTarget: self];
+        }
+    }
+    if (_speaker == nil)
+	[self selectItemAtIndex: [menu indexOfItemWithRepresentedObject: [NSSpeechSynthesizer defaultVoice]]];
+}
+
+- (id)initWithFrame:(NSRect)frame;
+{
+    if ( (self = [super initWithFrame: frame]) != nil) {
+        [self _refreshVoiceList];
+    }
+    return self;
+}
+
+- (id)initWithCoder:(NSCoder *)coder;
+{
+    if ( (self = [super initWithCoder: coder]) != nil) {
+        [self _refreshVoiceList];
+    }
+    return self;
+}
+
+- (NSSpeechSynthesizer *)_speaker;
+{
+    if (_speaker == nil) _speaker = [[NSSpeechSynthesizer alloc] initWithVoice: nil];
+    return _speaker;
+}
+
+- (void)_invalidateVoiceSelection;
+{
+    [self _refreshVoiceList];
+    [self selectItemAtIndex: 0];
+}
+
+- (void)setVoice:(NSString *)voice;
+{
+    int voiceIdx = [self indexOfItemWithRepresentedObject: voice];
+    if (voiceIdx == -1) {
+        [self _invalidateVoiceSelection];
+    } else {
+        [self selectItemAtIndex: voiceIdx];
+    }
+}
+
+- (void)_previewVoice;
+{
+    NSString *voice = [[self selectedItem] representedObject];
+    NSString *previewString = nil;
+
+    [_speaker stopSpeaking];
+
+    if (![[self _speaker] setVoice: voice]) {
+	// XXX localize title
+        NSBeginAlertSheet(@"Voice not available", nil, nil, nil, [self window], nil, nil, nil, nil, NSLocalizedString(@"The voice '%@' you selected could not be used.", "Message displayed in alert sheet when -[NSSpeechSynthesizer setVoice:] returns an error"), [self titleOfSelectedItem]);
+        [self _invalidateVoiceSelection];
+        return;
+    }
+
+    if (_delegate != nil && [_delegate respondsToSelector: @selector(voicePopUpButton:previewStringForVoice:)]) {
+        previewString = [_delegate voicePopUpButton: self previewStringForVoice: voice];
+    }
+
+    if (previewString == nil)
+        previewString = [[NSSpeechSynthesizer attributesForVoice: voice] objectForKey: NSVoiceDemoText];
+
+    [_speaker startSpeakingString: previewString];
+}
+
+- (void)dealloc;
+{
+    [_speaker release];
+    [super dealloc];
+}
+
+- (IBAction)stopVoicePreview:(id)sender;
+{
+    [_speaker stopSpeaking];
+}
+
+- (void)setEnabled:(BOOL)flag;
+{
+    [super setEnabled: flag];
+    if (flag) ; // XXX [self stopVoicePreview: self]; // need to prohibit at startup
+    else [self stopVoicePreview: self];
+}
+
+- (void)setDelegate:(id)delegate;
+{
+    _delegate = delegate;
+}
+
+- (id)delegate;
+{
+    return _delegate;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSAttributedString-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSAttributedString-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSAttributedString-NJRExtensions.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NSAttributedString-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Dec 16 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NSAttributedString (NJRExtensions)
+
+- (float)heightWrappedToWidth:(float)width;
+
+@end
Index: releases/Pester/1.1b6/Source/NSAttributedString-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSAttributedString-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSAttributedString-NJRExtensions.m	(revision 365)
@@ -0,0 +1,30 @@
+//
+//  NSAttributedString-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Dec 16 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSAttributedString-NJRExtensions.h"
+
+@implementation NSAttributedString (NJRExtensions)
+
+- (float)heightWrappedToWidth:(float)width;
+{
+    float height = 0;
+    NSTextStorage *storage = [[NSTextStorage alloc] initWithAttributedString: self];
+    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
+    NSTextContainer *container = [[NSTextContainer alloc]
+        initWithContainerSize: NSMakeSize(width, MAXFLOAT)];
+    [layoutManager addTextContainer: container];
+    [storage addLayoutManager: layoutManager];
+    (void) [layoutManager glyphRangeForTextContainer: container]; // force layout
+    height = [layoutManager usedRectForTextContainer: container].size.height;
+    [layoutManager release];
+    [container release];
+    [storage release];
+    return height;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSCalendarDate-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSCalendarDate-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSCalendarDate-NJRExtensions.h	(revision 365)
@@ -0,0 +1,17 @@
+//
+//  NSCalendarDate-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Dec 22 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NSCalendarDate (NJRExtensions)
+
++ (NSCalendarDate *)dateForDay:(NSDate *)date;
++ (NSCalendarDate *)dateWithDate:(NSDate *)date atTime:(NSDate *)time;
+
+@end
Index: releases/Pester/1.1b6/Source/NSCalendarDate-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSCalendarDate-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSCalendarDate-NJRExtensions.m	(revision 365)
@@ -0,0 +1,49 @@
+//
+//  NSCalendarDate-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Dec 22 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSCalendarDate-NJRExtensions.h"
+
+
+@interface NSCalendarDate (Private)
+
+// implemented in Foundation, but not declared in NSDate.h
++ (NSCalendarDate *)dateWithDate:(NSDate *)date;
+
+@end
+
+@implementation NSCalendarDate (NJRExtensions)
+
++ (NSCalendarDate *)dateForDay:(NSDate *)date;
+{
+    NSCalendarDate *calDate = [NSCalendarDate dateWithDate: date];
+    return [NSCalendarDate dateWithYear: [calDate yearOfCommonEra]
+                                  month: [calDate monthOfYear]
+                                    day: [calDate dayOfMonth]
+                                   hour: 0
+                                 minute: 0
+                                 second: 0
+                               timeZone: nil];
+}
+
++ (NSCalendarDate *)dateWithDate:(NSDate *)date atTime:(NSDate *)time;
+{
+    NSCalendarDate *calTime, *calDate;
+    if (time == nil || date == nil) return nil;
+    calTime = [NSCalendarDate dateWithDate: time];
+    calDate = [NSCalendarDate dateWithDate: date];
+    if (calTime == nil || calDate == nil) return nil;
+    return [NSCalendarDate dateWithYear: [calDate yearOfCommonEra]
+                                  month: [calDate monthOfYear]
+                                    day: [calDate dayOfMonth]
+                                   hour: [calTime hourOfDay]
+                                 minute: [calTime minuteOfHour]
+                                 second: [calTime secondOfMinute]
+                               timeZone: nil];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSCalendarDate-OFExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSCalendarDate-OFExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSCalendarDate-OFExtensions.h	(revision 365)
@@ -0,0 +1,25 @@
+// Copyright 1997-2002 Omni Development, Inc.  All rights reserved.
+//
+// This software may only be used and reproduced according to the
+// terms in the file OmniSourceLicense.html, which should be
+// distributed with this project and can also be found at
+// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.
+//
+// $Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSCalendarDate-OFExtensions.h,v 1.12 2002/03/09 01:54:02 kc Exp $
+
+#import <Foundation/NSCalendarDate.h>
+
+@interface NSCalendarDate (OFExtensions)
++ (NSCalendarDate *)unixReferenceDate;
+- (void)setToUnixDateFormat;
+- initWithTime_t:(int)time;
+
+- (NSCalendarDate *)safeReferenceDate;
+- (NSCalendarDate *)firstDayOfMonth;
+- (NSCalendarDate *)lastDayOfMonth;
+- (int)numberOfDaysInMonth;
+- (int)weekOfMonth;
+    // Returns 1 through 6. Weeks are Sunday-Saturday.
+- (BOOL)isInSameWeekAsDate:(NSCalendarDate *)otherDate;
+
+@end
Index: releases/Pester/1.1b6/Source/NSCalendarDate-OFExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSCalendarDate-OFExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSCalendarDate-OFExtensions.m	(revision 365)
@@ -0,0 +1,120 @@
+// Copyright 1997-2002 Omni Development, Inc.  All rights reserved.
+//
+// This software may only be used and reproduced according to the
+// terms in the file OmniSourceLicense.html, which should be
+// distributed with this project and can also be found at
+// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.
+
+#import "NSCalendarDate-OFExtensions.h"
+
+#import <Foundation/Foundation.h>
+
+// RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSCalendarDate-OFExtensions.m,v 1.15 2002/03/09 01:54:02 kc Exp $")
+
+@implementation NSCalendarDate (OFExtensions)
+
++ (NSCalendarDate *)unixReferenceDate;
+{
+    static NSCalendarDate *unixReferenceDate = nil;
+    const long zero = 0;
+
+    if (unixReferenceDate == nil) {
+        unixReferenceDate = [[NSCalendarDate dateWithString:[NSString stringWithCString:ctime(&zero)] calendarFormat:@"%a %b %d %H:%M:%S %Y\n"] retain];
+    }
+    return unixReferenceDate;
+}
+
+- (void)setToUnixDateFormat;
+{
+    if ([self yearOfCommonEra] == [(NSCalendarDate *)[NSCalendarDate date] yearOfCommonEra])
+	[self setCalendarFormat:@"%b %d %H:%M"];
+    else
+	[self setCalendarFormat:@"%b %d %Y"];
+}
+
+- initWithTime_t:(int)time;
+{
+    NSCalendarDate *date;
+
+    date = (id)[self initWithTimeInterval:time sinceDate:[isa unixReferenceDate]];
+    [date setToUnixDateFormat];
+    return date;
+}
+
+// We're going with Noon instead of midnight, since it's a bit more tolerant of
+// time zone switching. (When you're adding days.)
+
+- (NSCalendarDate *)safeReferenceDate;
+{
+    int year, month, day;
+
+    year = [self yearOfCommonEra];
+    month = [self monthOfYear];
+    day = [self dayOfMonth];
+
+    return [NSCalendarDate dateWithYear:year month:month day:day
+                           hour:12 minute:0 second:0 timeZone:[NSTimeZone localTimeZone]];
+}
+
+- (NSCalendarDate *)firstDayOfMonth;
+{
+    NSCalendarDate *firstDay;
+
+    firstDay = [[NSCalendarDate alloc] initWithYear:[self yearOfCommonEra]
+        month:[self monthOfYear]
+        day:1
+        hour:12
+        minute:0
+        second:0
+        timeZone:nil];
+    return [firstDay autorelease];
+}
+
+- (NSCalendarDate *)lastDayOfMonth;
+{
+    return [[self firstDayOfMonth] dateByAddingYears:0 months:1 days:-1 hours:0 minutes:0 seconds:0];
+}
+
+- (int)numberOfDaysInMonth;
+{
+    return [[self lastDayOfMonth] dayOfMonth];
+}
+
+- (int)weekOfMonth;
+{
+    // Returns 1 through 6. Weeks are Sunday-Saturday.
+    int dayOfMonth;
+    int firstWeekDayOfMonth;
+    
+    dayOfMonth = [self dayOfMonth];
+    firstWeekDayOfMonth = [[self firstDayOfMonth] dayOfWeek];
+    return (dayOfMonth - 1 + firstWeekDayOfMonth) / 7 + 1;
+}
+
+- (BOOL)isInSameWeekAsDate:(NSCalendarDate *)otherDate;
+{
+    int weekOfMonth;
+
+    // First, do a quick check to filter out dates which are more than a week away.
+    if (abs([self dayOfCommonEra] - [otherDate dayOfCommonEra]) > 6)
+        return NO;
+
+    // Then, handle the simple case, when both dates are the same year and month.
+    if ([self yearOfCommonEra] == [otherDate yearOfCommonEra] && [self monthOfYear] == [otherDate monthOfYear])
+        return ([self weekOfMonth] == [otherDate weekOfMonth]);
+
+    // Now we know the other date is within a week of us, and not in the same month. 
+    weekOfMonth = [self weekOfMonth];
+    if (weekOfMonth == 1) {
+        // We are in the first week of the month. The otherDate is in the same week if its weekday is earlier than ours.
+        return ([otherDate dayOfWeek] < [self dayOfWeek]);
+    } else if (weekOfMonth == [[self lastDayOfMonth] weekOfMonth]) {
+        // We are in the last week of the month. The otherDate is in the same week if its weekday is later than ours.
+        return ([otherDate dayOfWeek] > [self dayOfWeek]);
+    } else {
+        // We are somewhere in the middle of the month, so the otherDate cannot be in the same week.
+        return NO;
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSCharacterSet-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSCharacterSet-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSCharacterSet-NJRExtensions.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NSCharacterSet-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Nov 17 2002.
+//  Copyright (c) 2002 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NSCharacterSet (NJRExtensions)
+
++ (NSCharacterSet *)typeSelectSet;
+
+@end
Index: releases/Pester/1.1b6/Source/NSCharacterSet-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSCharacterSet-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSCharacterSet-NJRExtensions.m	(revision 365)
@@ -0,0 +1,28 @@
+//
+//  NSCharacterSet-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Nov 17 2002.
+//  Copyright (c) 2002 __MyCompanyName__. All rights reserved.
+//
+
+#import "NSCharacterSet-NJRExtensions.h"
+
+
+@implementation NSCharacterSet (NJRExtensions)
+
+static NSCharacterSet *_typeSelectSet = nil;
+
++ (NSCharacterSet *)typeSelectSet {
+    if (_typeSelectSet == nil) {
+        NSMutableCharacterSet *set;
+        set = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
+        [set formUnionWithCharacterSet: [NSCharacterSet punctuationCharacterSet]];
+        [set formUnionWithCharacterSet: [NSCharacterSet whitespaceCharacterSet]];
+        _typeSelectSet = [set copy]; // make immutable again - for efficiency
+        [set release];
+    }
+    return _typeSelectSet;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSDictionary-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSDictionary-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSDictionary-NJRExtensions.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NSDictionary-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Nov 19 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface NSDictionary (NJRExtensions)
+
+- (id)objectForRequiredKey:(NSString *)key;
+
+@end
Index: releases/Pester/1.1b6/Source/NSDictionary-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSDictionary-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSDictionary-NJRExtensions.m	(revision 365)
@@ -0,0 +1,21 @@
+//
+//  NSDictionary-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Nov 19 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSDictionary-NJRExtensions.h"
+
+
+@implementation NSDictionary (NJRExtensions)
+
+- (id)objectForRequiredKey:(NSString *)key;
+{
+    id obj = [self objectForKey: key];
+    NSAssert1(obj != nil, NSLocalizedString(@"Dictionary does not contain object for required key '%@'", "Assertion failure in -[NSDictionary objectForRequiredKey:]"), key);
+    return obj;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSDisclosureButtonCell.h
===================================================================
--- releases/Pester/1.1b6/Source/NSDisclosureButtonCell.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSDisclosureButtonCell.h	(revision 365)
@@ -0,0 +1,18 @@
+/*
+ *     Generated by class-dump (version 2.1.5).
+ *
+ *     class-dump is Copyright (C) 1997, 1999, 2000, 2001 by Steve Nygard.
+ */
+/*
+ * File: /System/Library/Frameworks/AppKit.framework/AppKit
+ */
+
+#import <AppKit/AppKit.h>
+
+@interface NSDisclosureButtonCell : NSButtonCell
+{
+}
+
+- (void)initWithCell:(NSCell *)cell;
+
+@end
Index: releases/Pester/1.1b6/Source/NSFont-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSFont-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSFont-NJRExtensions.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NSFont-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Mar 30 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#include <Carbon/Carbon.h>
+
+@interface NSFont (NJRExtensions)
+
++ (NSFont *)themeFont:(ThemeFontID)fontID;
+
+@end
Index: releases/Pester/1.1b6/Source/NSFont-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSFont-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSFont-NJRExtensions.m	(revision 365)
@@ -0,0 +1,33 @@
+//
+//  NSFont-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Mar 30 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "NSFont-NJRExtensions.h"
+
+@implementation NSFont (NJRExtensions)
+
++ (NSFont *)themeFont:(ThemeFontID)fontID;
+{
+    NSFont *themeFont = nil;
+    Str255 pstrFontName;
+    SInt16 fontSize = 0;
+    OSStatus status;
+    // can't simulate algorithmic styles in Cocoa in any case, so here's hoping nothing will be passed back - XXX guess this should at least accommodate the bold system font, but we don't need it
+    status = GetThemeFont(fontID, smSystemScript, pstrFontName, &fontSize, NULL);
+
+    if (status == noErr) {
+        NSString *fontName = (NSString *)CFStringCreateWithPascalString(NULL, pstrFontName, CFStringGetSystemEncoding());
+        themeFont = [NSFont fontWithName: fontName size: fontSize];
+        [fontName release];
+    }
+    if (themeFont == nil) {
+        themeFont = [NSFont systemFontOfSize: fontSize == 0 ? [NSFont systemFontSize] : fontSize];
+    }
+    return themeFont;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSImage-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSImage-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSImage-NJRExtensions.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NSImage-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Oct 28 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NSImage (NJRExtensions)
+
+- (NSImage *)bestFitImageForSize:(NSSize)imageSize;
+
+@end
Index: releases/Pester/1.1b6/Source/NSImage-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSImage-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSImage-NJRExtensions.m	(revision 365)
@@ -0,0 +1,62 @@
+//
+//  NSImage-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Oct 28 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSImage-NJRExtensions.h"
+
+
+@implementation NSImage (NJRExtensions)
+
+- (NSImage *)bestFitImageForSize:(NSSize)imageSize;
+{
+    NSSize repSize;
+    NSArray *imageReps;
+    int i, repCount;
+    NSImageRep *preferredRep, *rep;
+    imageReps = [self representations];
+    repCount = [imageReps count];
+    preferredRep = [imageReps objectAtIndex: 0];
+    for (i = 1 ; i < repCount ; i++) {
+        rep = [imageReps objectAtIndex: i];
+        repSize = [rep size];
+        if (repSize.width == imageSize.width && repSize.height == imageSize.height) {
+            preferredRep = rep;
+            break;
+        }
+        if (repSize.width >= imageSize.width || repSize.height >= imageSize.height) {
+            // pick the smallest of the larger representations
+            if (repSize.width <= [preferredRep size].width ||
+                repSize.height <= [preferredRep size].height) preferredRep = rep;
+        } else {
+            // or the largest of the smaller representations
+            if (([preferredRep size].width > imageSize.width ||
+                    [preferredRep size].height > imageSize.height) ||
+                // (assuming that the previous preferred rep was smaller, too)
+                // XXX fix this in HostLauncher too - or is the FSA code better?
+                (repSize.width >= [preferredRep size].width ||
+                    repSize.height >= [preferredRep size].height)) preferredRep = rep;
+        }
+    }
+    // Begin workaround code for bug in OS X 10.1 (removeRepresentation: has no effect)
+    if ([preferredRep size].width > imageSize.width || [preferredRep size].height > imageSize.height) {
+        NSImage *scaledImage = [[NSImage alloc] initWithSize: imageSize];
+        NSRect rect = { NSZeroPoint, imageSize };
+        [scaledImage setFlipped: [self isFlipped]]; // XXX this works, but is correct?
+        [scaledImage lockFocus];
+        [preferredRep drawInRect: rect];
+        [scaledImage unlockFocus];
+        return scaledImage;
+    } else if (repCount > 1) {
+        NSImage *sizedImage = [[NSImage alloc] initWithSize: [preferredRep size]];
+        [sizedImage addRepresentation: preferredRep];
+        return sizedImage;
+    } else {
+        return self;
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSImage-OAExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSImage-OAExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSImage-OAExtensions.h	(revision 365)
@@ -0,0 +1,28 @@
+// Copyright 1997-2002 Omni Development, Inc.  All rights reserved.
+//
+// This software may only be used and reproduced according to the
+// terms in the file OmniSourceLicense.html, which should be
+// distributed with this project and can also be found at
+// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.
+//
+// $Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSImage-OAExtensions.h,v 1.14 2002/03/09 01:53:54 kc Exp $
+
+#import <AppKit/NSImage.h>
+
+@class NSMutableSet;
+
+@interface NSImage (OAExtensions)
+
++ (NSImage *)imageNamed:(NSString *)imageName inBundleForClass:(Class)aClass;
++ (NSImage *)imageNamed:(NSString *)imageName inBundle:(NSBundle *)aBundle;
++ (NSImage *)imageForFileType:(NSString *)fileType;
+    // Caching wrapper for -[NSWorkspace iconForFileType:].  This method is not thread-safe at the moment.
++ (NSImage *)draggingIconWithTitle:(NSString *)title andImage:(NSImage *)image;
+
+- (void)drawFlippedInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(float)delta;
+- (void)drawFlippedInRect:(NSRect)rect operation:(NSCompositingOperation)op;
+
+    // Puts the image on the pasteboard as TIFF, and also supplies data from any PDF, EPS, or PICT representations available. Returns the number of types added to the pasteboard and adds their names to notThese. This routine uses -addTypes:owner:, so the pasteboard must have previously been set up using -declareTypes:owner.
+- (int)addDataToPasteboard:(NSPasteboard *)aPasteboard exceptTypes:(NSMutableSet *)notThese;
+
+@end
Index: releases/Pester/1.1b6/Source/NSImage-OAExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSImage-OAExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSImage-OAExtensions.m	(revision 365)
@@ -0,0 +1,211 @@
+// Copyright 1997-2002 Omni Development, Inc.  All rights reserved.
+//
+// This software may only be used and reproduced according to the
+// terms in the file OmniSourceLicense.html, which should be
+// distributed with this project and can also be found at
+// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.
+
+#import "NSImage-OAExtensions.h"
+
+#import <AppKit/AppKit.h>
+
+// RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSImage-OAExtensions.m,v 1.17 2002/03/09 01:53:54 kc Exp $")
+
+@implementation NSImage (OAExtensions)
+
++ (NSImage *)imageNamed:(NSString *)imageName inBundleForClass:(Class)aClass;
+{
+    return [self imageNamed:imageName inBundle:[NSBundle bundleForClass:aClass]];
+}
+
++ (NSImage *)imageNamed:(NSString *)imageName inBundle:(NSBundle *)aBundle;
+{
+    NSImage *image;
+    NSString *path;
+
+    image = [self imageNamed:imageName];
+    if (image && [image size].width != 0)
+        return image;
+
+    path = [aBundle pathForImageResource:imageName];
+    if (!path)
+        return nil;
+
+    image = [[NSImage alloc] initWithContentsOfFile:path];
+    [image setName:imageName];
+
+    return image;
+}
+
++ (NSImage *)imageForFileType:(NSString *)fileType;
+    // It turns out that -[NSWorkspace iconForFileType:] doesn't cache previously returned values, so we cache them here.
+{
+    static NSMutableDictionary *imageDictionary = nil;
+    id image;
+
+    // ASSERT_IN_MAIN_THREAD(@"+imageForFileType: is not thread-safe; must be called from the main thread");
+    // We could fix this by adding locks around imageDictionary
+
+    if (!fileType)
+        return nil;
+        
+    if (imageDictionary == nil)
+        imageDictionary = [[NSMutableDictionary alloc] init];
+
+    image = [imageDictionary objectForKey:fileType];
+    if (image == nil) {
+#ifdef DEBUG
+        // Make sure that our caching doesn't go insane (and that we don't ask it to cache insane stuff)
+        NSLog(@"Caching workspace image for file type '%@'", fileType);
+#endif
+        image = [[NSWorkspace sharedWorkspace] iconForFileType:fileType];
+        if (image == nil)
+            image = [NSNull null];
+        [imageDictionary setObject:image forKey:fileType];
+    }
+    return image != [NSNull null] ? image : nil;
+}
+
+#define X_SPACE_BETWEEN_ICON_AND_TEXT_BOX 2
+#define X_TEXT_BOX_BORDER 2
+#define Y_TEXT_BOX_BORDER 2
+static NSDictionary *titleFontAttributes;
+
++ (NSImage *)draggingIconWithTitle:(NSString *)title andImage:(NSImage *)image;
+{
+    NSImage *drawImage;
+    NSSize imageSize, totalSize;
+    NSSize titleSize, titleBoxSize;
+    NSRect titleBox;
+    NSPoint textPoint;
+
+    NSParameterAssert(image != nil);
+    imageSize = [image size];
+
+    if (!titleFontAttributes)
+        titleFontAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont systemFontOfSize:12.0], NSFontAttributeName, [NSColor textColor], NSForegroundColorAttributeName, nil];
+
+    if (!title || [title length] == 0)
+        return image;
+    
+    titleSize = [title sizeWithAttributes:titleFontAttributes];
+    titleBoxSize = NSMakeSize(titleSize.width + 2.0 * X_TEXT_BOX_BORDER, titleSize.height + Y_TEXT_BOX_BORDER);
+
+    totalSize = NSMakeSize(imageSize.width + X_SPACE_BETWEEN_ICON_AND_TEXT_BOX + titleBoxSize.width, MAX(imageSize.height, titleBoxSize.height));
+
+    drawImage = [[NSImage alloc] initWithSize:totalSize];
+    [drawImage setFlipped:YES];
+
+    [drawImage lockFocus];
+
+    // Draw transparent background
+    [[NSColor colorWithDeviceWhite:1.0 alpha:0.0] set];
+    NSRectFill(NSMakeRect(0, 0, totalSize.width, totalSize.height));
+
+    // Draw icon
+    [image compositeToPoint:NSMakePoint(0.0, rint(totalSize.height / 2.0 + imageSize.height / 2.0)) operation:NSCompositeSourceOver];
+    
+    // Draw box around title
+    titleBox = NSMakeRect(imageSize.width + X_SPACE_BETWEEN_ICON_AND_TEXT_BOX, floor((totalSize.height - titleBoxSize.height)/2.0), titleBoxSize.width, titleBoxSize.height);
+    [[[NSColor selectedTextBackgroundColor] colorWithAlphaComponent:0.5] set];
+    NSRectFill(titleBox);
+
+    // Draw title
+    textPoint = NSMakePoint(imageSize.width + X_SPACE_BETWEEN_ICON_AND_TEXT_BOX + X_TEXT_BOX_BORDER, Y_TEXT_BOX_BORDER - 1);
+
+    [title drawAtPoint:textPoint withAttributes:titleFontAttributes];
+
+    [drawImage unlockFocus];
+
+    return [drawImage autorelease];
+}
+
+//
+
+- (void)drawFlippedInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(float)delta;
+{
+    CGContextRef context;
+
+    context = [[NSGraphicsContext currentContext] graphicsPort];
+    CGContextSaveGState(context); {
+        CGContextTranslateCTM(context, 0, NSMaxY(rect));
+        CGContextScaleCTM(context, 1, -1);
+        
+        rect.origin.y = 0; // We've translated ourselves so it's zero
+        [self drawInRect:rect fromRect:NSZeroRect operation:op fraction:delta];
+    } CGContextRestoreGState(context);
+
+    /*
+        NSAffineTransform *flipTransform;
+        NSPoint transformedPoint;
+        NSSize transformedSize;
+        NSRect transformedRect;
+
+        flipTransform = [[NSAffineTransform alloc] init];
+        [flipTransform scaleXBy:1.0 yBy:-1.0];
+
+        transformedPoint = [flipTransform transformPoint:rect.origin];
+        transformedSize = [flipTransform transformSize:rect.size];
+        [flipTransform concat];
+        transformedRect = NSMakeRect(transformedPoint.x, transformedPoint.y + transformedSize.height, transformedSize.width, -transformedSize.height);
+        [anImage drawInRect:transformedRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
+        [flipTransform concat];
+        [flipTransform release];
+        */
+}
+
+- (void)drawFlippedInRect:(NSRect)rect operation:(NSCompositingOperation)op;
+{
+    [self drawFlippedInRect:rect operation:op fraction:1.0];
+}
+
+- (int)addDataToPasteboard:(NSPasteboard *)aPasteboard exceptTypes:(NSMutableSet *)notThese
+{
+    NSArray *myRepresentations;
+    int repIndex, repCount;
+    int count = 0;
+
+    if (!notThese)
+        notThese = [NSMutableSet set];
+
+#define IF_ADD(typename, dataOwner) if( ![notThese containsObject:(typename)] && [aPasteboard addTypes:[NSArray arrayWithObject:(typename)] owner:(dataOwner)] > 0 )
+
+#define ADD_CHEAP_DATA(typename, expr) IF_ADD(typename, nil) { [aPasteboard setData:(expr) forType:(typename)]; [notThese addObject:(typename)]; count ++; }
+        
+    /* If we have image representations lying around that already have data in some concrete format, add that data to the pasteboard. */
+    myRepresentations = [self representations];
+    repCount = [myRepresentations count];
+    for(repIndex = 0; repIndex < repCount; repIndex ++) {
+        NSImageRep *aRep = [myRepresentations objectAtIndex:repIndex];
+            
+        if ([aRep respondsToSelector:@selector(PDFRepresentation)]) {
+            ADD_CHEAP_DATA(NSPDFPboardType, [(NSPDFImageRep *)aRep PDFRepresentation]);
+        }
+
+        if ([aRep respondsToSelector:@selector(PICTRepresentation)]) {
+            ADD_CHEAP_DATA(NSPICTPboardType, [(NSPICTImageRep *)aRep PICTRepresentation]);
+        }
+
+        if ([aRep respondsToSelector:@selector(EPSRepresentation)]) {
+            ADD_CHEAP_DATA(NSPostScriptPboardType, [(NSEPSImageRep *)aRep EPSRepresentation]);
+        }
+    }
+    
+    /* Always offer to convert to TIFF. Do this lazily, though, since we probably have to extract it from a bitmap image rep. */
+    IF_ADD(NSTIFFPboardType, self) {
+        count ++;
+    }
+
+    return count;
+}
+
+- (void)pasteboard:(NSPasteboard *)aPasteboard provideDataForType:(NSString *)wanted
+{
+    if ([wanted isEqual:NSTIFFPboardType]) {
+        [aPasteboard setData:[self TIFFRepresentation] forType:NSTIFFPboardType];
+    }
+}
+
+
+
+@end
Index: releases/Pester/1.1b6/Source/NSMenuItem-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSMenuItem-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSMenuItem-NJRExtensions.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  NSMenuItem-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on 11/22/07.
+//  Copyright 2007 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface NSMenuItem (NJRExtensions)
+
+- (void)setImageFromPath:(NSString *)path;
+
+@end
Index: releases/Pester/1.1b6/Source/NSMenuItem-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSMenuItem-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSMenuItem-NJRExtensions.m	(revision 365)
@@ -0,0 +1,35 @@
+//
+//  NSMenuItem-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on 11/22/07.
+//  Copyright 2007 Nicholas Riley. All rights reserved.
+//
+
+#import "NSMenuItem-NJRExtensions.h"
+
+@interface NSMenuItem (Private)
+- (void)_setIconRef:(IconRef)iconRef;
+@end
+
+@implementation NSMenuItem (NJRExtensions)
+
+// from ICeCoffEE's ICCF_CopyIconRefForPath
+- (void)setImageFromPath:(NSString *)path;
+{
+    IconRef icon;
+    FSRef fsr;
+    SInt16 label;
+    OSStatus err = noErr;
+    
+    err = FSPathMakeRef((const UInt8 *)[path fileSystemRepresentation], &fsr, NULL);
+    if (err != noErr) return;
+    
+    err = GetIconRefFromFileInfo(&fsr, 0, NULL, kFSCatInfoNone, NULL, kIconServicesNormalUsageFlag, &icon, &label);
+    if (err != noErr) return;
+    
+    [self _setIconRef: icon];
+    ReleaseIconRef(icon);
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSMovie-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSMovie-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSMovie-NJRExtensions.h	(revision 365)
@@ -0,0 +1,18 @@
+//
+//  NSMovie-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NSMovie (NJRExtensions)
+
+- (BOOL)hasAudio;
+- (BOOL)hasVideo;
+- (BOOL)isStatic;
+
+@end
Index: releases/Pester/1.1b6/Source/NSMovie-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSMovie-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSMovie-NJRExtensions.m	(revision 365)
@@ -0,0 +1,41 @@
+//
+//  NSMovie-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSMovie-NJRExtensions.h"
+#import <QuickTime/Movies.h>
+
+@implementation NSMovie (NJRExtensions)
+
+- (BOOL)hasAudio;
+{
+    return (NULL != GetMovieIndTrackType((Movie)[self QTMovie], 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
+}
+
+- (BOOL)hasVideo;
+{
+    return (NULL != GetMovieIndTrackType((Movie)[self QTMovie], 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly));
+}
+
+- (BOOL)isStatic;
+{
+    Movie movie = (Movie)[self QTMovie];
+    long trackCount = (long)GetMovieTrackCount(movie);
+    long trackIdx = 0;
+    Track track;
+    Media media;
+    for (trackIdx = 1 ; trackIdx <= trackCount ; trackIdx++) {
+        track = GetMovieIndTrack(movie, trackIdx);
+        if (track == nil) continue;
+        media = GetTrackMedia(track);
+        if (media == nil) continue;
+        if (GetMediaSampleCount(media) > 1) return NO;
+    }
+    return YES;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSString-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSString-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSString-NJRExtensions.h	(revision 365)
@@ -0,0 +1,29 @@
+//
+//  NSString-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Dec 16 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NSString (NJRExtensions)
+
+- (NSAttributedString *)small;
+- (NSAttributedString *)smallBold;
+- (NSAttributedString *)underlined;
+
++ (NSString *)ellipsisString;
+
+- (NSAttributedString *)keyEquivalentAttributedStringWithModifierFlags:(unsigned int)modifierFlags;
+
+@end
+
+@interface NSMutableString (NJRExtensions)
+
+- (void)truncateToLength:(unsigned)maxLength by:(NSLineBreakMode)method;
+- (void)truncateToWidth:(float)maxWidth by:(NSLineBreakMode)method withAttributes:(NSDictionary *)attributes;
+
+@end
Index: releases/Pester/1.1b6/Source/NSString-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSString-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSString-NJRExtensions.m	(revision 365)
@@ -0,0 +1,182 @@
+//
+//  NSString-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Dec 16 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSString-NJRExtensions.h"
+#import "NSFont-NJRExtensions.h"
+#include <Carbon/Carbon.h>
+
+@implementation NSString (NJRExtensions)
+
++ (NSString *)stringWithCharacter:(unichar)character;
+{
+    return [self stringWithCharacters: &character length: 1];
+}
+
+- (NSAttributedString *)attributedStringWithFont:(NSFont *)font;
+{
+    return [[[NSAttributedString alloc] initWithString: self attributes: [NSDictionary dictionaryWithObject: font forKey: NSFontAttributeName]] autorelease];
+}
+
+- (NSAttributedString *)underlined;
+{
+    return [[[NSAttributedString alloc] initWithString: self attributes: [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: NSSingleUnderlineStyle] forKey: NSUnderlineStyleAttributeName]] autorelease];
+}
+
+- (NSAttributedString *)small;
+{
+    return [self attributedStringWithFont: [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]];
+}
+
+- (NSAttributedString *)smallBold;
+{
+    return [self attributedStringWithFont: [NSFont boldSystemFontOfSize: [NSFont smallSystemFontSize]]];
+}
+
+static unichar combiningHelpChar[] = {0x003F, 0x20DD};
+
+static NSFont *menuItemCmdKeyFont = nil;
+static NSFont *menuItemFont = nil;
+
+static void initialize() {
+    if (menuItemCmdKeyFont != nil) return;
+
+    menuItemCmdKeyFont = [[NSFont themeFont: kThemeMenuItemCmdKeyFont] retain];
+    menuItemFont = [[NSFont themeFont: kThemeMenuItemFont] retain];
+}
+
+- (NSString *)keyEquivalentString;
+{
+    if ([self length] != 0) {
+        const char *str = [self UTF8String];
+        unichar keyChar;
+        if (str[1] != '\0') {
+            keyChar = [self characterAtIndex: 0];
+            switch (keyChar) {
+                case NSUpArrowFunctionKey: keyChar = 0x21E1; break;
+                case NSDownArrowFunctionKey: keyChar = 0x21E3; break;
+                case NSLeftArrowFunctionKey: keyChar = 0x21E0; break;
+                case NSRightArrowFunctionKey: keyChar = 0x21E2; break;
+                case NSInsertFunctionKey:
+                    return [NSString stringWithCharacters: combiningHelpChar length: 2];
+                case NSDeleteFunctionKey: keyChar = 0x2326; break;
+                case NSHomeFunctionKey: keyChar = 0x2196; break;
+                case NSEndFunctionKey: keyChar = 0x2198; break;
+                case NSPageUpFunctionKey: keyChar = 0x21DE; break;
+                case NSPageDownFunctionKey: keyChar = 0x21DF; break;
+                case NSClearLineFunctionKey: keyChar = 0x2327; break;
+                default:
+                if (keyChar >= NSF1FunctionKey && keyChar <= NSF35FunctionKey) {
+                    return [NSString stringWithFormat: @"F%u", keyChar - NSF1FunctionKey + 1];
+                }
+                return [NSString stringWithFormat: @"[unknown %lX]", keyChar];
+            }
+        } else if (str[0] >= 'A' && str[0] <= 'Z') {
+            return self;
+        } else if (str[0] >= 'a' && str[0] <= 'z') return [self uppercaseString];
+        else switch (str[0]) {
+            case '\t': keyChar = 0x21e5; break;
+            case '\r': keyChar = 0x21a9; break;
+            case '\e': keyChar = 0x238b; break;
+            case ' ': keyChar = 0x2423; break;
+            case 0x7f: keyChar = 0x232b; break; // delete
+            case 0x03: keyChar = 0x2324; break; // enter
+            case 0x19: keyChar = 0x21e4; break; // backtab
+            case 0: return @"";
+            // case '': keyChar = 0x; break;
+            default: return self; // return [NSString stringWithFormat: @"[huh? %x]", (int)str[0]]; // 
+        }
+        return [NSString stringWithCharacter: keyChar];
+    }
+    return self;
+}
+
+- (NSAttributedString *)keyEquivalentAttributedStringWithModifierFlags:(unsigned int)modifierFlags;
+{
+    initialize();
+    NSString *keyEquivalentStringNoMask = [self keyEquivalentString];
+    NSAttributedString *keyEquivalentAttributedString =
+        [[NSString stringWithFormat: @"%@%@%@%@%@",
+            (modifierFlags & NSControlKeyMask) ? [NSString stringWithCharacter: kControlUnicode] : @"",
+            (modifierFlags & NSAlternateKeyMask) ? [NSString stringWithCharacter: kOptionUnicode] : @"",
+            (modifierFlags & NSShiftKeyMask) ? [NSString stringWithCharacter: kShiftUnicode] : @"",
+            (modifierFlags & NSCommandKeyMask) ? [NSString stringWithCharacter: kCommandUnicode] : @"",
+                keyEquivalentStringNoMask]
+        attributedStringWithFont: menuItemCmdKeyFont];
+    unsigned noMaskLength = [keyEquivalentStringNoMask length];
+    if (noMaskLength > 3 || // Fxx
+        (noMaskLength == 1 && [keyEquivalentStringNoMask characterAtIndex: 0] <= 0x7F)) {
+        NSMutableAttributedString *astr = [keyEquivalentAttributedString mutableCopy];
+        [astr setAttributes: [NSDictionary dictionaryWithObject: menuItemFont forKey: NSFontAttributeName] range: NSMakeRange([astr length] - noMaskLength, noMaskLength)];
+        keyEquivalentAttributedString = [[astr copy] autorelease];
+        [astr release];
+    }
+    return keyEquivalentAttributedString;
+}
+
++ (NSString *)ellipsisString;
+{
+    static NSString *ellipsis = nil;
+    if (ellipsis == nil) {
+        const unichar ellipsisChar = 0x2026;
+        ellipsis = [[NSString alloc] initWithCharacters: &ellipsisChar length: 1];
+    }
+    return ellipsis;
+}
+
+@end
+
+@implementation NSMutableString (NJRExtensions)
+
+- (void)truncateToLength:(unsigned)maxLength by:(NSLineBreakMode)method;
+{
+    if ([self length] > maxLength) {
+        NSRange range = {0, [self length] - maxLength};
+        switch (method) {
+            case NSLineBreakByTruncatingHead:
+                range.location = 0;
+                break;
+            case NSLineBreakByTruncatingMiddle:
+                range.location = maxLength / 2;
+                break;
+            case NSLineBreakByTruncatingTail:
+                range.location = maxLength;
+                break;
+            default:
+                range.location = maxLength;
+                break;
+        }
+        [self replaceCharactersInRange: range withString: [NSString ellipsisString]];
+    }
+}
+
+- (void)truncateToWidth:(float)maxWidth by:(NSLineBreakMode)method withAttributes:(NSDictionary *)attributes;
+{
+    if ([self sizeWithAttributes: attributes].width > maxWidth) {
+        float width = maxWidth;
+        int min = 0, max = [self length], avg;
+        NSMutableString *original = [self mutableCopy];
+        while (max >= min) {
+            avg = (max + min) / 2;
+            [self truncateToLength: avg by: method];
+            width = [self sizeWithAttributes: attributes].width;
+            if (width > maxWidth) {
+                max = avg - 1; // too wide
+            } else if (width == maxWidth) {
+                break;
+            } else {
+                min = avg + 1; // too narrow
+                [self setString: original];
+            }
+        }
+        if (width != maxWidth)
+            [self truncateToLength: max by: method];
+        [original release];
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/NSTableView-NJRExtensions.h
===================================================================
--- releases/Pester/1.1b6/Source/NSTableView-NJRExtensions.h	(revision 365)
+++ releases/Pester/1.1b6/Source/NSTableView-NJRExtensions.h	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  NSTableView-NJRExtensions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Oct 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface NSTableView (NJRExtensions)
+
++ (NSImage *)ascendingSortIndicator;
++ (NSImage *)descendingSortIndicator;
+
+- (float)cellHeight;
+
+@end
Index: releases/Pester/1.1b6/Source/NSTableView-NJRExtensions.m
===================================================================
--- releases/Pester/1.1b6/Source/NSTableView-NJRExtensions.m	(revision 365)
+++ releases/Pester/1.1b6/Source/NSTableView-NJRExtensions.m	(revision 365)
@@ -0,0 +1,47 @@
+//
+//  NSTableView-NJRExtensions.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Oct 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NSTableView-NJRExtensions.h"
+
+
+@interface NSTableView (PumaPrivate)
+// Declarations of 10.1 private methods, just to make the compiler happy.
++ (id) _defaultTableHeaderReverseSortImage;
++ (id) _defaultTableHeaderSortImage;
+@end
+
+@implementation NSTableView (NJRExtensions)
+
++ (NSImage *)ascendingSortIndicator;
+{
+    NSImage *result = [NSImage imageNamed: @"NSAscendingSortIndicator"];
+    if (result == nil && [[NSTableView class] respondsToSelector: @selector(_defaultTableHeaderSortImage)])
+        result = [NSTableView _defaultTableHeaderSortImage];
+    return result;
+}
+
++ (NSImage *)descendingSortIndicator;
+{
+    NSImage *result = [NSImage imageNamed: @"NSDescendingSortIndicator"];
+    if (result == nil && [[NSTableView class] respondsToSelector: @selector(_defaultTableHeaderReverseSortImage)])
+        result = [NSTableView _defaultTableHeaderReverseSortImage];
+    return result;
+}
+
+- (float)cellHeight;
+{
+    return [self rowHeight] + [self intercellSpacing].height;
+}
+
+// causes NSTableView to get keyboard focus (with thanks to Pierre-Olivier Latour)
+- (BOOL)needsPanelToBecomeKey
+{
+    return YES;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/OACalendarView.h
===================================================================
--- releases/Pester/1.1b6/Source/OACalendarView.h	(revision 365)
+++ releases/Pester/1.1b6/Source/OACalendarView.h	(revision 365)
@@ -0,0 +1,90 @@
+// Copyright 2001-2002 Omni Development, Inc.  All rights reserved.
+//
+// This software may only be used and reproduced according to the
+// terms in the file OmniSourceLicense.html, which should be
+// distributed with this project and can also be found at
+// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.
+//
+// $Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniAppKit/Widgets.subproj/OACalendarView.h,v 1.16 2002/12/07 00:23:40 andrew Exp $
+
+#import <AppKit/NSControl.h>
+
+@class NSCalendarDate, NSMutableArray;
+@class NSTableHeaderCell, NSTextFieldCell;
+@class OACalendarView;
+
+#import <AppKit/NSNibDeclarations.h>
+
+@interface NSObject (OACalendarViewDelegate)
+- (int)calendarView:(OACalendarView *)aCalendarView highlightMaskForVisibleMonth:(NSCalendarDate *)visibleMonth;
+- (void)calendarView:(OACalendarView *)aCalendarView willDisplayCell:(id)aCell forDate:(NSCalendarDate *)aDate;	// implement this on the target if you want to be able to set up the date cell. The cell is only used for drawing (and is reused for every date), so you can not, for instance, enable/disable dates by enabling or disabling the cell.
+- (BOOL)calendarView:(OACalendarView *)aCalendarView shouldSelectDate:(NSCalendarDate *)aDate;	// implement this on the target if you need to prevent certain dates from being selected. The target is responsible for taking into account the selection type
+- (void)calendarView:(OACalendarView *)aCalendarView didChangeVisibleMonth:(NSCalendarDate *)aDate;	// implement this on the target if you want to know when the visible month changes
+- (void)calendarViewShouldDismiss:(OACalendarView *)aCalendarView;	// implement this on the target if you want to know when someone double-clicks on a day
+@end
+
+
+typedef enum _OACalendarViewSelectionType {
+    OACalendarViewSelectByDay = 0,		// one day
+    OACalendarViewSelectByWeek = 1,		// one week (from Sunday to Saturday) 
+    OACalendarViewSelectByWeekday = 2,    	// all of one weekday (e.g. Monday) for a whole month
+} OACalendarViewSelectionType;
+
+@interface OACalendarView : NSControl
+{
+    NSCalendarDate *visibleMonth;
+    NSCalendarDate *selectedDay;
+    NSMutableString *typed;
+    NSDate *typeSelectResetTime;
+
+    NSView *monthAndYearView;
+    NSTextFieldCell *monthAndYearTextFieldCell;
+    NSTableHeaderCell *dayOfWeekCell[7];
+    NSTextFieldCell *dayOfMonthCell;
+    NSMutableArray *buttons;
+
+    int dayHighlightMask;
+    OACalendarViewSelectionType selectionType;
+    
+    float columnWidth;
+    float rowHeight;
+    NSRect monthAndYearRect;
+    NSRect gridHeaderAndBodyRect;
+    NSRect gridHeaderRect;
+    NSRect gridBodyRect;
+    
+    struct {
+        unsigned int showsDaysForOtherMonths:1;
+        unsigned int targetProvidesHighlightMask:1;
+        unsigned int targetWatchesCellDisplay:1;
+        unsigned int targetWatchesVisibleMonth:1;
+        unsigned int targetApprovesDateSelection:1;
+        unsigned int targetReceivesDismiss:1;
+    } flags;
+}
+
+- (NSCalendarDate *)visibleMonth;
+- (void)setVisibleMonth:(NSCalendarDate *)aDate;
+
+- (NSCalendarDate *)selectedDay;
+- (void)setSelectedDay:(NSCalendarDate *)newSelectedDay;
+
+- (int)dayHighlightMask;
+- (void)setDayHighlightMask:(int)newMask;
+- (void)updateHighlightMask;
+
+- (BOOL)showsDaysForOtherMonths;
+- (void)setShowsDaysForOtherMonths:(BOOL)value;
+
+- (OACalendarViewSelectionType)selectionType;
+- (void)setSelectionType:(OACalendarViewSelectionType)value;
+
+- (NSArray *)selectedDays;
+
+// Actions
+- (IBAction)previousMonth:(id)sender;
+- (IBAction)nextMonth:(id)sender;
+- (IBAction)previousYear:(id)sender;
+- (IBAction)nextYear:(id)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/OACalendarView.m
===================================================================
--- releases/Pester/1.1b6/Source/OACalendarView.m	(revision 365)
+++ releases/Pester/1.1b6/Source/OACalendarView.m	(revision 365)
@@ -0,0 +1,872 @@
+// Copyright 2001-2002 Omni Development, Inc.  All rights reserved.
+//
+// This software may only be used and reproduced according to the
+// terms in the file OmniSourceLicense.html, which should be
+// distributed with this project and can also be found at
+// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.
+
+#import "OACalendarView.h"
+#import "NSImage-OAExtensions.h"
+#import "NSCalendarDate-OFExtensions.h"
+
+#import <AppKit/AppKit.h>
+
+// RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniAppKit/Widgets.subproj/OACalendarView.m,v 1.20 2002/12/07 00:23:40 andrew Exp $")
+
+
+/*
+    Some Notes:
+    
+    - Setting the View Size: see the notes in -initWithFrame: for some guidelines for determining what size you will want to give this view. Those notes also give information about font sizes and how they affect us and the size calculations. If you set the view size to a non-optimal size, we won't use all the space.
+    
+    - Dynamically Adjusting the Cell Display: check out the "delegate" method -calendarView:willDisplayCell:forDate: in order to adjust the cell attributes (such as the font color, etc.). Note that if you make any changes which impact the cell size, the calendar is unlikely to draw as desired, so this is mostly useful for color changes. You can also use -calendarView:highlightMaskForVisibleMonth: to get highlighting of certain days. This is more efficient since we need only ask once for the month rather than once for each cell, but it is far less flexible, and currently doesn't allow control over the highlight color used. Also, don't bother to implement both methods: only the former will be used if it is available.
+    
+    - We should have a real delegate instead of treating the target as the delgate.
+    
+    - We could benefit from some more configurability: specify whether or not to draw vertical/horizontal grid lines, grid and border widths, fonts, whether or not to display the top control area, whether or not the user can change the displayed month/year independant of whether they can change the selected date, etc.
+    
+    - We could be more efficient, such as in only calculating things we need. The biggest problem (probably) is that we recalculate everything on every -drawRect:, simply because I didn't see an ideal place to know when we've resized. (With the current implementation, the monthAndYearRect would also need to be recalculated any time the month or year changes, so that the month and year will be correctly centered.)
+*/
+
+
+@interface OACalendarView (Private)
+
+- (NSButton *)_createButtonWithFrame:(NSRect)buttonFrame;
+
+- (void)_calculateSizes;
+- (void)_drawDaysOfMonthInRect:(NSRect)rect;
+- (void)_drawGridInRect:(NSRect)rect;
+
+- (float)_maximumDayOfWeekWidth;
+- (NSSize)_maximumDayOfMonthSize;
+- (float)_minimumColumnWidth;
+- (float)_minimumRowHeight;
+
+- (NSCalendarDate *)_hitDateWithLocation:(NSPoint)targetPoint;
+- (NSCalendarDate *)_hitWeekdayWithLocation:(NSPoint)targetPoint;
+
+@end
+
+
+@implementation OACalendarView
+
+const float OACalendarViewButtonWidth = 15.0;
+const float OACalendarViewButtonHeight = 15.0;
+const float OACalendarViewSpaceBetweenMonthYearAndGrid = 6.0;
+const int OACalendarViewNumDaysPerWeek = 7;
+const int OACalendarViewMaxNumWeeksIntersectedByMonth = 6;
+
+//
+// Init / dealloc
+//
+
+- (id)initWithFrame:(NSRect)frameRect;
+{
+    // The calendar will only resize on certain boundaries. "Ideal" sizes are: 
+    //     - width = (multiple of 7) + 1, where multiple >= 22; "minimum" width is 162
+    //     - height = (multiple of 6) + 39, where multiple >= 15; "minimum" height is 129
+    
+    // In reality you can shrink it smaller than the minimums given here, and it tends to look ok for a bit, but this is the "optimum" minimum. But you will want to set your size based on the guidelines above, or the calendar will not actually fill the view exactly.
+
+    // The "minimum" view size comes out to be 162w x 129h. (Where minimum.width = 23 [minimum column width] * 7 [num days per week] + 1.0 [for the side border], and minimum.height = 22 [month/year control area height; includes the space between control area and grid] + 17 [the  grid header height] + (15 [minimum row height] * 6 [max num weeks in month]). [Don't need to allow 1 for the bottom border due to the fact that there's no top border per se.]) (We used to say that the minimum height was 155w x 123h, but that was wrong - we weren't including the grid lines in the row/column sizes.)
+    // These sizes will need to be adjusted if the font changes, grid or border widths change, etc. We use the controlContentFontOfSize:11.0 for the  - if the control content font is changed our calculations will change and the above sizes will be incorrect. Similarly, we use the default NSTextFieldCell font/size for the month/year header, and the default NSTableHeaderCell font/size for the day of week headers; if either of those change, the aove sizes will be incorrect.
+
+    NSDateFormatter *monthAndYearFormatter;
+    int index;
+    NSUserDefaults *defaults;
+    NSArray *shortWeekDays;
+    NSRect buttonFrame;
+    NSButton *button;
+    NSBundle *thisBundle;
+
+    if ([super initWithFrame:frameRect] == nil)
+        return nil;
+    
+    thisBundle = [NSBundle bundleForClass: [OACalendarView class]];
+    monthAndYearTextFieldCell = [[NSTextFieldCell alloc] init];
+    monthAndYearFormatter = [[NSDateFormatter alloc] initWithDateFormat:@"%B %Y" allowNaturalLanguage:NO];
+    [monthAndYearTextFieldCell setFormatter:monthAndYearFormatter];
+    [monthAndYearTextFieldCell setFont: [NSFont boldSystemFontOfSize: [NSFont systemFontSize]]];
+    [monthAndYearFormatter release];
+
+    defaults = [NSUserDefaults standardUserDefaults];
+    shortWeekDays = [defaults objectForKey:NSShortWeekDayNameArray];
+    for (index = 0; index < OACalendarViewNumDaysPerWeek; index++) {
+        dayOfWeekCell[index] = [[NSTableHeaderCell alloc] init];
+        [dayOfWeekCell[index] setAlignment:NSCenterTextAlignment];
+        [dayOfWeekCell[index] setStringValue:[[shortWeekDays objectAtIndex:index] substringToIndex:1]];
+    }
+
+    dayOfMonthCell = [[NSTextFieldCell alloc] init];
+    [dayOfMonthCell setAlignment:NSCenterTextAlignment];
+    [dayOfMonthCell setFont:[NSFont controlContentFontOfSize:11.0]];
+
+    buttons = [[NSMutableArray alloc] initWithCapacity:2];
+
+    monthAndYearView = [[NSView alloc] initWithFrame:NSMakeRect(0.0, 0.0, frameRect.size.width, OACalendarViewButtonHeight + 2)];
+    [monthAndYearView setAutoresizingMask:NSViewWidthSizable];
+
+    // Add left/right buttons
+
+    buttonFrame = NSMakeRect(0.0, 0.0, OACalendarViewButtonWidth, OACalendarViewButtonHeight);
+    button = [self _createButtonWithFrame:buttonFrame];
+    [button setImage:[NSImage imageNamed:@"OALeftArrow" inBundle:thisBundle]];
+    [button setAlternateImage:[NSImage imageNamed:@"OALeftArrowPressed" inBundle:thisBundle]];
+    [button setAction:@selector(previous:)];
+    [button setAutoresizingMask:NSViewMaxXMargin];
+    [monthAndYearView addSubview:button];
+
+    buttonFrame = NSMakeRect(frameRect.size.width - OACalendarViewButtonWidth, 0.0, OACalendarViewButtonWidth, OACalendarViewButtonHeight);
+    button = [self _createButtonWithFrame:buttonFrame];
+    [button setImage:[NSImage imageNamed:@"OARightArrow" inBundle:thisBundle]];
+    [button setAlternateImage:[NSImage imageNamed:@"OARightArrowPressed" inBundle:thisBundle]];
+    [button setAction:@selector(next:)];
+    [button setAutoresizingMask:NSViewMinXMargin];
+    [monthAndYearView addSubview:button];
+
+    [self addSubview:monthAndYearView];
+    [monthAndYearView release];
+
+//[self sizeToFit];
+//NSLog(@"frame: %@", NSStringFromRect([self frame]));
+
+    [self setVisibleMonth:[NSCalendarDate calendarDate]];
+    [self setSelectedDay:[NSCalendarDate calendarDate]];
+    
+    return self;
+}
+
+- (void)dealloc;
+{
+    int index;
+
+    [dayOfMonthCell release];
+
+    for (index = 0; index < OACalendarViewNumDaysPerWeek; index++)
+        [dayOfWeekCell[index] release];
+
+    [monthAndYearTextFieldCell release];
+    [buttons release];
+    [selectedDay release];
+    [visibleMonth release];
+
+    [super dealloc];
+}
+
+
+//
+// NSControl overrides
+//
+
++ (Class)cellClass;
+{
+    // We need to have an NSActionCell (or subclass of that) to handle the target and action; otherwise, you just can't set those values.
+    return [NSActionCell class];
+}
+
+- (BOOL)mouseDownCanMoveWindow;
+{
+    return YES;
+}
+
+- (void)setEnabled:(BOOL)flag;
+{
+    unsigned int buttonIndex;
+
+    [super setEnabled:flag];
+    
+    buttonIndex = [buttons count];
+    while (buttonIndex--)
+        [[buttons objectAtIndex:buttonIndex] setEnabled:flag];
+}
+
+- (void)sizeToFit;
+{
+    NSSize minimumSize;
+
+    // we need calculateSizes in order to get the monthAndYearRect; would be better to restructure some of that
+    // it would be good to refactor the size calculation (or pass it some parameters) so that we could merely calculate the stuff we need (or have _calculateSizes do all our work, based on the parameters we provide)
+    [self _calculateSizes];
+
+    minimumSize.height = monthAndYearRect.size.height + gridHeaderRect.size.height + ((OACalendarViewMaxNumWeeksIntersectedByMonth * [self _minimumRowHeight]));
+    // This should really check the lengths of the months, and include space for the buttons.
+    minimumSize.width = ([self _minimumColumnWidth] * OACalendarViewNumDaysPerWeek) + 1.0;
+
+    [self setFrameSize:minimumSize];
+    [self setNeedsDisplay:YES];
+}
+
+
+//
+// NSView overrides
+//
+
+- (BOOL)needsPanelToBecomeKey;
+{
+    return YES;
+}
+
+- (BOOL)isFlipped;
+{
+    return YES;
+}
+
+- (void)drawRect:(NSRect)rect;
+{
+    int columnIndex;
+    NSRect tempRect;
+    
+    [self _calculateSizes];
+    
+// for testing, to see if there's anything we're not covering
+//[[NSColor greenColor] set];
+//NSRectFill(gridHeaderAndBodyRect);
+// or...
+//NSRectFill([self bounds]);
+    
+    // draw the month/year
+    [monthAndYearTextFieldCell drawWithFrame:monthAndYearRect inView:self];
+    
+    // draw the grid header
+    tempRect = gridHeaderRect;
+    tempRect.size.width = columnWidth;
+    for (columnIndex = 0; columnIndex < OACalendarViewNumDaysPerWeek; columnIndex++) {
+        [dayOfWeekCell[columnIndex] drawWithFrame:tempRect inView:self];
+        tempRect.origin.x += columnWidth;
+    }
+
+    // draw the grid background
+    [[NSColor controlBackgroundColor] set];
+    NSRectFill(gridBodyRect);
+
+    // fill in the grid
+    [self _drawGridInRect:gridBodyRect];
+    [self _drawDaysOfMonthInRect:gridBodyRect];
+    
+    // draw a border around the whole thing. This ends up drawing over the top and right side borders of the header, but that's ok because we don't want their border, we want ours. Also, it ends up covering any overdraw from selected sundays and saturdays, since the selected day covers the bordering area where vertical grid lines would be (an aesthetic decision because we don't draw vertical grid lines, another aesthetic decision).
+    [[NSColor gridColor] set];
+    NSFrameRect(gridHeaderAndBodyRect);
+
+}
+
+- (void)mouseDown:(NSEvent *)mouseEvent;
+{
+    if ([self isEnabled]) {
+        NSCalendarDate *hitDate;
+        NSPoint location;
+    
+        location = [self convertPoint:[mouseEvent locationInWindow] fromView:nil];
+        hitDate = [self _hitDateWithLocation:location];
+        if (hitDate) {
+            id target = [self target];
+            if (!flags.targetApprovesDateSelection || [target calendarView:self shouldSelectDate:hitDate]) {
+                [self setSelectedDay:hitDate];
+                [self setVisibleMonth:hitDate];
+                if (flags.targetReceivesDismiss && [mouseEvent clickCount] == 2)
+                    [target calendarViewShouldDismiss: target];
+                [self sendAction:[self action] to:target];
+            }
+            
+        } else if (selectionType == OACalendarViewSelectByWeekday) {
+            NSCalendarDate *hitWeekday;
+            
+            hitWeekday = [self _hitWeekdayWithLocation:location];
+            if (hitWeekday) {
+                id target = [self target];
+                if (!flags.targetApprovesDateSelection || [target calendarView:self shouldSelectDate:hitWeekday]) {
+                    [self setSelectedDay:hitWeekday];
+                    [self sendAction:[self action] to: target];
+                    if (flags.targetReceivesDismiss && [mouseEvent clickCount] == 2)
+                        [target calendarViewShouldDismiss: target];
+                }
+            }
+        }
+    }
+}
+
+
+//
+// API
+//
+
+- (NSCalendarDate *)visibleMonth;
+{
+    return visibleMonth;
+}
+
+- (void)setVisibleMonth:(NSCalendarDate *)aDate;
+{
+    [visibleMonth release];
+    visibleMonth = [[aDate firstDayOfMonth] retain];
+    [monthAndYearTextFieldCell setObjectValue:visibleMonth];
+
+    [self updateHighlightMask];
+    [self setNeedsDisplay:YES];
+    
+    if (flags.targetWatchesVisibleMonth)
+        [[self target] calendarView:self didChangeVisibleMonth:visibleMonth];
+}
+
+- (NSCalendarDate *)selectedDay;
+{
+    return selectedDay;
+}
+
+- (void)setSelectedDay:(NSCalendarDate *)newSelectedDay;
+{
+    if (newSelectedDay == selectedDay || [newSelectedDay isEqual:selectedDay])
+        return;
+    
+    [selectedDay release];
+    selectedDay = [newSelectedDay retain];
+    [self setNeedsDisplay:YES];
+}
+
+- (int)dayHighlightMask;
+{
+    return dayHighlightMask;
+}
+
+- (void)setDayHighlightMask:(int)newMask;
+{
+    dayHighlightMask = newMask;
+    [self setNeedsDisplay:YES];
+}
+
+- (void)updateHighlightMask;
+{
+    if (flags.targetProvidesHighlightMask) {
+        int mask;
+        mask = [[self target] calendarView:self highlightMaskForVisibleMonth:visibleMonth];
+        [self setDayHighlightMask:mask];
+    } else
+        [self setDayHighlightMask:0];
+
+    [self setNeedsDisplay:YES];
+}
+
+- (BOOL)showsDaysForOtherMonths;
+{
+    return flags.showsDaysForOtherMonths;
+}
+
+- (void)setShowsDaysForOtherMonths:(BOOL)value;
+{
+    if (value != flags.showsDaysForOtherMonths) {
+        flags.showsDaysForOtherMonths = value;
+
+        [self setNeedsDisplay:YES];
+    }
+}
+
+- (OACalendarViewSelectionType)selectionType;
+{
+    return selectionType;
+}
+
+- (void)setSelectionType:(OACalendarViewSelectionType)value;
+{
+    NSParameterAssert((value == OACalendarViewSelectByDay) || (value == OACalendarViewSelectByWeek) || (value == OACalendarViewSelectByWeekday));
+    if (selectionType != value) {
+        selectionType = value;
+
+        [self setNeedsDisplay:YES];
+    }
+}
+
+- (NSArray *)selectedDays;
+{
+    if (!selectedDay)
+        return nil;
+
+    switch (selectionType) {
+        case OACalendarViewSelectByDay:
+            return [NSArray arrayWithObject:selectedDay];
+            break;
+            
+        case OACalendarViewSelectByWeek:
+            {
+                NSMutableArray *days;
+                NSCalendarDate *day;
+                int index;
+                
+                days = [NSMutableArray arrayWithCapacity:OACalendarViewNumDaysPerWeek];
+                day = [selectedDay dateByAddingYears:0 months:0 days:-[selectedDay dayOfWeek] hours:0 minutes:0 seconds:0];
+                for (index = 0; index < OACalendarViewNumDaysPerWeek; index++) {
+                    NSCalendarDate *nextDay;
+
+                    nextDay = [day dateByAddingYears:0 months:0 days:index hours:0 minutes:0 seconds:0];
+                    if (flags.showsDaysForOtherMonths || [nextDay monthOfYear] == [selectedDay monthOfYear])
+                        [days addObject:nextDay];                    
+                }
+            
+                return days;
+            }            
+            break;
+
+        case OACalendarViewSelectByWeekday:
+            {
+                NSMutableArray *days;
+                NSCalendarDate *day;
+                int index;
+                
+                days = [NSMutableArray arrayWithCapacity:OACalendarViewMaxNumWeeksIntersectedByMonth];
+                day = [selectedDay dateByAddingYears:0 months:0 days:-(([selectedDay weekOfMonth] - 1) * OACalendarViewNumDaysPerWeek) hours:0 minutes:0 seconds:0];
+                for (index = 0; index < OACalendarViewMaxNumWeeksIntersectedByMonth; index++) {
+                    NSCalendarDate *nextDay;
+
+                    nextDay = [day dateByAddingYears:0 months:0 days:(index * OACalendarViewNumDaysPerWeek) hours:0 minutes:0 seconds:0];
+                    if (flags.showsDaysForOtherMonths || [nextDay monthOfYear] == [selectedDay monthOfYear])
+                        [days addObject:nextDay];
+                }
+
+                return days;
+            }
+            break;
+            
+        default:
+            [NSException raise:NSInvalidArgumentException format:@"OACalendarView: Unknown selection type: %d", selectionType];
+            return nil;
+            break;
+    }
+}
+
+
+//
+// Actions
+//
+
+- (IBAction)previous:(id)sender;
+{
+    if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
+        [self previousYear: sender];
+    else
+        [self previousMonth: sender];
+}
+
+- (IBAction)next:(id)sender;
+{
+    if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
+        [self nextYear: sender];
+    else
+        [self nextMonth: sender];
+}
+
+- (IBAction)previousMonth:(id)sender;
+{
+    NSCalendarDate *newDate;
+
+    newDate = [visibleMonth dateByAddingYears:0 months:-1 days:0 hours:0 minutes:0 seconds:0];
+    [self setVisibleMonth:newDate];
+}
+
+- (IBAction)nextMonth:(id)sender;
+{
+    NSCalendarDate *newDate;
+
+    newDate = [visibleMonth dateByAddingYears:0 months:1 days:0 hours:0 minutes:0 seconds:0];
+    [self setVisibleMonth:newDate];
+}
+
+- (IBAction)previousYear:(id)sender;
+{
+    NSCalendarDate *newDate;
+
+    newDate = [visibleMonth dateByAddingYears:-1 months:0 days:0 hours:0 minutes:0 seconds:0];
+    [self setVisibleMonth:newDate];
+}
+
+- (IBAction)nextYear:(id)sender;
+{
+    NSCalendarDate *newDate;
+
+    newDate = [visibleMonth dateByAddingYears:1 months:0 days:0 hours:0 minutes:0 seconds:0];
+    [self setVisibleMonth:newDate];
+}
+
+- (void)keyDown:(NSEvent *)theEvent;
+{
+    BOOL commandKey = ([theEvent modifierFlags] & NSCommandKeyMask) != 0;
+    BOOL optionKey = ([theEvent modifierFlags] & NSAlternateKeyMask) != 0;
+    NSCalendarDate *newDate = nil;
+    unichar firstCharacter = [[theEvent characters] characterAtIndex: 0];
+    // move by week, or month/year if modified
+    if (firstCharacter == NSUpArrowFunctionKey) {
+        if (commandKey) firstCharacter = NSLeftArrowFunctionKey;
+        else newDate = [selectedDay dateByAddingYears:0 months:0 days:-7 hours:0 minutes:0 seconds:0];
+    } else if (firstCharacter == NSDownArrowFunctionKey) {
+        if (commandKey) firstCharacter = NSRightArrowFunctionKey;
+        else newDate = [selectedDay dateByAddingYears:0 months:0 days:7 hours:0 minutes:0 seconds:0];
+    }
+    // move by day, or month/year if modified
+    if (firstCharacter == NSLeftArrowFunctionKey) {
+        if (commandKey) {
+            if (optionKey)
+                newDate = [selectedDay dateByAddingYears:-1 months:0 days:0 hours:0 minutes:0 seconds:0];
+            else
+                newDate = [selectedDay dateByAddingYears:0 months:-1 days:0 hours:0 minutes:0 seconds:0];
+        } else newDate = [selectedDay dateByAddingYears:0 months:0 days:-1 hours:0 minutes:0 seconds:0];
+    } else if (firstCharacter == NSRightArrowFunctionKey) {
+        if (commandKey) {
+            if (optionKey)
+                newDate = [selectedDay dateByAddingYears:1 months:0 days:0 hours:0 minutes:0 seconds:0];
+            else
+                newDate = [selectedDay dateByAddingYears:0 months:1 days:0 hours:0 minutes:0 seconds:0];
+        } else newDate = [selectedDay dateByAddingYears:0 months:0 days:1 hours:0 minutes:0 seconds:0];
+    } else if (firstCharacter >= '0' && firstCharacter <= '9') {
+        // For consistency with List Manager as documented, reset the typeahead buffer after twice the delay until key repeat (in ticks).
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        int keyRepeatTicks = [defaults integerForKey: @"InitialKeyRepeat"];
+        NSTimeInterval resetDelay;
+
+        if (keyRepeatTicks == 0) keyRepeatTicks = 35; // default may be missing; if so, set default
+
+        resetDelay = MIN(2.0 / 60.0 * keyRepeatTicks, 2.0);
+
+        if (typed == nil) typed = [[NSMutableString alloc] init];
+        else if (typeSelectResetTime != nil && [typeSelectResetTime compare: [NSDate date]] == NSOrderedAscending)
+            [typed setString: @""];
+        if ([typed length] != 0 || firstCharacter != '0') // don't construct a string 000... because it'll mess up length measurement for deciding whether to select a day
+            CFStringAppendCharacters((CFMutableStringRef)typed, &firstCharacter, 1);
+
+        [typeSelectResetTime release];
+        typeSelectResetTime = [[NSDate dateWithTimeIntervalSinceNow: resetDelay] retain];
+
+        int length = [typed length];
+        if (length > 2) {
+            [typed deleteCharactersInRange: NSMakeRange(0, length - 2)];
+            length = 2;
+        }
+        if (length == 1 || length == 2) {
+            int dayOfMonth = [typed intValue], daysInMonth = [selectedDay numberOfDaysInMonth];
+            if (dayOfMonth >= daysInMonth) {
+                [typed deleteCharactersInRange: NSMakeRange(0, 1)];
+                dayOfMonth = [typed intValue];
+            }
+            if (dayOfMonth > 0)
+                newDate = [selectedDay dateByAddingYears:0 months:0 days:dayOfMonth - [selectedDay dayOfMonth] hours:0 minutes:0 seconds:0];
+        }
+    }
+    if (newDate != nil) {
+        if (flags.targetApprovesDateSelection && ![[self target] calendarView: self shouldSelectDate: newDate])
+            return;
+        if (([selectedDay monthOfYear] != [newDate monthOfYear]) || ([selectedDay yearOfCommonEra] != [newDate yearOfCommonEra]))
+            [self setVisibleMonth: newDate];
+        [self setSelectedDay: newDate];
+        return;
+    }
+    [super keyDown: theEvent];
+}
+
+@end
+
+
+@implementation OACalendarView (Private)
+
+- (NSButton *)_createButtonWithFrame:(NSRect)buttonFrame;
+{
+    NSButton *button;
+    
+    button = [[NSButton alloc] initWithFrame:buttonFrame];
+    [button setBezelStyle:NSShadowlessSquareBezelStyle];
+    [button setBordered:NO];
+    [button setImagePosition:NSImageOnly];
+    [button setTarget:self];
+    [button setContinuous:YES];
+//    [self addSubview:button];
+    [buttons addObject:button];
+    [button release];
+
+    return button;
+}
+
+- (void)setTarget:(id)value;
+{
+    [super setTarget:value];
+    flags.targetProvidesHighlightMask = [value respondsToSelector:@selector(calendarView:highlightMaskForVisibleMonth:)];
+    flags.targetWatchesCellDisplay = [value respondsToSelector:@selector(calendarView:willDisplayCell:forDate:)];
+    flags.targetApprovesDateSelection = [value respondsToSelector:@selector(calendarView:shouldSelectDate:)];
+    flags.targetWatchesVisibleMonth = [value respondsToSelector:@selector(calendarView:didChangeVisibleMonth:)];
+    flags.targetReceivesDismiss = [value respondsToSelector:@selector(calendarViewShouldDismiss:)];
+}
+
+- (void)_calculateSizes;
+{
+    NSSize cellSize;
+    NSRect viewBounds;
+    NSRect topRect;
+    NSRect discardRect;
+    NSRect tempRect;
+
+    viewBounds = [self bounds];
+    
+    // get the grid cell width (subtract 1.0 from the bounds width to allow for the border)
+    columnWidth = floor((viewBounds.size.width - 1.0) / OACalendarViewNumDaysPerWeek);
+    viewBounds.size.width = (columnWidth * OACalendarViewNumDaysPerWeek) + 1.0;
+    
+    // resize the month & year view to be the same width as the grid
+    [monthAndYearView setFrameSize:NSMakeSize(viewBounds.size.width, [monthAndYearView frame].size.height)];
+
+    // get the rect for the month and year text field cell
+    cellSize = [monthAndYearTextFieldCell cellSize];
+    NSDivideRect(viewBounds, &topRect, &gridHeaderAndBodyRect, ceil(cellSize.height + OACalendarViewSpaceBetweenMonthYearAndGrid), NSMinYEdge);
+    NSDivideRect(topRect, &discardRect, &monthAndYearRect, floor((viewBounds.size.width - cellSize.width) / 2), NSMinXEdge);
+    monthAndYearRect.size.width = cellSize.width;
+    
+    tempRect = gridHeaderAndBodyRect;
+    // leave space for a one-pixel border on each side
+    tempRect.size.width -= 2.0;
+    tempRect.origin.x += 1.0;
+    // leave space for a one-pixel border at the bottom (the top already looks fine)
+    tempRect.size.height -= 1.0;
+
+    // get the grid header rect
+    cellSize = [dayOfWeekCell[0] cellSize];
+    NSDivideRect(tempRect, &gridHeaderRect, &gridBodyRect, ceil(cellSize.height), NSMinYEdge);
+    
+    // get the grid row height (add 1.0 to the body height because while we can't actually draw on that extra pixel, our bottom row doesn't have to draw a bottom grid line as there's a border right below us, so we need to account for that, which we do by pretending that next pixel actually does belong to us)
+    rowHeight = floor((gridBodyRect.size.height + 1.0) / OACalendarViewMaxNumWeeksIntersectedByMonth);
+    
+    // get the grid body rect
+    gridBodyRect.size.height = (rowHeight * OACalendarViewMaxNumWeeksIntersectedByMonth) - 1.0;
+    
+    // adjust the header and body rect to account for any adjustment made while calculating even row heights
+    gridHeaderAndBodyRect.size.height = NSMaxY(gridBodyRect) - NSMinY(gridHeaderAndBodyRect) + 1.0;
+}
+
+- (void)_drawDaysOfMonthInRect:(NSRect)rect;
+{
+    NSRect cellFrame;
+    NSRect dayOfMonthFrame;
+    NSRect discardRect;
+    int visibleMonthIndex;
+    NSCalendarDate *thisDay;
+    int index, row, column;
+    NSSize cellSize;
+    BOOL isFirstResponder = ([[self window] firstResponder] == self);
+
+    // the cell is actually one pixel shorter than the row height, because the row height includes the bottom grid line (or the top grid line, depending on which way you prefer to think of it)
+    cellFrame.size.height = rowHeight - 1.0;
+    // the cell would actually be one pixel narrower than the column width but we don't draw vertical grid lines. instead, we want to include the area that would be grid line (were we drawing it) in our cell, because that looks a bit better under the header, which _does_ draw column separators. actually, we want to include the grid line area on _both sides_ or it looks unbalanced, so we actually _add_ one pixel, to cover that. below, our x position as we draw will have to take that into account. note that this means that sunday and saturday overwrite the outside borders, but the outside border is drawn last, so it ends up ok. (if we ever start drawing vertical grid lines, change this to be - 1.0, and adjust the origin appropriately below.)
+    cellFrame.size.width = columnWidth + 1.0;
+
+    cellSize = [dayOfMonthCell cellSize];
+    
+    visibleMonthIndex = [visibleMonth monthOfYear];
+
+    thisDay = [visibleMonth dateByAddingYears:0 months:0 days:-[visibleMonth dayOfWeek] hours:0 minutes:0 seconds:0];
+
+    for (row = column = index = 0; index < OACalendarViewMaxNumWeeksIntersectedByMonth * OACalendarViewNumDaysPerWeek; index++) {
+        NSColor *textColor;
+        BOOL isVisibleMonth;
+
+        // subtract 1.0 from the origin because we're including the area where vertical grid lines would be were we drawing them
+        cellFrame.origin.x = rect.origin.x + (column * columnWidth) - 1.0;
+        cellFrame.origin.y = rect.origin.y + (row * rowHeight);
+
+        [dayOfMonthCell setIntValue:[thisDay dayOfMonth]];
+        isVisibleMonth = ([thisDay monthOfYear] == visibleMonthIndex);
+
+        if (flags.showsDaysForOtherMonths || isVisibleMonth) {
+            if (selectedDay) {
+                BOOL shouldHighlightThisDay = NO;
+
+                // We could just check if thisDay is in [self selectedDays]. However, that makes the selection look somewhat weird when we
+                // are selecting by weekday, showing days for other months, and the visible month is the previous/next from the selected day.
+                // (Some of the weekdays are shown as highlighted, and later ones are not.)
+                // So, we fib a little to make things look better.
+                switch (selectionType) {
+                    case OACalendarViewSelectByDay:
+                        shouldHighlightThisDay = ([selectedDay dayOfCommonEra] == [thisDay dayOfCommonEra]);
+                        break;
+                        
+                    case OACalendarViewSelectByWeek:
+                        shouldHighlightThisDay = [selectedDay isInSameWeekAsDate:thisDay];
+                        break;
+                        
+                    case OACalendarViewSelectByWeekday:
+                        shouldHighlightThisDay = ([selectedDay monthOfYear] == visibleMonthIndex && [selectedDay dayOfWeek] == [thisDay dayOfWeek]);
+                        break;
+                        
+                    default:
+                        [NSException raise:NSInvalidArgumentException format:@"OACalendarView: Unknown selection type: %d", selectionType];
+                        break;
+                }
+                
+                if (shouldHighlightThisDay) {
+                    [(isFirstResponder ? [NSColor selectedControlColor] : [NSColor secondarySelectedControlColor]) set];
+                    NSRectFill(cellFrame);
+                }
+            }
+            
+            if (flags.targetWatchesCellDisplay) {
+                [[self target] calendarView:self willDisplayCell:dayOfMonthCell forDate:thisDay];
+            } else {
+                if ((dayHighlightMask & (1 << index)) == 0) {
+                    textColor = (isVisibleMonth ? [NSColor blackColor] : [NSColor grayColor]);
+                } else {
+                    textColor = [NSColor blueColor];
+                }
+                [dayOfMonthCell setTextColor:textColor];
+            }
+            NSDivideRect(cellFrame, &discardRect, &dayOfMonthFrame, floor((cellFrame.size.height - cellSize.height) / 2.0), NSMinYEdge);
+            [dayOfMonthCell drawWithFrame:dayOfMonthFrame inView:self];
+        }
+        
+        thisDay = [thisDay dateByAddingYears:0 months:0 days:1 hours:0 minutes:0 seconds:0];
+        column++;
+        if (column > OACalendarViewMaxNumWeeksIntersectedByMonth) {
+            column = 0;
+            row++;
+        }
+    }
+}
+
+- (void)_drawGridInRect:(NSRect)rect;
+{
+    NSPoint pointA;
+    NSPoint pointB;
+    int weekIndex;
+    
+    // we will be adding the row height each time, so subtract 1.0 (the grid thickness) from the starting y position (for example, if starting y = 0 and row height = 10, then starting y + row height = 10, so we would draw at pixel 10... which is the 11th pixel. Basically, we subtract 1.0 to make the result zero-based, so that we draw at pixel 10 - 1 = 9, which is the 10th pixel)
+    // add 0.5 to move to the center of the pixel before drawing a line 1.0 pixels thick, centered around 0.0 (which would mean half a pixel above the starting point and half a pixel below - not what we want)
+    // we could just subtract 0.5, but I think this is clearer, and the compiler will optimize it to the appropriate value for us
+    pointA = NSMakePoint(NSMinX(rect), NSMinY(rect) - 1.0 + 0.5);
+    pointB = NSMakePoint(NSMaxX(rect), NSMinY(rect) - 1.0 + 0.5);
+    
+    [[NSColor controlHighlightColor] set];
+    for (weekIndex = 1; weekIndex < OACalendarViewMaxNumWeeksIntersectedByMonth; weekIndex++) {
+        pointA.y += rowHeight;
+        pointB.y += rowHeight;
+        [NSBezierPath strokeLineFromPoint:pointA toPoint:pointB];
+    }
+    
+#if 0
+// we would do this if we wanted to draw columns in the grid
+    {
+        int dayIndex;
+        
+        // see aov for explanation of why we subtract 1.0 and add 0.5 to the x position
+        pointA = NSMakePoint(NSMinX(rect) - 1.0 + 0.5, NSMinY(rect));
+        pointB = NSMakePoint(NSMinX(rect) - 1.0 + 0.5, NSMaxY(rect));
+        
+        for (dayIndex = 1; dayIndex < OACalendarViewNumDaysPerWeek; dayIndex++) {
+            pointA.x += columnWidth;
+            pointB.x += columnWidth;
+            [NSBezierPath strokeLineFromPoint:pointA toPoint:pointB];
+        }
+    }
+#endif
+}
+
+- (float)_maximumDayOfWeekWidth;
+{
+    float maxWidth;
+    int index;
+
+    maxWidth = 0;
+    for (index = 0; index < OACalendarViewNumDaysPerWeek; index++) {
+        NSSize cellSize;
+
+        cellSize = [dayOfWeekCell[index] cellSize];
+        if (maxWidth < cellSize.width)
+            maxWidth = cellSize.width;
+    }
+
+    return ceil(maxWidth);
+}
+
+- (NSSize)_maximumDayOfMonthSize;
+{
+    NSSize maxSize;
+    int index;
+
+    maxSize = NSZeroSize; // I'm sure the height doesn't change, but I need to know the height anyway.
+    for (index = 1; index <= 31; index++) {
+        NSString *str;
+        NSSize cellSize;
+
+        str = [NSString stringWithFormat:@"%d", index];
+        [dayOfMonthCell setStringValue:str];
+        cellSize = [dayOfMonthCell cellSize];
+        if (maxSize.width < cellSize.width)
+            maxSize.width = cellSize.width;
+        if (maxSize.height < cellSize.height)
+            maxSize.height = cellSize.height;
+    }
+
+    maxSize.width = ceil(maxSize.width);
+    maxSize.height = ceil(maxSize.height);
+
+    return maxSize;
+}
+
+- (float)_minimumColumnWidth;
+{
+    float dayOfWeekWidth;
+    float dayOfMonthWidth;
+    
+    dayOfWeekWidth = [self _maximumDayOfWeekWidth];	// we don't have to add 1.0 because the day of week cell whose width is returned here includes it's own border
+    dayOfMonthWidth = [self _maximumDayOfMonthSize].width + 1.0;	// add 1.0 to allow for the grid. We don't actually draw the vertical grid, but we treat it as if there was one (don't respond to clicks "on" the grid, we have a vertical separator in the header, etc.) 
+    return (dayOfMonthWidth > dayOfWeekWidth) ? dayOfMonthWidth : dayOfWeekWidth;
+}
+
+- (float)_minimumRowHeight;
+{
+    return [self _maximumDayOfMonthSize].height + 1.0;	// add 1.0 to allow for a bordering grid line
+}
+
+- (NSCalendarDate *)_hitDateWithLocation:(NSPoint)targetPoint;
+{
+    int hitRow, hitColumn;
+    int firstDayOfWeek, targetDayOfMonth;
+    NSPoint offset;
+
+    if (NSPointInRect(targetPoint, gridBodyRect) == NO)
+        return nil;
+
+    firstDayOfWeek = [[visibleMonth firstDayOfMonth] dayOfWeek];
+
+    offset = NSMakePoint(targetPoint.x - gridBodyRect.origin.x, targetPoint.y - gridBodyRect.origin.y);
+    // if they exactly hit the grid between days, treat that as a miss
+    if ((selectionType != OACalendarViewSelectByWeekday) && (((int)offset.y % (int)rowHeight) == 0))
+        return nil;
+    // if they exactly hit the grid between days, treat that as a miss
+    if ((selectionType != OACalendarViewSelectByWeek) && ((int)offset.x % (int)columnWidth) == 0)
+        return nil;
+    hitRow = (int)(offset.y / rowHeight);
+    hitColumn = (int)(offset.x / columnWidth);
+
+    targetDayOfMonth = (hitRow * OACalendarViewNumDaysPerWeek) + hitColumn - firstDayOfWeek + 1;
+    if (!flags.showsDaysForOtherMonths && (targetDayOfMonth < 1 || targetDayOfMonth > [visibleMonth numberOfDaysInMonth]))
+        return nil;
+
+    return [visibleMonth dateByAddingYears:0 months:0 days:targetDayOfMonth-1 hours:0 minutes:0 seconds:0];
+}
+
+- (NSCalendarDate *)_hitWeekdayWithLocation:(NSPoint)targetPoint;
+{
+    int hitDayOfWeek;
+    int firstDayOfWeek, targetDayOfMonth;
+    float offsetX;
+
+    if (NSPointInRect(targetPoint, gridHeaderRect) == NO)
+        return nil;
+    
+    offsetX = targetPoint.x - gridHeaderRect.origin.x;
+    // if they exactly hit a border between weekdays, treat that as a miss (besides being neat in general, this avoids the problem where clicking on the righthand border would result in us incorrectly calculating that the _first_ day of the week was hit)
+    if (((int)offsetX % (int)columnWidth) == 0)
+        return nil;
+    
+    hitDayOfWeek = offsetX / columnWidth;
+
+    firstDayOfWeek = [[visibleMonth firstDayOfMonth] dayOfWeek];
+    if (hitDayOfWeek >= firstDayOfWeek)
+        targetDayOfMonth = hitDayOfWeek - firstDayOfWeek + 1;
+    else
+        targetDayOfMonth = hitDayOfWeek + OACalendarViewNumDaysPerWeek - firstDayOfWeek + 1;
+
+    return [visibleMonth dateByAddingYears:0 months:0 days:targetDayOfMonth-1 hours:0 minutes:0 seconds:0];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarm.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarm.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarm.h	(revision 365)
@@ -0,0 +1,82 @@
+//
+//  PSAlarm.h
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Oct 09 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "PSPropertyListSerialization.h"
+
+typedef enum {
+    PSAlarmInvalid, // incorrectly specified
+    PSAlarmInterval, // interval specified (possibly repeating)
+    PSAlarmDate, // date specified
+    PSAlarmSet, // pending, timer set
+    PSAlarmSnooze, // expired alarm with snooze interval set (possibly repeating)
+    PSAlarmExpired // expired alarm (possibly repeating)
+} PSAlarmType;
+
+extern NSString * const PSAlarmTimerSetNotification;
+extern NSString * const PSAlarmTimerExpiredNotification;
+extern NSString * const PSAlarmDiedNotification;
+
+@class PSAlert, PSAlerts, PSTimer;
+
+@interface PSAlarm : NSObject <NSCoding, PSPropertyListSerialization> {
+    PSAlarmType alarmType; // changes during lifetime of alarm; more like a state
+    NSCalendarDate *alarmDate;
+    NSTimeInterval alarmInterval;
+    NSTimeInterval snoozeInterval;
+    NSTimeInterval timeRemaining;
+    NSString *alarmMessage;
+    NSString *invalidMessage;
+    PSTimer *timer;
+    PSAlerts *alerts;
+    BOOL repeating;
+}
+
+- (void)setInterval:(NSTimeInterval)anInterval;
+- (void)setForDateAtTime:(NSCalendarDate *)dateTime;
+- (void)setForDate:(NSDate *)date atTime:(NSDate *)time;
+- (void)setMessage:(NSString *)aMessage;
+- (void)setAlerts:(PSAlerts *)theAlerts;
+- (void)setRepeating:(BOOL)isRepeating;
+- (void)setSnoozeInterval:(NSTimeInterval)anInterval;
+- (void)setWakeUp:(BOOL)doWake;
+
+- (NSCalendarDate *)date;
+- (NSCalendarDate *)time;
+- (NSTimeInterval)interval;
+- (NSTimeInterval)timeRemaining;
+- (NSString *)message;
+- (PSAlerts *)alerts;
+- (BOOL)isRepeating;
+- (NSTimeInterval)snoozeInterval; // most recent interval (nonzero return does not indicate alarm is snoozing or set to snooze)
+
+- (NSString *)dateString;
+- (NSString *)shortDateString;
+- (NSString *)timeString;
+- (NSString *)dateTimeString; // current or next alarm time
+- (NSString *)nextDateTimeString; // next alarm time
+- (NSString *)intervalString;
+- (NSString *)timeRemainingString;
+
+- (BOOL)isValid;
+- (NSString *)invalidMessage;
+
+- (NSAttributedString *)prettyDescription;
+
+- (NSComparisonResult)compareDate:(PSAlarm *)otherAlarm;
+- (NSComparisonResult)compareMessage:(PSAlarm *)otherAlarm;
+
+- (BOOL)setTimer; // or die, if expired and no snooze/repeat
+- (void)cancelTimer;
+- (void)resetTimer; // use after cancel, only effective on set alarms
+
+// 1.1 only, going away when we move to keyed archiving
+- (NSDictionary *)propertyListRepresentation;
+- (id)initWithPropertyList:(NSDictionary *)dict;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarm.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarm.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarm.m	(revision 365)
@@ -0,0 +1,579 @@
+//
+//  PSAlarm.m
+//  Pester
+//
+//  Created by Nicholas Riley on Wed Oct 09 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarm.h"
+#import "PSAlert.h"
+#import "PSAlerts.h"
+#import "PSTimer.h"
+#import "NSCalendarDate-NJRExtensions.h"
+#import "NSDictionary-NJRExtensions.h"
+#import "NSString-NJRExtensions.h"
+
+NSString * const PSAlarmTimerSetNotification = @"PSAlarmTimerSetNotification";
+NSString * const PSAlarmTimerExpiredNotification = @"PSAlarmTimerExpiredNotification";
+NSString * const PSAlarmDiedNotification = @"PSAlarmDiedNotification";
+
+// property list keys
+static NSString * const PLAlarmType = @"type"; // NSString
+static NSString * const PLAlarmDate = @"date"; // NSNumber
+static NSString * const PLAlarmInterval = @"interval"; // NSNumber
+static NSString * const PLAlarmSnoozeInterval = @"snooze interval"; // NSNumber
+static NSString * const PLAlarmMessage = @"message"; // NSString
+static NSString * const PLAlarmAlerts = @"alerts"; // NSDictionary
+static NSString * const PLAlarmRepeating = @"repeating"; // NSNumber
+
+static NSDateFormatter *dateFormatter, *shortDateFormatter, *timeFormatter;
+
+@implementation PSAlarm
+
+#pragma mark initialize-release
+
++ (void)initialize;
+{
+    [NSDateFormatter setDefaultFormatterBehavior: NSDateFormatterBehavior10_4];
+    dateFormatter = [[NSDateFormatter alloc] init];
+    [dateFormatter setTimeStyle: NSDateFormatterNoStyle];
+    [dateFormatter setDateStyle: NSDateFormatterFullStyle];
+    shortDateFormatter = [[NSDateFormatter alloc] init];
+    [shortDateFormatter setTimeStyle: NSDateFormatterNoStyle];
+    [shortDateFormatter setDateStyle: NSDateFormatterShortStyle];
+    timeFormatter = [[NSDateFormatter alloc] init];
+    [timeFormatter setTimeStyle: NSDateFormatterMediumStyle];
+    [timeFormatter setDateStyle: NSDateFormatterNoStyle];
+}
+
+- (void)dealloc;
+{
+    // NSLog(@"DEALLOC %@", self);
+    alarmType = PSAlarmInvalid;
+    [alarmDate release]; alarmDate = nil;
+    [alarmMessage release]; alarmMessage = nil;
+    [invalidMessage release]; invalidMessage = nil;
+    [timer invalidate]; [timer release]; timer = nil;
+    [alerts release]; alerts = nil;
+    [super dealloc];
+}
+
+#pragma mark private
+
+- (void)_setAlarmDate:(NSCalendarDate *)aDate;
+{
+    if (alarmDate != aDate) {
+        [alarmDate release];
+        alarmDate = nil;
+        alarmDate = [aDate retain];
+    }
+}
+
+- (void)_beInvalid:(NSString *)aMessage;
+{
+    alarmType = PSAlarmInvalid;
+    if (aMessage != invalidMessage) {
+        [invalidMessage release];
+        invalidMessage = nil;
+        [self _setAlarmDate: nil];
+        alarmInterval = 0;
+        invalidMessage = [aMessage retain];
+    }
+}
+
+- (void)_beValidWithType:(PSAlarmType)type;
+{
+    if (alarmType == PSAlarmSet) return; // already valid
+    [invalidMessage release];
+    invalidMessage = nil;
+    alarmType = type;
+    if (type != PSAlarmInterval) [self setRepeating: NO];
+}
+
+- (void)_setDateFromInterval;
+{
+    [self _setAlarmDate: [NSCalendarDate dateWithTimeIntervalSinceNow: alarmInterval]];
+    [self _beValidWithType: PSAlarmInterval];
+}
+
+- (void)_setIntervalFromDate;
+{
+    alarmInterval = [alarmDate timeIntervalSinceNow];
+    if (alarmInterval <= 0) {
+        [self _beInvalid: @"Please specify an alarm time in the future."];
+        return;
+    }
+    [self _beValidWithType: PSAlarmDate];
+}
+
+- (PSAlarmType)_alarmTypeForString:(NSString *)string;
+{
+    if ([string isEqualToString: @"PSAlarmDate"]) return PSAlarmDate;
+    if ([string isEqualToString: @"PSAlarmInterval"]) return PSAlarmInterval;
+    if ([string isEqualToString: @"PSAlarmSet"]) return PSAlarmSet;
+    if ([string isEqualToString: @"PSAlarmInvalid"]) return PSAlarmInvalid;
+    if ([string isEqualToString: @"PSAlarmSnooze"]) return PSAlarmSnooze;
+    if ([string isEqualToString: @"PSAlarmExpired"]) return PSAlarmExpired;
+    NSLog(@"unknown alarm type string: %@", string);
+    return nil;
+}
+
+- (NSString *)_alarmTypeString;
+{
+    switch (alarmType) {
+        case PSAlarmDate: return @"PSAlarmDate";
+        case PSAlarmInterval: return @"PSAlarmInterval";
+        case PSAlarmSet: return @"PSAlarmSet";
+        case PSAlarmInvalid: return @"PSAlarmInvalid";
+        case PSAlarmSnooze: return @"PSAlarmSnooze";
+        case PSAlarmExpired: return @"PSAlarmExpired";
+        default: return [NSString stringWithFormat: @"<unknown: %u>", alarmType];
+    }
+}
+
+- (NSString *)_stringForInterval:(unsigned long long)interval;
+{
+    const unsigned long long minute = 60, hour = minute * 60, day = hour * 24, year = day * 365.26;
+    // +[NSString stringWithFormat:] in 10.1 does not support long longs: work around it by converting to unsigned ints or longs for display
+    if (interval == 0) return nil;
+    if (interval < minute) return [NSString stringWithFormat: @"%us", (unsigned)interval];
+    if (interval < hour) return [NSString stringWithFormat: @"%um", (unsigned)(interval / minute)];
+    if (interval < day) return [NSString stringWithFormat: @"%uh %um", (unsigned)(interval / hour), (unsigned)((interval % hour) / minute)];
+    if (interval < 2 * day) return @"One day";
+    if (interval < year) return [NSString stringWithFormat: @"%u days", (unsigned)(interval / day)];
+    if (interval < 2 * year) return @"One year";
+    return [NSString stringWithFormat: @"%lu years", (unsigned long)(interval / year)];
+}
+
+- (void)_timerExpired:(PSTimer *)aTimer;
+{
+    NSLog(@"expired: %@; now %@", [[aTimer fireDate] description], [[NSDate date] description]);
+    alarmType = PSAlarmExpired;
+    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmTimerExpiredNotification object: self];
+    [timer release]; timer = nil;
+}
+
+#pragma mark alarm setting
+
+- (void)setInterval:(NSTimeInterval)anInterval;
+{
+    alarmInterval = anInterval;
+    if (alarmInterval <= 0) {
+        [self _beInvalid: @"Please specify an alarm interval."]; return;
+    }
+    [self _setDateFromInterval];
+}
+
+- (void)setForDateAtTime:(NSCalendarDate *)dateTime;
+{
+    [self _setAlarmDate: dateTime];
+    [self _setIntervalFromDate];
+}
+
+- (void)setForDate:(NSDate *)date atTime:(NSDate *)time;
+{
+    NSCalendarDate *dateTime;
+    if (time == nil && date == nil) {
+        [self _beInvalid: @"Please specify an alarm date and time."]; return;
+    }
+    if (time == nil) {
+        [self _beInvalid: @"Please specify an alarm time."]; return;
+    }
+    if (date == nil) {
+        [self _beInvalid: @"Please specify an alarm date."]; return;
+    }
+    // XXX if calTime's date is different from the default date, complain
+    dateTime = [NSCalendarDate dateWithDate: date atTime: time];
+    if (dateTime == nil) {
+        [self _beInvalid: @"Please specify a reasonable date and time."]; return;
+    }
+    [self setForDateAtTime: dateTime];
+}
+
+- (void)setRepeating:(BOOL)isRepeating;
+{
+    repeating = isRepeating;
+}
+
+- (void)setSnoozeInterval:(NSTimeInterval)anInterval;
+{
+    snoozeInterval = anInterval;
+    NSAssert(alarmType == PSAlarmExpired, NSLocalizedString(@"Can't snooze an alarm that hasn't expired", "Assertion for PSAlarm snooze setting"));
+    alarmType = PSAlarmSnooze;
+}
+
+- (void)setWakeUp:(BOOL)doWake;
+{
+    [timer setWakeUp: doWake];
+}
+
+#pragma mark accessing
+
+- (NSString *)message;
+{
+    if (alarmMessage == nil || [alarmMessage isEqualToString: @""])
+        return @"Alarm!";
+    return alarmMessage;
+}
+
+- (void)setMessage:(NSString *)aMessage;
+{
+    if (aMessage != alarmMessage) {
+        [alarmMessage release];
+        alarmMessage = nil;
+        alarmMessage = [aMessage retain];
+    }
+}
+
+- (BOOL)isValid;
+{
+    if (alarmType == PSAlarmDate) [self _setIntervalFromDate];
+    if (alarmType == PSAlarmInvalid ||
+        (alarmType == PSAlarmExpired && ![self isRepeating])) return NO;
+    return YES;
+}
+
+- (NSString *)invalidMessage;
+{
+    if (invalidMessage == nil) return @"";
+    return invalidMessage;
+}
+
+- (NSCalendarDate *)date;
+{
+    if (alarmType == PSAlarmInterval) [self _setDateFromInterval];
+    return alarmDate;
+}
+
+- (NSCalendarDate *)time;
+{
+    if (alarmType == PSAlarmInterval) [self _setDateFromInterval];
+    return [[NSCalendarDate alloc] initWithYear: 0
+                                          month: 1
+                                            day: 1
+                                           hour: [alarmDate hourOfDay]
+                                         minute: [alarmDate minuteOfHour]
+                                         second: [alarmDate secondOfMinute]
+                                       timeZone: nil];
+}
+
+- (NSTimeInterval)interval;
+{
+    if (alarmType == PSAlarmDate) [self _setIntervalFromDate];
+    return alarmInterval;
+}
+
+- (NSTimeInterval)snoozeInterval;
+{
+    return snoozeInterval;
+}
+
+- (NSTimeInterval)timeRemaining;
+{
+    NSAssert1(alarmType == PSAlarmSet, NSLocalizedString(@"Can't get time remaining on alarm with no timer set: %@", "Assertion for PSAlarm time remaining, internal error; %@ replaced by alarm description"), self);
+    return -[[NSDate date] timeIntervalSinceDate: alarmDate];
+}
+
+- (void)setAlerts:(PSAlerts *)theAlerts;
+{
+    [alerts release]; alerts = nil;
+    alerts = [theAlerts retain];
+}
+
+- (PSAlerts *)alerts;
+{
+    if (alerts == nil) alerts = [[PSAlerts alloc] init];
+    return alerts;
+}
+
+- (BOOL)isRepeating;
+{
+    return repeating;
+}
+
+- (NSString *)dateString;
+{
+    return [dateFormatter stringFromDate: [self date]];
+}
+
+- (NSString *)shortDateString;
+{
+    return [shortDateFormatter stringFromDate: [self date]];
+}
+
+- (NSString *)timeString;
+{
+    return [timeFormatter stringFromDate: [self date]];
+}
+
+- (NSString *)dateTimeString;
+{
+    return [NSString stringWithFormat: @"%@ at %@", [self dateString], [self timeString]];
+}
+
+- (NSString *)nextDateTimeString;
+{
+    if (![self isRepeating]) {
+        return nil;
+    } else {
+        NSCalendarDate *date = [[NSCalendarDate alloc] initWithTimeIntervalSinceNow: [self interval]];
+        NSString *nextDateTimeString = [NSString stringWithFormat: @"%@ at %@",
+					[dateFormatter stringFromDate: date],
+					[timeFormatter stringFromDate: date]];
+        [date release];
+        return nextDateTimeString;
+    }
+}
+
+- (NSString *)intervalString;
+{
+    const unsigned long long mval = 99, minute = 60, hour = minute * 60;
+    unsigned long long interval = [self interval];
+    if (interval == 0) return nil;
+    if (interval == 1) return @"One second";
+    if (interval == minute) return @"One minute";
+    if (interval % minute == 0) return [NSString stringWithFormat: @"%u minutes", (unsigned)(interval / minute)];
+    if (interval <= mval) return [NSString stringWithFormat: @"%u seconds", (unsigned)interval];
+    if (interval == hour) return @"One hour";
+    if (interval % hour == 0) return [NSString stringWithFormat: @"%u hours", (unsigned)(interval / hour)];
+    if (interval <= mval * minute) return [NSString stringWithFormat: @"%u minutes", (unsigned)(interval / minute)];
+    if (interval <= mval * hour) return [NSString stringWithFormat: @"%u hours", (unsigned)(interval / hour)];
+    return [self _stringForInterval: interval];
+}
+
+- (NSString *)timeRemainingString;
+{
+    NSString *timeRemainingString = [self _stringForInterval: llround([self timeRemaining])];
+    
+    if (timeRemainingString == nil) return @"«expired»";
+    return timeRemainingString;
+}
+
+- (NSAttributedString *)prettyDescription;
+{
+    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init];
+    NSAttributedString *alertList = [alerts prettyList];
+
+    [string appendAttributedString:
+        [[NSString stringWithFormat: NSLocalizedString(@"At alarm time for '%@':\n", "Alert list title in pretty description, %@ replaced with message"), [self message]] small]];
+    if (alertList != nil) {
+        [string appendAttributedString: alertList];
+    } else {
+        [string appendAttributedString: [@"Do nothing." small]];
+    }
+    if ([self isRepeating]) {
+        [string appendAttributedString:
+            [[NSString stringWithFormat: @"\nAlarm repeats every %@.", [[self intervalString] lowercaseString]] small]];
+    }
+    return [string autorelease];
+}
+
+#pragma mark actions
+
+- (BOOL)setTimer;
+{
+    if (alarmType == PSAlarmExpired) {
+        if ([self isRepeating]) {
+            [self _setDateFromInterval];
+        } else {
+            [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmDiedNotification object: self];
+            return NO;
+        }
+    } else if (alarmType == PSAlarmDate) {
+        if (![self isValid]) return NO;
+    } else if (alarmType == PSAlarmSnooze) {
+        [self _setAlarmDate: [NSCalendarDate dateWithTimeIntervalSinceNow: snoozeInterval]];
+    } else if (alarmType != PSAlarmInterval) {
+        return NO;
+    }
+    timer = [PSTimer scheduledTimerWithTimeInterval: (alarmType == PSAlarmSnooze ? snoozeInterval : alarmInterval) target: self selector: @selector(_timerExpired:) userInfo: nil repeats: NO];
+    if (timer == nil) return NO;
+    [timer retain];
+    alarmType = PSAlarmSet;
+    [alerts prepareForAlarm: self];
+
+    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmTimerSetNotification object: self];
+    // NSLog(@"set: %@; now %@; remaining %@", [[timer fireDate] description], [[NSDate date] description], [self timeRemainingString]);
+    return YES;
+}
+
+- (void)cancelTimer;
+{
+    [timer invalidate]; [timer release]; timer = nil;
+}
+
+- (void)resetTimer;
+{
+    if (timer != nil || alarmType != PSAlarmSet)
+        return;
+
+    alarmType = PSAlarmDate;
+    if (![self isRepeating]) {
+        [self setTimer];
+    } else {
+        // don't want to put this logic in setTimer or isValid because it can cause invalid alarms to be set (consider when someone clicks the "repeat" checkbox, then switches to a [nonrepeating, by design] date alarm, and enters a date that has passed: we do -not- want the alarm to magically morph into a repeating interval alarm)
+        NSTimeInterval savedInterval = alarmInterval;
+        if ([self setTimer]) {
+	    // alarm is set, but not repeating - and the interval is wrong because it was computed from the date
+	    alarmInterval = savedInterval;
+	    [self setRepeating: YES];
+	} else {
+	    // alarm is now invalid: expired in the past, so we start the timer over again
+	    // We could potentially start counting from the expiration date (or expiration date + n * interval), but this doesn't match our existing behavior.
+            alarmType = PSAlarmInterval;
+            [self setInterval: savedInterval];
+            [self setTimer];
+        }
+    }
+}
+
+#pragma mark comparing
+
+- (NSComparisonResult)compareDate:(PSAlarm *)otherAlarm;
+{
+    return [[self date] compare: [otherAlarm date]];
+}
+
+- (NSComparisonResult)compareMessage:(PSAlarm *)otherAlarm;
+{
+    return [[self message] caseInsensitiveCompare: [otherAlarm message]];
+}
+
+#pragma mark printing
+
+- (NSString *)description;
+{
+    return [NSString stringWithFormat: @"%@: type %@ date %@ interval %.1f%@%@",
+        [super description], [self _alarmTypeString], alarmDate, alarmInterval,
+        (repeating ? @" repeating" : @""),
+        (alarmType == PSAlarmInvalid ?
+         [NSString stringWithFormat: @"\ninvalid message: %@", invalidMessage]
+        : (alarmType == PSAlarmSet ?
+           [NSString stringWithFormat: @"\ntimer: %@", timer] : @""))];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity: 5];
+    if (![self isValid]) return nil;
+    [dict setObject: [self _alarmTypeString] forKey: PLAlarmType];
+    switch (alarmType) {
+        case PSAlarmDate:
+        case PSAlarmSet:
+            [dict setObject: [NSNumber numberWithDouble: [alarmDate timeIntervalSinceReferenceDate]] forKey: PLAlarmDate];
+        case PSAlarmSnooze:
+        case PSAlarmInterval:
+        case PSAlarmExpired:
+            break;
+        default:
+            NSAssert1(NO, NSLocalizedString(@"Can't save alarm type %@", "Assertion for invalid PSAlarm type on string; %@ replaced with alarm type string"), [self _alarmTypeString]);
+            break;
+    }
+    if ((alarmType != PSAlarmSet || repeating) && alarmType != PSAlarmDate) {
+        [dict setObject: [NSNumber numberWithBool: repeating] forKey: PLAlarmRepeating];
+        [dict setObject: [NSNumber numberWithDouble: alarmInterval] forKey: PLAlarmInterval];
+    }
+    if (snoozeInterval != 0)
+        [dict setObject: [NSNumber numberWithDouble: snoozeInterval] forKey: PLAlarmSnoozeInterval];
+    [dict setObject: alarmMessage forKey: PLAlarmMessage];
+    if (alerts != nil) {
+        [dict setObject: [alerts propertyListRepresentation] forKey: PLAlarmAlerts];
+    }
+    return dict;
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [self init]) != nil) {
+        PSAlerts *alarmAlerts;
+        alarmType = [self _alarmTypeForString: [dict objectForRequiredKey: PLAlarmType]];
+        switch (alarmType) {
+            case PSAlarmDate:
+            case PSAlarmSet:
+               { NSCalendarDate *date = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate: [[dict objectForRequiredKey: PLAlarmDate] doubleValue]];
+                [self _setAlarmDate: date];
+                [date release];
+               }
+                break;
+            case PSAlarmSnooze: // snooze interval set but not confirmed; ignore
+                alarmType = PSAlarmExpired;
+            case PSAlarmInterval:
+            case PSAlarmExpired:
+                break;
+            default:
+                NSAssert1(NO, NSLocalizedString(@"Can't load alarm type %@", "Assertion for invalid PSAlarm type on load; %@ replaced with alarm type string"), [self _alarmTypeString]);
+                break;
+        }
+        repeating = [[dict objectForKey: PLAlarmRepeating] boolValue];
+        if ((alarmType != PSAlarmSet || repeating) && alarmType != PSAlarmDate)
+            alarmInterval = [[dict objectForRequiredKey: PLAlarmInterval] doubleValue];
+        snoozeInterval = [[dict objectForKey: PLAlarmSnoozeInterval] doubleValue];
+        [self setMessage: [dict objectForRequiredKey: PLAlarmMessage]];
+        alarmAlerts = [[PSAlerts alloc] initWithPropertyList: [dict objectForRequiredKey: PLAlarmAlerts]];
+        [self setAlerts: alarmAlerts];
+        [alarmAlerts release];
+        [self resetTimer];
+        if (alarmType == PSAlarmExpired) {
+            [self setTimer];
+            if (alarmType == PSAlarmExpired) { // failed to restart
+                [self release];
+                self = nil;
+            }
+        }
+    }
+    return self;
+}
+
+#pragma mark archiving (Pester 1.0)
+
+- (void)encodeWithCoder:(NSCoder *)coder;
+{
+    if (![self isValid]) return;
+    [coder encodeValueOfObjCType: @encode(PSAlarmType) at: &alarmType];
+    switch (alarmType) {
+        case PSAlarmDate:
+        case PSAlarmSet:
+            [coder encodeObject: alarmDate];
+            break;
+        case PSAlarmInterval:
+            [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &alarmInterval];
+            break;
+        default:
+            break;
+    }
+    [coder encodeObject: alarmMessage];
+    // NSLog(@"encoded: %@", self); // XXX happening twice, gdb refuses to show proper backtrace, grr
+    return;
+}
+
+- (id)initWithCoder:(NSCoder *)coder;
+{
+    if ( (self = [self init]) != nil) {
+        PSAlerts *legacyAlerts = [[PSAlerts alloc] initWithPesterVersion1Alerts];
+        [self setAlerts: legacyAlerts];
+        [legacyAlerts release];
+        [coder decodeValueOfObjCType: @encode(PSAlarmType) at: &alarmType];
+        switch (alarmType) {
+            case PSAlarmDate:
+            case PSAlarmSet:
+                [self _setAlarmDate: [coder decodeObject]];
+                break;
+            case PSAlarmInterval:
+                [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &alarmInterval];
+                break;
+            default:
+                break;
+        }
+        [self setMessage: [coder decodeObject]];
+        if (alarmType == PSAlarmSet)
+            alarmType = PSAlarmDate;
+	// Note: the timer is not set here, so these alarms are inert.
+	// This helps make importing atomic (see -[PSAlarms importVersion1Alarms])
+    }
+    return self;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmAlertController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmAlertController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmAlertController.h	(revision 365)
@@ -0,0 +1,26 @@
+//
+//  PSAlarmAlertController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSAlarm.h"
+
+extern NSString * const PSAlarmAlertStopNotification;
+
+@interface PSAlarmAlertController : NSObject {
+    NSMutableSet *pendingAlerts;
+    ProcessSerialNumber frontmostApp;
+    BOOL appWasHidden;
+}
+
++ (PSAlarmAlertController *)controllerWithTimerExpiredNotification:(NSNotification *)notification;
+
++ (IBAction)stopAlerts:(id)sender;
+
+- (id)initWithAlarm:(PSAlarm *)alarm;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmAlertController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmAlertController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmAlertController.m	(revision 365)
@@ -0,0 +1,91 @@
+//
+//  PSAlarmAlertController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarmAlertController.h"
+#import "PSAlert.h"
+#import "PSAlerts.h"
+
+NSString * const PSAlarmAlertStopNotification = @"PSAlarmAlertStopNotification";
+
+@implementation PSAlarmAlertController
+
++ (PSAlarmAlertController *)controllerWithTimerExpiredNotification:(NSNotification *)notification;
+{
+    return [[[self alloc] initWithAlarm: [notification object]] autorelease];
+}
+
++ (IBAction)stopAlerts:(id)sender;
+{
+    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmAlertStopNotification object: nil];
+}
+
+- (void)_resumeAlarm:(PSAlarm *)alarm;
+{
+    [[NSNotificationCenter defaultCenter] removeObserver: self];
+    [alarm setTimer]; // if snooze not set and not repeating, alarm will die
+    if (frontmostApp.highLongOfPSN != 0 || frontmostApp.lowLongOfPSN != 0) {
+        SetFrontProcess(&frontmostApp);
+        if (appWasHidden)
+            [NSApp hide: self];
+    }
+}
+
+- (void)_alertCompleted:(NSNotification *)notification;
+{
+    PSAlert *alert = [[notification userInfo] objectForKey: @"alert"];
+    unsigned count = [pendingAlerts count];
+    [pendingAlerts removeObject: alert];
+    NSLog(@"removed: %@; still pending: %@", alert, [pendingAlerts description]);
+    NSLog(@"alarm: %@ retainCount %d", [notification object], [[notification object] retainCount]);
+    NSAssert2([pendingAlerts count] == count - 1, @"alert not in set: %@\n%@", alert, notification);
+    if ([pendingAlerts count] == 0) {
+        [self _resumeAlarm: [notification object]];
+        [self release];
+    }
+}
+
+- (void)performAlertsForAlarm:(PSAlarm *)alarm;
+{
+    PSAlerts *alerts = [alarm alerts];
+    NSArray *allAlerts = [alerts allAlerts];
+    if ([allAlerts count] == 0) {
+        [self _resumeAlarm: alarm];
+    } else {
+        pendingAlerts = [[NSMutableSet alloc] init];
+        [pendingAlerts addObjectsFromArray: allAlerts];
+        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alertCompleted:)
+                                                     name: PSAlarmAlertCompletedNotification object: alarm];
+        [self retain]; // release in _alertCompleted:
+    }
+    [alerts triggerForAlarm: alarm];
+    if ([alerts requirePesterFrontmost] && ![NSApp isActive]) { // restore frontmost process afterward
+        NSDictionary *activeProcessInfo = [[NSWorkspace sharedWorkspace] activeApplication];
+        frontmostApp.highLongOfPSN = [[activeProcessInfo objectForKey: @"NSApplicationProcessSerialNumberHigh"] longValue];
+        frontmostApp.lowLongOfPSN = [[activeProcessInfo objectForKey: @"NSApplicationProcessSerialNumberLow"] longValue];
+        appWasHidden = [NSApp isHidden];
+        [NSApp activateIgnoringOtherApps: YES];
+    }
+}
+
+- (id)initWithAlarm:(PSAlarm *)alarm;
+{
+    if ( (self = [super init]) != nil) {
+        // because we're called within a notification, and alerts may deliver further notifications, make sure the rest of the notification clients are able to execute first
+        [self performSelector: @selector(performAlertsForAlarm:) withObject: alarm afterDelay: 0];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    NSLog(@"%@ dealloc", self);
+    [pendingAlerts release];
+    [super dealloc];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmNotifierController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmNotifierController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmNotifierController.h	(revision 365)
@@ -0,0 +1,40 @@
+//
+//  PSAlarmNotifierController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Oct 08 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@class NJRIntervalField;
+@class PSAlarm;
+
+@interface PSAlarmNotifierController : NSWindowController {
+    IBOutlet NSTextField *messageField;
+    IBOutlet NSTextField *dateField;
+    IBOutlet NSTextField *intervalField;
+    IBOutlet NSTextField *nextDateField;
+    IBOutlet NJRIntervalField *snoozeIntervalField;
+    IBOutlet NSButton *okButton;
+    IBOutlet NSButton *snoozeButton;
+    NSTimer *updateTimer;
+    PSAlarm *alarm;
+    BOOL canSnooze;
+    NSTimeInterval snoozeInterval;
+}
+
+- (id)initWithAlarm:(PSAlarm *)anAlarm;
+
+- (NSTimeInterval)snoozeInterval;
+- (BOOL)setSnoozeInterval:(NSTimeInterval)interval;
+- (void)snoozeUntilDate:(NSCalendarDate *)date;
+
+- (IBAction)snoozeIntervalUnitsChanged:(NSPopUpButton *)sender;
+- (IBAction)close:(id)sender;
+- (IBAction)snooze:(NSButton *)sender;
+- (IBAction)snoozeUntil:(NSMenuItem *)sender;
+- (IBAction)stopRepeating:(NSButton *)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmNotifierController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmNotifierController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmNotifierController.m	(revision 365)
@@ -0,0 +1,197 @@
+//
+//  PSAlarmNotifierController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Oct 08 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarmNotifierController.h"
+#import "PSAlarmAlertController.h"
+#import "PSAlarm.h"
+#import "PSNotifierAlert.h"
+#import "PSSnoozeUntilController.h"
+#import "NJRIntervalField.h"
+
+static NSString * const PSAlarmSnoozeInterval = @"Pester alarm snooze interval"; // NSUserDefaults key
+
+@interface PSAlarmNotifierController (Private)
+
+- (void)update:(id)sender;
+- (void)updateNextDateDisplay:(id)sender;
+
+@end
+
+@implementation PSAlarmNotifierController
+
+// XXX should use NSNonactivatingPanelMask on 10.2?
+
+- (id)initWithAlarm:(PSAlarm *)anAlarm;
+{
+    if ([self initWithWindowNibName: @"Notifier"]) {
+        NSWindow *window = [self window];
+        NSRect frameRect = [window frame];
+        alarm = [anAlarm retain];
+        [messageField setStringValue: [alarm message]];
+        [dateField setStringValue: [alarm dateTimeString]];
+        if (![self setSnoozeInterval: [alarm snoozeInterval]] &&
+            ![self setSnoozeInterval: [[[NSUserDefaults standardUserDefaults] objectForKey: PSAlarmSnoozeInterval] doubleValue]])
+            [self setSnoozeInterval: 15 * 60]; // 15 minutes
+        if ([alarm isRepeating]) {
+            [intervalField setStringValue:
+                [NSString stringWithFormat: @"every %@", [[alarm intervalString] lowercaseString]]];
+            [self updateNextDateDisplay: nil];
+            updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateNextDateDisplay:) userInfo: nil repeats: YES];
+            frameRect.size = [window maxSize];
+        } else {
+            frameRect.size = [window minSize];
+        }
+        [window setFrame: frameRect display: NO];
+        [window center];
+        [window makeKeyAndOrderFront: nil];
+        [window orderFrontRegardless];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [alarm release]; alarm = nil;
+    [updateTimer invalidate]; updateTimer = nil;
+    [super dealloc];
+}
+
+- (void)updateNextDateDisplay:(id)sender;
+{
+    if (!canSnooze) {
+        NSString *nextDateTimeString = [alarm nextDateTimeString];
+        if (nextDateTimeString == nil) { // no longer repeating
+            [updateTimer invalidate]; updateTimer = nil;
+        } else {
+            [nextDateField setStringValue: nextDateTimeString];
+        }
+    }
+}
+
+- (void)update:(id)sender;
+{
+    snoozeInterval = [snoozeIntervalField interval];
+    canSnooze = (snoozeInterval > 0);
+    if (canSnooze) [nextDateField setStringValue: @"after snooze"];
+    [snoozeButton setEnabled: canSnooze];
+    [canSnooze ? snoozeButton : okButton setKeyEquivalent: @"\r"];
+    [canSnooze ? okButton : snoozeButton setKeyEquivalent: @""];
+}
+
+- (IBAction)close:(id)sender;
+{
+    [PSAlarmAlertController stopAlerts: sender];
+    [self retain];
+    [self close]; // releases self in windowWillClose:
+    [[PSNotifierAlert alert] completedForAlarm: alarm];
+    [self release];
+}
+
+- (IBAction)snoozeUntil:(NSMenuItem *)sender;
+{
+    [PSSnoozeUntilController snoozeUntilControllerWithNotifierController: self];
+}
+
+- (IBAction)snoozeIntervalUnitsChanged:(NSPopUpButton *)sender;
+{
+    if ([[sender selectedItem] tag] > 0) [self update: nil];
+}
+
+- (NSTimeInterval)snoozeInterval;
+{
+    return snoozeInterval;
+}
+
+- (BOOL)setSnoozeInterval:(NSTimeInterval)interval;
+{
+    snoozeInterval = interval;
+    return [snoozeIntervalField setInterval: interval];
+}
+
+- (IBAction)snooze:(NSButton *)sender;
+{
+    snoozeInterval = [snoozeIntervalField interval];
+    [alarm setSnoozeInterval: snoozeInterval];
+    [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithDouble: snoozeInterval] forKey: PSAlarmSnoozeInterval];
+    [self close: sender];
+}
+
+- (void)snoozeUntilDate:(NSCalendarDate *)date;
+{
+    [alarm setSnoozeInterval: [date timeIntervalSinceNow]];
+    [self close: self];
+}
+
+- (IBAction)stopRepeating:(NSButton *)sender;
+{
+    NSWindow *window = [self window];
+    NSRect frameRect = [window frame];
+    NSSize newSize = [window minSize];
+    
+    [alarm setRepeating: NO];
+    [sender setEnabled: NO];
+    frameRect.origin.y += frameRect.size.height - newSize.height;
+    frameRect.size = newSize;
+    [window setFrame: frameRect display: YES animate: YES];
+}
+
+@end
+
+@implementation PSAlarmNotifierController (NSControlSubclassDelegate)
+
+- (BOOL)control:(NSControl *)control didFailToFormatString:(NSString *)string errorDescription:(NSString *)error;
+{
+    if (control == snoozeIntervalField)
+        [snoozeIntervalField handleDidFailToFormatString: string errorDescription: error label: @"snooze interval"];
+    return NO;
+}
+
+- (void)control:(NSControl *)control didFailToValidatePartialString:(NSString *)string errorDescription:(NSString *)error;
+{
+    // NSLog(@"UPDATING FROM validation");
+    [self update: control]; // switch to snooze if someone types something weird...
+}
+
+- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector;
+{
+    // NSLog(@"UPDATING from textView: %@", NSStringFromSelector(commandSelector));
+    if (commandSelector == @selector(cancel:)) {
+        // if someone just wants the stupid thing to go away and presses escape, donÕt hinder them
+        [self close: control];
+        return YES;
+    }
+    // if someone invokes the default button or switches fields, donÕt override it
+    if (commandSelector == @selector(insertNewline:) ||
+        commandSelector == @selector(insertTab:) ||
+        commandSelector == @selector(insertBacktab:)) return NO;
+    [self update: control]; // ...or if they type a navigation key...
+    return NO; // we donÕt handle it
+}
+
+@end
+
+@implementation PSAlarmNotifierController (NSControlSubclassNotifications)
+
+- (void)controlTextDidChange:(NSNotification *)notification;
+{
+    // NSLog(@"UPDATING FROM controlTextDidChange: %@", [notification object]);
+    [self update: [notification object]]; // ...or if they modify the snooze interval
+}
+
+@end
+
+@implementation PSAlarmNotifierController (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    // canÕt rely on dealloc to invalidate the timer, because it retains this object
+    [updateTimer invalidate]; updateTimer = nil;
+    [self release]; // in non-document-based apps, this is needed; see docs
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmSetController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmSetController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmSetController.h	(revision 365)
@@ -0,0 +1,70 @@
+//
+//  PSAlarmSetController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Oct 08 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSAlarm.h"
+
+@class NJRFSObjectSelector;
+@class NJRIntervalField;
+@class NJRQTMediaPopUpButton;
+@class NJRValidatingField;
+@class NJRVoicePopUpButton;
+
+@interface PSAlarmSetController : NSWindowController <NSUserInterfaceValidations> {
+    IBOutlet NSTextField *messageField;
+    IBOutlet NSButton *removeMessageButton;
+    IBOutlet NSMatrix *inAtMatrix;
+    IBOutlet NJRIntervalField *timeInterval;
+    IBOutlet NSPopUpButton *timeIntervalUnits;
+    IBOutlet NSButton *timeIntervalRepeats;
+    IBOutlet NSTextField *timeOfDay;
+    IBOutlet NSTextField *timeDate;
+    IBOutlet NSPopUpButton *timeDateCompletions; // XXX should go away when bug preventing both formatters and popup menus from existing is fixed
+    IBOutlet NSButton *timeCalendarButton;
+    IBOutlet NSButtonCell *editAlert;
+    IBOutlet NSTextField *alertView;
+    IBOutlet NSTabView *alertTabs;
+    IBOutlet NSButtonCell *displayMessage;
+    IBOutlet NSButton *bounceDockIcon;
+    IBOutlet NSButtonCell *playSound;
+    IBOutlet NJRQTMediaPopUpButton *sound;
+    IBOutlet NSButton *soundVolumeButton;
+    IBOutlet NJRValidatingField *soundRepetitions;
+    IBOutlet NSStepper *soundRepetitionStepper;
+    IBOutlet NSTextField *soundRepetitionsLabel;
+    IBOutlet NSButtonCell *doScript;
+    IBOutlet NJRFSObjectSelector *script;
+    IBOutlet NSButtonCell *doSpeak;
+    IBOutlet NJRVoicePopUpButton *voice;
+    IBOutlet NSButton *scriptSelectButton;
+    IBOutlet NSButtonCell *wakeUp;
+    IBOutlet NSTextField *timeSummary;
+    IBOutlet NSButton *cancelButton;
+    IBOutlet NSButton *setButton;
+    NSString *status;
+    NSTimer *updateTimer;
+    PSAlarm *alarm;
+    BOOL isInterval;
+}
+
+- (IBAction)update:(id)sender;
+- (IBAction)dateCompleted:(NSPopUpButton *)sender;
+- (IBAction)showCalendar:(NSButton *)sender;
+- (IBAction)inAtChanged:(id)sender;
+- (IBAction)toggleAlertEditor:(id)sender;
+- (IBAction)editAlertChanged:(id)sender;
+- (IBAction)playSoundChanged:(id)sender;
+- (IBAction)showVolume:(NSButton *)sender;
+- (IBAction)doScriptChanged:(id)sender;
+- (IBAction)doSpeakChanged:(id)sender;
+- (IBAction)setAlarm:(NSButton *)sender;
+- (IBAction)setSoundRepetitionCount:(id)sender;
+
+- (IBAction)silence:(id)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmSetController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmSetController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmSetController.m	(revision 365)
@@ -0,0 +1,629 @@
+//
+//  PSAlarmSetController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Oct 08 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarmSetController.h"
+#import "PSAlarmAlertController.h"
+#import "PSCalendarController.h"
+#import "PSPowerManager.h"
+#import "PSTimeDateEditor.h"
+#import "PSVolumeController.h"
+#import "NJRDateFormatter.h"
+#import "NJRFSObjectSelector.h"
+#import "NJRIntervalField.h"
+#import "NJRQTMediaPopUpButton.h"
+#import "NJRSoundManager.h"
+#import "NJRValidatingField.h"
+#import "NJRVoicePopUpButton.h"
+#import "NSString-NJRExtensions.h"
+#import "NSAttributedString-NJRExtensions.h"
+#import "NSCalendarDate-NJRExtensions.h"
+#import <Carbon/Carbon.h>
+
+#import "PSAlerts.h"
+#import "PSDockBounceAlert.h"
+#import "PSScriptAlert.h"
+#import "PSNotifierAlert.h"
+#import "PSBeepAlert.h"
+#import "PSMovieAlert.h"
+#import "PSSpeechAlert.h"
+#import "PSWakeAlert.h"
+
+/* Bugs to file:
+
+¥ any trailing spaces: -> exception for +[NSCalendarDate dateWithNaturalLanguageString]:
+ > NSCalendarDate dateWithNaturalLanguageString: '12 '
+  format error: internal error
+
+¥ NSDate natural language stuff in NSCalendarDate (why?), misspelled category name
+¥ NSCalendarDate natural language stuff behaves differently from NSDateFormatter (AM/PM has no effect, shouldn't they share code?)
+¥ descriptionWithCalendarFormat:, dateWithNaturalLanguageString: does not default to current locale, instead it defaults to US unless you tell it otherwise
+¥ NSDateFormatter doc class description gives two examples for natural language that are incorrect, no link to NSDate doc that describes exactly how natural language dates are parsed
+¥ NSTimeFormatString does not include %p when it should, meaning that AM/PM is stripped yet 12-hour time is still used
+¥ NSNextDayDesignations, NSNextNextDayDesignations are noted as 'a string' in NSUserDefaults docs, but maybe they are actually an array, or either an array or a string, given their names?
+¥ "Setting the Format for Dates" does not document how to get 1:15 AM, the answer is %1I - strftime has no exact equivalent; the closest is %l.  strftime does not permit numeric prefixes.  It also refers to "NSCalendar" when no such class exists.
+¥ none of many mentions of NSAMPMDesignation indicates that they include the leading spaces (" AM", " PM").  In "Setting the Format for Dates", needs to mention that the leading spaces are not included in %p with strftime.  But if you use the NSCalendarDate stuff, it appears %p doesn't include the space (because it doesn't use the locale dictionary).
+¥ If you feed NSCalendarDate dateWithNaturalLanguageString: an " AM"/" PM" locale, it doesn't accept that date format.
+¥ descriptions for %X and %x are reversed (time zone is in %X, not %x)
+¥ NSComboBox data source issues, canÕt have it appear as ÒtodayÓ because the formatter doesnÕt like that.  Should be able to enter text into the data source and have the formatter process it without altering it.
+¥ too hard to implement date-only or time-only formatters
+¥ should be able to specify that natural language favors date or time (10 = 10th of month, not 10am)
+¥ please expose the iCal controls!
+
+*/
+
+static NSString * const PSAlertsSelected = @"Pester alerts selected"; // NSUserDefaults key
+static NSString * const PSAlertsEditing = @"Pester alerts editing"; // NSUserDefaults key
+
+@interface PSAlarmSetController (Private)
+
+- (void)_readAlerts:(PSAlerts *)alerts;
+- (BOOL)_setAlerts;
+- (void)_setVolume:(float)volume withPreview:(BOOL)preview;
+- (void)_stopUpdateTimer;
+
+@end
+
+@implementation PSAlarmSetController
+
+- (void)awakeFromNib;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+    alarm = [[PSAlarm alloc] init];
+    [[self window] center];
+    if ([[removeMessageButton image] size].width != 0)
+	[removeMessageButton setTitle: @""];
+    [PSTimeDateEditor setUpTimeField: timeOfDay dateField: timeDate completions: timeDateCompletions];
+    { // volume defaults, usually overridden by restored alert info
+        float volume = 0.5;
+        [NJRSoundManager getDefaultOutputVolume: &volume];
+        [self _setVolume: volume withPreview: NO];
+    }
+    [editAlert setIntValue: 1]; // XXX temporary for 1.1b5
+    {
+        NSDictionary *plAlerts = [defaults dictionaryForKey: PSAlertsSelected];
+        PSAlerts *alerts = nil;
+        if (plAlerts == nil) {
+            alerts = [[PSAlerts alloc] initWithPesterVersion1Alerts];
+        } else {
+            @try {
+                alerts = [[PSAlerts alloc] initWithPropertyList: plAlerts];
+	    } @catch (NSException *exception) {
+                NSRunAlertPanel(@"Unable to restore alerts", @"Pester could not restore recent alert information for one or more alerts in the Set Alarm window.  The default set of alerts will be used instead.\n\n%@", nil, nil, nil, [exception reason]);
+                alerts = [[PSAlerts alloc] initWithPesterVersion1Alerts];
+            }
+        }
+        [self _readAlerts: alerts];
+    }
+    [self inAtChanged: nil]; // by convention, if sender is nil, we're initializing
+    [self playSoundChanged: nil];
+    [self doScriptChanged: nil];
+    [self doSpeakChanged: nil];
+    [self editAlertChanged: nil];
+    [script setFileTypes: [NSArray arrayWithObjects: @"applescript", @"script", NSFileTypeForHFSTypeCode(kOSAFileType), NSFileTypeForHFSTypeCode('TEXT'), nil]];
+    [notificationCenter addObserver: self selector: @selector(silence:) name: PSAlarmAlertStopNotification object: nil];
+    [notificationCenter addObserver: self selector: @selector(playSoundChanged:) name: NJRQTMediaPopUpButtonMovieChangedNotification object: sound];
+    [notificationCenter addObserver: self selector: @selector(applicationWillHide:) name: NSApplicationWillHideNotification object: NSApp];
+    [notificationCenter addObserver: self selector: @selector(applicationDidUnhide:) name: NSApplicationDidUnhideNotification object: NSApp];
+    [notificationCenter addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: NSApp];
+    [voice setDelegate: self]; // XXX why don't we do this in IB?  It should use the accessor...
+    [wakeUp setEnabled: [PSPowerManager autoWakeSupported]];
+    // XXX workaround for 10.1.x and 10.2.x bug which sets the first responder to the wrong field alternately, but it works if I set the initial first responder to nil... go figure.
+    [[self window] setInitialFirstResponder: nil];
+    [[self window] makeKeyAndOrderFront: nil];
+}
+
+- (void)setStatus:(NSString *)aString;
+{
+    // NSLog(@"%@", alarm);
+    if (aString != status) {
+        [status release]; status = nil;
+        status = [aString retain];
+        [timeSummary setStringValue: status];
+    }
+}
+
+// XXX with -[NSControl currentEditor] don't need to compare?  Also check -[NSControl validateEditing]
+- (id)objectValueForTextField:(NSTextField *)field whileEditing:(id)sender;
+{
+    if (sender == field) {
+        NSString *stringValue = [[[self window] fieldEditor: NO forObject: field] string];
+        id obj = nil;
+        [[field formatter] getObjectValue: &obj forString: stringValue errorDescription: NULL];
+        // NSLog(@"from field editor: %@", obj);
+        return obj;
+    } else {
+        // NSLog(@"from field: %@", [field objectValue]);
+        return [field objectValue];
+    }
+}
+
+#pragma mark date/interval setting
+
+- (void)setAlarmDateAndInterval:(id)sender;
+{
+    if (isInterval) {
+        [alarm setInterval: [timeInterval interval]];
+    } else {
+        [alarm setForDate: [self objectValueForTextField: timeDate whileEditing: sender]
+                   atTime: [self objectValueForTextField: timeOfDay whileEditing: sender]];
+    }
+}
+
+- (void)_stopUpdateTimer;
+{
+    [updateTimer invalidate]; [updateTimer release]; updateTimer = nil;
+}
+
+- (IBAction)updateDateDisplay:(id)sender;
+{
+    // NSLog(@"updateDateDisplay: %@", sender);
+    if ([alarm isValid]) {
+        [self setStatus: [NSString stringWithFormat: @"Alarm will be set for %@ on %@.", [alarm timeString], [alarm dateString]]];
+        [setButton setEnabled: YES];
+        if (updateTimer == nil || ![updateTimer isValid]) {
+            // XXX this logic (and the timer) should really go into PSAlarm, to send notifications for status updates instead.  Timer starts when people are watching, stops when people aren't.
+            // NSLog(@"setting timer");
+            if (isInterval) {
+                updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateDateDisplay:) userInfo: nil repeats: YES];
+            } else {
+                updateTimer = [NSTimer scheduledTimerWithTimeInterval: [alarm interval] target: self selector: @selector(updateDateDisplay:) userInfo: nil repeats: NO];
+            }
+            [updateTimer retain];
+        }
+    } else {
+        [setButton setEnabled: NO];
+        [self setStatus: [alarm invalidMessage]];
+        [self _stopUpdateTimer];
+    }
+}
+
+// Be careful not to hook up any of the text fields' actions to update: because we handle them in controlTextDidChange: instead.  If we could get the active text field somehow via public API (guess we could use controlTextDidBegin/controlTextDidEndEditing) then we'd not need to overload the update sender for this purpose.  Or, I guess, we could use another method other than update.  It should not be this hard to implement what is essentially standard behavior.  Sigh.
+// Note: finding out whether a given control is editing is easier.  See: <http://cocoa.mamasam.com/COCOADEV/2002/03/2/28501.php>.
+
+- (IBAction)update:(id)sender;
+{
+    // NSLog(@"update: %@", sender);
+    [self setAlarmDateAndInterval: sender];
+    [self updateDateDisplay: sender];
+}
+
+- (IBAction)inAtChanged:(id)sender;
+{
+    NSButtonCell *new = [inAtMatrix selectedCell], *old;
+    isInterval = ([inAtMatrix selectedTag] == 0);
+    old = [inAtMatrix cellWithTag: isInterval];
+    NSAssert(new != old, @"in and at buttons should be distinct!");
+    
+    if (sender != nil) {
+	// XXX validation doesn't work properly for date/time, so we just universally cancel editing now
+        if (![[self window] makeFirstResponder: nil] && !isInterval) {
+	    // This works fine synchronously only if you're using the keyboard shortcut to switch in/at.  Directly activating the button, a delayed invocation is necessary.
+	    NSInvocation *i = [NSInvocation invocationWithMethodSignature:
+			       [inAtMatrix methodSignatureForSelector: @selector(selectCellWithTag:)]];
+	    int tag = [old tag];
+	    [i setSelector: @selector(selectCellWithTag:)];
+	    [i setTarget: inAtMatrix];
+	    [i setArgument: &tag atIndex: 2];
+	    [NSTimer scheduledTimerWithTimeInterval: 0 invocation: i repeats: NO];
+	    return;
+	}
+    }
+    
+    [old setKeyEquivalent: [new keyEquivalent]];
+    [old setKeyEquivalentModifierMask: [new keyEquivalentModifierMask]];
+    [new setKeyEquivalent: @""];
+    [new setKeyEquivalentModifierMask: 0];
+    [timeInterval setEnabled: isInterval];
+    [timeIntervalUnits setEnabled: isInterval];
+    [timeIntervalRepeats setEnabled: isInterval];
+    [timeOfDay setEnabled: !isInterval];
+    [timeDate setEnabled: !isInterval];
+    [timeDateCompletions setEnabled: !isInterval && [NJRDateFormatter naturalLanguageParsingAvailable]];
+    [timeCalendarButton setEnabled: !isInterval];
+    if (sender != nil)
+	[[self window] makeFirstResponder: isInterval ? (NSTextField *)timeInterval : timeOfDay];
+    if (!isInterval) // need to do this every time the controls are enabled
+        [timeOfDay setNextKeyView: timeDate];
+    // NSLog(@"UPDATING FROM inAtChanged");
+    [self update: nil];
+}
+
+- (IBAction)dateCompleted:(NSPopUpButton *)sender;
+{
+    [timeDate setStringValue: [sender titleOfSelectedItem]];
+    [self update: sender];
+}
+
+#pragma mark calendar
+
+- (IBAction)showCalendar:(NSButton *)sender;
+{
+    [PSCalendarController controllerWithDate: [NSCalendarDate dateForDay: [timeDate objectValue]] delegate: self];
+}
+
+- (void)calendarController:(PSCalendarController *)calendar didSetDate:(NSCalendarDate *)date;
+{
+    [timeDate setObjectValue: date];
+    [self update: self];
+}
+
+- (NSView *)calendarControllerLaunchingView:(PSCalendarController *)controller;
+{
+    return timeCalendarButton;
+}
+
+#pragma mark volume
+
+- (IBAction)showVolume:(NSButton *)sender;
+{
+    [PSVolumeController controllerWithVolume: [sound outputVolume] delegate: self];
+}
+
+#define VOLUME_IMAGE_INDEX(vol) (vol * 4) - 0.01
+
+- (void)_setVolume:(float)volume withPreview:(BOOL)preview;
+{
+    float outputVolume = [sound outputVolume];
+    short volumeImageIndex = VOLUME_IMAGE_INDEX(volume);
+
+    if (outputVolume > 0 && volumeImageIndex == VOLUME_IMAGE_INDEX(outputVolume)) return;
+    NSString *volumeImageName = [NSString stringWithFormat: @"Volume %ld", volumeImageIndex];
+    [soundVolumeButton setImage: [NSImage imageNamed: volumeImageName]];
+
+    [sound setOutputVolume: volume withPreview: preview];
+}
+
+- (void)volumeController:(PSVolumeController *)controller didSetVolume:(float)volume;
+{
+    [self _setVolume: volume withPreview: YES];
+}
+
+- (NSView *)volumeControllerLaunchingView:(PSVolumeController *)controller;
+{
+    return soundVolumeButton;
+}
+
+#pragma mark alert editing
+
+- (IBAction)toggleAlertEditor:(id)sender;
+{
+    [editAlert performClick: self];
+}
+
+- (IBAction)editAlertChanged:(id)sender;
+{
+    BOOL editAlertSelected = [editAlert intValue];
+    NSView *editAlertControl = [editAlert controlView];
+    NSWindow *window = [self window];
+    NSRect frame = [window frame];
+    if (editAlertSelected) {
+        NSSize editWinSize = [window maxSize];
+        [editAlertControl setNextKeyView: [displayMessage controlView]];
+        frame.origin.y += frame.size.height - editWinSize.height;
+        frame.size = editWinSize;
+        [window setFrame: frame display: (sender != nil) animate: (sender != nil)];
+        [self updateDateDisplay: sender];
+        [alertTabs selectTabViewItemWithIdentifier: @"edit"];
+    } else {
+        NSSize viewWinSize = [window minSize];
+        NSRect textFrame = [alertView frame];
+        float textHeight;
+        if (![self _setAlerts]) {
+            [alertView setStringValue: [NSString stringWithFormat: @"%@\n%@", NSLocalizedString(@"Couldn't process alert information.", "Message shown in collapsed alert area when alert information is invalid or inconsistent (prevents setting alarm)"), status]];
+        } else {
+            NSAttributedString *string = [[alarm alerts] prettyList];
+            if (string == nil) {
+                [alertView setStringValue: NSLocalizedString(@"Do nothing. Click the button labeled 'Edit' to add an alert.", "Message shown in collapsed alert edit area when no alerts have been specified")];
+            } else {
+                [alertView setAttributedStringValue: string];
+                [self updateDateDisplay: sender];
+            }
+        }
+        if (sender != nil) { // nil == we're initializing, don't mess with focus
+            NSResponder *oldResponder = [window firstResponder];
+            // make sure focus doesn't get stuck in the edit tab: it is confusing and leaves behind artifacts
+            if (oldResponder == editAlertControl || [oldResponder isKindOfClass: [NSView class]] && [(NSView *)oldResponder isDescendantOf: alertTabs])
+                [window makeFirstResponder: messageField]; // would use editAlertControl, but can't get it to display anomaly-free.
+            [self silence: sender];
+        }
+        // allow height to expand, though not arbitrarily (should still fit on an 800x600 screen)
+        textHeight = [[alertView cell] cellSizeForBounds: NSMakeRect(0, 0, textFrame.size.width, 400)].height;
+        textFrame.origin.y += textFrame.size.height - textHeight;
+        textFrame.size.height = textHeight;
+        [alertView setFrame: textFrame];
+        viewWinSize.height += textHeight;
+        [alertTabs selectTabViewItemWithIdentifier: @"view"];
+        frame.origin.y += frame.size.height - viewWinSize.height;
+        frame.size = viewWinSize;
+        [window setFrame: frame display: (sender != nil) animate: (sender != nil)];
+        [editAlertControl setNextKeyView: cancelButton];
+    }
+    if (sender != nil) {
+        [[NSUserDefaults standardUserDefaults] setBool: editAlertSelected forKey: PSAlertsEditing];
+    }
+}
+
+- (IBAction)playSoundChanged:(id)sender;
+{
+    BOOL playSoundSelected = [playSound intValue];
+    BOOL canRepeat = playSoundSelected; // ? [sound canRepeat] : NO; // XXX temporary for 1.1b6
+    [sound setEnabled: playSoundSelected];
+    [soundRepetitions setEnabled: canRepeat];
+    [soundVolumeButton setEnabled: canRepeat && [sound hasAudio]];
+    [soundRepetitionStepper setEnabled: canRepeat];
+    [soundRepetitionsLabel setTextColor: canRepeat ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]];
+    if (playSoundSelected && sender == playSound) {
+        [[self window] makeFirstResponder: soundRepetitions]; // sound]; // XXX temporary for 1.1b6
+    }
+}
+
+- (IBAction)setSoundRepetitionCount:(id)sender;
+{
+    NSTextView *fieldEditor = (NSTextView *)[soundRepetitions currentEditor];
+    BOOL isEditing = (fieldEditor != nil);
+    int newReps = [sender intValue], oldReps;
+    if (isEditing) {
+        // XXX work around bug where if you ask soundRepetitions for its intValue too often while it's editing, the field begins to flash
+        oldReps = [[[fieldEditor textStorage] string] intValue];
+    } else oldReps = [soundRepetitions intValue];
+    if (newReps != oldReps) {
+        [soundRepetitions setIntValue: newReps];
+        // NSLog(@"updating: new value %d, old value %d%@", newReps, oldReps, isEditing ? @", is editing" : @"");
+        // XXX work around 10.1 bug, otherwise field only displays every second value
+        if (isEditing) [soundRepetitions selectText: self];
+    }
+}
+
+// XXX should check the 'Do script:' button when someone drops a script on the button
+
+- (IBAction)doScriptChanged:(id)sender;
+{
+    BOOL doScriptSelected = [doScript intValue];
+    [script setEnabled: doScriptSelected];
+    [scriptSelectButton setEnabled: doScriptSelected];
+    if (doScriptSelected && sender != nil) {
+        [[self window] makeFirstResponder: scriptSelectButton];
+        if ([script alias] == nil) [scriptSelectButton performClick: sender];
+    } else {
+	[[self window] makeFirstResponder: sender];
+    }
+}
+
+- (IBAction)doSpeakChanged:(id)sender;
+{
+    BOOL doSpeakSelected = [doSpeak state] == NSOnState;
+    [voice setEnabled: doSpeakSelected];
+    if (doSpeakSelected && sender != nil)
+        [[self window] makeFirstResponder: voice];
+    else
+	[[self window] makeFirstResponder: sender];	
+}
+
+- (void)_readAlerts:(PSAlerts *)alerts;
+{
+    NSEnumerator *e = [alerts alertEnumerator];
+    PSAlert *alert;
+    
+    [alarm setAlerts: alerts];
+
+    // turn off all alerts
+    [bounceDockIcon setState: NSOffState];
+    [doScript setIntValue: NO];
+    [displayMessage setIntValue: NO];
+    [playSound setIntValue: NO];
+    [doSpeak setIntValue: NO];
+
+    while ( (alert = [e nextObject]) != nil) {
+        if ([alert isKindOfClass: [PSDockBounceAlert class]]) {
+            [bounceDockIcon setState: NSOnState];
+        } else if ([alert isKindOfClass: [PSScriptAlert class]]) {
+            [doScript setIntValue: YES];
+            [script setAlias: [(PSScriptAlert *)alert scriptFileAlias]];
+        } else if ([alert isKindOfClass: [PSNotifierAlert class]]) {
+            [displayMessage setIntValue: YES];
+        } else if ([alert isKindOfClass: [PSMediaAlert class]]) {
+            unsigned int repetitions = [(PSMediaAlert *)alert repetitions];
+            [playSound setIntValue: YES];
+            [soundRepetitions setIntValue: repetitions];
+            [soundRepetitionStepper setIntValue: repetitions];
+            [self _setVolume: [(PSMediaAlert *)alert outputVolume] withPreview: NO];
+            if ([alert isKindOfClass: [PSBeepAlert class]]) {
+                [sound setAlias: nil];
+            } else if ([alert isKindOfClass: [PSMovieAlert class]]) {
+                [sound setAlias: [(PSMovieAlert *)alert movieFileAlias]];
+            }
+        } else if ([alert isKindOfClass: [PSSpeechAlert class]]) {
+            [doSpeak setIntValue: YES];
+            [voice setVoice: [(PSSpeechAlert *)alert voice]];
+        } else if ([alert isKindOfClass: [PSWakeAlert class]]) {
+            [wakeUp setIntValue: YES];
+        }
+}
+}
+
+- (BOOL)_setAlerts;
+{
+    PSAlerts *alerts = [alarm alerts];
+    
+    [alerts removeAlerts];
+    @try {
+        // dock bounce alert
+        if ([bounceDockIcon state] == NSOnState)
+            [alerts addAlert: [PSDockBounceAlert alert]];
+        // script alert
+        if ([doScript intValue]) {
+            BDAlias *scriptFileAlias = [script alias];
+            if (scriptFileAlias == nil) {
+                [self setStatus: @"Unable to set script alert (no script specified?)"];
+                return NO;
+            }
+            [alerts addAlert: [PSScriptAlert alertWithScriptFileAlias: scriptFileAlias]];
+        }
+        // notifier alert
+        if ([displayMessage intValue])
+            [alerts addAlert: [PSNotifierAlert alert]];
+        // sound alerts
+        if ([playSound intValue]) {
+            BDAlias *soundAlias = [sound selectedAlias];
+            unsigned short numReps = [soundRepetitions intValue];
+            PSMediaAlert *alert;
+            if (soundAlias == nil) // beep alert
+                alert = [PSBeepAlert alertWithRepetitions: numReps];
+            else // movie alert
+                alert = [PSMovieAlert alertWithMovieFileAlias: soundAlias repetitions: numReps];
+            [alerts addAlert: alert];
+            [alert setOutputVolume: [sound outputVolume]];
+        }
+        // speech alert
+        if ([doSpeak intValue])
+            [alerts addAlert: [PSSpeechAlert alertWithVoice: [[voice selectedItem] representedObject]]];
+        // wake alert
+        if ([wakeUp intValue])
+            [alerts addAlert: [PSWakeAlert alert]];
+        [[NSUserDefaults standardUserDefaults] setObject: [alerts propertyListRepresentation] forKey: PSAlertsSelected];
+    } @catch (NSException *exception) {
+	[self setStatus: [exception reason]];
+        return NO;
+    }
+    return YES;
+}
+
+#pragma mark actions
+
+// to ensure proper updating of interval, this should be the only method by which the window is shown (e.g. from the Alarm menu)
+- (IBAction)showWindow:(id)sender;
+{
+    if (![[self window] isVisible]) {
+        NSDate *today = [NSCalendarDate dateForDay: [NSDate date]];
+        if ([(NSDate *)[timeDate objectValue] compare: today] == NSOrderedAscending) {
+            [timeDate setObjectValue: today];
+        }
+        [self update: self];
+        // XXX bug workaround - otherwise, first responder appears to alternate every time the window is shown.  And if you set the initial first responder, you can't tab in the window. :(
+        [[self window] makeFirstResponder: [[self window] initialFirstResponder]];
+    }
+    [super showWindow: sender];
+}
+
+- (IBAction)setAlarm:(NSButton *)sender;
+{
+    // set alerts before setting alarm...
+    if (![self _setAlerts]) return;
+
+    // set alarm
+    [self setAlarmDateAndInterval: sender];
+    [alarm setRepeating: [timeIntervalRepeats state] == NSOnState];
+    [alarm setMessage: [messageField stringValue]];
+    if (![alarm setTimer]) {
+        [self setStatus: [@"Unable to set alarm. " stringByAppendingString: [alarm invalidMessage]]];
+        return;
+    }
+    
+    [self setStatus: [[alarm date] descriptionWithCalendarFormat: @"Alarm set for %x at %X" timeZone: nil locale: nil]];
+    [[self window] close];
+    [alarm release];
+    alarm = [[PSAlarm alloc] init];
+}
+
+- (IBAction)silence:(id)sender;
+{
+    [sound stopSoundPreview: self];
+    [voice stopVoicePreview: self];
+}
+
+- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem;
+{
+    if ([anItem action] == @selector(toggleAlertEditor:)) {
+        if ([NSApp keyWindow] != [self window])
+            return NO;
+        [(NSMenuItem *)anItem setState: [editAlert intValue] ? NSOnState : NSOffState];
+    }
+    return YES;
+}
+
+@end
+
+@implementation PSAlarmSetController (NSControlSubclassDelegate)
+
+- (BOOL)control:(NSControl *)control didFailToFormatString:(NSString *)string errorDescription:(NSString *)error;
+{
+    if (control == timeInterval)
+        [timeInterval handleDidFailToFormatString: string errorDescription: error label: @"alarm interval"];
+    else if (control == soundRepetitions)
+	[soundRepetitions handleDidFailToFormatString: string errorDescription: error label: @"alert repetitions"];
+    return NO;
+}
+
+- (void)control:(NSControl *)control didFailToValidatePartialString:(NSString *)string errorDescription:(NSString *)error;
+{
+    // NSLog(@"UPDATING FROM validation");
+    if (control == timeInterval) [self update: timeInterval]; // make sure we still examine the field editor, otherwise if the existing numeric string is invalid, it'll be cleared
+}
+
+@end
+
+@implementation PSAlarmSetController (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    // NSLog(@"stopping update timer");
+    [self silence: nil];
+    [self _stopUpdateTimer];
+    [self _setAlerts];
+}
+
+@end
+
+@implementation PSAlarmSetController (NSControlSubclassNotifications)
+
+// called because we're the delegate
+
+- (void)controlTextDidChange:(NSNotification *)notification;
+{
+    // NSLog(@"UPDATING FROM controlTextDidChange: %@", [notification object]);
+    [self update: [notification object]];
+}
+
+@end
+
+@implementation PSAlarmSetController (NJRVoicePopUpButtonDelegate)
+
+- (NSString *)voicePopUpButton:(NJRVoicePopUpButton *)sender previewStringForVoice:(NSString *)voice;
+{
+    NSString *message = [messageField stringValue];
+    if (message == nil || [message length] == 0)
+        message = [alarm message];
+    return message;
+}
+
+@end
+
+@implementation PSAlarmSetController (NSApplicationNotifications)
+
+- (void)applicationWillTerminate:(NSNotification *)notification;
+{
+    [self _setAlerts];
+}
+
+- (void)applicationWillHide:(NSNotification *)notification;
+{
+    if ([[self window] isVisible]) {
+        [self silence: nil];
+        [self _stopUpdateTimer];
+    }
+}
+
+- (void)applicationDidUnhide:(NSNotification *)notification;
+{
+    if ([[self window] isVisible]) {
+        [self update: self];
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarms.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarms.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarms.h	(revision 365)
@@ -0,0 +1,42 @@
+//
+//  PSAlarms.h
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Oct 11 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSPropertyListSerialization.h"
+
+extern NSString * const PSAlarmsDidChangeNotification;
+extern NSString * const PSAlarmsNextAlarmDidChangeNotification;
+
+@class PSAlarm;
+
+@interface PSAlarms : NSObject <PSPropertyListSerialization> {
+    NSMutableArray *alarms;
+    NSMutableSet *expiredAlarms;
+    PSAlarm *nextAlarm;
+}
+
++ (void)setUp;
++ (PSAlarms *)allAlarms;
+
+- (unsigned)countOfVersion1Alarms;
+- (void)importVersion1Alarms;
+- (void)discardVersion1Alarms;
+
+- (NSArray *)alarms;
+
+- (PSAlarm *)nextAlarm;
+- (int)alarmCount;
+- (PSAlarm *)alarmAtIndex:(int)index;
+- (void)removeAlarmAtIndex:(int)index;
+- (void)removeAlarmsAtIndices:(NSArray *)indices;
+- (void)removeAlarms:(NSSet *)alarmsToRemove;
+- (void)restoreAlarms:(NSSet *)alarmsToRestore;
+
+- (BOOL)alarmsExpiring;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarms.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarms.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarms.m	(revision 365)
@@ -0,0 +1,331 @@
+//
+//  PSAlarms.m
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Oct 11 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarms.h"
+#import "PSAlarm.h"
+#import "PSTimer.h"
+#import "NSDictionary-NJRExtensions.h"
+
+NSString * const PSAlarmImportException = @"PSAlarmImportException";
+
+NSString * const PSAlarmsDidChangeNotification = @"PSAlarmsDidChangeNotification";
+NSString * const PSAlarmsNextAlarmDidChangeNotification = @"PSAlarmsNextAlarmDidChangeNotification";
+
+// NSUserDefaults key
+static NSString * const PSPendingAlarms = @"Pester pending alarms"; // 1.0 Ð 1.1a3
+static NSString * const PSAllAlarms = @"Pester alarms"; // 1.1a4 Ð 
+
+// property list keys
+static NSString * const PLAlarmsPending = @"pending";
+static NSString * const PLAlarmsExpired = @"expired";
+
+static PSAlarms *PSAlarmsAllAlarms = nil;
+
+@interface PSAlarms (Private)
+
+- (void)_updateNextAlarm;
+
+@end
+
+@implementation PSAlarms
+
++ (void)setUp;
+{
+    [PSTimer setUp];
+    if (PSAlarmsAllAlarms == nil) {
+        NSDictionary *plAlarms = [[NSUserDefaults standardUserDefaults] objectForKey: PSAllAlarms];
+        if (plAlarms == nil) {
+            PSAlarmsAllAlarms = [[self alloc] init];
+        } else {
+            PSAlarmsAllAlarms = [[self alloc] initWithPropertyList: plAlarms];
+        }
+        [PSAlarmsAllAlarms _updateNextAlarm]; // only generate notifications after singleton established
+    }
+}
+
++ (PSAlarms *)allAlarms;
+{
+    NSAssert(PSAlarmsAllAlarms != nil, @"Attempt to use +[PSAlarms allAlarms] before setup complete");
+    return PSAlarmsAllAlarms;
+}
+
+#pragma mark private
+
+- (void)_changed;
+{
+    NSMutableArray *alarmsData = [[NSMutableArray alloc] init];
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    [self _updateNextAlarm];
+    // NSLog(@"PSAlarms _changed:\n%@", alarms);
+    [defaults setObject: [self propertyListRepresentation] forKey: PSAllAlarms];
+    [defaults synchronize];
+    [alarmsData release];
+    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsDidChangeNotification object: self];
+}
+
+- (void)_alarmTimerExpired:(NSNotification *)notification;
+{
+    PSAlarm *alarm = [notification object];
+    NSLog(@"timer expired: %@ retainCount %d", alarm, [alarm retainCount]);
+    [expiredAlarms addObject: alarm];
+    NSLog(@"expired alarms: %@", [expiredAlarms description]);
+    [alarms removeObject: alarm];
+    [self _changed];
+}
+
+- (void)_alarmTimerSet:(NSNotification *)notification;
+{
+    PSAlarm *alarm = [notification object];
+    NSLog(@"timer set: %@ retainCount %d", alarm, [alarm retainCount]);
+    [alarms addObject: alarm];
+    [expiredAlarms removeObject: alarm];
+    [self _changed];
+}
+
+- (void)_alarmDied:(NSNotification *)notification;
+{
+    PSAlarm *alarm = [notification object];
+    // NSLog(@"alarm died: %@ retainCount %d", alarm, [alarm retainCount]);
+    [alarms removeObject: alarm];
+    [expiredAlarms removeObject: alarm];
+    [self _changed];
+}
+
+- (void)_updateNextAlarm;
+{
+    NSEnumerator *e;
+    PSAlarm *alarm, *oldNextAlarm = nextAlarm;
+    [nextAlarm release];
+    nextAlarm = nil;
+    // sort alarms so earliest is first
+    [alarms sortUsingSelector: @selector(compareDate:)];
+    // find first un-expired alarm
+    e = [alarms objectEnumerator];
+    while ( (alarm = [e nextObject]) != nil) {
+        if ([alarm isValid]) {
+            nextAlarm = [alarm retain];
+            break;
+        }
+    }
+    if (oldNextAlarm != nextAlarm)
+        [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsNextAlarmDidChangeNotification object: nextAlarm];
+}
+
+- (void)_setUpNotifications;
+{
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerSet:) name: PSAlarmTimerSetNotification object: nil];
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerExpired:) name: PSAlarmTimerExpiredNotification object: nil];
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmDied:) name: PSAlarmDiedNotification object: nil];
+}
+
+#pragma mark initialize-release
+
+- (id)init;
+{
+    if ( (self = [super init]) != nil) {
+        alarms = [[NSMutableArray alloc] init];
+        expiredAlarms = [[NSMutableSet alloc] init];
+        [self _setUpNotifications];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [alarms release];
+    [expiredAlarms release];
+    [[NSNotificationCenter defaultCenter] removeObserver: self];
+    [super dealloc];
+}
+
+#pragma mark accessing
+
+- (NSArray *)alarms;
+{
+    return alarms;
+}
+
+- (PSAlarm *)nextAlarm;
+{
+    return nextAlarm;
+}
+
+- (int)alarmCount;
+{
+    return [alarms count];
+}
+
+- (PSAlarm *)alarmAtIndex:(int)index;
+{
+    return [alarms objectAtIndex: index];
+}
+
+- (void)removeAlarmAtIndex:(int)index;
+{
+    [(PSAlarm *)[alarms objectAtIndex: index] cancelTimer];
+    [alarms removeObjectAtIndex: index];
+}
+
+- (void)removeAlarmsAtIndices:(NSArray *)indices;
+{
+    NSEnumerator *e = [indices objectEnumerator];
+    NSNumber *n;
+    unsigned indexCount = [indices count], i = 0, alarmIndex;
+    unsigned *indexArray = (unsigned *)malloc(indexCount * sizeof(unsigned));
+    @try {
+        while ( (n = [e nextObject]) != nil) {
+            alarmIndex = [n intValue];
+            [(PSAlarm *)[alarms objectAtIndex: alarmIndex] cancelTimer];
+            indexArray[i] = alarmIndex;
+            i++;
+        }
+        [alarms removeObjectsFromIndices: indexArray numIndices: indexCount];
+    } @finally {
+        free(indexArray); indexArray = NULL;
+        [self _changed];
+    }
+}
+
+- (void)removeAlarms:(NSSet *)alarmsToRemove;
+{
+    NSEnumerator *e = [alarms objectEnumerator];
+    PSAlarm *alarm;
+    NSMutableArray *indices = [NSMutableArray arrayWithCapacity: [alarmsToRemove count]];
+    int alarmIndex = 0;
+
+    while ( (alarm = [e nextObject]) != nil) {
+        if ([alarmsToRemove containsObject: alarm])
+            [indices addObject: [NSNumber numberWithInt: alarmIndex]];
+        alarmIndex++;
+    }
+    [self removeAlarmsAtIndices: indices];
+}
+
+- (void)restoreAlarms:(NSSet *)alarmsToRestore;
+{
+    [alarmsToRestore makeObjectsPerformSelector: @selector(resetTimer)];
+}
+
+- (BOOL)alarmsExpiring;
+{
+    return [expiredAlarms count] != 0;
+}
+
+#pragma mark printing
+
+- (NSString *)description;
+{
+    return [NSString stringWithFormat: @"%@ pending %@\n%@\n",
+        [super description], alarms,
+        [expiredAlarms count] > 0 ? [NSString stringWithFormat: @"expired %@\n", expiredAlarms]
+                                  : @""];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableArray *plPendingAlarms = [[NSMutableArray alloc] initWithCapacity: [alarms count]];
+    NSMutableArray *plExpiredAlarms = [[NSMutableArray alloc] initWithCapacity: [expiredAlarms count]];
+    NSDictionary *plAllAlarms, *plAlarm;
+    NSEnumerator *e;
+    PSAlarm *alarm;
+
+    e = [alarms objectEnumerator];
+    while ( (alarm = [e nextObject]) != nil) {
+        plAlarm = [alarm propertyListRepresentation];
+        if (plAlarm != nil)
+            [plPendingAlarms addObject: plAlarm];
+    }
+
+    e = [expiredAlarms objectEnumerator];
+    while ( (alarm = [e nextObject]) != nil) {
+        plAlarm = [alarm propertyListRepresentation];
+        if (plAlarm != nil)
+            [plExpiredAlarms addObject: plAlarm];
+    }
+    
+    plAllAlarms = [NSDictionary dictionaryWithObjectsAndKeys:
+        plPendingAlarms, PLAlarmsPending, plExpiredAlarms, PLAlarmsExpired, nil];
+    [plPendingAlarms release];
+    [plExpiredAlarms release];
+
+    return plAllAlarms;
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [super init]) != nil) {
+        NSArray *plPendingAlarms = [dict objectForRequiredKey: PLAlarmsPending];
+        NSArray *plExpiredAlarms = [dict objectForRequiredKey: PLAlarmsExpired];
+        NSEnumerator *e;
+        NSDictionary *plAlarm;
+        PSAlarm *alarm;
+
+        alarms = [[NSMutableArray alloc] initWithCapacity: [plPendingAlarms count]];
+        e = [plPendingAlarms objectEnumerator];
+        while ( (plAlarm = [e nextObject]) != nil) {
+	    alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm];
+            [alarms addObject: alarm];
+	    [alarm release];
+        }
+
+        e = [plExpiredAlarms objectEnumerator];
+        while ( (plAlarm = [e nextObject]) != nil) {
+            // expired alarms may be ready for deletion, or may repeat - if the latter, PSAlarm will reschedule the alarm so the repeat interval begins at restoration time. 
+            if ( (alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm]) != nil) {
+                [alarms addObject: alarm];
+		[alarm release];
+	    }
+        }
+        expiredAlarms = [[NSMutableSet alloc] init];
+        
+        [self _setUpNotifications];
+    }
+    return self;
+}
+
+#pragma mark archiving (Pester 1.0)
+
+- (unsigned)countOfVersion1Alarms;
+{
+    return [[[NSUserDefaults standardUserDefaults] objectForKey: PSPendingAlarms] count];
+}
+
+- (void)discardVersion1Alarms;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    [defaults removeObjectForKey: PSPendingAlarms];
+    [defaults synchronize];
+}
+
+- (void)importVersion1Alarms;
+{
+    NSArray *alarmsData = [[NSUserDefaults standardUserDefaults] arrayForKey: PSPendingAlarms];
+    NSEnumerator *e = [alarmsData objectEnumerator];
+    NSData *alarmData;
+    PSAlarm *alarm;
+    NSMutableArray *importedAlarms = [[NSMutableArray alloc] initWithCapacity: [alarmsData count]];
+    @try {
+	while ( (alarmData = [e nextObject]) != nil) {
+	    alarm = [NSUnarchiver unarchiveObjectWithData: alarmData];
+	    if (alarm == nil)
+		@throw [NSException exceptionWithName: NSInternalInconsistencyException reason: @"Failed to decode Pester 1.0 alarm." userInfo: nil];
+	    [importedAlarms addObject: alarm];
+	    if (![alarm setTimer]) // expired
+		[alarms addObject: alarm];
+	}
+    } @catch (NSException *exception) {
+	[self removeAlarms: [NSSet setWithArray: importedAlarms]];
+	@throw;
+    } @finally {
+	[importedAlarms release];
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmsController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmsController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmsController.h	(revision 365)
@@ -0,0 +1,22 @@
+//
+//  PSAlarmsController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Oct 11 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSAlarms.h"
+
+@interface PSAlarmsController : NSWindowController {
+    IBOutlet NSTableView *alarmList;
+    IBOutlet NSButton *removeButton;
+    PSAlarms *alarms;
+    NSArray *reorderedAlarms;
+    NSDictionary *messageAttributes;
+}
+
+- (IBAction)remove:(id)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlarmsController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlarmsController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlarmsController.m	(revision 365)
@@ -0,0 +1,181 @@
+//
+//  PSAlarmsController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Oct 11 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarmsController.h"
+#import "PSAlarm.h"
+#import "PSAlerts.h"
+#import "NSString-NJRExtensions.h"
+#import "NSTableView-NJRExtensions.h"
+#import "NJRTableView.h"
+#import "NJRTableDelegate.h"
+
+@interface PSAlarmsController (PrivateUndoSupport)
+
+- (void)_restoreAlarms:(NSSet *)selectedAlarms;
+- (void)_removeAlarms:(NSSet *)selectedAlarms;
+
+@end
+
+@implementation PSAlarmsController
+
+- (void)alarmsChanged;
+{
+    reorderedAlarms = [[alarmList delegate] reorderedDataForData: [alarms alarms]];
+}
+
+- (id)init;
+{
+    if ( (self = [super initWithWindowNibName: @"Alarms"]) != nil) {
+        alarms = [PSAlarms allAlarms];
+        // XXX workaround for bug in 10.2.1, 10.1.5: autosave name set in IB doesn't show up
+        [self setWindowFrameAutosaveName: @"Pester alarm list"];
+        // Apple documents the NSUserDefaults key, so we can rely on it hopefully.
+        if (nil == [[NSUserDefaults standardUserDefaults] objectForKey:
+            [@"NSWindow Frame " stringByAppendingString: [[self window] frameAutosaveName]]])
+           {
+            [[self window] center];
+           }
+        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(alarmsChanged) name: PSAlarmsDidChangeNotification object: alarms];
+        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(tableViewSelectionDidChange:) name: NSTableViewSelectionDidChangeNotification object: alarmList];
+        messageAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: [[[alarmList tableColumnWithIdentifier: @"message"] dataCell] font], NSFontAttributeName, nil];
+        [alarmList setAutosaveName: @"Alarm list"];
+        [alarmList setAutosaveTableColumns: YES];
+        [self alarmsChanged];
+        [[self window] makeFirstResponder: alarmList];
+        [[self window] setResizeIncrements: NSMakeSize(1, [alarmList cellHeight])];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [reorderedAlarms release];
+    [messageAttributes release];
+    [super dealloc];
+}
+
+- (NSUndoManager *)undoManager;
+{
+    return [[self window] undoManager];
+}
+
+- (void)_restoreAlarms:(NSSet *)selectedAlarms;
+{
+    [alarms restoreAlarms: selectedAlarms];
+    [[alarmList delegate] selectItems: selectedAlarms];
+    [[self undoManager] setActionName: NSLocalizedString(@"Alarm Removal", "Undo action")];
+    [[[self undoManager] prepareWithInvocationTarget: self] _removeAlarms: selectedAlarms];
+}
+
+- (void)_removeAlarms:(NSSet *)selectedAlarms;
+{
+    [alarms removeAlarms: selectedAlarms];
+    [[self undoManager] setActionName: NSLocalizedString(@"Alarm Removal", "Undo action")];
+    [[[self undoManager] prepareWithInvocationTarget: self] _restoreAlarms: selectedAlarms];
+}
+
+- (IBAction)remove:(id)sender;
+{
+    [self _removeAlarms: [[alarmList delegate] selectedItems]];
+}
+
+@end
+
+@implementation PSAlarmsController (NSTableDataSource)
+
+- (int)numberOfRowsInTableView:(NSTableView *)tableView;
+{
+    return [alarms alarmCount];
+}
+
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
+{
+    PSAlarm *alarm = [reorderedAlarms objectAtIndex: row];
+
+    if ([[tableColumn identifier] isEqualToString: @"message"]) {
+        NSMutableString *message = [[alarm message] mutableCopy];
+        [message truncateToWidth: [tableView frameOfCellAtColumn: 0 row: row].size.width by: NSLineBreakByTruncatingTail withAttributes: messageAttributes];
+        return [message autorelease];
+    } else {
+        NSCalendarDate *date = [alarm date];
+        if ([[tableColumn identifier] isEqualToString: @"date"]) return [alarm shortDateString];
+        if ([[tableColumn identifier] isEqualToString: @"time"]) {
+            if (date == nil) return @"«expired»";
+            return [alarm timeString];
+        }
+    }
+    return nil;
+}
+@end
+
+@implementation PSAlarmsController (NJRTableViewDataSource)
+
+- (void)removeSelectedRowsFromTableView:(NSTableView *)aTableView;
+{
+    [self remove: aTableView];
+}
+
+- (NSString *)toolTipForRow:(int)rowIndex;
+{
+    PSAlarm *alarm = [reorderedAlarms objectAtIndex: rowIndex];
+    
+    return [[alarm prettyDescription] string];
+}
+
+@end
+
+@implementation PSAlarmsController (NSTableViewNotifications)
+
+- (void)tableViewSelectionDidChange:(NSNotification *)aNotification;
+{
+    [removeButton setEnabled: ([alarmList numberOfSelectedRows] != 0)];
+}
+
+@end
+
+@implementation PSAlarmsController (NSWindowDelegate)
+
+// XXX workaround for bug in 10.1.5, 10.2.1 (and earlier?): no autosave on window move
+- (void)windowDidMove:(NSNotification *)aNotification
+{
+    NSString *autosaveName = [[self window] frameAutosaveName];
+    // on initial display, we get a notification inside -[NSWindow setFrameAutosaveName]!
+    if (autosaveName != nil) {
+        [[self window] saveFrameUsingName: autosaveName];
+    }
+}
+
+- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame;
+{
+    NSWindow *window = [alarmList window];
+    NSRect frame = [window frame];
+    NSScrollView *scrollView = [alarmList enclosingScrollView];
+    float displayedHeight = [[scrollView contentView] bounds].size.height;
+    float heightChange = [[scrollView documentView] bounds].size.height - displayedHeight;
+    float heightExcess;
+
+    if (heightChange >= 0 && heightChange <= 1) {
+        // either the window is already optimal size, or it's too big
+        float rowHeight = [alarmList cellHeight];
+        heightChange = (rowHeight * [alarmList numberOfRows]) - displayedHeight;
+    }
+
+    frame.size.height += heightChange;
+
+    if ( (heightExcess = [window minSize].height - frame.size.height) > 1 ||
+         (heightExcess = [window maxSize].height - frame.size.height) < 1) {
+        heightChange += heightExcess;
+        frame.size.height += heightExcess;
+    }
+
+    frame.origin.y -= heightChange;
+
+    return frame;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlert.h	(revision 365)
@@ -0,0 +1,33 @@
+//
+//  PSAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "PSAlarm.h"
+#import "PSPropertyListSerialization.h"
+#import "NSString-NJRExtensions.h"
+
+extern NSString * const PSAlertCreationException;
+
+extern NSString * const PSAlarmAlertCompletedNotification; // userInfo key: "alert" -> PSAlert
+
+@interface PSAlert : NSObject <PSPropertyListSerialization> {
+    
+}
+
+// subclasses should implement these methods
++ (PSAlert *)alert;
+- (void)prepareForAlarm:(PSAlarm *)alarm;
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+- (BOOL)requiresPesterFrontmost;
+
+- (NSAttributedString *)actionDescription;
+
+// after alert completes, invoke method of superclass
+- (void)completedForAlarm:(PSAlarm *)alarm;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlert.m	(revision 365)
@@ -0,0 +1,78 @@
+//
+//  PSAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+#import "NSDictionary-NJRExtensions.h"
+
+NSString * const PSAlertCreationException = @"PSAlertCreationException";
+
+NSString * const PSAlarmAlertCompletedNotification = @"PSAlarmAlertCompletedNotification";
+
+// property list keys
+static NSString * const PLAlertClass = @"class"; // NSString
+
+@implementation PSAlert
+
++ (PSAlert *)alert;
+{
+    NSAssert(NO, @"Class is abstract");
+    return nil;
+}
+
+- (void)prepareForAlarm:(PSAlarm *)alarm;
+{
+    return;
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    NSAssert(NO, @"Class is abstract");
+}
+
+- (BOOL)requiresPesterFrontmost;
+{
+    return NO;
+}
+
+- (void)completedForAlarm:(PSAlarm *)alarm;
+{
+    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmAlertCompletedNotification object: alarm userInfo: [NSDictionary dictionaryWithObject: self forKey: @"alert"]];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    NSAssert(NO, @"Class is abstract");
+    return nil;
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    return [NSDictionary dictionaryWithObject: NSStringFromClass([self class]) forKey: PLAlertClass];
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [self init]) != nil) {
+        IMP myImp = [self methodForSelector: _cmd];
+        NSString *clsString = [dict objectForRequiredKey: PLAlertClass];
+        Class cls = NSClassFromString(clsString);
+        NSAssert1(cls != nil, @"Alert class %@ is not available", clsString);
+        [super release];
+        self = [cls alloc];
+        if (self != nil) {
+            IMP subImp = [self methodForSelector: @selector(initWithPropertyList:)];
+            NSAssert1(subImp != myImp, @"No implementation of initWithPropertyList: for alert class %@", clsString);
+            self = [self initWithPropertyList: dict];
+        }
+    }
+    return self;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlerts.h
===================================================================
--- releases/Pester/1.1b6/Source/PSAlerts.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlerts.h	(revision 365)
@@ -0,0 +1,35 @@
+//
+//  PSAlerts.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Dec 21 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSPropertyListSerialization.h"
+
+@class PSAlert, PSAlarm;
+
+@interface PSAlerts : NSObject <PSPropertyListSerialization>
+{
+    NSMutableArray *alerts;
+    BOOL requirePesterFrontmost;
+}
+
+- (id)init;
+- (id)initWithPesterVersion1Alerts;
+
+- (void)addAlert:(PSAlert *)alert;
+- (void)removeAlerts;
+
+- (NSArray *)allAlerts;
+- (NSEnumerator *)alertEnumerator;
+- (BOOL)requirePesterFrontmost; // do any alerts require Pester be in front?
+
+- (void)prepareForAlarm:(PSAlarm *)alarm;
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+
+- (NSAttributedString *)prettyList;
+
+@end
Index: releases/Pester/1.1b6/Source/PSAlerts.m
===================================================================
--- releases/Pester/1.1b6/Source/PSAlerts.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSAlerts.m	(revision 365)
@@ -0,0 +1,149 @@
+//
+//  PSAlerts.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Dec 21 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlerts.h"
+#import "PSAlert.h"
+#import "PSDockBounceAlert.h"
+#import "PSNotifierAlert.h"
+#import "PSBeepAlert.h"
+
+// property list keys
+static NSString * const PLAlerts = @"alerts"; // NSString
+
+@implementation PSAlerts
+
+#pragma mark initialize-release
+
+- (id)init;
+{
+    if ( (self = [super init]) != nil) {
+        alerts = [[NSMutableArray alloc] initWithCapacity: 4];
+    }
+    return self;
+}
+
+- (id)initWithPesterVersion1Alerts;
+{
+    if ( (self = [self init]) != nil) {
+        [self addAlert: [PSDockBounceAlert alert]];
+        [self addAlert: [PSNotifierAlert alert]];
+        [self addAlert: [PSBeepAlert alertWithRepetitions: 1]];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [alerts release]; alerts = nil;
+    [super dealloc];
+}
+
+#pragma mark accessing
+
+- (void)addAlert:(PSAlert *)alert;
+{
+    [alerts addObject: alert];
+    if ([alert requiresPesterFrontmost])
+        requirePesterFrontmost = YES;
+}
+
+- (void)removeAlerts;
+{
+    [alerts removeAllObjects];
+    requirePesterFrontmost = NO;
+}
+
+- (NSEnumerator *)alertEnumerator;
+{
+    return [alerts objectEnumerator];
+}
+
+- (NSArray *)allAlerts;
+{
+    return [[alerts copy] autorelease];
+}
+
+- (BOOL)requirePesterFrontmost;
+{
+    return requirePesterFrontmost;
+}
+
+#pragma mark actions
+
+- (void)prepareForAlarm:(PSAlarm *)alarm;
+{
+    [alerts makeObjectsPerformSelector: @selector(prepareForAlarm:) withObject: alarm];
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    [alerts makeObjectsPerformSelector: @selector(triggerForAlarm:) withObject: alarm];
+}
+
+#pragma mark printing
+
+- (NSAttributedString *)prettyList;
+{
+    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init];
+    NSEnumerator *e = [self alertEnumerator];
+    PSAlert *alert;
+    unsigned int length;
+    while ( (alert = [e nextObject]) != nil) {
+	if ([alert isKindOfClass: [PSDockBounceAlert class]]) // XXX temporary for 1.1b5
+	    continue;
+        [string appendAttributedString: [NSLocalizedString(@"* ", "Unordered list label (usually a bullet followed by a space)") small]];
+        [string appendAttributedString: [alert actionDescription]];
+        [string appendAttributedString: [@"\n" small]];
+    }
+    if ( (length = [string length]) == 0) {
+        [string release];
+        return nil;
+    } else {
+        NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+        [string deleteCharactersInRange: NSMakeRange(length - 1, 1)]; // remove trailing newline
+        [paraStyle setHeadIndent: [[string attribute: NSFontAttributeName atIndex: 0 effectiveRange: NULL] widthOfString: NSLocalizedString(@"* ", "Unordered list label (usually a bullet followed by a space)")]];
+        [string addAttribute: NSParagraphStyleAttributeName value: paraStyle range: NSMakeRange(0, length - 1)];
+        [paraStyle release]; paraStyle = nil;
+        return [string autorelease];
+    }
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableArray *plAlerts = [[NSMutableArray alloc] initWithCapacity: [alerts count]];
+    NSEnumerator *e = [self alertEnumerator];
+    PSAlert *alert;
+    while ( (alert = [e nextObject]) != nil) {
+        [plAlerts addObject: [alert propertyListRepresentation]];
+    }
+    NSDictionary *dict = [NSDictionary dictionaryWithObject: plAlerts forKey: PLAlerts];
+    [plAlerts release];
+    return dict;
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [self init]) != nil) {
+	@try {
+	    NSArray *plAlerts = [dict objectForKey: PLAlerts];
+	    NSEnumerator *e = [plAlerts objectEnumerator];
+	    NSDictionary *alertDict;
+	    while ( (alertDict = [e nextObject]) != nil) {
+		[self addAlert: [[PSAlert alloc] initWithPropertyList: alertDict]];
+	    }
+	} @catch (NSException *e) {
+	    [self release];
+	    @throw;
+	}
+    }
+    return self;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSApplication.h
===================================================================
--- releases/Pester/1.1b6/Source/PSApplication.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSApplication.h	(revision 365)
@@ -0,0 +1,30 @@
+//
+//  PSApplication.h
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Oct 11 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@class PSAlarmsController;
+@class PSAlarmSetController;
+@class PSTimer;
+@class PSPreferencesController;
+
+@interface PSApplication : NSApplication {
+    PSAlarmsController *alarmsController;
+    IBOutlet PSAlarmSetController *alarmSetController;
+    PSPreferencesController *preferencesController;
+    PSTimer *dockUpdateTimer;
+    NSTimeInterval dockUpdateInterval;
+    NSImage *appIconImage;
+}
+
+- (IBAction)orderFrontSetAlarmPanel:(id)sender;
+- (IBAction)orderFrontAlarmsPanel:(id)sender;
+- (IBAction)orderFrontPreferencesPanel:(id)sender;
+- (IBAction)stopAlerts:(id)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/PSApplication.m
===================================================================
--- releases/Pester/1.1b6/Source/PSApplication.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSApplication.m	(revision 365)
@@ -0,0 +1,251 @@
+//
+//  PSApplication.m
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Oct 11 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSApplication.h"
+#import "PSAlarmSetController.h"
+#import "PSAlarmAlertController.h"
+#import "PSAlarmsController.h"
+#import "PSPreferencesController.h"
+#import "NJRReadMeController.h"
+#import "NJRSoundManager.h"
+#import "PSAlarm.h"
+#import "PSAlarms.h"
+#import "PSTimer.h"
+#import "NJRHotKey.h"
+
+#import <Carbon/Carbon.h>
+
+@implementation PSApplication
+
+- (void)finishLaunching;
+{
+    appIconImage = [[NSImage imageNamed: @"NSApplicationIcon"] retain];
+    [[NSNotificationCenter defaultCenter] addObserver: [PSAlarmAlertController class] selector: @selector(controllerWithTimerExpiredNotification:) name: PSAlarmTimerExpiredNotification object: nil];
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(nextAlarmDidChange:) name: PSAlarmsNextAlarmDidChangeNotification object: nil];
+    // XXX exception handling
+    [PSAlarms setUp];
+    [self setDelegate: self];
+    [PSPreferencesController readPreferences];
+    [super finishLaunching];
+}
+
+#pragma mark actions
+
+- (IBAction)showHelp:(id)sender;
+{
+    [NJRReadMeController readMeControllerWithRTFDocument: [[NSBundle mainBundle] pathForResource: @"Read Me" ofType: @"rtfd"]];
+}
+
+- (IBAction)stopAlerts:(id)sender;
+{
+    [PSAlarmAlertController stopAlerts: sender];
+}	
+
+- (IBAction)orderFrontSetAlarmPanel:(id)sender;
+{
+    [NSApp activateIgnoringOtherApps: YES];
+    [alarmSetController showWindow: self];
+}
+
+- (IBAction)orderFrontAlarmsPanel:(id)sender;
+{
+    [NSApp activateIgnoringOtherApps: YES];
+    if (alarmsController == nil) {
+        alarmsController = [[PSAlarmsController alloc] init];
+    }
+    [alarmsController showWindow: self];
+}
+
+- (IBAction)orderFrontPreferencesPanel:(id)sender;
+{
+    if (!preferencesController)  {
+        preferencesController = [[PSPreferencesController alloc] init];
+    }
+    [preferencesController showWindow: self];
+}
+
+#pragma mark update timer
+
+- (void)_resetUpdateTimer;
+{
+    if (dockUpdateTimer != nil) {
+        [dockUpdateTimer invalidate];
+        [dockUpdateTimer release];
+        dockUpdateInterval = 0;
+        dockUpdateTimer = nil;
+    }
+}
+
+- (void)_setUpdateTimerForInterval:(NSTimeInterval)interval alarm:(PSAlarm *)alarm repeats:(BOOL)repeats;
+{
+    dockUpdateTimer = [PSTimer scheduledTimerWithTimeInterval: interval target: self selector: @selector(_updateDockTile:) userInfo: alarm repeats: repeats];
+    [dockUpdateTimer retain];
+    dockUpdateInterval = interval; // because [timer timeInterval] always returns 0 once set
+}
+
+- (void)_updateDockTile:(PSTimer *)timer;
+{
+    PSAlarm *alarm = [timer userInfo];
+    NSTimeInterval timeRemaining;
+    NSString *tileString;
+    if (timer == nil) alarm = [[PSAlarms allAlarms] nextAlarm];
+    if (alarm == nil) return;
+    tileString = [alarm timeRemainingString];
+    timeRemaining = [alarm timeRemaining]; // want to err on the side of timeRemaining being smaller, otherwise ÇexpiredÈ can appear
+    {
+        NSMutableDictionary *atts = [NSMutableDictionary dictionary];
+        NSSize imageSize = [appIconImage size];
+        NSImage *tile = [[NSImage alloc] initWithSize: imageSize];
+        NSSize textSize;
+        NSPoint textOrigin;
+        NSRect frameRect;
+        float fontSize = 37;
+        
+        do {
+            fontSize -= 1;
+            [atts setObject: [NSFont boldSystemFontOfSize: fontSize] forKey: NSFontAttributeName];
+            textSize = [tileString sizeWithAttributes: atts];
+        } while (textSize.width > imageSize.width - 8);
+
+        textOrigin = NSMakePoint(imageSize.width / 2 - textSize.width / 2,
+                                 imageSize.height / 2 - textSize.height / 2);
+        frameRect = NSInsetRect(NSMakeRect(textOrigin.x, textOrigin.y, textSize.width, textSize.height), -4, -2);
+        
+        [tile lockFocus];
+        // draw the grayed-out app icon
+        [appIconImage dissolveToPoint: NSZeroPoint fraction: 0.5];
+        // draw the frame
+        [[NSColor colorWithCalibratedWhite: 0.1 alpha: 0.5] set];
+        NSRectFill(frameRect);
+        // draw a gray two-pixel text shadow
+        [atts setObject: [NSColor grayColor] forKey: NSForegroundColorAttributeName];
+        textOrigin.x++; textOrigin.y--;
+        [tileString drawAtPoint: textOrigin withAttributes: atts];
+        textOrigin.x++; textOrigin.y--;
+        [tileString drawAtPoint: textOrigin withAttributes: atts];
+        // draw white text
+        textOrigin.x -= 2; textOrigin.y += 2;
+        [atts setObject: [NSColor whiteColor] forKey: NSForegroundColorAttributeName];
+        [tileString drawAtPoint: textOrigin withAttributes: atts];
+        
+        [tile unlockFocus];
+        [NSApp setApplicationIconImage: tile];
+        [tile release];
+    }
+    // NSLog(@"_updateDockTile > time remaining %@ (%.6lf), last time interval %.6lf", tileString, timeRemaining, dockUpdateInterval);
+    if (timeRemaining > 61) {
+        NSTimeInterval nextUpdate = ((unsigned long long)timeRemaining) % 60;
+        if (nextUpdate <= 1) nextUpdate = 60;
+        [self _resetUpdateTimer];
+        [self _setUpdateTimerForInterval: nextUpdate alarm: alarm repeats: NO];
+        // NSLog(@"_updateDockTile > set timer for %.0lf seconds", nextUpdate);
+    } else if (timer == nil || dockUpdateInterval > 1) {
+        [self _resetUpdateTimer]; 
+        [self _setUpdateTimerForInterval: 1 alarm: alarm repeats: YES];
+        // NSLog(@"_updateDockTile > set timer for 1 second");
+    } else if (timeRemaining <= 1) {
+        [self _resetUpdateTimer];
+    }
+}
+
+- (void)nextAlarmDidChange:(NSNotification *)notification;
+{
+    PSAlarm *nextAlarm = [notification object];
+    // NSLog(@"nextAlarmDidChange: %@", nextAlarm);
+    [self _resetUpdateTimer];
+    if (nextAlarm == nil) {
+        [NSApp setApplicationIconImage: appIconImage];
+    } else {
+        [self _updateDockTile: nil];
+    }
+}
+
+@end
+
+@implementation PSApplication (NSApplicationDelegate)
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag;
+{
+    // XXX sometimes alarmsExpiring is NO (?), and we display the alarm set controller on top of an expiring alarm, try to reproduce
+    if (!flag && ![[PSAlarms allAlarms] alarmsExpiring] && [NSApp modalWindow] == nil)
+        [alarmSetController showWindow: self];
+    return YES;
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
+{
+    NSMenu *dockMenu = [[NSMenu alloc] initWithTitle: @""];
+    PSAlarms *alarms = [PSAlarms allAlarms];
+    PSAlarm *nextAlarm = [alarms nextAlarm];
+    NSMenuItem *item;
+    if (nextAlarm == nil) {
+        [dockMenu addItemWithTitle: @"No Pending Alarms" action: nil keyEquivalent: @""];
+    } else {
+        [dockMenu addItemWithTitle: @"Next Alarm" action: nil keyEquivalent: @""];
+        [dockMenu addItemWithTitle: [NSString stringWithFormat: @"   %@", [nextAlarm message]] action: nil keyEquivalent: @""];
+        [dockMenu addItemWithTitle: [NSString stringWithFormat: @"   %@ %@", [nextAlarm shortDateString], [nextAlarm timeString]] action: nil keyEquivalent: @""];
+        [dockMenu addItemWithTitle: [NSString stringWithFormat: @"   Remaining: %@", [nextAlarm timeRemainingString]] action: nil keyEquivalent: @""];
+    }
+    [dockMenu addItem: [NSMenuItem separatorItem]];
+    item = [dockMenu addItemWithTitle: NSLocalizedString(@"Set Alarm...", "Dock menu item") action: @selector(orderFrontSetAlarmPanel:) keyEquivalent: @""];
+    [item setTarget: self];
+    item = [dockMenu addItemWithTitle: [NSString stringWithFormat: NSLocalizedString(@"All Alarms (%d)...", "Dock menu item (%d replaced by number of alarms)"), [alarms alarmCount]] action: @selector(orderFrontAlarmsPanel:) keyEquivalent: @""];
+    [item setTarget: self];
+    return [dockMenu autorelease];
+}
+
+@end
+
+@implementation PSApplication (NSApplicationNotifications)
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification;
+{
+    // XXX import panel will not be frontmost window if you switch to another app while Pester is launching; Mac OS X bug?
+    PSAlarms *allAlarms = [PSAlarms allAlarms];
+    unsigned version1AlarmCount = [allAlarms countOfVersion1Alarms];
+    if (version1AlarmCount > 0) {
+        int answer = NSRunAlertPanel(@"Import alarms from older Pester version?", @"Pester found %u alarm%@ created with an older version. These alarms must be converted for use with this version of Pester, and will be unavailable in previous versions after conversion. New alarms created with this version of Pester will not appear in Pester version 1.1a3 or earlier.",
+                                     @"Import", @"Discard", NSLocalizedString(@"Don't Import", "Pester <= 1.1a3 format alarms button"),
+                                     version1AlarmCount, version1AlarmCount == 1 ? @"" : @"s");
+        switch (answer) {
+            case NSAlertDefaultReturn:
+                @try {
+                    [allAlarms importVersion1Alarms];
+                } @catch (NSException *exception) {
+                    NSRunAlertPanel(@"Error occurred importing alarms", @"Pester was unable to convert some alarms created with an older version. Those alarms which could be read have been converted. The previous-format alarms have been retained; try using an older version of Pester to read them.\n\n%@", nil, nil, nil, [exception reason]);
+                    return;
+                }
+            case NSAlertAlternateReturn:
+                [allAlarms discardVersion1Alarms];
+                break;
+            case NSAlertOtherReturn:
+                break;
+        }
+    }
+}
+
+- (void)applicationWillTerminate:(NSNotification *)notification;
+{
+    [NJRSoundManager restoreSavedDefaultOutputVolume];
+    [NSApp setApplicationIconImage: appIconImage];
+}
+
+// calendar window (running in modal session) will appear even when app is in background; shouldn't
+- (void)applicationWillBecomeActive:(NSNotification *)notification;
+{
+    NSWindow *modalWindow = [NSApp modalWindow];
+    if (modalWindow != nil) [modalWindow makeKeyAndOrderFront: nil];
+}
+
+- (void)applicationWillResignActive:(NSNotification *)notification;
+{
+    NSWindow *modalWindow = [NSApp modalWindow];
+    if (modalWindow != nil) [modalWindow orderOut: nil];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSBeepAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSBeepAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSBeepAlert.h	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  PSBeepAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSMediaAlert.h"
+
+@interface PSBeepAlert : PSMediaAlert {
+    PSAlarm *alarm;
+    unsigned short repetitionsRemaining;
+    BOOL savedVolume;
+}
+
++ (PSBeepAlert *)alertWithRepetitions:(unsigned short)numReps;
+
+@end
Index: releases/Pester/1.1b6/Source/PSBeepAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSBeepAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSBeepAlert.m	(revision 365)
@@ -0,0 +1,70 @@
+//
+//  PSBeepAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSBeepAlert.h"
+#import "PSAlarmAlertController.h"
+#import "NJRSoundManager.h"
+#import "NSDictionary-NJRExtensions.h"
+
+// property list keys
+static NSString * const PLAlertRepetitions = @"times"; // NSNumber
+
+@implementation PSBeepAlert
+
++ (PSBeepAlert *)alertWithRepetitions:(unsigned short)numReps;
+{
+    return [[self alloc] initWithRepetitions: numReps];
+}
+
+- (void)dealloc;
+{
+    [[NSNotificationCenter defaultCenter] removeObserver: self];
+    [super dealloc];
+}
+
+- (void)beep;
+{
+    NSBeep();
+    repetitionsRemaining--;
+    if (repetitionsRemaining == 0) {
+        if (savedVolume) {
+            [NJRSoundManager restoreSavedDefaultOutputVolumeIfCurrently: outputVolume];
+        }
+        [self completedForAlarm: alarm];
+        [self release];
+        return;
+    }
+    [self performSelector: @selector(beep) withObject: nil afterDelay: 0.15 inModes: [NSArray arrayWithObject: NSDefaultRunLoopMode]];
+}
+
+- (void)_stopBeeping:(NSNotification *)notification;
+{
+    repetitionsRemaining = 1;
+}
+
+- (void)triggerForAlarm:(PSAlarm *)anAlarm;
+{
+    alarm = anAlarm;
+    repetitionsRemaining = repetitions;
+    [self retain];
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_stopBeeping:) name: PSAlarmAlertStopNotification object: nil];
+    savedVolume = [NJRSoundManager saveDefaultOutputVolume];
+    [self beep];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    return // XXX [
+        [[@"Play the system alert sound" stringByAppendingString:
+                                             repetitions == 1 ? @"" : [NSString stringWithFormat: @" %hu times", repetitions]]
+        // XXX stringByAppendingString: outputVolume == kNoVolume ? @"" : [NSString stringWithFormat: @" at %.0f%% volume", outputVolume * 100]]
+            small];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSCalendarController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSCalendarController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSCalendarController.h	(revision 365)
@@ -0,0 +1,34 @@
+//
+//  PSCalendarController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Feb 14 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@class OACalendarView;
+
+@interface PSCalendarController : NSWindowController {
+    IBOutlet OACalendarView *calendarView;
+    IBOutlet NSButton *okButton;
+    id delegate;
+}
+
++ (PSCalendarController *)controllerWithDate:(NSCalendarDate *)aDate delegate:(id)aDelegate;
+
+- (id)initWithDate:(NSCalendarDate *)aDate delegate:(id)aDelegate;
+
+- (IBAction)close:(NSButton *)sender;
+- (IBAction)cancel:(NSButton *)sender;
+- (IBAction)today:(NSButton *)sender;
+
+@end
+
+@interface NSObject (PSCalendarControllerDelegate)
+
+- (void)calendarController:(PSCalendarController *)controller didSetDate:(NSCalendarDate *)date;
+- (NSView *)calendarControllerLaunchingView:(PSCalendarController *)controller;
+
+@end
Index: releases/Pester/1.1b6/Source/PSCalendarController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSCalendarController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSCalendarController.m	(revision 365)
@@ -0,0 +1,115 @@
+//
+//  PSCalendarController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Fri Feb 14 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSCalendarController.h"
+#import "OACalendarView.h"
+#import "NSCalendarDate-OFExtensions.h"
+#import "NSCalendarDate-NJRExtensions.h"
+
+@implementation PSCalendarController
+
++ (PSCalendarController *)controllerWithDate:(NSCalendarDate *)aDate delegate:(id)aDelegate;
+{
+    return [[self alloc] initWithDate: aDate delegate: aDelegate];
+}
+
+- (id)initWithDate:(NSCalendarDate *)aDate delegate:(id)aDelegate;
+{
+    if ([self initWithWindowNibName: @"Calendar"]) {
+        NSWindow *window = [self window]; // connect outlets
+        [calendarView setTarget: self]; // delegate
+        [calendarView setSelectionType: OACalendarViewSelectByDay];
+        [calendarView setShowsDaysForOtherMonths: YES];
+        if (aDate == nil) aDate = [NSCalendarDate calendarDate];
+        [calendarView setSelectedDay: aDate];
+        [calendarView setVisibleMonth: aDate];
+        delegate = [aDelegate retain];
+
+        NSView *view = [aDelegate calendarControllerLaunchingView: self];
+        if (view != nil) {
+            NSRect rect = [view convertRect: [view bounds] toView: nil];
+            NSWindow *parentWindow = [view window];
+            rect.origin = [parentWindow convertBaseToScreen: rect.origin];
+            rect.origin.x -= [window frame].size.width - rect.size.width;
+            [window setFrameTopLeftPoint: rect.origin];
+            NSRect visibleFrame = [[parentWindow screen] visibleFrame];
+            if (!NSContainsRect(visibleFrame, [window frame])) {
+                NSPoint viewTopLeft = { rect.origin.x, rect.origin.y + rect.size.height };
+                [window setFrameOrigin: viewTopLeft];
+            }
+        }
+        
+        [window setOpaque: NO];
+        [window setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.81f alpha: 0.9f]];
+        [window setHasShadow: NO];
+        [window setLevel: NSModalPanelWindowLevel];
+        [NSApp runModalForWindow: window];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [delegate release];
+    [super dealloc];
+}
+
+- (IBAction)close:(NSButton *)sender;
+{
+    [NSApp stopModal];
+    [delegate calendarController: self didSetDate: [calendarView selectedDay]];
+    [self close];
+}
+
+- (IBAction)cancel:(NSButton *)sender;
+{
+    [NSApp stopModal];
+    [self close];
+}
+
+- (IBAction)today:(NSButton *)sender;
+{
+    NSCalendarDate *today = [NSCalendarDate calendarDate];
+    [calendarView setSelectedDay: today];
+    [calendarView setVisibleMonth: today];
+}
+
+@end
+
+@implementation PSCalendarController (OACalendarViewDelegate)
+
+- (BOOL)calendarView:(OACalendarView *)aCalendarView shouldSelectDate:(NSCalendarDate *)aDate;
+{
+    return ([[NSCalendarDate dateForDay: aDate] compare: [NSCalendarDate dateForDay: [NSCalendarDate calendarDate]]] != NSOrderedAscending);
+}
+
+- (int)calendarView:(OACalendarView *)aCalendarView highlightMaskForVisibleMonth:(NSCalendarDate *)visibleMonth;
+{
+    NSCalendarDate *today = [NSCalendarDate calendarDate];
+    if ([visibleMonth yearOfCommonEra] == [today yearOfCommonEra] && [visibleMonth monthOfYear] == [today monthOfYear]) {
+        return 1 << ([today dayOfMonth] - 1 + [[today firstDayOfMonth] dayOfWeek]);
+    }
+
+    return 0;
+}
+
+- (void)calendarViewShouldDismiss:(OACalendarView *)aCalendarView;
+{
+    [okButton performClick: aCalendarView];
+}
+
+@end
+
+@implementation PSCalendarController (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    [self autorelease];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSDockBounceAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSDockBounceAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSDockBounceAlert.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  PSDockBounceAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+
+
+@interface PSDockBounceAlert : PSAlert {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSDockBounceAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSDockBounceAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSDockBounceAlert.m	(revision 365)
@@ -0,0 +1,48 @@
+//
+//  PSDockBounceAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSDockBounceAlert.h"
+
+static PSDockBounceAlert *PSDockBounceAlertShared;
+
+@implementation PSDockBounceAlert
+
++ (PSAlert *)alert;
+{
+    if (PSDockBounceAlertShared == nil)
+        PSDockBounceAlertShared = [[PSDockBounceAlert alloc] init];
+    return PSDockBounceAlertShared;
+}
+
++ (void)stopBouncing;
+{
+    [NSApp cancelUserAttentionRequest: NSInformationalRequest];
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    [NSApp requestUserAttention: NSInformationalRequest];
+    [[self class] performSelector: @selector(stopBouncing) withObject: nil afterDelay: 1 inModes: [NSArray arrayWithObject: NSDefaultRunLoopMode]];
+    [self completedForAlarm: alarm];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    return [@"Bounce dock icon" small];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    [self release];
+    return [[PSDockBounceAlert alert] retain];
+}
+        
+@end
Index: releases/Pester/1.1b6/Source/PSError.h
===================================================================
--- releases/Pester/1.1b6/Source/PSError.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSError.h	(revision 365)
@@ -0,0 +1,24 @@
+//
+//  PSError.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Dec 29 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface PSError : NSObject {
+    NSCountedSet *reasons;
+    NSString *operation;
+    unsigned count;
+}
+
+// create an error object
++ (PSError *)error;
+
+// raise exception, invalidates error object
+- (void)raiseIfNeededForOperation:(NSString *)anOperation count:(unsigned)aCount;
+
+@end
Index: releases/Pester/1.1b6/Source/PSError.m
===================================================================
--- releases/Pester/1.1b6/Source/PSError.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSError.m	(revision 365)
@@ -0,0 +1,60 @@
+//
+//  PSError.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Dec 29 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSError.h"
+
+static NSString * const PSErrorException = @"PSErrorException";
+
+@implementation PSError
+
++ (PSError *)error;
+{
+    return [[self alloc] init];
+}
+
+- (void)dealloc;
+{
+    [reasons release]; reasons = nil;
+    [operation release]; operation = nil;
+    [super dealloc];
+}
+
+- (NSCountedSet *)reasons;
+{
+    if (reasons == nil)
+        reasons = [[NSCountedSet alloc] initWithCapacity: 2];
+    return reasons;
+}
+
+- (void)addException:(NSException *)exception;
+{
+
+}
+
+- (void)raiseIfNeededForOperation:(NSString *)anOperation count:(unsigned)aCount;
+{
+    if (reasons != nil && [reasons count] > 0) {
+        // NSMutableString *
+        [self autorelease];
+        [NSException raise: PSErrorException
+                    format: @"Pester encountered errors while %@ %@."];
+    } else {
+        [self release];
+    }
+}
+
+@end
+
+/* Use cases:
+   - PSAlerts => can't restore one or more alerts, keep the rest
+   - 
+
+   Need to add error to initWithPropertyList: protocol, otherwise we can't get back partially-formed objects.
+   - (id)initWithPropertyList:(NSDictionary *)dict error:(PSException **)exception;
+
+ */
Index: releases/Pester/1.1b6/Source/PSMediaAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSMediaAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSMediaAlert.h	(revision 365)
@@ -0,0 +1,23 @@
+//
+//  PSMediaAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 08 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+
+
+@interface PSMediaAlert : PSAlert {
+    unsigned short repetitions;
+    float outputVolume;
+}
+
+- (id)initWithRepetitions:(unsigned short)numReps;
+
+- (unsigned short)repetitions;
+- (float)outputVolume;
+- (void)setOutputVolume:(float)volume;
+
+@end
Index: releases/Pester/1.1b6/Source/PSMediaAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSMediaAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSMediaAlert.m	(revision 365)
@@ -0,0 +1,75 @@
+//
+//  PSMediaAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 08 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSMediaAlert.h"
+#import "NSDictionary-NJRExtensions.h"
+#import "NJRSoundManager.h"
+#import <QuickTime/Movies.h>
+
+// property list keys
+static NSString * const PLAlertRepetitions = @"times"; // NSString
+static NSString * const PLAlertOutputVolume = @"volume"; // NSNumber
+
+@implementation PSMediaAlert
+
+#pragma mark initialize-release
+
+- (id)initWithRepetitions:(unsigned short)numReps;
+{
+    if ( (self = [super init]) != nil) {
+	// XXX not DRY, but can't think of a more sensible way
+ 	if (numReps < 1)
+	    numReps = 1;
+	else if (numReps > 99)
+	    numReps = 99;
+       repetitions = numReps;
+    }
+    return self;
+}
+
+#pragma mark accessing
+
+- (unsigned short)repetitions;
+{
+    return repetitions;
+}
+
+- (float)outputVolume;
+{
+    return outputVolume;
+}
+
+- (void)setOutputVolume:(float)volume;
+{
+    if ([NJRSoundManager volumeIsNotMutedOrInvalid: volume])
+        outputVolume = volume;
+    else
+        outputVolume = kNoVolume;
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableDictionary *plAlert = [[super propertyListRepresentation] mutableCopy];
+    [plAlert setObject: [NSNumber numberWithUnsignedShort: repetitions] forKey: PLAlertRepetitions];
+    if (outputVolume != kNoVolume) {
+        [plAlert setObject: [NSNumber numberWithFloat: outputVolume] forKey: PLAlertOutputVolume];
+    }
+    return [plAlert autorelease];
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [self initWithRepetitions: [[dict objectForRequiredKey: PLAlertRepetitions] unsignedShortValue]]) != nil) {
+        [self setOutputVolume: [[dict objectForKey: PLAlertOutputVolume] floatValue]];
+    }
+    return self;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSMovieAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSMovieAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSMovieAlert.h	(revision 365)
@@ -0,0 +1,29 @@
+//
+//  PSMovieAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSMediaAlert.h"
+
+@class BDAlias;
+
+@interface PSMovieAlert : PSMediaAlert {
+    BDAlias *alias;
+    NSMovie *movie;
+    BOOL hasAudio;
+    BOOL hasVideo;
+}
+
++ (PSMovieAlert *)alertWithMovieFileAlias:(BDAlias *)anAlias repetitions:(unsigned short)numReps;
+
+- (id)initWithMovieFileAlias:(BDAlias *)anAlias repetitions:(unsigned int) numReps;
+
+- (BOOL)hasVideo;
+- (NSMovie *)movie;
+- (BDAlias *)movieFileAlias;
+
+@end
Index: releases/Pester/1.1b6/Source/PSMovieAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSMovieAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSMovieAlert.m	(revision 365)
@@ -0,0 +1,132 @@
+//
+//  PSMovieAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <QuickTime/Movies.h>
+#import "PSMovieAlert.h"
+#import "PSMovieAlertController.h"
+#import "NSDictionary-NJRExtensions.h"
+#import "NSMovie-NJRExtensions.h"
+#import "BDAlias.h"
+
+// property list keys
+static NSString * const PLAlertRepetitions = @"times"; // NSString
+static NSString * const PLAlertAlias = @"alias"; // NSData
+
+@implementation PSMovieAlert
+
++ (PSMovieAlert *)alertWithMovieFileAlias:(BDAlias *)anAlias repetitions:(unsigned short)numReps;
+{
+    return [[[self alloc] initWithMovieFileAlias: anAlias repetitions: numReps] autorelease];
+}
+
+- (id)initWithMovieFileAlias:(BDAlias *)anAlias repetitions:(unsigned int) numReps;
+{
+    if ( (self = [super init]) != nil) {
+        NSString *path = [anAlias fullPath];
+        if (path == nil) {
+            [self release];
+            [NSException raise: PSAlertCreationException format: NSLocalizedString(@"Can't locate media to play as alert.", "Exception message on PSMovieAlert initialization when alias doesn't resolve")];
+        }
+        alias = [anAlias retain];
+        repetitions = numReps;
+        // XXX if we support remote movie URLs, need to call EnterMovies() ourselves at least in Jaguar (_MacTech_ December 2002, p. 64); also should do async movie loading (p. 73Ð74).
+        movie = [[NSMovie alloc] initWithURL: [NSURL fileURLWithPath: path] byReference: YES];
+        if (movie == nil) {
+            [self release];
+            self = nil;
+        } else {
+            hasAudio = [movie hasAudio];
+            hasVideo = [movie hasVideo];
+            
+            if (!hasAudio && !hasVideo) {
+                [self release]; self = nil;
+            }
+        }
+    }
+    
+    return self;
+}
+
+- (BOOL)hasVideo;
+{
+    return hasVideo;
+}
+
+- (BOOL)requiresPesterFrontmost;
+{
+    return hasVideo;
+}
+
+- (NSMovie *)movie;
+{
+    return movie;
+}
+
+- (BDAlias *)movieFileAlias;
+{
+    return alias;
+}
+
+- (unsigned short)repetitions;
+{
+    return repetitions;
+}
+
+- (void)dealloc;
+{
+    [alias release];
+    [movie release];
+    [super dealloc];
+}
+
+- (NSString *)description;
+{
+    return [NSString stringWithFormat: @"PSMovieAlert (%@%@): %@, repeats %hu times%@", hasAudio ? @"A" : @"", hasVideo ? @"V" : @"", [alias fullPath], repetitions, hasAudio && outputVolume != kNoVolume ? [NSString stringWithFormat: @" at %.0f%% volume", outputVolume * 100] : @""];
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    [PSMovieAlertController controllerWithAlarm: alarm movieAlert: self];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    BOOL isStatic = [movie isStatic];
+    NSMutableAttributedString *string = [[(isStatic ? @"Show " : @"Play ") small] mutableCopy];
+    NSString *kindString = nil, *name = [alias displayNameWithKindString: &kindString];
+    if (name == nil) name = NSLocalizedString(@"<<can't locate media file>>", "Movie alert description surrogate for media display name when alias doesn't resolve");
+    else [string appendAttributedString: [[NSString stringWithFormat: @"%@ ", kindString] small]];
+    [string appendAttributedString: [name underlined]];
+    if (repetitions > 1 && !isStatic) {
+        [string appendAttributedString: [[NSString stringWithFormat: @" %hu times", repetitions] small]];
+    }
+    if (hasAudio && outputVolume != kNoVolume) {
+        [string appendAttributedString: [[NSString stringWithFormat: @" at %.0f%% volume", outputVolume * 100] small]];
+    }
+    return [string autorelease];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableDictionary *plAlert = [[super propertyListRepresentation] mutableCopy];
+    [plAlert setObject: [NSNumber numberWithUnsignedShort: repetitions] forKey: PLAlertRepetitions];
+    [plAlert setObject: [alias aliasData] forKey: PLAlertAlias];
+    return [plAlert autorelease];
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [super initWithPropertyList: dict]) != nil)
+        [self initWithMovieFileAlias: [BDAlias aliasWithData: [dict objectForRequiredKey: PLAlertAlias]]
+                         repetitions: [[dict objectForRequiredKey: PLAlertRepetitions] unsignedShortValue]];
+    return self;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSMovieAlertController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSMovieAlertController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSMovieAlertController.h	(revision 365)
@@ -0,0 +1,27 @@
+//
+//  PSMovieAlertController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@class PSAlarm;
+@class PSMovieAlert;
+
+@interface PSMovieAlertController : NSWindowController {
+    PSAlarm *alarm;
+    PSMovieAlert *alert;
+    IBOutlet NSMovieView *movieView;
+    void *theMovie; /* Movie */
+    unsigned short repetitions;
+    unsigned short repetitionsRemaining;
+}
+
++ (PSMovieAlertController *)controllerWithAlarm:(PSAlarm *)anAlarm movieAlert:(PSMovieAlert *)anAlert;
+
+- (id)initWithAlarm:(PSAlarm *)anAlarm movieAlert:(PSMovieAlert *)anAlert;
+
+@end
Index: releases/Pester/1.1b6/Source/PSMovieAlertController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSMovieAlertController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSMovieAlertController.m	(revision 365)
@@ -0,0 +1,123 @@
+//
+//  PSMovieAlertController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarmAlertController.h"
+#import "PSMovieAlertController.h"
+#import "PSMovieAlert.h"
+#import "NSMovie-NJRExtensions.h"
+#import "NJRSoundManager.h"
+#import <QuickTime/Movies.h>
+
+@implementation PSMovieAlertController
+
++ (PSMovieAlertController *)controllerWithAlarm:(PSAlarm *)anAlarm movieAlert:(PSMovieAlert *)anAlert;
+{
+    return [[self alloc] initWithAlarm: anAlarm movieAlert: anAlert];
+}
+
+- (void)close;
+{
+    [super close];
+    [NJRSoundManager restoreSavedDefaultOutputVolumeIfCurrently: [alert outputVolume]];
+}
+
+- (void)play;
+{
+    NSTimeInterval delay;
+    if (repetitions == 0) return;
+    if (IsMovieDone((Movie)theMovie) || repetitionsRemaining == repetitions) {
+        if (repetitionsRemaining == 0) {
+            [self close];
+            return;
+        }
+        repetitionsRemaining--;
+        [movieView gotoBeginning: self];
+        [movieView start: self];
+    }
+    delay = (GetMovieDuration((Movie)theMovie) - GetMovieTime((Movie)theMovie, NULL)) / (double)GetMovieTimeScale((Movie)theMovie);
+    // XXX should use a timebase callback for this instead (see NJRQTMediaPopUpButton); also, use QuickTimeÕs built-in loop functionality instead of rolling our own?
+    [self performSelector: @selector(play) withObject: nil afterDelay: delay inModes: [NSArray arrayWithObject: NSDefaultRunLoopMode]];
+}
+
+- (id)initWithAlarm:(PSAlarm *)anAlarm movieAlert:(PSMovieAlert *)anAlert;
+{
+    if ([self initWithWindowNibName: @"Movie alert"]) {
+        NSMovie *movie = [anAlert movie];
+        NSWindow *window = [self window]; // connect outlets
+        alarm = anAlarm;
+        alert = anAlert;
+        [movieView setMovie: movie];
+        theMovie = [movie QTMovie];
+        if ([alert hasVideo]) {
+            NSRect screenRect = [[window screen] visibleFrame];
+            float magnification = 1.0;
+            NSSize movieSize;
+            NSSize minSize = [window minSize];
+            float windowFrameHeight = [window frame].size.height - [[window contentView] frame].size.height;
+            NSRect frame;
+            screenRect.size.height -= windowFrameHeight;
+            minSize.height -= windowFrameHeight;
+            while (1) {
+                movieSize = [movieView sizeForMagnification: magnification];
+                movieSize.height -= 16; // controller is hidden, but its size is included (documented, ergh)
+                if (movieSize.width > screenRect.size.width || movieSize.height > screenRect.size.height)
+                    magnification /= 2;
+                else
+                    break;
+            }
+            if (movieSize.width < minSize.width) movieSize.width = minSize.width;
+            if (movieSize.height < minSize.height) movieSize.width = minSize.height;
+            [window setContentSize: movieSize];
+            [window center];
+            frame = [window frame];
+            frame.origin.y -= 400; // appear below notifier window - XXX this is very inaccurate, fix
+            if (frame.origin.y < screenRect.origin.y) frame.origin.y = screenRect.origin.y;
+            [window setFrame: frame display: NO];
+            [window setTitle: [alarm message]];
+            {	// XXX workaround for (IMO) ugly appearance of Cocoa utility windows
+                NSView *miniButton = [window standardWindowButton: NSWindowMiniaturizeButton],
+                *zoomButton = [window standardWindowButton: NSWindowZoomButton];
+                // NOTE: this will not work if the window is resizable: when the frame is reset, the standard buttons reappear
+                [miniButton setFrameOrigin: NSMakePoint(-100, -100)];
+                [zoomButton setFrameOrigin: NSMakePoint(-100, -100)];
+                [[miniButton superview] setNeedsDisplay: YES];
+                [[zoomButton superview] setNeedsDisplay: YES];
+            }
+            [[self window] orderFrontRegardless];
+        }
+        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(close) name: PSAlarmAlertStopNotification object: nil];
+        repetitions = [alert repetitions];
+        repetitionsRemaining = repetitions;
+        if ([movie hasAudio] && [NJRSoundManager volumeIsNotMutedOrInvalid: [alert outputVolume]] && [NJRSoundManager saveDefaultOutputVolume]) {
+            [NJRSoundManager setDefaultOutputVolume: [alert outputVolume]];
+        }
+        if (![movie isStatic]) [self play]; // if it's an image, don't close the window automatically
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [[NSNotificationCenter defaultCenter] removeObserver: self];
+    [super dealloc];
+}
+
+@end
+
+@implementation PSMovieAlertController (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    repetitions = 0;
+    [movieView stop: self];
+    [alert completedForAlarm: alarm];
+    [self autorelease];
+    // note: there may still be a retained copy of this object until the runloop timer has let go of us at the end of the current movie playback cycle; donÕt worry about it.
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSNotifierAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSNotifierAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSNotifierAlert.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  PSNotifierAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+
+
+@interface PSNotifierAlert : PSAlert {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSNotifierAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSNotifierAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSNotifierAlert.m	(revision 365)
@@ -0,0 +1,46 @@
+//
+//  PSNotifierAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSNotifierAlert.h"
+#import "PSAlarmNotifierController.h"
+
+static PSNotifierAlert *PSNotifierAlertShared;
+
+@implementation PSNotifierAlert
+
++ (PSAlert *)alert;
+{
+    if (PSNotifierAlertShared == nil)
+        PSNotifierAlertShared = [[PSNotifierAlert alloc] init];
+    return PSNotifierAlertShared;
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    [[PSAlarmNotifierController alloc] initWithAlarm: alarm];
+}
+
+- (BOOL)requiresPesterFrontmost;
+{
+    return YES;
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    return [@"Display message and time" small];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    [self release];
+    return [[PSNotifierAlert alert] retain];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSPowerManager.h
===================================================================
--- releases/Pester/1.1b6/Source/PSPowerManager.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSPowerManager.h	(revision 365)
@@ -0,0 +1,34 @@
+//
+//  PSPowerManager.h
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Dec 23 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <IOKit/IOKitLib.h>
+
+extern NSString * const PSPowerManagerException;
+
+@interface PSPowerManager : NSObject {
+    id delegate;
+    io_connect_t root_port;
+    io_object_t notifier;
+}
+
+- (id)initWithDelegate:(id)aDelegate;
+
++ (BOOL)autoWakeSupported;
++ (void)setWakeTime:(NSDate *)time;
++ (void)clearWakeTime;
+
+@end
+
+@interface NSObject (PSPowerManagerDelegate)
+
+- (void)powerManagerWillDemandSleep:(PSPowerManager *)powerManager;
+- (BOOL)powerManagerShouldIdleSleep:(PSPowerManager *)powerManager;
+- (void)powerManagerDidWake:(PSPowerManager *)powerManager;
+
+@end
Index: releases/Pester/1.1b6/Source/PSPowerManager.m
===================================================================
--- releases/Pester/1.1b6/Source/PSPowerManager.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSPowerManager.m	(revision 365)
@@ -0,0 +1,86 @@
+//
+//  PSPowerManager.m
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Dec 23 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSPowerManager.h"
+
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <IOKit/IOMessage.h>
+
+@implementation PSPowerManager
+
++ (BOOL)autoWakeSupported;
+{
+    // XXX imagine it's supported on all machines that support 10.4
+    return NO; // XXX temporary for 1.1b5
+}
+
++ (void)setWakeTime:(NSDate *)time;
+{
+    IOPMSchedulePowerEvent((CFDateRef)time, (CFStringRef)[[NSBundle mainBundle] bundleIdentifier], CFSTR(kIOPMAutoWake));
+}
+
++ (void)clearWakeTime;
+{
+    // XXX implement (IOPMCancelScheduledPowerEvent)
+}
+
+// modified from RegisterForSleep sample code
+
+- (void)_messageReceived:(natural_t)messageType withArgument:(void *)messageArgument;
+{
+    switch (messageType) {
+        case kIOMessageSystemWillSleep:
+            if ([delegate respondsToSelector: @selector(powerManagerWillDemandSleep:)]) {
+                [delegate powerManagerWillDemandSleep: self];
+                IOAllowPowerChange(root_port, (long)messageArgument);
+            }
+            break;
+        case kIOMessageCanSystemSleep:
+            if ([delegate respondsToSelector: @selector(powerManagerShouldIdleSleep:)]) {
+                if ([delegate powerManagerShouldIdleSleep: self]) {
+                    IOAllowPowerChange(root_port, (long)messageArgument);
+                } else {
+                    IOCancelPowerChange(root_port, (long)messageArgument);
+                }
+            }
+            break;
+        case kIOMessageSystemHasPoweredOn:
+            if ([delegate respondsToSelector: @selector(powerManagerDidWake:)])
+                [delegate powerManagerDidWake: self];
+            break;
+    }
+}
+
+void
+powerCallback(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
+{
+    [(PSPowerManager *)refCon _messageReceived: messageType withArgument: messageArgument];
+}
+
+- (id)initWithDelegate:(id)aDelegate;
+{
+    if ( (self = [super init]) != nil) {
+        IONotificationPortRef notificationPort;
+
+        delegate = [aDelegate retain];
+        root_port = IORegisterForSystemPower(self, &notificationPort, powerCallback, &notifier);
+        NSAssert(root_port != 0, @"IORegisterForSystemPower failed");
+
+        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notificationPort), kCFRunLoopDefaultMode);
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    IODeregisterForSystemPower(&notifier);
+    [delegate release];
+    [super dealloc];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSPreferencesController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSPreferencesController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSPreferencesController.h	(revision 365)
@@ -0,0 +1,25 @@
+//
+//  PSPreferencesController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Mar 29 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@class NJRHotKeyField;
+
+@interface PSPreferencesController : NSWindowController {
+    IBOutlet NJRHotKeyField *setAlarmHotKey;
+    NSCharacterSet *textRejectSet;
+    NSCharacterSet *commandRejectSet;
+    NSCharacterSet *commandShiftRejectSet;
+    NSCharacterSet *commandOptionRejectSet;
+}
+
++ (void)readPreferences;
+
+- (IBAction)hotKeySet:(NJRHotKeyField *)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/PSPreferencesController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSPreferencesController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSPreferencesController.m	(revision 365)
@@ -0,0 +1,148 @@
+//
+//  PSPreferencesController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Mar 29 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSPreferencesController.h"
+#import "PSApplication.h"
+#import "NJRHotKeyField.h"
+#import "NJRHotKey.h"
+#import "NJRHotKeyManager.h"
+
+// NSUserDefaults key
+static NSString * const PSSetAlarmHotKey = @"Pester set alarm system-wide keyboard shortcut";
+
+// NJRHotKeyManager shortcut identifier
+static NSString * const PSSetAlarmHotKeyShortcut = @"PSSetAlarmHotKeyShortcut";
+
+@implementation PSPreferencesController
+
++ (void)readPreferences;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    NJRHotKeyManager *hotKeyManager = [NJRHotKeyManager sharedManager];
+    NJRHotKey *hotKey = [[[NJRHotKey alloc] initWithPropertyList: [defaults dictionaryForKey: PSSetAlarmHotKey]] autorelease];
+
+    if (hotKey == nil) {
+        [hotKeyManager removeShortcutWithIdentifier: PSSetAlarmHotKeyShortcut];
+    } else {
+        if (![hotKeyManager addShortcutWithIdentifier: PSSetAlarmHotKeyShortcut
+                                               hotKey: hotKey
+                                               target: NSApp
+                                               action: @selector(orderFrontSetAlarmPanel:)]) {
+            [defaults removeObjectForKey: PSSetAlarmHotKey];
+            NSRunAlertPanel(NSLocalizedString(@"Can't reserve alarm key equivalent", "Hot key set failure"),
+                            NSLocalizedString(@"Pester was unable to reserve the key equivalent %@. Please select another in Pester's Preferences, or click Clear to remove it.", "Hot key set failure"), nil, nil, nil, [hotKey keyGlyphs]);
+            [(PSApplication *)NSApp performSelector: @selector(orderFrontPreferencesPanel:) withObject: self afterDelay: 0.1];
+        }
+    }
+}
+
+#pragma mark interface updating
+
+- (void)update;
+{
+    // perform any interface propagation that needs to be done
+}
+
+#pragma mark preferences I/O
+
+- (void)readFromPrefs;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    NJRHotKey *hotKey = [[NJRHotKey alloc] initWithPropertyList: [defaults dictionaryForKey: PSSetAlarmHotKey]];
+    [setAlarmHotKey setHotKey: hotKey];
+    [hotKey release];
+}
+
+- (void)writeToPrefs;
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    [defaults setObject: [[setAlarmHotKey hotKey] propertyListRepresentation] forKey: @"Pester set alarm system-wide keyboard shortcut"];
+    [defaults synchronize];
+    [[self class] readPreferences];
+}
+
+#pragma mark initialize-release
+
+- (id)init {
+    if ( (self = [super initWithWindowNibName: @"Preferences"]) != nil) {
+        [[self window] center]; // connect outlets
+        [self readFromPrefs];
+        [self update];
+        // command
+        NSMutableCharacterSet *set = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
+        [set addCharactersInString: @"`-=[]/\\, "];
+        commandRejectSet = [set copy];
+        [set release];
+        // no modifiers, shift, option, option-shift
+        set = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
+        [set formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+        [set formUnionWithCharacterSet: [NSCharacterSet punctuationCharacterSet]];
+        [set addCharactersInString: @"\t\r\e\x7f\x03\x19"]; // tab, CR, escape, delete, enter, backtab
+        [set addCharactersInRange: NSMakeRange(0xF700, 0x1FF)]; // reserved function key range
+        [set removeCharactersInRange: NSMakeRange(NSF1FunctionKey, 15)]; // F1-F15
+        textRejectSet = [set copy];
+        [set release];
+        // command-shift
+        commandShiftRejectSet = [[NSCharacterSet characterSetWithCharactersInString: @"ACFGHIPQS~? "] retain];
+        // command-option
+        commandOptionRejectSet = [[NSCharacterSet characterSetWithCharactersInString: @"DW\\-= "] retain];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [textRejectSet release];
+    [commandRejectSet release];
+    [commandShiftRejectSet release];
+    [commandOptionRejectSet release];
+    [super dealloc];
+}
+
+#pragma mark actions
+
+- (IBAction)hotKeySet:(NJRHotKeyField *)sender;
+{
+    [self writeToPrefs];
+}
+
+- (IBAction)showWindow:(id)sender;
+{
+    [self readFromPrefs];
+    [super showWindow: sender];
+}
+
+@end
+
+@implementation PSPreferencesController (NJRHotKeyFieldDelegate)
+
+- (BOOL)hotKeyField:(NJRHotKeyField *)hotKeyField shouldAcceptCharacter:(unichar)keyChar modifierFlags:(unsigned)modifierFlags rejectionMessage:(NSString **)message;
+{
+    *message = nil;
+    
+    if (modifierFlags == 0 || modifierFlags == NSShiftKeyMask || modifierFlags == NSAlternateKeyMask || modifierFlags == (NSShiftKeyMask | NSAlternateKeyMask)) {
+        *message = modifierFlags == 0 ? @"key is reserved for typing text" :
+                                        @"key combination is reserved for typing text";
+        return ![textRejectSet characterIsMember: keyChar];
+    }
+    if (modifierFlags == NSCommandKeyMask) {
+        *message = @"key combination is reserved for application use";
+        return ![commandRejectSet characterIsMember: keyChar];
+    }
+    if (modifierFlags == (NSCommandKeyMask | NSShiftKeyMask)) {
+        *message = @"key combination is reserved for application use";
+        return ![commandShiftRejectSet characterIsMember: keyChar];
+    }
+    if (modifierFlags == (NSCommandKeyMask | NSAlternateKeyMask)) {
+        *message = @"key combination is reserved for Mac OS X use";
+        return ![commandOptionRejectSet characterIsMember: keyChar];
+    }
+    return YES;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSPropertyListSerialization.h
===================================================================
--- releases/Pester/1.1b6/Source/PSPropertyListSerialization.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSPropertyListSerialization.h	(revision 365)
@@ -0,0 +1,19 @@
+//
+//  PSPropertyListSerialization.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Dec 21 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@protocol PSPropertyListSerialization
+
+// 1.1 only, deprecated when we move to keyed archiving
+- (NSDictionary *)propertyListRepresentation;
+// subclasses should NOT call [super initWithPropertyList:] - infinite recursion will result!
+- (id)initWithPropertyList:(NSDictionary *)dict;
+
+@end
Index: releases/Pester/1.1b6/Source/PSScriptAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSScriptAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSScriptAlert.h	(revision 365)
@@ -0,0 +1,22 @@
+//
+//  PSScriptAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+
+@class BDAlias;
+
+@interface PSScriptAlert : PSAlert {
+    BDAlias *alias;
+}
+
++ (PSScriptAlert *)alertWithScriptFileAlias:(BDAlias *)anAlias;
+- (id)initWithScriptFileAlias:(BDAlias *)anAlias;
+
+- (BDAlias *)scriptFileAlias;
+
+@end
Index: releases/Pester/1.1b6/Source/PSScriptAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSScriptAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSScriptAlert.m	(revision 365)
@@ -0,0 +1,99 @@
+//
+//  PSScriptAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSScriptAlert.h"
+#import "BDAlias.h"
+#import "NSDictionary-NJRExtensions.h"
+
+// property list keys
+static NSString * const PLAlertAlias = @"alias"; // NSData
+
+@implementation PSScriptAlert
+
++ (PSScriptAlert *)alertWithScriptFileAlias:(BDAlias *)anAlias;
+{
+    return [[[self alloc] initWithScriptFileAlias: anAlias] autorelease];
+}
+
+- (id)initWithScriptFileAlias:(BDAlias *)anAlias;
+{
+    if ( (self = [super init]) != nil) {
+        alias = [anAlias retain];
+    }
+    return self;
+}
+
+- (BDAlias *)scriptFileAlias;
+{
+    return alias;
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    NSString *scriptPath = [alias fullPath];
+
+    if (scriptPath == nil) {
+        NSRunAlertPanel(NSLocalizedString(@"Can't find script", "Title of alert sheet when alias to script didn't resolve"), NSLocalizedString(@"Pester couldn't find the script for the alarm '%@'.", "Message displayed in alert sheet when alias to script didn't resolve"),
+                        nil, nil, nil, [alarm message]);
+    } else {
+        NSDictionary *errorInfo;
+        NSAppleScript *script = [[NSAppleScript alloc] initWithContentsOfURL: [NSURL fileURLWithPath: scriptPath] error: &errorInfo];
+        if (script == nil) {
+            NSString *errorMessage = [errorInfo objectForKey: NSAppleScriptErrorMessage];
+            NSNumber *errorNumber = [errorInfo objectForKey: NSAppleScriptErrorNumber];
+            NSString *appName = [errorInfo objectForKey: NSAppleScriptErrorAppName];
+            if (errorMessage == nil) errorMessage = [errorInfo objectForKey: NSAppleScriptErrorBriefMessage];
+            NSRunAlertPanel(@"Script loading error",
+                            @"Pester encountered an error while attempting to load “%@”%@ %@",
+                            nil, nil, nil,
+                            [[NSFileManager defaultManager] displayNameAtPath: scriptPath],
+                            errorMessage == nil ? @"" : [NSString stringWithFormat: @":\n\n%@%@", appName == nil ? @"" : @"“%@” reported an error: ", errorMessage],
+                            errorNumber == nil ? @"" : [NSString stringWithFormat: @"(%@)", errorNumber]);
+        } else {
+            NSAppleEventDescriptor *scriptResult = [script executeAndReturnError: &errorInfo];
+            if (scriptResult == nil) {
+                NSString *errorMessage = [errorInfo objectForKey: NSAppleScriptErrorMessage];
+                NSNumber *errorNumber = [errorInfo objectForKey: NSAppleScriptErrorNumber];
+                NSString *appName = [errorInfo objectForKey: NSAppleScriptErrorAppName];
+                if (errorMessage == nil) errorMessage = [errorInfo objectForKey: NSAppleScriptErrorBriefMessage];
+                NSRunAlertPanel(@"Script execution error",
+                                @"Pester encountered an error while attempting to execute the script “%@”%@ %@",
+                                nil, nil, nil,
+                                [[NSFileManager defaultManager] displayNameAtPath: scriptPath],
+                                errorMessage == nil ? @"" : [NSString stringWithFormat: @":\n\n%@%@", appName == nil ? @"" : @"“%@” reported an error: ", errorMessage],
+                                errorNumber == nil ? @"" : [NSString stringWithFormat: @"(%@)", errorNumber]);
+            }
+        }
+    }
+    [self completedForAlarm: alarm];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    NSMutableAttributedString *string = [[@"Do script " small] mutableCopy];
+    NSString *scriptName = [alias displayNameWithKindString: NULL];
+    if (scriptName == nil) scriptName = NSLocalizedString(@"<<can't locate script>>", "Script alert description surrogate for script name when alias doesn't resolve");
+    [string appendAttributedString: [scriptName underlined]];
+    return [string autorelease];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableDictionary *plAlert = [[super propertyListRepresentation] mutableCopy];
+    [plAlert setObject: [alias aliasData] forKey: PLAlertAlias];
+    return [plAlert autorelease];
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    return [self initWithScriptFileAlias: [BDAlias aliasWithData: [dict objectForRequiredKey: PLAlertAlias]]];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSSnoozeUntilController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSSnoozeUntilController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSSnoozeUntilController.h	(revision 365)
@@ -0,0 +1,34 @@
+//
+//  PSSnoozeUntilController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Feb 16 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#import "PSAlarm.h"
+
+@class PSAlarmNotifierController;
+
+@interface PSSnoozeUntilController : NSWindowController {
+    IBOutlet NSTextField *messageField;
+    IBOutlet NSTextField *timeOfDay;
+    IBOutlet NSTextField *timeDate;
+    IBOutlet NSPopUpButton *timeDateCompletions; // XXX should go away when bug preventing both formatters and popup menus from existing is fixed
+    IBOutlet NSButton *timeCalendarButton;
+    IBOutlet NSButton *snoozeButton;
+    NSTimeInterval snoozeInterval;
+    PSAlarm *alarm; // not a real alarm, used for date<->interval conversion
+}
+
++ (PSSnoozeUntilController *)snoozeUntilControllerWithNotifierController:(PSAlarmNotifierController *)aController;
+- (id)initWithNotifierController:(PSAlarmNotifierController *)aController;
+
+- (IBAction)update:(id)sender;
+- (IBAction)dateCompleted:(NSPopUpButton *)sender;
+- (IBAction)showCalendar:(NSButton *)sender;
+- (IBAction)close:(id)sender;
+- (IBAction)snooze:(NSButton *)sender;
+
+@end
Index: releases/Pester/1.1b6/Source/PSSnoozeUntilController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSSnoozeUntilController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSSnoozeUntilController.m	(revision 365)
@@ -0,0 +1,162 @@
+//
+//  PSSnoozeUntilController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Feb 16 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlarmNotifierController.h"
+#import "PSCalendarController.h"
+#import "PSSnoozeUntilController.h"
+#import "PSTimeDateEditor.h"
+#import "NSCalendarDate-NJRExtensions.h"
+
+@implementation PSSnoozeUntilController
+
++ (PSSnoozeUntilController *)snoozeUntilControllerWithNotifierController:(PSAlarmNotifierController *)aController;
+{
+    return [[self alloc] initWithNotifierController: aController];
+}
+
+- (id)initWithNotifierController:(PSAlarmNotifierController *)aController;
+{
+    if ([self initWithWindowNibName: @"Snooze until"]) {
+        NSWindow *window = [self window];
+        alarm = [[PSAlarm alloc] init];
+        snoozeInterval = [aController snoozeInterval];
+        [alarm setInterval: snoozeInterval];
+        [PSTimeDateEditor setUpTimeField: timeOfDay dateField: timeDate completions: timeDateCompletions];
+        if ([alarm isValid]) {
+            [timeOfDay setObjectValue: [alarm time]];
+            [timeDate setObjectValue: [alarm date]];
+        }
+        [self update: self];
+        
+        [NSApp beginSheet: window modalForWindow: [aController window] modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: aController];
+
+        // work around Cocoa confusion with our bizarre key loop
+        [timeOfDay setNextKeyView: timeDate];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [alarm release];
+    [super dealloc];
+}
+
+- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(PSAlarmNotifierController *)aController;
+{
+    if (returnCode == NSRunAbortedResponse) {
+        [aController setSnoozeInterval: snoozeInterval];
+        [sheet close];
+    } else {
+        [aController snoozeUntilDate: [alarm date]];
+    }
+    // XXX according to Cocoa documentation, the sheet should hide after the didEnd method returns, but it doesn't.
+}
+
+// XXX yuck, should not be duplicating this method between PSAlarmSetController and PSSnoozeUntilController
+// XXX with -[NSControl currentEditor] don't need to compare?  Also check -[NSControl validateEditing]
+- (id)objectValueForTextField:(NSTextField *)field whileEditing:(id)sender;
+{
+    if (sender == field) {
+        NSString *stringValue = [[[self window] fieldEditor: NO forObject: field] string];
+        id obj = nil;
+        [[field formatter] getObjectValue: &obj forString: stringValue errorDescription: NULL];
+        // NSLog(@"from field editor: %@", obj);
+        return obj;
+    } else {
+        // NSLog(@"from field: %@", [field objectValue]);
+        return [field objectValue];
+    }
+}
+
+#pragma mark date setting
+
+- (void)setAlarmDateAndInterval:(id)sender;
+{
+    [alarm setForDate: [self objectValueForTextField: timeDate whileEditing: sender]
+               atTime: [self objectValueForTextField: timeOfDay whileEditing: sender]];
+}
+
+// Be careful not to hook up any of the text fields' actions to update: because we handle them in controlTextDidChange: instead.  If we could get the active text field somehow via public API (guess we could use controlTextDidBegin/controlTextDidEndEditing) then we'd not need to overload the update sender for this purpose.  Or, I guess, we could use another method other than update.  It should not be this hard to implement what is essentially standard behavior.  Sigh.
+// Note: finding out whether a given control is editing is easier.  See: <http://cocoa.mamasam.com/COCOADEV/2002/03/2/28501.php>.
+
+- (IBAction)update:(id)sender;
+{
+    BOOL isValid;
+    [self setAlarmDateAndInterval: sender];
+    isValid = [alarm isValid];
+    [snoozeButton setEnabled: isValid];
+    if (!isValid) {
+        [messageField setStringValue: [alarm invalidMessage]];
+    } else {
+        [messageField setStringValue: @""];
+    }
+}
+
+- (IBAction)dateCompleted:(NSPopUpButton *)sender;
+{
+    [timeDate setStringValue: [sender titleOfSelectedItem]];
+    [self update: sender];
+}
+
+#pragma mark calendar
+
+- (IBAction)showCalendar:(NSButton *)sender;
+{
+    [PSCalendarController controllerWithDate: [NSCalendarDate dateForDay: [timeDate objectValue]] delegate: self];
+}
+
+- (void)calendarController:(PSCalendarController *)calendar didSetDate:(NSCalendarDate *)date;
+{
+    [timeDate setObjectValue: date];
+    [self update: self];
+}
+
+- (NSView *)calendarControllerLaunchingView:(PSCalendarController *)controller;
+{
+    return timeCalendarButton;
+}
+
+#pragma mark actions
+
+- (IBAction)close:(id)sender;
+{
+    [NSApp endSheet: [self window] returnCode: NSRunAbortedResponse];
+}
+
+- (IBAction)snooze:(NSButton *)sender;
+{
+    if ([alarm isValid]) {
+        [NSApp endSheet: [self window] returnCode: NSRunStoppedResponse];
+    } else {
+        [messageField setStringValue: [@"Unable to snooze. " stringByAppendingString: [alarm invalidMessage]]];
+        [sender setEnabled: NO];
+    }
+}
+
+@end
+
+@implementation PSSnoozeUntilController (NSControlSubclassNotifications)
+
+// called because we're the delegate
+
+- (void)controlTextDidChange:(NSNotification *)notification;
+{
+    [self update: [notification object]];
+}
+
+@end
+
+@implementation PSSnoozeUntilController (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    [self autorelease];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSSpeechAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSSpeechAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSSpeechAlert.h	(revision 365)
@@ -0,0 +1,23 @@
+//
+//  PSSpeechAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+
+@interface PSSpeechAlert : PSAlert {
+    NSSpeechSynthesizer *speaker;
+    NSString *voice;
+    PSAlarm *alarm;
+}
+
++ (PSSpeechAlert *)alertWithVoice:(NSString *)aVoice;
+
+- (id)initWithVoice:(NSString *)aVoice;
+
+- (NSString *)voice;
+
+@end
Index: releases/Pester/1.1b6/Source/PSSpeechAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSSpeechAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSSpeechAlert.m	(revision 365)
@@ -0,0 +1,96 @@
+//
+//  PSSpeechAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sat Oct 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "PSSpeechAlert.h"
+#import "PSAlarmAlertController.h"
+#import "NSDictionary-NJRExtensions.h"
+
+// property list keys
+static NSString * const PLAlertVoice = @"voice"; // NSString
+
+@implementation PSSpeechAlert
+
++ (PSSpeechAlert *)alertWithVoice:(NSString *)aVoice;
+{
+    return [[[self alloc] initWithVoice: aVoice] autorelease];
+}
+
+- (id)initWithVoice:(NSString *)aVoice;
+{
+    if ( (self = [super init]) != nil) {
+        voice = [aVoice retain];
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [[NSNotificationCenter defaultCenter] removeObserver: self];
+    [speaker release]; speaker = nil;
+    [voice release]; voice = nil;
+    [super dealloc];
+}
+
+- (NSString *)voice;
+{
+    return voice;
+}
+
+- (void)_stopSpeaking:(NSNotification *)notification;
+{
+    [speaker stopSpeaking]; // triggers didFinishSpeaking:
+}
+
+- (void)triggerForAlarm:(PSAlarm *)anAlarm;
+{
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_stopSpeaking:) name: PSAlarmAlertStopNotification object: nil];
+    
+    if ( (speaker = [[NSSpeechSynthesizer alloc] initWithVoice: voice]) == nil) return;
+    alarm = anAlarm;
+    [speaker setDelegate: self];
+    [speaker startSpeakingString: [alarm message]];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    NSMutableAttributedString *string = [[@"Speak message with voice " small] mutableCopy];
+    [string appendAttributedString: [voice underlined]];
+    return [string autorelease];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (NSDictionary *)propertyListRepresentation;
+{
+    NSMutableDictionary *plAlert = [[super propertyListRepresentation] mutableCopy];
+    if (voice == nil)
+	@throw [NSException exceptionWithName: NSInvalidArgumentException
+				       reason: @"The selected voice is not available." userInfo: nil];
+    [plAlert setObject: voice forKey: PLAlertVoice];
+    return [plAlert autorelease];
+}
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    if ( (self = [self init]) != nil) {
+        voice = [[dict objectForRequiredKey: PLAlertVoice] retain];
+    }
+    return self;
+}
+
+@end
+
+@implementation PSSpeechAlert (NSSpeechSynthesizerDelegate)
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking;
+{
+    [self completedForAlarm: alarm];
+    [speaker release]; speaker = nil;
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSTimeDateEditor.h
===================================================================
--- releases/Pester/1.1b6/Source/PSTimeDateEditor.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSTimeDateEditor.h	(revision 365)
@@ -0,0 +1,18 @@
+//
+//  PSDateCompletions.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Feb 16 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface PSTimeDateEditor : NSObject {
+
+}
+
++ (void)setUpTimeField:(NSTextField *)timeOfDay dateField:(NSTextField *)timeDate completions:(NSPopUpButton *)timeDateCompletions;
+
+@end
Index: releases/Pester/1.1b6/Source/PSTimeDateEditor.m
===================================================================
--- releases/Pester/1.1b6/Source/PSTimeDateEditor.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSTimeDateEditor.m	(revision 365)
@@ -0,0 +1,58 @@
+//
+//  PSTimeDateEditor.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Feb 16 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSTimeDateEditor.h"
+#import "NJRDateFormatter.h"
+
+@implementation PSTimeDateEditor
+
++ (void)setUpTimeField:(NSTextField *)timeOfDay dateField:(NSTextField *)timeDate completions:(NSPopUpButton *)timeDateCompletions;
+{
+    [[NJRDateFormatter alloc] init]; // XXX testing
+    [NSDateFormatter setDefaultFormatterBehavior: NSDateFormatterBehavior10_4];
+    static NSDateFormatter *timeFormatter = nil, *dateFormatter = nil;
+    if (timeFormatter == nil) {
+        timeFormatter = [[NJRDateFormatter timeFormatter] retain];
+	[timeFormatter setLenient: YES];
+	[timeFormatter setDateStyle: NSDateFormatterNoStyle];
+	[timeFormatter setTimeStyle: NSDateFormatterShortStyle];
+	dateFormatter = [[NJRDateFormatter dateFormatter] retain];
+	[dateFormatter setLenient: YES];
+	[dateFormatter setDateStyle: NSDateFormatterLongStyle];
+	[dateFormatter setTimeStyle: NSDateFormatterNoStyle];
+    }
+    [timeOfDay setFormatter: timeFormatter];
+    [timeDate setFormatter: dateFormatter];
+    [timeDate setObjectValue: [NSDate date]];
+
+    // add completions
+    NSArray *dayNames = [dateFormatter weekdaySymbols];
+    NSArray *completions = [timeDateCompletions itemTitles];
+    NSEnumerator *e = [completions objectEnumerator];
+    NSString *title;
+    int itemIndex = 0;
+    NSRange matchingRange;
+    while ( (title = [e nextObject]) != nil) {
+        matchingRange = [title rangeOfString: @"«day»"];
+        if (matchingRange.location != NSNotFound) {
+            NSMutableString *format = [title mutableCopy];
+            NSEnumerator *we = [dayNames objectEnumerator];
+            NSString *dayName;
+            [format deleteCharactersInRange: matchingRange];
+            [format insertString: @"%@" atIndex: matchingRange.location];
+            [timeDateCompletions removeItemAtIndex: itemIndex];
+            while ( (dayName = [we nextObject]) != nil) {
+                [timeDateCompletions insertItemWithTitle: [NSString stringWithFormat: format, dayName] atIndex: itemIndex];
+                itemIndex++;
+            }
+	    [format release];
+        } else itemIndex++;
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSTimer.h
===================================================================
--- releases/Pester/1.1b6/Source/PSTimer.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSTimer.h	(revision 365)
@@ -0,0 +1,49 @@
+//
+//  PSTimer.h
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Jan 05 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface PSTimer : NSObject {
+    BOOL isValid;
+    BOOL repeats;
+    BOOL isWakeUp;
+    NSDate *fireDate;
+    NSTimeInterval timeInterval;
+    NSInvocation *invocation;
+    id userInfo;
+}
+
++ (void)setUp;
+
+// partial emulation of Mac OS X 10.1 NSTimer interface
+// + (PSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+// + (PSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+
+// + (PSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
++ (PSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+
+// - (void)fire;
+
+- (NSDate *)fireDate;
+
+// - (NSTimeInterval)timeInterval; // NSTimer’s version returns 0 once timer is scheduled
+
+- (void)invalidate;
+- (BOOL)isValid;
+
+- (id)userInfo;
+
+// other methods
+
+- (BOOL)isWakeUp;
+- (void)setWakeUp:(BOOL)doWake;
+
+- (NSComparisonResult)compare:(PSTimer *)other;
+
+@end
Index: releases/Pester/1.1b6/Source/PSTimer.m
===================================================================
--- releases/Pester/1.1b6/Source/PSTimer.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSTimer.m	(revision 365)
@@ -0,0 +1,259 @@
+//
+//  PSTimer.m
+//  Pester
+//
+//  Created by Nicholas Riley on Sun Jan 05 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSTimer.h"
+#import "PSAlarm.h"
+#import "PSPowerManager.h"
+
+NSTimer *PSTimerCurrent = nil;
+PSTimer *PSTimerOnWake = nil;
+NSMutableArray *PSTimerAllTimers = nil;
+
+@interface PSTimer (Private)
++ (void)_schedule;
+@end
+
+@implementation PSTimer
+
++ (void)setUp;
+{
+    static PSPowerManager *powerManager;
+
+    if (powerManager == nil) {
+        powerManager = [[PSPowerManager alloc] initWithDelegate: self];
+        PSTimerAllTimers = [[NSMutableArray alloc] init];
+    }
+}
+
++ (void)_schedule;
+{
+    NSDate *aboutNow = [NSDate dateWithTimeIntervalSinceNow: 0.1];
+    [PSTimerCurrent invalidate]; [PSTimerCurrent release]; PSTimerCurrent = nil;
+    PSTimerOnWake = nil;
+    if ([PSTimerAllTimers count] > 0) {
+        PSTimer *timer = nil;
+        NSEnumerator *e;
+        [PSTimerAllTimers sortUsingSelector: @selector(compare:)];
+        // NSLog(@"_schedule: timers %@", [PSTimerAllTimers description]);
+        e = [PSTimerAllTimers objectEnumerator];
+        while ( (timer = [e nextObject]) != nil) {
+            if ([timer isWakeUp]) {
+                PSTimerOnWake = timer;
+                // NSLog(@"scheduling wake timer %@", timer);
+                break;
+            }
+        }
+        e = [PSTimerAllTimers objectEnumerator];
+        while ( (timer = [e nextObject]) != nil) {
+            if ([[timer fireDate] compare: aboutNow] != NSOrderedDescending) {
+                [timer performSelector: @selector(_timerExpired) withObject: nil afterDelay: 0];
+                return;
+            } else {
+                NSTimeInterval ti = [[timer fireDate] timeIntervalSinceNow];
+                if (ti > 0.1) {
+                    PSTimerCurrent = [[NSTimer scheduledTimerWithTimeInterval: ti target: timer selector: @selector(_timerExpired) userInfo: nil repeats: NO] retain];
+                    // NSLog(@"setting timer: %@", PSTimerCurrent);
+                } else {
+                    // NSLog(@"timer would have been too fast, setting: %@", timer);
+                    [timer performSelector: @selector(_timerExpired) withObject: nil afterDelay: 0];
+                }
+                return;
+            }
+        }
+        NSAssert(NO, @"shouldn't get here");
+    } else {
+        // NSLog(@"_schedule: no timers");
+    }
+}
+
++ (void)_timerAdded:(PSTimer *)timer;
+{
+    NSAssert1([PSTimerAllTimers indexOfObject: timer] == NSNotFound, @"PSTimerAllTimers already contains %@", timer);
+    [PSTimerAllTimers addObject: timer];
+    [self _schedule];
+}
+
++ (void)_timerDeleted:(PSTimer *)timer;
+{
+    NSAssert1([PSTimerAllTimers indexOfObject: timer] != NSNotFound, @"PSTimerAllTimers does not contain %@", timer);
+    [PSTimerAllTimers removeObject: timer];
+    [self _schedule];
+}
+
+#pragma mark private
+
+- (void)_setFireDate:(NSDate *)date;
+{
+    if (fireDate != date) {
+        [fireDate release];
+        fireDate = [date retain];
+        if (fireDate != nil) {
+            isValid = YES;
+        }
+    }
+}
+
+- (void)_setFireDateFromInterval;
+{
+    [self _setFireDate: [NSDate dateWithTimeIntervalSinceNow: timeInterval]];
+}
+
+- (void)_invalidate;
+{
+    if (isValid) {
+        isValid = NO;
+        [[self class] _timerDeleted: self];
+    }
+}
+
+- (void)_timerExpired;
+{
+    if (!isValid) return; // in case the timer went off after we were invalidated
+    [self retain]; // make sure we’re still accessible during the invocation
+    // NSLog(@"timer expired: %@", self);
+    if (repeats) {
+        [invocation invoke];
+        if (isValid) {
+            [self _setFireDateFromInterval];
+            [[self class] _schedule];
+            // NSLog(@"timer repeats: %@", self);
+        }
+    } else {
+        [self _invalidate];
+        [invocation invoke];
+    }
+    [self release];
+}
+
+#pragma mark initialize-release
+
+- (id)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)anObject repeats:(BOOL)yesOrNo;
+{
+    if ( (self = [self init]) != nil) {
+        invocation = [[NSInvocation invocationWithMethodSignature:
+            [aTarget methodSignatureForSelector: aSelector]] retain];
+        [invocation setSelector: aSelector];
+        [invocation setTarget: aTarget];
+        [invocation setArgument: &self atIndex: 2];
+        userInfo = [anObject retain];
+        repeats = yesOrNo;
+        timeInterval = ti;
+        // don't do this or we leak: [invocation retainArguments];
+        [aTarget retain]; // mimics retain behavior
+        [self _setFireDateFromInterval];
+        [[self class] _timerAdded: self]; // mimics runloop retention behavior
+    }
+    return self;
+}
+
++ (PSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)anObject repeats:(BOOL)yesOrNo;
+{
+    PSTimer *timer = [[self alloc] initWithTimeInterval: ti target: aTarget selector: aSelector userInfo: anObject repeats: yesOrNo];
+    [timer release];
+    return timer;
+}
+
+- (void)dealloc;
+{
+    // NSLog(@"DEALLOC %@", self);
+    isValid = NO;
+    [fireDate release]; fireDate = nil;
+    [[invocation target] release];
+    [invocation release]; invocation = nil;
+    [userInfo release]; userInfo = nil;
+    [super dealloc];
+}
+
+#pragma mark accessing
+
+- (NSDate *)fireDate;
+{
+    return fireDate;
+}
+
+- (void)invalidate;
+{
+    repeats = NO;
+    [self _invalidate];
+}
+
+- (BOOL)isValid;
+{
+    return isValid;
+}
+
+- (id)userInfo;
+{
+    return userInfo;
+}
+
+- (BOOL)isWakeUp;
+{
+    return isWakeUp;
+}
+
+- (void)setWakeUp:(BOOL)doWake;
+{
+    isWakeUp = doWake;
+}
+
+- (NSComparisonResult)compare:(PSTimer *)other;
+{
+    return [fireDate compare: [other fireDate]];
+}
+
+- (NSString *)description;
+{
+    return [NSString stringWithFormat: @"%@: at %@ do %@ on %@", [super description], [self fireDate], NSStringFromSelector([invocation selector]), [[invocation target] class]];
+}
+
+@end
+
+@implementation PSTimer (PSPowerManagerDelegate)
+
++ (void)_runScheduledWakeErrorPanel:(NSString *)error;
+{
+    NSRunAlertPanel(NSLocalizedString(@"Can't schedule wake from sleep", "Wake timer set failure panel title"), NSLocalizedString(@"Pester is unable to set this computer to wake up at a later date (%@)", "Wake timer set failure panel message"), NSLocalizedString(@"Sleep", "Wake timer set failure panel button"), nil, nil, error);
+}
+
++ (BOOL)powerManagerShouldIdleSleep:(PSPowerManager *)powerManager;
+{
+    [PSTimerCurrent invalidate];
+    if (PSTimerOnWake != nil) {
+        NSDate *date = [PSTimerOnWake fireDate];
+        // NSLog(@"%lf sec remain until alarm", [date timeIntervalSinceNow]);
+        if ([date timeIntervalSinceNow] > 30) {
+            // NSLog(@"going to sleep, setting timer %@", PSTimerOnWake);
+	    @try {
+                [PSPowerManager setWakeTime: [[PSTimerOnWake fireDate] addTimeInterval: -15]];
+            } @catch (NSException *exception) {
+                [self performSelectorOnMainThread: @selector(_runScheduledWakeErrorPanel:)
+				       withObject: [exception description] waitUntilDone: YES];
+            }
+            return YES;
+        } else {
+            // NSLog(@"not setting timer, will attempt to prevent idle sleep");
+            return NO;
+        }
+    }
+    return YES;
+}
+
++ (void)powerManagerWillDemandSleep:(PSPowerManager *)powerManager;
+{
+    [self powerManagerShouldIdleSleep: powerManager];
+}
+
++ (void)powerManagerDidWake:(PSPowerManager *)powerManager;
+{
+    if (PSTimerCurrent != nil) {
+        [self _schedule];
+    }
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSVolumeController.h
===================================================================
--- releases/Pester/1.1b6/Source/PSVolumeController.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSVolumeController.h	(revision 365)
@@ -0,0 +1,32 @@
+//
+//  PSVolumeController.h
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 08 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface PSVolumeController : NSWindowController {
+    id delegate;
+    IBOutlet NSView *contentView;
+    IBOutlet NSSlider *volumeSlider;
+}
+
++ (PSVolumeController *)controllerWithVolume:(float)volume delegate:(id)aDelegate;
+
+- (id)initWithVolume:(float)volume delegate:(id)aDelegate;
+
+- (IBAction)volumeSet:(NSSlider *)sender;
+
+@end
+
+@interface NSObject (PSVolumeControllerDelegate)
+
+- (void)volumeController:(PSVolumeController *)controller didSetVolume:(float)volume;
+- (void)volumeControllerDidDismiss:(PSVolumeController *)controller;
+- (NSView *)volumeControllerLaunchingView:(PSVolumeController *)controller;
+
+@end
Index: releases/Pester/1.1b6/Source/PSVolumeController.m
===================================================================
--- releases/Pester/1.1b6/Source/PSVolumeController.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSVolumeController.m	(revision 365)
@@ -0,0 +1,74 @@
+//
+//  PSVolumeController.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Apr 08 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSVolumeController.h"
+#import "NJRSoundManager.h"
+#import "NJRNonCenteringWindow.h"
+
+@implementation PSVolumeController
+
++ (PSVolumeController *)controllerWithVolume:(float)volume delegate:(id)aDelegate;
+{
+    return [[self alloc] initWithVolume: volume delegate: aDelegate];
+}
+
+- (id)initWithVolume:(float)volume delegate:(id)aDelegate;
+{
+    if ( (self = [self initWithWindowNibName: @"Volume"]) != nil) {
+        [self window]; // connect outlets
+        NSWindow *window = [[NJRNonCenteringWindow alloc] initWithContentRect: [contentView bounds] styleMask: NSBorderlessWindowMask | NSTexturedBackgroundWindowMask backing: NSBackingStoreBuffered defer: NO];
+
+        if ([NJRSoundManager volumeIsNotMutedOrInvalid: volume])
+            [volumeSlider setFloatValue: volume];
+
+        delegate = [aDelegate retain];
+
+        [window setContentView: contentView];
+        [window setOpaque: NO];
+        [window setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.81f alpha: 0.9f]];
+        [window setHasShadow: YES];
+        [window setOneShot: YES];
+        [window setDelegate: self];
+        NSView *view = [aDelegate volumeControllerLaunchingView: self];
+        if (view != nil) {
+            NSRect rect = [view convertRect: [view bounds] toView: nil];
+            NSWindow *parentWindow = [view window];
+            rect.origin = [parentWindow convertBaseToScreen: rect.origin];
+            rect.origin.x -= [window frame].size.width - rect.size.width + 1;
+            [window setFrameTopLeftPoint: rect.origin];
+            NSRect visibleFrame = [[parentWindow screen] visibleFrame];
+            if (!NSContainsRect(visibleFrame, [window frame])) {
+                NSPoint viewTopLeft = { rect.origin.x, rect.origin.y + rect.size.height };
+                [window setFrameOrigin: viewTopLeft];
+            }
+        }
+
+        // -[NSApplication beginModalSessionForWindow:] shows and centers the window; we use NJRNonCenteringWindow to prevent the repositioning from succeeding
+        NSModalSession session = [NSApp beginModalSessionForWindow: window];
+        [volumeSlider mouseDown: [NSApp currentEvent]];
+        [NSApp runModalSession: session];
+        [NSApp endModalSession: session];
+        [window close];
+        [self autorelease];
+        // XXX make sure window and self are released
+    }
+    return self;
+}
+
+- (void)dealloc;
+{
+    [delegate release];
+    [super dealloc];
+}
+
+- (IBAction)volumeSet:(NSSlider *)sender;
+{
+    [delegate volumeController: self didSetVolume: [sender floatValue]];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSWakeAlert.h
===================================================================
--- releases/Pester/1.1b6/Source/PSWakeAlert.h	(revision 365)
+++ releases/Pester/1.1b6/Source/PSWakeAlert.h	(revision 365)
@@ -0,0 +1,16 @@
+//
+//  PSWakeAlert.h
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Jan 06 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSAlert.h"
+
+
+@interface PSWakeAlert : PSAlert {
+
+}
+
+@end
Index: releases/Pester/1.1b6/Source/PSWakeAlert.m
===================================================================
--- releases/Pester/1.1b6/Source/PSWakeAlert.m	(revision 365)
+++ releases/Pester/1.1b6/Source/PSWakeAlert.m	(revision 365)
@@ -0,0 +1,46 @@
+//
+//  PSWakeAlert.m
+//  Pester
+//
+//  Created by Nicholas Riley on Mon Jan 06 2003.
+//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
+//
+
+#import "PSWakeAlert.h"
+#import "PSPowerManager.h"
+
+static PSWakeAlert *PSWakeAlertShared;
+
+@implementation PSWakeAlert
+
++ (PSAlert *)alert;
+{
+    if (PSWakeAlertShared == nil)
+        PSWakeAlertShared = [[PSWakeAlert alloc] init];
+    return PSWakeAlertShared;
+}
+
+- (void)prepareForAlarm:(PSAlarm *)alarm;
+{
+    [alarm setWakeUp: YES];
+}
+
+- (void)triggerForAlarm:(PSAlarm *)alarm;
+{
+    [self completedForAlarm: alarm];
+}
+
+- (NSAttributedString *)actionDescription;
+{
+    return [@"Wake up computer if asleep" small];
+}
+
+#pragma mark property list serialization (Pester 1.1)
+
+- (id)initWithPropertyList:(NSDictionary *)dict;
+{
+    [self release];
+    return [[PSWakeAlert alert] retain];
+}
+
+@end
Index: releases/Pester/1.1b6/Source/Pester.xcodeproj/nicholas.mode2v3
===================================================================
--- releases/Pester/1.1b6/Source/Pester.xcodeproj/nicholas.mode2v3	(revision 365)
+++ releases/Pester/1.1b6/Source/Pester.xcodeproj/nicholas.mode2v3	(revision 365)
@@ -0,0 +1,1501 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ActivePerspectiveName</key>
+	<string>Project</string>
+	<key>AllowedModules</key>
+	<array>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXSmartGroupTreeModule</string>
+			<key>Name</key>
+			<string>Groups and Files Outline View</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXNavigatorGroup</string>
+			<key>Name</key>
+			<string>Editor</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCTaskListModule</string>
+			<key>Name</key>
+			<string>Task List</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCDetailModule</string>
+			<key>Name</key>
+			<string>File and Smart Group Detail Viewer</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXBuildResultsModule</string>
+			<key>Name</key>
+			<string>Detailed Build Results Viewer</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXProjectFindModule</string>
+			<key>Name</key>
+			<string>Project Batch Find Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCProjectFormatConflictsModule</string>
+			<key>Name</key>
+			<string>Project Format Conflicts List</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXBookmarksModule</string>
+			<key>Name</key>
+			<string>Bookmarks Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXClassBrowserModule</string>
+			<key>Name</key>
+			<string>Class Browser</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXCVSModule</string>
+			<key>Name</key>
+			<string>Source Code Control Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXDebugBreakpointsModule</string>
+			<key>Name</key>
+			<string>Debug Breakpoints Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCDockableInspector</string>
+			<key>Name</key>
+			<string>Inspector</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXOpenQuicklyModule</string>
+			<key>Name</key>
+			<string>Open Quickly Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXDebugSessionModule</string>
+			<key>Name</key>
+			<string>Debugger</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXDebugCLIModule</string>
+			<key>Name</key>
+			<string>Debug Console</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCSnapshotModule</string>
+			<key>Name</key>
+			<string>Snapshots Tool</string>
+		</dict>
+	</array>
+	<key>BundlePath</key>
+	<string>/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources</string>
+	<key>Description</key>
+	<string>CondensedDescriptionKey</string>
+	<key>DockingSystemVisible</key>
+	<false/>
+	<key>Extension</key>
+	<string>mode2v3</string>
+	<key>FavBarConfig</key>
+	<dict>
+		<key>PBXProjectModuleGUID</key>
+		<string>E1B11F970CF5F49B004975AA</string>
+		<key>XCBarModuleItemNames</key>
+		<dict/>
+		<key>XCBarModuleItems</key>
+		<array/>
+	</dict>
+	<key>FirstTimeWindowDisplayed</key>
+	<false/>
+	<key>Identifier</key>
+	<string>com.apple.perspectives.project.mode2v3</string>
+	<key>MajorVersion</key>
+	<integer>34</integer>
+	<key>MinorVersion</key>
+	<integer>0</integer>
+	<key>Name</key>
+	<string>Condensed</string>
+	<key>Notifications</key>
+	<array/>
+	<key>OpenEditors</key>
+	<array/>
+	<key>PerspectiveWidths</key>
+	<array>
+		<integer>-1</integer>
+	</array>
+	<key>Perspectives</key>
+	<array>
+		<dict>
+			<key>ChosenToolbarItems</key>
+			<array>
+				<string>buildOrClean</string>
+				<string>build-and-goOrGo</string>
+				<string>com.apple.ide.PBXToolbarStopButton</string>
+				<string>NSToolbarFlexibleSpaceItem</string>
+				<string>get-info</string>
+			</array>
+			<key>ControllerClassBaseName</key>
+			<string></string>
+			<key>IconName</key>
+			<string>WindowOfProject</string>
+			<key>Identifier</key>
+			<string>perspective.project</string>
+			<key>IsVertical</key>
+			<false/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Proportion</key>
+					<string>351pt</string>
+					<key>Tabs</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXBottomSmartGroupGIDs</key>
+								<array/>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C9437FD063B20B00039CFAC</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Files</string>
+								<key>PBXProjectStructureProvided</key>
+								<string>yes</string>
+								<key>PBXSmartGroupTreeModuleColumnData</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>22</real>
+										<real>312</real>
+									</array>
+									<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+									<array>
+										<string>SCMStatusColumn</string>
+										<string>MainColumn</string>
+									</array>
+								</dict>
+								<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+									<array>
+										<string>29B97314FDCFA39411CA2CEA</string>
+										<string>080E96DDFE201D6D7F000001</string>
+										<string>F560E311035787D501A4E466</string>
+										<string>F560E314035787F901A4E466</string>
+										<string>F59E47AF036AAC31016B311C</string>
+										<string>F560E312035787E001A4E466</string>
+										<string>29B97317FDCFA39411CA2CEA</string>
+										<string>E10E71F3040C335800050002</string>
+										<string>E19DCBE503BE516000B934BF</string>
+										<string>E17562BC0CF8E7A800FC5057</string>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+									<array>
+										<array>
+											<integer>110</integer>
+											<integer>108</integer>
+											<integer>0</integer>
+										</array>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+									<string>{{0, 1497}, {334, 441}}</string>
+								</dict>
+								<key>PBXTopSmartGroupGIDs</key>
+								<array/>
+								<key>XCIncludePerspectivesSwitch</key>
+								<false/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{10, 27}, {351, 459}}</string>
+								<key>GroupTreeTableConfiguration</key>
+								<array>
+									<string>SCMStatusColumn</string>
+									<real>22</real>
+									<string>MainColumn</string>
+									<real>312</real>
+								</array>
+								<key>RubberWindowFrame</key>
+								<string>1570 647 351 527 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXSmartGroupTreeModule</string>
+						</dict>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXBottomSmartGroupGIDs</key>
+								<array>
+									<string>1C37FBAC04509CD000000102</string>
+									<string>1C37FAAC04509CD000000102</string>
+								</array>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C9437FE063B20B00039CFAC</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Targets</string>
+								<key>PBXProjectStructureProvided</key>
+								<string>no</string>
+								<key>PBXSmartGroupTreeModuleColumnData</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>334</real>
+									</array>
+									<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+									<array>
+										<string>MainColumn</string>
+									</array>
+								</dict>
+								<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+									<array>
+										<string>1C37FBAC04509CD000000102</string>
+										<string>1C37FAAC04509CD000000102</string>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+									<array>
+										<array>
+											<integer>0</integer>
+										</array>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+									<string>{{0, 0}, {334, 441}}</string>
+								</dict>
+								<key>PBXTopSmartGroupGIDs</key>
+								<array/>
+								<key>XCIncludePerspectivesSwitch</key>
+								<false/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{10, 27}, {351, 459}}</string>
+								<key>GroupTreeTableConfiguration</key>
+								<array>
+									<string>MainColumn</string>
+									<real>334</real>
+								</array>
+							</dict>
+							<key>Module</key>
+							<string>PBXSmartGroupTreeModule</string>
+						</dict>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXBottomSmartGroupGIDs</key>
+								<array>
+									<string>1C08E77C0454961000C914BD</string>
+									<string>1C37FABC05509CD000000102</string>
+									<string>1C37FABC05539CD112110102</string>
+									<string>E2644B35053B69B200211256</string>
+									<string>1C37FABC04509CD000100104</string>
+									<string>1CC0EA4004350EF90044410B</string>
+									<string>1CC0EA4004350EF90041110B</string>
+								</array>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C9437FF063B20B00039CFAC</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Other</string>
+								<key>PBXProjectStructureProvided</key>
+								<string>no</string>
+								<key>PBXSmartGroupTreeModuleColumnData</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>555</real>
+									</array>
+									<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+									<array>
+										<string>MainColumn</string>
+									</array>
+								</dict>
+								<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+									<array/>
+									<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+									<array>
+										<array>
+											<integer>0</integer>
+										</array>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+									<string>{{0, 0}, {555, 301}}</string>
+								</dict>
+								<key>PBXTopSmartGroupGIDs</key>
+								<array/>
+								<key>XCIncludePerspectivesSwitch</key>
+								<false/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {572, 319}}</string>
+								<key>GroupTreeTableConfiguration</key>
+								<array>
+									<string>MainColumn</string>
+									<real>555</real>
+								</array>
+							</dict>
+							<key>Module</key>
+							<string>PBXSmartGroupTreeModule</string>
+						</dict>
+					</array>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Project</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCModuleDock</string>
+				<string>XCDockableTabModule</string>
+				<string>PBXSmartGroupTreeModule</string>
+				<string>PBXSmartGroupTreeModule</string>
+				<string>PBXSmartGroupTreeModule</string>
+			</array>
+			<key>TableOfContents</key>
+			<array>
+				<string>E15045D00CFCF47100DE47E9</string>
+				<string>E15045D10CFCF47100DE47E9</string>
+				<string>1C9437FD063B20B00039CFAC</string>
+				<string>1C9437FE063B20B00039CFAC</string>
+				<string>1C9437FF063B20B00039CFAC</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.default.shortV3</string>
+		</dict>
+	</array>
+	<key>PerspectivesBarVisible</key>
+	<false/>
+	<key>ShelfIsVisible</key>
+	<false/>
+	<key>SourceDescription</key>
+	<string>file at '/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources/XCPerspectivesSpecificationMode2.xcperspec'</string>
+	<key>StatusbarIsVisible</key>
+	<true/>
+	<key>TimeStamp</key>
+	<real>0.0</real>
+	<key>ToolbarDisplayMode</key>
+	<integer>2</integer>
+	<key>ToolbarIsVisible</key>
+	<true/>
+	<key>ToolbarSizeMode</key>
+	<integer>2</integer>
+	<key>Type</key>
+	<string>Perspectives</string>
+	<key>UpdateMessage</key>
+	<string></string>
+	<key>WindowJustification</key>
+	<integer>0</integer>
+	<key>WindowOrderList</key>
+	<array>
+		<string>E1B123120CF664D6004975AA</string>
+		<string>/Users/nicholas/Documents/Development/Pester/Source/Pester.xcodeproj</string>
+	</array>
+	<key>WindowString</key>
+	<string>1570 647 351 527 0 0 1920 1178 </string>
+	<key>WindowToolsV3</key>
+	<array>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.detail</string>
+			<key>IsVertical</key>
+			<integer>0</integer>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXBottomSmartGroupGIDs</key>
+								<array>
+									<string>1C37FBAC04509CD000000102</string>
+									<string>1C37FAAC04509CD000000102</string>
+									<string>1C08E77C0454961000C914BD</string>
+									<string>1C37FABC05509CD000000102</string>
+									<string>1C37FABC05539CD112110102</string>
+									<string>E2644B35053B69B200211256</string>
+									<string>1C37FABC04509CD000100104</string>
+									<string>1CC0EA4004350EF90044410B</string>
+									<string>1CC0EA4004350EF90041110B</string>
+								</array>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CE0B1FE06471DED0097A5F4</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Files</string>
+								<key>PBXProjectStructureProvided</key>
+								<string>yes</string>
+								<key>PBXSmartGroupTreeModuleColumnData</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>245</real>
+									</array>
+									<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+									<array>
+										<string>MainColumn</string>
+									</array>
+								</dict>
+								<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+									<array>
+										<string>00C654E9FEEE28EC7F000001</string>
+										<string>1C37FABC05509CD000000102</string>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+									<array>
+										<array>
+											<integer>0</integer>
+										</array>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+									<string>{{0, 0}, {245, 350}}</string>
+								</dict>
+								<key>PBXTopSmartGroupGIDs</key>
+								<array/>
+								<key>XCIncludePerspectivesSwitch</key>
+								<integer>0</integer>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {262, 368}}</string>
+								<key>GroupTreeTableConfiguration</key>
+								<array>
+									<string>MainColumn</string>
+									<real>245</real>
+								</array>
+								<key>RubberWindowFrame</key>
+								<string>31 446 744 409 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXSmartGroupTreeModule</string>
+							<key>Proportion</key>
+							<string>262pt</string>
+						</dict>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CA1AED706398EBD00589147</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Detail</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{267, 0}, {477, 368}}</string>
+								<key>RubberWindowFrame</key>
+								<string>31 446 744 409 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>XCDetailModule</string>
+							<key>Proportion</key>
+							<string>477pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Detail</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXSmartGroupTreeModule</string>
+				<string>XCDetailModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C335F2C07B51CD20023D4EE</string>
+				<string>1C335F2D07B51CD20023D4EE</string>
+				<string>1C335F2E07B51CD20023D4EE</string>
+				<string>1CE0B1FE06471DED0097A5F4</string>
+				<string>1CA1AED706398EBD00589147</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.defaultV3</string>
+			<key>WindowString</key>
+			<string>31 446 744 409 0 0 1440 878 </string>
+			<key>WindowToolGUID</key>
+			<string>1C335F2C07B51CD20023D4EE</string>
+			<key>WindowToolIsVisible</key>
+			<integer>1</integer>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>MENUSEPARATOR</string>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.build</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CD0528F0623707200166675</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>NJRValidatingField.h</string>
+								<key>StatusBarVisibility</key>
+								<true/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {930, 253}}</string>
+								<key>RubberWindowFrame</key>
+								<string>928 514 930 535 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>253pt</string>
+						</dict>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>XCMainBuildResultsModuleGUID</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Build</string>
+								<key>XCBuildResultsTrigger_Collapse</key>
+								<integer>1021</integer>
+								<key>XCBuildResultsTrigger_Open</key>
+								<integer>1011</integer>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 258}, {930, 236}}</string>
+								<key>RubberWindowFrame</key>
+								<string>928 514 930 535 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXBuildResultsModule</string>
+							<key>Proportion</key>
+							<string>236pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>494pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Build Results</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXBuildResultsModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C530D52069F1CE1000CFCEE</string>
+				<string>E15043790CFB91AF00DE47E9</string>
+				<string>1CD0528F0623707200166675</string>
+				<string>XCMainBuildResultsModuleGUID</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.buildV3</string>
+			<key>WindowString</key>
+			<string>928 514 930 535 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>1C530D52069F1CE1000CFCEE</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.debugger</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>Debugger</key>
+								<dict>
+									<key>HorizontalSplitView</key>
+									<dict>
+										<key>_collapsingFrameDimension</key>
+										<real>0.0</real>
+										<key>_indexOfCollapsedView</key>
+										<integer>0</integer>
+										<key>_percentageOfCollapsedView</key>
+										<real>0.0</real>
+										<key>isCollapsed</key>
+										<string>yes</string>
+										<key>sizes</key>
+										<array>
+											<string>{{0, 0}, {445, 368}}</string>
+											<string>{{445, 0}, {536, 368}}</string>
+										</array>
+									</dict>
+									<key>VerticalSplitView</key>
+									<dict>
+										<key>_collapsingFrameDimension</key>
+										<real>0.0</real>
+										<key>_indexOfCollapsedView</key>
+										<integer>0</integer>
+										<key>_percentageOfCollapsedView</key>
+										<real>0.0</real>
+										<key>isCollapsed</key>
+										<string>yes</string>
+										<key>sizes</key>
+										<array>
+											<string>{{0, 0}, {981, 368}}</string>
+											<string>{{0, 368}, {981, 350}}</string>
+										</array>
+									</dict>
+								</dict>
+								<key>LauncherConfigVersion</key>
+								<string>8</string>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C162984064C10D400B95A72</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Debug - GLUTExamples (Underwater)</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>DebugConsoleVisible</key>
+								<string>None</string>
+								<key>DebugConsoleWindowFrame</key>
+								<string>{{200, 200}, {500, 300}}</string>
+								<key>DebugSTDIOWindowFrame</key>
+								<string>{{200, 200}, {500, 300}}</string>
+								<key>Frame</key>
+								<string>{{0, 0}, {981, 718}}</string>
+								<key>PBXDebugSessionStackFrameViewKey</key>
+								<dict>
+									<key>DebugVariablesTableConfiguration</key>
+									<array>
+										<string>Name</string>
+										<real>120</real>
+										<string>Value</string>
+										<real>85</real>
+										<string>Summary</string>
+										<real>306</real>
+									</array>
+									<key>Frame</key>
+									<string>{{445, 0}, {536, 368}}</string>
+									<key>RubberWindowFrame</key>
+									<string>843 200 981 759 0 0 1920 1178 </string>
+								</dict>
+								<key>RubberWindowFrame</key>
+								<string>843 200 981 759 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXDebugSessionModule</string>
+							<key>Proportion</key>
+							<string>718pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>718pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Debugger</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXDebugSessionModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C530D54069F1CE1000CFCEE</string>
+				<string>E15043670CFB8D4800DE47E9</string>
+				<string>1C162984064C10D400B95A72</string>
+				<string>E15043680CFB8D4800DE47E9</string>
+				<string>E15043690CFB8D4800DE47E9</string>
+				<string>E150436A0CFB8D4800DE47E9</string>
+				<string>E150436B0CFB8D4800DE47E9</string>
+				<string>E150436C0CFB8D4800DE47E9</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.debugV3</string>
+			<key>WindowString</key>
+			<string>843 200 981 759 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>1C530D54069F1CE1000CFCEE</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.find</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Dock</key>
+							<array>
+								<dict>
+									<key>ContentConfiguration</key>
+									<dict>
+										<key>PBXProjectModuleGUID</key>
+										<string>1CDD528C0622207200134675</string>
+										<key>PBXProjectModuleLabel</key>
+										<string>PSApplication.m</string>
+										<key>StatusBarVisibility</key>
+										<true/>
+									</dict>
+									<key>GeometryConfiguration</key>
+									<dict>
+										<key>Frame</key>
+										<string>{{0, 0}, {878, 241}}</string>
+										<key>RubberWindowFrame</key>
+										<string>619 85 878 716 0 0 1920 1178 </string>
+									</dict>
+									<key>Module</key>
+									<string>PBXNavigatorGroup</string>
+									<key>Proportion</key>
+									<string>878pt</string>
+								</dict>
+							</array>
+							<key>Proportion</key>
+							<string>241pt</string>
+						</dict>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CD0528E0623707200166675</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Project Find</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 246}, {878, 429}}</string>
+								<key>RubberWindowFrame</key>
+								<string>619 85 878 716 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXProjectFindModule</string>
+							<key>Proportion</key>
+							<string>429pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>675pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Project Find</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXProjectFindModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C530D57069F1CE1000CFCEE</string>
+				<string>E15043970CFBA77500DE47E9</string>
+				<string>E15043980CFBA77500DE47E9</string>
+				<string>1CDD528C0622207200134675</string>
+				<string>1CD0528E0623707200166675</string>
+			</array>
+			<key>WindowString</key>
+			<string>619 85 878 716 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>1C530D57069F1CE1000CFCEE</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>MENUSEPARATOR</string>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.debuggerConsole</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C78EAAC065D492600B07095</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Debugger Console</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {1144, 216}}</string>
+								<key>RubberWindowFrame</key>
+								<string>677 91 1144 257 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXDebugCLIModule</string>
+							<key>Proportion</key>
+							<string>216pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>216pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Debugger Console</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXDebugCLIModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C530D5B069F1CE1000CFCEE</string>
+				<string>E150436D0CFB8D4800DE47E9</string>
+				<string>1C78EAAC065D492600B07095</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.consoleV3</string>
+			<key>WindowString</key>
+			<string>677 91 1144 257 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>1C530D5B069F1CE1000CFCEE</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.snapshots</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>XCSnapshotModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Snapshots</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCSnapshotModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<string>Yes</string>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.snapshots</string>
+			<key>WindowString</key>
+			<string>315 824 300 550 0 0 1440 878 </string>
+			<key>WindowToolIsVisible</key>
+			<string>Yes</string>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.scm</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C78EAB2065D492600B07095</string>
+								<key>PBXProjectModuleLabel</key>
+								<string></string>
+								<key>StatusBarVisibility</key>
+								<true/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {586, 0}}</string>
+								<key>RubberWindowFrame</key>
+								<string>960 488 586 680 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>0pt</string>
+						</dict>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXCVSModuleFilterTypeKey</key>
+								<integer>1032</integer>
+								<key>PBXCVSModuleTreeModuleColumnData</key>
+								<dict>
+									<key>PBXCVSModuleTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>257</real>
+										<real>40.66162109375</real>
+										<real>53</real>
+										<real>33</real>
+										<real>51</real>
+										<real>116</real>
+									</array>
+									<key>PBXCVSModuleTreeModuleColumnsKey</key>
+									<array>
+										<string>Name</string>
+										<string>Status</string>
+										<string>Update</string>
+										<string>Revision</string>
+										<string>Author</string>
+										<string>Date</string>
+									</array>
+								</dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CD052920623707200166675</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>SCM Results</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 5}, {586, 634}}</string>
+								<key>RubberWindowFrame</key>
+								<string>960 488 586 680 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXCVSModule</string>
+							<key>Proportion</key>
+							<string>634pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>639pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>SCM</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXCVSModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>E1B123120CF664D6004975AA</string>
+				<string>E150465E0CFCFB0200DE47E9</string>
+				<string>1C78EAB2065D492600B07095</string>
+				<string>1CD052920623707200166675</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.scm</string>
+			<key>WindowString</key>
+			<string>960 488 586 680 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>E1B123120CF664D6004975AA</string>
+			<key>WindowToolIsVisible</key>
+			<true/>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.breakpoints</string>
+			<key>IsVertical</key>
+			<false/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXBottomSmartGroupGIDs</key>
+								<array>
+									<string>1C77FABC04509CD000000102</string>
+								</array>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CE0B1FE06471DED0097A5F4</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Files</string>
+								<key>PBXProjectStructureProvided</key>
+								<string>no</string>
+								<key>PBXSmartGroupTreeModuleColumnData</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>168</real>
+									</array>
+									<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+									<array>
+										<string>MainColumn</string>
+									</array>
+								</dict>
+								<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+									<array>
+										<string>1C77FABC04509CD000000102</string>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+									<array>
+										<array>
+											<integer>0</integer>
+										</array>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+									<string>{{0, 0}, {168, 350}}</string>
+								</dict>
+								<key>PBXTopSmartGroupGIDs</key>
+								<array/>
+								<key>XCIncludePerspectivesSwitch</key>
+								<false/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {185, 368}}</string>
+								<key>GroupTreeTableConfiguration</key>
+								<array>
+									<string>MainColumn</string>
+									<real>168</real>
+								</array>
+								<key>RubberWindowFrame</key>
+								<string>290 117 744 409 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXSmartGroupTreeModule</string>
+							<key>Proportion</key>
+							<string>185pt</string>
+						</dict>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CA1AED706398EBD00589147</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Detail</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{190, 0}, {554, 368}}</string>
+								<key>RubberWindowFrame</key>
+								<string>290 117 744 409 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>XCDetailModule</string>
+							<key>Proportion</key>
+							<string>554pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>368pt</string>
+				</dict>
+			</array>
+			<key>MajorVersion</key>
+			<integer>3</integer>
+			<key>MinorVersion</key>
+			<integer>0</integer>
+			<key>Name</key>
+			<string>Breakpoints</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXSmartGroupTreeModule</string>
+				<string>XCDetailModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>E15043BB0CFCC31200DE47E9</string>
+				<string>E15043BC0CFCC31200DE47E9</string>
+				<string>1CE0B1FE06471DED0097A5F4</string>
+				<string>1CA1AED706398EBD00589147</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.breakpointsV3</string>
+			<key>WindowString</key>
+			<string>290 117 744 409 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>E15043BB0CFCC31200DE47E9</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.debugAnimator</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Debug Visualizer</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXNavigatorGroup</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.debugAnimatorV3</string>
+			<key>WindowString</key>
+			<string>100 100 700 500 0 0 1280 1002 </string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.bookmarks</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>PBXBookmarksModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Bookmarks</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXBookmarksModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>0</integer>
+			<key>WindowString</key>
+			<string>538 42 401 187 0 0 1280 1002 </string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.projectFormatConflicts</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>XCProjectFormatConflictsModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Project Format Conflicts</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCProjectFormatConflictsModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>0</integer>
+			<key>WindowContentMinSize</key>
+			<string>450 300</string>
+			<key>WindowString</key>
+			<string>50 850 472 307 0 0 1440 877</string>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.classBrowser</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>OptionsSetName</key>
+								<string>Hierarchy, all classes</string>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CA6456E063B45B4001379D8</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Class Browser - NSObject</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>ClassesFrame</key>
+								<string>{{0, 0}, {372, 96}}</string>
+								<key>ClassesTreeTableConfiguration</key>
+								<array>
+									<string>PBXClassNameColumnIdentifier</string>
+									<real>208</real>
+									<string>PBXClassBookColumnIdentifier</string>
+									<real>22</real>
+								</array>
+								<key>Frame</key>
+								<string>{{0, 0}, {624, 319}}</string>
+								<key>MembersFrame</key>
+								<string>{{0, 101}, {372, 218}}</string>
+								<key>MembersTreeTableConfiguration</key>
+								<array>
+									<string>PBXMemberTypeIconColumnIdentifier</string>
+									<real>22</real>
+									<string>PBXMemberNameColumnIdentifier</string>
+									<real>216</real>
+									<string>PBXMemberTypeColumnIdentifier</string>
+									<real>95</real>
+									<string>PBXMemberBookColumnIdentifier</string>
+									<real>22</real>
+								</array>
+								<key>RubberWindowFrame</key>
+								<string>834 307 624 339 0 0 1920 1178 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXClassBrowserModule</string>
+							<key>Proportion</key>
+							<string>319pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>319pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Class Browser</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXClassBrowserModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<false/>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C530D60069F1CE1000CFCEE</string>
+				<string>E17CA70F0CF7A25800018B34</string>
+				<string>1CA6456E063B45B4001379D8</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.classbrowser</string>
+			<key>WindowString</key>
+			<string>834 307 624 339 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>1C530D60069F1CE1000CFCEE</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.refactoring</string>
+			<key>IncludeInToolsMenu</key>
+			<integer>0</integer>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>E15044550CFCDC1D00DE47E9</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {891, 710}}</string>
+								<key>RubberWindowFrame</key>
+								<string>371 185 891 751 0 0 1920 1178 </string>
+								<key>XCRefactoringSplitViewLowerHeight</key>
+								<real>310</real>
+								<key>XCRefactoringSplitViewTotalHeight</key>
+								<real>627</real>
+							</dict>
+							<key>Module</key>
+							<string>XCRefactoringModule</string>
+							<key>Proportion</key>
+							<string>710pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>710pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Refactoring</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCRefactoringModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>E15044560CFCDC1D00DE47E9</string>
+				<string>E15044570CFCDC1D00DE47E9</string>
+				<string>E15044550CFCDC1D00DE47E9</string>
+			</array>
+			<key>WindowString</key>
+			<string>371 185 891 751 0 0 1920 1178 </string>
+			<key>WindowToolGUID</key>
+			<string>E15044560CFCDC1D00DE47E9</string>
+			<key>WindowToolIsVisible</key>
+			<false/>
+		</dict>
+	</array>
+</dict>
+</plist>
Index: releases/Pester/1.1b6/Source/Pester.xcodeproj/nicholas.pbxuser
===================================================================
--- releases/Pester/1.1b6/Source/Pester.xcodeproj/nicholas.pbxuser	(revision 365)
+++ releases/Pester/1.1b6/Source/Pester.xcodeproj/nicholas.pbxuser	(revision 365)
@@ -0,0 +1,1604 @@
+// !$*UTF8*$!
+{
+	089C165DFE840E0CC02AAC07 /* English */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 899}}";
+			sepNavSelRange = "{105, 0}";
+			sepNavVisRange = "{0, 229}";
+			sepNavWindowFrame = "{{15, 238}, {872, 935}}";
+		};
+	};
+	29B97313FDCFA39411CA2CEA /* Project object */ = {
+		activeArchitecture = i386;
+		activeBuildConfigurationName = Release;
+		activeExecutable = E1B11F8D0CF5F30F004975AA /* Pester */;
+		activeTarget = E1B11EE30CF5F30F004975AA /* Pester */;
+		addToTargets = (
+		);
+		breakpoints = (
+			F596730D0355CFA301407038 /* -[_NSZombie methodSignatureForSelector:] */,
+			F500E5370359719B01AEEDB1 /* szone_error */,
+			E1A9DEE703BF3C110089E8B2 /* -[_NSZombie release] */,
+			E17CA6640CF697FE00018B34 /* SetPortRGBForeColor */,
+			E15043CF0CFCC41500DE47E9 /* objc_exception_throw */,
+		);
+		codeSenseManager = E1B11EDD0CF5F2EB004975AA /* Code sense */;
+		executables = (
+			E1B11F8D0CF5F30F004975AA /* Pester */,
+		);
+		perUserDictionary = {
+			"PBXConfiguration.PBXBreakpointsDataSource.v1:1CA1AED706398EBD00589147" = {
+				PBXFileTableDataSourceColumnSortingDirectionKey = "-1";
+				PBXFileTableDataSourceColumnSortingKey = PBXBreakpointsDataSource_BreakpointID;
+				PBXFileTableDataSourceColumnWidthsKey = (
+					20,
+					20,
+					198,
+					20,
+					99,
+					99,
+					29,
+					20,
+				);
+				PBXFileTableDataSourceColumnsKey = (
+					PBXBreakpointsDataSource_ActionID,
+					PBXBreakpointsDataSource_TypeID,
+					PBXBreakpointsDataSource_BreakpointID,
+					PBXBreakpointsDataSource_UseID,
+					PBXBreakpointsDataSource_LocationID,
+					PBXBreakpointsDataSource_ConditionID,
+					PBXBreakpointsDataSource_IgnoreCountID,
+					PBXBreakpointsDataSource_ContinueID,
+				);
+			};
+			PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = {
+				PBXFileTableDataSourceColumnSortingDirectionKey = "-1";
+				PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID;
+				PBXFileTableDataSourceColumnWidthsKey = (
+					20,
+					315,
+					20,
+					48,
+					43,
+					43,
+					20,
+				);
+				PBXFileTableDataSourceColumnsKey = (
+					PBXFileDataSource_FiletypeID,
+					PBXFileDataSource_Filename_ColumnID,
+					PBXFileDataSource_Built_ColumnID,
+					PBXFileDataSource_ObjectSize_ColumnID,
+					PBXFileDataSource_Errors_ColumnID,
+					PBXFileDataSource_Warnings_ColumnID,
+					PBXFileDataSource_Target_ColumnID,
+				);
+			};
+			PBXPerProjectTemplateStateSaveDate = 217904241;
+			"PBXTemplateGeometry-F5314676015831810DCA290F" = {
+				ContentSize = "{618, 257}";
+				LeftSlideOut = {
+					Collapsed = NO;
+					Frame = "{{0, 23}, {618, 234}}";
+					Split0 = {
+						ActiveTab = 2;
+						ActiveTabName = PBXBuildResultsModule;
+						Collapsed = YES;
+						Frame = "{{0, 0}, {618, 234}}";
+						Split0 = {
+							Frame = "{{1e+06, 1e+06}, {618, 0}}";
+						};
+						SplitCount = 1;
+						Tab0 = {
+							Frame = "{{0, 0}, {952, 321}}";
+						};
+						Tab1 = {
+							Debugger = {
+								Collapsed = NO;
+								Frame = "{{0, 0}, {781, 452}}";
+								Split0 = {
+									Frame = "{{0, 24}, {781, 428}}";
+									Split0 = {
+										Frame = "{{0, 0}, {383, 428}}";
+									};
+									Split1 = {
+										DebugVariablesTableConfiguration = (
+											Name,
+											123,
+											Value,
+											85,
+											Summary,
+											155.123,
+										);
+										Frame = "{{392, 0}, {389, 428}}";
+									};
+									SplitCount = 2;
+								};
+								SplitCount = 1;
+								Tab0 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								Tab1 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								TabCount = 2;
+								TabsVisible = YES;
+							};
+							Frame = "{{0, 0}, {781, 452}}";
+							LauncherConfigVersion = 7;
+						};
+						Tab2 = {
+							Frame = "{{0, 0}, {618, 228}}";
+							LauncherConfigVersion = 3;
+							Runner = {
+								Frame = "{{0, 0}, {618, 228}}";
+							};
+						};
+						Tab3 = {
+							BuildMessageFrame = "{{0, 0}, {620, 195}}";
+							BuildTranscriptFrame = "{{0, 204}, {620, 2}}";
+							BuildTranscriptFrameExpanded = YES;
+							Frame = "{{0, 0}, {618, 228}}";
+						};
+						Tab4 = {
+							Frame = "{{0, 0}, {612, 295}}";
+						};
+						TabCount = 5;
+						TabsVisible = NO;
+					};
+					SplitCount = 1;
+					Tab0 = {
+						Frame = "{{0, 0}, {300, 533}}";
+						GroupTreeTableConfiguration = (
+							TargetStatusColumn,
+							18,
+							MainColumn,
+							267,
+						);
+					};
+					Tab1 = {
+						ClassesFrame = "{{0, 0}, {280, 398}}";
+						ClassesTreeTableConfiguration = (
+							PBXBookColumnIdentifier,
+							20,
+							PBXClassColumnIdentifier,
+							237,
+						);
+						Frame = "{{0, 0}, {278, 659}}";
+						MembersFrame = "{{0, 407}, {280, 252}}";
+						MembersTreeTableConfiguration = (
+							PBXBookColumnIdentifier,
+							20,
+							PBXMethodColumnIdentifier,
+							236,
+						);
+					};
+					Tab2 = {
+						Frame = "{{0, 0}, {200, 100}}";
+					};
+					Tab3 = {
+						Frame = "{{0, 0}, {200, 100}}";
+						TargetTableConfiguration = (
+							ActiveObject,
+							16,
+							ObjectNames,
+							202.296,
+						);
+					};
+					Tab4 = {
+						BreakpointsTreeTableConfiguration = (
+							breakpointColumn,
+							197,
+							enabledColumn,
+							31,
+						);
+						Frame = "{{0, 0}, {250, 100}}";
+					};
+					TabCount = 5;
+					TabsVisible = NO;
+				};
+				NavBarShownByDefault = YES;
+				StatusViewVisible = YES;
+				Template = F5314676015831810DCA290F;
+				ToolbarVisible = YES;
+				WindowLocation = "{565, 336}";
+			};
+			"PBXTemplateGeometry-F5534CB2020F3F8A0DCA290F" = {
+				ContentSize = "{594, 303}";
+				LeftSlideOut = {
+					Collapsed = NO;
+					Frame = "{{0, 23}, {594, 280}}";
+					Split0 = {
+						ActiveTab = 1;
+						ActiveTabName = PBXRunSessionModule;
+						Collapsed = YES;
+						Frame = "{{0, 0}, {594, 280}}";
+						Split0 = {
+							Frame = "{{1e+06, 1e+06}, {594, 0}}";
+						};
+						SplitCount = 1;
+						Tab0 = {
+							Frame = "{{0, 0}, {804, 321}}";
+						};
+						Tab1 = {
+							Debugger = {
+								Collapsed = NO;
+								Frame = "{{0, 0}, {594, 274}}";
+								Split0 = {
+									Frame = "{{0, 24}, {594, 250}}";
+									Split0 = {
+										Frame = "{{0, 0}, {290, 250}}";
+									};
+									Split1 = {
+										DebugVariablesTableConfiguration = (
+											Name,
+											123,
+											Value,
+											85,
+											Summary,
+											62.123,
+										);
+										Frame = "{{299, 0}, {295, 250}}";
+									};
+									SplitCount = 2;
+								};
+								SplitCount = 1;
+								Tab0 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								Tab1 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								TabCount = 2;
+								TabsVisible = YES;
+							};
+							Frame = "{{0, 0}, {594, 274}}";
+							LauncherConfigVersion = 7;
+						};
+						Tab2 = {
+							Frame = "{{0, 0}, {594, 274}}";
+							LauncherConfigVersion = 3;
+							Runner = {
+								Frame = "{{0, 0}, {594, 274}}";
+							};
+						};
+						Tab3 = {
+							BuildMessageFrame = "{{0, 0}, {614, 262}}";
+							BuildTranscriptFrame = "{{0, 271}, {614, 2}}";
+							BuildTranscriptFrameExpanded = YES;
+							Frame = "{{0, 0}, {612, 295}}";
+						};
+						Tab4 = {
+							Frame = "{{0, 0}, {612, 295}}";
+						};
+						TabCount = 5;
+						TabsVisible = NO;
+					};
+					SplitCount = 1;
+					Tab0 = {
+						Frame = "{{0, 0}, {300, 533}}";
+						GroupTreeTableConfiguration = (
+							TargetStatusColumn,
+							18,
+							MainColumn,
+							267,
+						);
+					};
+					Tab1 = {
+						ClassesFrame = "{{0, 0}, {280, 398}}";
+						ClassesTreeTableConfiguration = (
+							PBXBookColumnIdentifier,
+							20,
+							PBXClassColumnIdentifier,
+							237,
+						);
+						Frame = "{{0, 0}, {278, 659}}";
+						MembersFrame = "{{0, 407}, {280, 252}}";
+						MembersTreeTableConfiguration = (
+							PBXBookColumnIdentifier,
+							20,
+							PBXMethodColumnIdentifier,
+							236,
+						);
+					};
+					Tab2 = {
+						Frame = "{{0, 0}, {200, 100}}";
+					};
+					Tab3 = {
+						Frame = "{{0, 0}, {200, 386}}";
+						TargetTableConfiguration = (
+							ActiveObject,
+							16,
+							ObjectNames,
+							202.296,
+						);
+					};
+					Tab4 = {
+						BreakpointsTreeTableConfiguration = (
+							breakpointColumn,
+							197,
+							enabledColumn,
+							31,
+						);
+						Frame = "{{0, 0}, {250, 386}}";
+					};
+					TabCount = 5;
+					TabsVisible = NO;
+				};
+				NavBarShownByDefault = YES;
+				StatusViewVisible = YES;
+				Template = F5534CB2020F3F8A0DCA290F;
+				ToolbarVisible = YES;
+				WindowLocation = "{4, 633}";
+			};
+			"PBXTemplateGeometry-F5CA7ECB015C094F0DCA290F" = {
+				ContentSize = "{668, 621}";
+				LeftSlideOut = {
+					Collapsed = NO;
+					Frame = "{{0, 0}, {668, 621}}";
+					Split0 = {
+						Collapsed = NO;
+						Frame = "{{0, 0}, {668, 621}}";
+						Split0 = {
+							Frame = "{{0, 0}, {668, 621}}";
+						};
+						SplitCount = 1;
+						Tab0 = {
+							Frame = "{{0, 0}, {484, 208}}";
+						};
+						Tab1 = {
+							Debugger = {
+								Collapsed = NO;
+								Frame = "{{0, 0}, {664, 208}}";
+								Split0 = {
+									Frame = "{{0, 24}, {664, 184}}";
+									Split0 = {
+										Frame = "{{0, 0}, {325, 184}}";
+									};
+									Split1 = {
+										DebugVariablesTableConfiguration = (
+											Name,
+											123,
+											Value,
+											85,
+											Summary,
+											96.123,
+										);
+										Frame = "{{334, 0}, {330, 184}}";
+									};
+									SplitCount = 2;
+								};
+								SplitCount = 1;
+								Tab0 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								Tab1 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								TabCount = 2;
+								TabsVisible = YES;
+							};
+							Frame = "{{0, 0}, {664, 208}}";
+							LauncherConfigVersion = 7;
+						};
+						Tab2 = {
+							Frame = "{{0, 0}, {664, 50}}";
+							LauncherConfigVersion = 3;
+							Runner = {
+								Frame = "{{0, 0}, {664, 50}}";
+							};
+						};
+						Tab3 = {
+							BuildMessageFrame = "{{0, 0}, {614, 262}}";
+							BuildTranscriptFrame = "{{0, 271}, {614, 2}}";
+							BuildTranscriptFrameExpanded = YES;
+							Frame = "{{0, 0}, {612, 295}}";
+						};
+						Tab4 = {
+							Frame = "{{0, 0}, {612, 295}}";
+						};
+						TabCount = 5;
+						TabsVisible = NO;
+					};
+					SplitCount = 1;
+					Tab0 = {
+						Frame = "{{0, 0}, {313, 531}}";
+						GroupTreeTableConfiguration = (
+							TargetStatusColumn,
+							18,
+							MainColumn,
+							280,
+						);
+					};
+					Tab1 = {
+						ClassesFrame = "{{0, 0}, {280, 398}}";
+						ClassesTreeTableConfiguration = (
+							PBXBookColumnIdentifier,
+							20,
+							PBXClassColumnIdentifier,
+							237,
+						);
+						Frame = "{{0, 0}, {278, 659}}";
+						MembersFrame = "{{0, 407}, {280, 252}}";
+						MembersTreeTableConfiguration = (
+							PBXBookColumnIdentifier,
+							20,
+							PBXMethodColumnIdentifier,
+							236,
+						);
+					};
+					Tab2 = {
+						Frame = "{{0, 0}, {200, 100}}";
+					};
+					Tab3 = {
+						Frame = "{{0, 0}, {200, 557}}";
+						TargetTableConfiguration = (
+							ActiveObject,
+							16,
+							ObjectNames,
+							202.296,
+						);
+					};
+					Tab4 = {
+						BreakpointsTreeTableConfiguration = (
+							breakpointColumn,
+							197,
+							enabledColumn,
+							31,
+						);
+						Frame = "{{0, 0}, {250, 100}}";
+					};
+					TabCount = 5;
+					TabsVisible = NO;
+				};
+				NavBarShownByDefault = YES;
+				StatusViewVisible = NO;
+				Template = F5CA7ECB015C094F0DCA290F;
+				ToolbarVisible = NO;
+				WindowLocation = "{357, 379}";
+			};
+			PBXWorkspaceContents = (
+				{
+					LeftSlideOut = {
+						Split0 = {
+							Split0 = {
+								NavCount = 1;
+								NavGeometry0 = {
+									Frame = "{{0, 0}, {150, 530}}";
+									NavBarVisible = YES;
+								};
+								NavSplitVertical = NO;
+							};
+							SplitCount = 1;
+							Tab1 = {
+								Debugger = {
+									Split0 = {
+										SplitCount = 2;
+									};
+									SplitCount = 1;
+									TabCount = 2;
+								};
+								LauncherConfigVersion = 7;
+							};
+							Tab2 = {
+								LauncherConfigVersion = 3;
+								Runner = {
+								};
+							};
+							TabCount = 5;
+						};
+						SplitCount = 1;
+						Tab1 = {
+							OptionsSetName = "Hierarchy, all classes";
+						};
+						TabCount = 5;
+					};
+				},
+				{
+					LeftSlideOut = {
+						Split0 = {
+							Split0 = {
+								NavContent0 = {
+									bookmark = E18829600478B3C300AEEDB1;
+									history = (
+										E188295F0478B3C300AEEDB1,
+									);
+								};
+								NavCount = 1;
+								NavGeometry0 = {
+									Frame = "{{0, 0}, {668, 621}}";
+									NavBarVisible = YES;
+								};
+								NavSplitVertical = NO;
+							};
+							SplitCount = 1;
+							Tab1 = {
+								Debugger = {
+									Split0 = {
+										SplitCount = 2;
+									};
+									SplitCount = 1;
+									TabCount = 2;
+								};
+								LauncherConfigVersion = 7;
+							};
+							Tab2 = {
+								LauncherConfigVersion = 3;
+								Runner = {
+								};
+							};
+							TabCount = 5;
+						};
+						SplitCount = 1;
+						Tab1 = {
+							OptionsSetName = "Hierarchy, all classes";
+						};
+						TabCount = 5;
+					};
+				},
+			);
+			PBXWorkspaceGeometries = (
+				{
+					ContentSize = "{326, 763}";
+					LeftSlideOut = {
+						ActiveTab = 0;
+						ActiveTabName = PBXGroupTreeModule;
+						Collapsed = YES;
+						Frame = "{{0, 23}, {326, 740}}";
+						Split0 = {
+							Collapsed = NO;
+							Frame = "{{1e+06, 1e+06}, {0, 740}}";
+							Split0 = {
+								Frame = "{{0, 0}, {0, 740}}";
+							};
+							SplitCount = 1;
+							Tab0 = {
+								Frame = "{{0, 0}, {484, 208}}";
+							};
+							Tab1 = {
+								Debugger = {
+									Collapsed = NO;
+									Frame = "{{0, 0}, {664, 208}}";
+									Split0 = {
+										Frame = "{{0, 24}, {664, 184}}";
+										Split0 = {
+											Frame = "{{0, 0}, {325, 184}}";
+										};
+										Split1 = {
+											DebugVariablesTableConfiguration = (
+												Name,
+												123,
+												Value,
+												85,
+												Summary,
+												96.123,
+											);
+											Frame = "{{334, 0}, {330, 184}}";
+										};
+										SplitCount = 2;
+									};
+									SplitCount = 1;
+									Tab0 = {
+										Frame = "{{0, 0}, {100, 50}}";
+									};
+									Tab1 = {
+										Frame = "{{0, 0}, {100, 50}}";
+									};
+									TabCount = 2;
+									TabsVisible = YES;
+								};
+								Frame = "{{0, 0}, {664, 208}}";
+								LauncherConfigVersion = 7;
+							};
+							Tab2 = {
+								Frame = "{{0, 0}, {664, 50}}";
+								LauncherConfigVersion = 3;
+								Runner = {
+									Frame = "{{0, 0}, {664, 50}}";
+								};
+							};
+							Tab3 = {
+								BuildMessageFrame = "{{0, 0}, {614, 262}}";
+								BuildTranscriptFrame = "{{0, 271}, {614, 2}}";
+								BuildTranscriptFrameExpanded = YES;
+								Frame = "{{0, 0}, {612, 295}}";
+							};
+							Tab4 = {
+								Frame = "{{0, 0}, {612, 295}}";
+							};
+							TabCount = 5;
+							TabsVisible = NO;
+						};
+						SplitCount = 1;
+						Tab0 = {
+							Frame = "{{0, 0}, {302, 740}}";
+							GroupTreeTableConfiguration = (
+								TargetStatusColumn,
+								18,
+								MainColumn,
+								269,
+							);
+						};
+						Tab1 = {
+							ClassesFrame = "{{0, 0}, {343, 348}}";
+							ClassesTreeTableConfiguration = (
+								PBXBookColumnIdentifier,
+								20,
+								PBXClassColumnIdentifier,
+								300,
+							);
+							Frame = "{{0, 0}, {341, 578}}";
+							MembersFrame = "{{0, 357}, {343, 221}}";
+							MembersTreeTableConfiguration = (
+								PBXBookColumnIdentifier,
+								20,
+								PBXMethodColumnIdentifier,
+								299,
+							);
+						};
+						Tab2 = {
+							Frame = "{{0, 0}, {319, 576}}";
+						};
+						Tab3 = {
+							Frame = "{{0, 0}, {341, 578}}";
+							TargetTableConfiguration = (
+								ActiveObject,
+								16,
+								ObjectNames,
+								308.296,
+							);
+						};
+						Tab4 = {
+							BreakpointsTreeTableConfiguration = (
+								breakpointColumn,
+								266,
+								enabledColumn,
+								31,
+							);
+							Frame = "{{0, 0}, {319, 576}}";
+						};
+						TabCount = 5;
+						TabsVisible = YES;
+					};
+					NavBarShownByDefault = YES;
+					StatusViewVisible = YES;
+					Template = F5E465990156DFB90DCA290F;
+					ToolbarVisible = YES;
+					WindowLocation = "{421, 275}";
+				},
+				{
+					ContentSize = "{668, 621}";
+					LeftSlideOut = {
+						Collapsed = NO;
+						Frame = "{{0, 0}, {668, 621}}";
+						Split0 = {
+							Collapsed = NO;
+							Frame = "{{0, 0}, {668, 621}}";
+							Split0 = {
+								Frame = "{{0, 0}, {668, 621}}";
+							};
+							SplitCount = 1;
+							Tab0 = {
+								Frame = "{{0, 0}, {484, 208}}";
+							};
+							Tab1 = {
+								Debugger = {
+									Collapsed = NO;
+									Frame = "{{0, 0}, {664, 208}}";
+									Split0 = {
+										Frame = "{{0, 24}, {664, 184}}";
+										Split0 = {
+											Frame = "{{0, 0}, {325, 184}}";
+										};
+										Split1 = {
+											DebugVariablesTableConfiguration = (
+												Name,
+												123,
+												Value,
+												85,
+												Summary,
+												96.123,
+											);
+											Frame = "{{334, 0}, {330, 184}}";
+										};
+										SplitCount = 2;
+									};
+									SplitCount = 1;
+									Tab0 = {
+										Frame = "{{0, 0}, {100, 50}}";
+									};
+									Tab1 = {
+										Frame = "{{0, 0}, {100, 50}}";
+									};
+									TabCount = 2;
+									TabsVisible = YES;
+								};
+								Frame = "{{0, 0}, {664, 208}}";
+								LauncherConfigVersion = 7;
+							};
+							Tab2 = {
+								Frame = "{{0, 0}, {664, 50}}";
+								LauncherConfigVersion = 3;
+								Runner = {
+									Frame = "{{0, 0}, {664, 50}}";
+								};
+							};
+							Tab3 = {
+								BuildMessageFrame = "{{0, 0}, {614, 262}}";
+								BuildTranscriptFrame = "{{0, 271}, {614, 2}}";
+								BuildTranscriptFrameExpanded = YES;
+								Frame = "{{0, 0}, {612, 295}}";
+							};
+							Tab4 = {
+								Frame = "{{0, 0}, {612, 295}}";
+							};
+							TabCount = 5;
+							TabsVisible = NO;
+						};
+						SplitCount = 1;
+						Tab0 = {
+							Frame = "{{0, 0}, {313, 531}}";
+							GroupTreeTableConfiguration = (
+								TargetStatusColumn,
+								18,
+								MainColumn,
+								280,
+							);
+						};
+						Tab1 = {
+							ClassesFrame = "{{0, 0}, {280, 398}}";
+							ClassesTreeTableConfiguration = (
+								PBXBookColumnIdentifier,
+								20,
+								PBXClassColumnIdentifier,
+								237,
+							);
+							Frame = "{{0, 0}, {278, 659}}";
+							MembersFrame = "{{0, 407}, {280, 252}}";
+							MembersTreeTableConfiguration = (
+								PBXBookColumnIdentifier,
+								20,
+								PBXMethodColumnIdentifier,
+								236,
+							);
+						};
+						Tab2 = {
+							Frame = "{{0, 0}, {200, 100}}";
+						};
+						Tab3 = {
+							Frame = "{{0, 0}, {200, 557}}";
+							TargetTableConfiguration = (
+								ActiveObject,
+								16,
+								ObjectNames,
+								202.296,
+							);
+						};
+						Tab4 = {
+							BreakpointsTreeTableConfiguration = (
+								breakpointColumn,
+								197,
+								enabledColumn,
+								31,
+							);
+							Frame = "{{0, 0}, {250, 100}}";
+						};
+						TabCount = 5;
+						TabsVisible = NO;
+					};
+					NavBarShownByDefault = YES;
+					StatusViewVisible = NO;
+					Template = F5CA7ECB015C094F0DCA290F;
+					ToolbarVisible = NO;
+					WindowLocation = "{357, 379}";
+				},
+			);
+			PBXWorkspaceStateSaveDate = 217904241;
+		};
+		sourceControlManager = E1B11EDC0CF5F2EB004975AA /* Source Control */;
+		userBookmarkGroup = E130B91603B69C6300000028 /* PBXBookmarkGroup */;
+		userBuildSettings = {
+		};
+	};
+	29B97316FDCFA39411CA2CEA /* main.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {920, 349}}";
+			sepNavSelRange = "{208, 0}";
+			sepNavVisRange = "{0, 252}";
+		};
+	};
+	E10E71F4040C335800050002 /* English */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 1503}}";
+			sepNavSelRange = "{1411, 0}";
+			sepNavVisRange = "{0, 3709}";
+			sepNavWindowFrame = "{{61, 198}, {1052, 933}}";
+		};
+	};
+	E10F2A3F0402E71400050002 /* NJRReadMeController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 3366}}";
+			sepNavSelRange = "{11592, 0}";
+			sepNavVisRange = "{9859, 1788}";
+			sepNavWindowFrame = "{{268, 244}, {889, 698}}";
+		};
+	};
+	E11373150439FCC700050002 /* NJRHotKey.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 1298}}";
+			sepNavSelRange = "{2509, 35}";
+			sepNavVisRange = "{1321, 1515}";
+			sepNavWindowFrame = "{{199, 307}, {889, 698}}";
+		};
+	};
+	E11373180439FE6A00050002 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = E11373150439FCC700050002 /* NJRHotKey.m */;
+		name = "NJRHotKey.m: 27";
+		rLen = 0;
+		rLoc = 1801;
+		rType = 0;
+		vrLen = 1001;
+		vrLoc = 30;
+	};
+	E130B91603B69C6300000028 /* PBXBookmarkGroup */ = {
+		isa = PBXBookmarkGroup;
+		children = (
+			E1F99AAA0436379100050002 /* PBXTextBookmark */,
+			E1F99AAB0436379900050002 /* PBXTextBookmark */,
+			E1F99AAD043637C200050002 /* PBXTextBookmark */,
+			E11373180439FE6A00050002 /* PBXTextBookmark */,
+			E18DAB000442DCB300050002 /* PBXTextBookmark */,
+		);
+		name = Root;
+	};
+	E130B9AC03B7C59700000028 /* PSPowerManager.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {940, 1093}}";
+			sepNavSelRange = "{451, 11}";
+			sepNavVisRange = "{0, 756}";
+		};
+	};
+	E130B9AD03B7C59700000028 /* PSPowerManager.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 1078}}";
+			sepNavSelRange = "{387, 16}";
+			sepNavVisRange = "{180, 425}";
+			sepNavWindowFrame = "{{153, 349}, {889, 698}}";
+		};
+	};
+	E13919E203B9E4E100000028 /* NJRIntervalField.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 780}}";
+			sepNavSelRange = "{419, 0}";
+			sepNavVisRange = "{0, 425}";
+			sepNavWindowFrame = "{{15, 49}, {834, 1124}}";
+		};
+	};
+	E13919E303B9E4E100000028 /* NJRIntervalField.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 946}}";
+			sepNavSelRange = "{2674, 0}";
+			sepNavVisRange = "{329, 2345}";
+			sepNavWindowFrame = "{{364, 159}, {820, 839}}";
+		};
+	};
+	E145383F043A1DB200050002 /* NJRHotKeyManager.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {920, 2937}}";
+			sepNavSelRange = "{4910, 0}";
+			sepNavVisRange = "{4514, 884}";
+			sepNavWindowFrame = "{{176, 328}, {889, 698}}";
+		};
+	};
+	E15043CF0CFCC41500DE47E9 /* objc_exception_throw */ = {
+		isa = PBXSymbolicBreakpoint;
+		actions = (
+		);
+		breakpointStyle = 1;
+		continueAfterActions = 0;
+		countType = 0;
+		delayBeforeContinue = 0;
+		hitCount = 1;
+		ignoreCount = 0;
+		location = libobjc.A.dylib;
+		modificationTime = 217892071.677906;
+		state = 2;
+		symbolName = objc_exception_throw;
+	};
+	E15044650CFCDC7E00DE47E9 /* NJRValidatingField.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {869, 213}}";
+			sepNavSelRange = "{170, 0}";
+			sepNavVisRange = "{0, 344}";
+		};
+	};
+	E15044660CFCDC7E00DE47E9 /* NJRValidatingField.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {775, 1122}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 4696}";
+			sepNavWindowFrame = "{{15, 49}, {834, 1124}}";
+		};
+	};
+	E162698303B6189000000028 /* NSTableView-NJRExtensions.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 803}}";
+			sepNavSelRange = "{748, 0}";
+			sepNavVisRange = "{0, 1295}";
+			sepNavWindowFrame = "{{84, 271}, {820, 839}}";
+		};
+	};
+	E162698903B6190B00000028 /* NSCalendarDate-NJRExtensions.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 307}}";
+			sepNavSelRange = "{213, 14}";
+			sepNavVisRange = "{0, 369}";
+		};
+	};
+	E16959290437A4A400050002 /* NSFont-NJRExtensions.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 803}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 304}";
+			sepNavWindowFrame = "{{717, 228}, {820, 839}}";
+		};
+	};
+	E169592A0437A4A400050002 /* NSFont-NJRExtensions.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 803}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 1070}";
+		};
+	};
+	E17562BD0CF8E7D000FC5057 /* updates.xml */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {736, 865}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 1323}";
+			sepNavWindowFrame = "{{15, 249}, {795, 924}}";
+		};
+	};
+	E17562BE0CF8E7D000FC5057 /* release-notes.html */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 897}}";
+			sepNavSelRange = "{369, 87}";
+			sepNavVisRange = "{0, 952}";
+			sepNavWindowFrame = "{{560, 24}, {1052, 933}}";
+		};
+	};
+	E17CA6640CF697FE00018B34 /* SetPortRGBForeColor */ = {
+		isa = PBXSymbolicBreakpoint;
+		actions = (
+		);
+		breakpointStyle = 1;
+		continueAfterActions = 0;
+		countType = 0;
+		delayBeforeContinue = 0;
+		hitCount = 0;
+		ignoreCount = 0;
+		location = "";
+		modificationTime = 217892051.042254;
+		state = 2;
+		symbolName = SetPortRGBForeColor;
+	};
+	E17F2F4303FDF67700050002 /* NSImage-OAExtensions.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 2365}}";
+			sepNavSelRange = "{6730, 19}";
+			sepNavVisRange = "{6628, 252}";
+		};
+	};
+	E18DA4090442C56D00050002 /* PSMediaAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 847}}";
+			sepNavSelRange = "{643, 0}";
+			sepNavVisRange = "{0, 1827}";
+			sepNavWindowFrame = "{{153, 208}, {820, 839}}";
+		};
+	};
+	E18DAAEB0442CC3E00050002 /* NJRSoundManager.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 1210}}";
+			sepNavSelRange = "{576, 0}";
+			sepNavVisRange = "{0, 2216}";
+			sepNavWindowFrame = "{{61, 292}, {820, 839}}";
+		};
+	};
+	E18DAB000442DCB300050002 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = F5F240CD036A877301FE7503 /* NJRQTMediaPopUpButton.h */;
+		name = "NJRQTMediaPopUpButton.h: 21";
+		rLen = 0;
+		rLoc = 562;
+		rType = 0;
+		vrLen = 777;
+		vrLoc = 0;
+	};
+	E19FD39F03C98645001509F3 /* PSWakeAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 876}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 847}";
+			sepNavWindowFrame = "{{130, 133}, {872, 935}}";
+		};
+	};
+	E1A18CAC03B4344C00000028 /* PSAlerts.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 1672}}";
+			sepNavSelRange = "{3864, 7}";
+			sepNavVisRange = "{1868, 2150}";
+			sepNavWindowFrame = "{{528, 47}, {889, 698}}";
+		};
+	};
+	E1A9DEE703BF3C110089E8B2 /* -[_NSZombie release] */ = {
+		isa = PBXSymbolicBreakpoint;
+		actions = (
+		);
+		breakpointStyle = 0;
+		continueAfterActions = 0;
+		countType = 0;
+		delayBeforeContinue = 0;
+		hitCount = 0;
+		ignoreCount = 0;
+		location = "";
+		modificationTime = 217892051.041767;
+		state = 2;
+		symbolName = "-[_NSZombie release]";
+	};
+	E1B11EDC0CF5F2EB004975AA /* Source Control */ = {
+		isa = PBXSourceControlManager;
+		fallbackIsa = XCSourceControlManager;
+		isSCMEnabled = 0;
+		scmConfiguration = {
+			repositoryName = dev.sabi.net;
+		};
+	};
+	E1B11EDD0CF5F2EB004975AA /* Code sense */ = {
+		isa = PBXCodeSenseManager;
+		indexTemplatePath = "";
+	};
+	E1B11EE30CF5F30F004975AA /* Pester */ = {
+		activeExec = 0;
+		executables = (
+			E1B11F8D0CF5F30F004975AA /* Pester */,
+		);
+	};
+	E1B11F8B0CF5F30F004975AA /* Info-Pester.plist */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {736, 888}}";
+			sepNavSelRange = "{346, 0}";
+			sepNavVisRange = "{0, 2337}";
+			sepNavWindowFrame = "{{549, 47}, {795, 924}}";
+		};
+	};
+	E1B11F8D0CF5F30F004975AA /* Pester */ = {
+		isa = PBXExecutable;
+		activeArgIndices = (
+		);
+		argumentStrings = (
+		);
+		autoAttachOnCrash = 1;
+		breakpointsEnabled = 0;
+		configStateDict = {
+			"PBXLSLaunchAction-0" = {
+				PBXLSLaunchAction = 0;
+				PBXLSLaunchStartAction = 1;
+				PBXLSLaunchStdioStyle = 2;
+				PBXLSLaunchStyle = 0;
+				class = PBXLSRunLaunchConfig;
+				commandLineArgs = (
+				);
+				displayName = "Executable Runner";
+				environment = {
+				};
+				identifier = com.apple.Xcode.launch.runConfig;
+				remoteHostInfo = "";
+				startActionInfo = "";
+			};
+		};
+		customDataFormattersEnabled = 1;
+		debuggerPlugin = GDBDebugging;
+		disassemblyDisplayState = 0;
+		dylibVariantSuffix = "";
+		enableDebugStr = 1;
+		environmentEntries = (
+			{
+				active = NO;
+				name = NSZombieEnabled;
+				value = YES;
+			},
+			{
+				active = NO;
+				name = MallocStackLogging;
+				value = 1;
+			},
+		);
+		executableSystemSymbolLevel = 0;
+		executableUserSymbolLevel = 0;
+		libgmallocEnabled = 0;
+		name = Pester;
+		savedGlobals = {
+		};
+		sourceDirectories = (
+		);
+		variableFormatDictionary = {
+		};
+	};
+	E1B121950CF62FD1004975AA /* NSMenuItem-NJRExtensions.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 803}}";
+			sepNavSelRange = "{216, 43}";
+			sepNavVisRange = "{0, 265}";
+		};
+	};
+	E1B121960CF62FD1004975AA /* NSMenuItem-NJRExtensions.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 803}}";
+			sepNavSelRange = "{264, 0}";
+			sepNavVisRange = "{0, 815}";
+			sepNavWindowFrame = "{{144, -29}, {820, 839}}";
+		};
+	};
+	E1BF6E9103C928D200050002 /* PSTimer.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {920, 2842}}";
+			sepNavSelRange = "{4580, 0}";
+			sepNavVisRange = "{409, 1359}";
+			sepNavWindowFrame = "{{38, 50}, {999, 1128}}";
+		};
+	};
+	E1CDEC3D0444053F00AEEDB1 /* NJRNonCenteringWindow.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 874}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 242}";
+			sepNavWindowFrame = "{{176, 93}, {1052, 933}}";
+		};
+	};
+	E1CDEC3E0444053F00AEEDB1 /* NJRNonCenteringWindow.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 874}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 261}";
+			sepNavWindowFrame = "{{153, 114}, {1052, 933}}";
+		};
+	};
+	E1DEF10303FF816300050002 /* PSSnoozeUntilController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 1859}}";
+			sepNavSelRange = "{1020, 0}";
+			sepNavVisRange = "{0, 2780}";
+			sepNavWindowFrame = "{{107, 154}, {872, 935}}";
+		};
+	};
+	E1DEF116040014EB00050002 /* PSTimeDateEditor.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 662}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 366}";
+		};
+	};
+	E1DEF117040014EB00050002 /* PSTimeDateEditor.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 671}}";
+			sepNavSelRange = "{1543, 0}";
+			sepNavVisRange = "{0, 2238}";
+			sepNavWindowFrame = "{{835, 4}, {889, 698}}";
+		};
+	};
+	E1ECA00703C002B300F54C66 /* PSError.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 874}}";
+			sepNavSelRange = "{254, 0}";
+			sepNavVisRange = "{0, 1270}";
+			sepNavWindowFrame = "{{153, 114}, {1052, 933}}";
+		};
+	};
+	E1F0B14F03ADBE3800AEFBE0 /* NSString-NJRExtensions.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {940, 2035}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 3838}";
+			sepNavWindowFrame = "{{15, 44}, {999, 1129}}";
+		};
+	};
+	E1F993C20436280200050002 /* NJRHotKeyField.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 2057}}";
+			sepNavSelRange = "{1811, 0}";
+			sepNavVisRange = "{1261, 1684}";
+			sepNavWindowFrame = "{{245, 265}, {889, 698}}";
+		};
+	};
+	E1F993C70436310F00050002 /* PSPreferencesController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 1694}}";
+			sepNavSelRange = "{2514, 11}";
+			sepNavVisRange = "{1964, 940}";
+			sepNavWindowFrame = "{{222, 286}, {889, 698}}";
+		};
+	};
+	E1F99AAA0436379100050002 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = E1F993C70436310F00050002 /* PSPreferencesController.m */;
+		name = "PSPreferencesController.m: 25";
+		rLen = 0;
+		rLoc = 2265;
+		rType = 0;
+		vrLen = 756;
+		vrLoc = 3;
+	};
+	E1F99AAB0436379900050002 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = E1F993C20436280200050002 /* NJRHotKeyField.m */;
+		name = "NJRHotKeyField.m: 15";
+		rLen = 0;
+		rLoc = 3335;
+		rType = 0;
+		vrLen = 517;
+		vrLoc = 0;
+	};
+	E1F99AAD043637C200050002 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = E1F0B14F03ADBE3800AEFBE0 /* NSString-NJRExtensions.m */;
+		name = "NSString-NJRExtensions.m: keyEquivalentString";
+		rLen = 0;
+		rLoc = 1550;
+		rType = 0;
+		vrLen = 1295;
+		vrLoc = 0;
+	};
+	F500E5370359719B01AEEDB1 /* szone_error */ = {
+		isa = PBXSymbolicBreakpoint;
+		actions = (
+		);
+		breakpointStyle = 0;
+		continueAfterActions = 0;
+		countType = 0;
+		delayBeforeContinue = 0;
+		hitCount = 0;
+		ignoreCount = 0;
+		location = libSystem.B.dylib;
+		modificationTime = 217892051.041262;
+		state = 2;
+		symbolName = szone_error;
+	};
+	F517D63E0356B80C016D68B9 /* PSAlarmsController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 2123}}";
+			sepNavSelRange = "{2816, 14}";
+			sepNavVisRange = "{2384, 686}";
+			sepNavWindowFrame = "{{744, 79}, {889, 698}}";
+		};
+	};
+	F517D63F0356B80C016D68B9 /* PSAlarmsController.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 639}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 460}";
+		};
+	};
+	F517D6420356B8CC016D68B9 /* PSAlarms.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 3737}}";
+			sepNavSelRange = "{9736, 0}";
+			sepNavVisRange = "{7626, 2401}";
+			sepNavWindowFrame = "{{938, 170}, {820, 839}}";
+		};
+	};
+	F517D6430356B8CC016D68B9 /* PSAlarms.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 780}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 953}";
+		};
+	};
+	F560E2F10357506401A4E466 /* PSApplication.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 2772}}";
+			sepNavSelRange = "{9502, 21}";
+			sepNavVisRange = "{8732, 887}";
+			sepNavWindowFrame = "{{586, 75}, {820, 839}}";
+		};
+	};
+	F560E3150357895001A4E466 /* NJRHistoryTrackingComboBox.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 231}}";
+			sepNavSelRange = "{231, 10}";
+			sepNavVisRange = "{190, 55}";
+		};
+	};
+	F560E3160357895001A4E466 /* NJRHistoryTrackingComboBox.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 858}}";
+			sepNavSelRange = "{1238, 11}";
+			sepNavVisRange = "{591, 1028}";
+			sepNavWindowFrame = "{{548, 60}, {820, 839}}";
+		};
+	};
+	F562F809037E5D0101AEEDB1 /* VERSION */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {736, 888}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 6}";
+			sepNavWindowFrame = "{{176, 102}, {795, 924}}";
+		};
+	};
+	F589C9C6035AA12B0155F4B2 /* Read Me.rtfd */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {960, 4408}}";
+			sepNavSelRange = "{16, 0}";
+			sepNavVisRect = "{{0, 199}, {960, 1091}}";
+			sepNavWindowFrame = "{{15, 50}, {999, 1128}}";
+		};
+	};
+	F596730D0355CFA301407038 /* -[_NSZombie methodSignatureForSelector:] */ = {
+		isa = PBXSymbolicBreakpoint;
+		actions = (
+		);
+		breakpointStyle = 0;
+		continueAfterActions = 0;
+		countType = 0;
+		delayBeforeContinue = 0;
+		hitCount = 0;
+		ignoreCount = 0;
+		location = "";
+		modificationTime = 217892051.040556;
+		state = 2;
+		symbolName = "-[_NSZombie methodSignatureForSelector:]";
+	};
+	F59DC6D30353C9E601AEEDB1 /* PSAlarmSetController.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {775, 1065}}";
+			sepNavSelRange = "{1563, 0}";
+			sepNavVisRange = "{0, 2381}";
+			sepNavWindowFrame = "{{23, 50}, {999, 1128}}";
+		};
+	};
+	F59DC6D40353C9E601AEEDB1 /* PSAlarmSetController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {775, 7139}}";
+			sepNavSelRange = "{21486, 11}";
+			sepNavVisRange = "{18453, 3820}";
+			sepNavWindowFrame = "{{561, 54}, {834, 1124}}";
+		};
+	};
+	F59DC6DC0353DE5101AEEDB1 /* PSAlarmNotifierController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 2391}}";
+			sepNavSelRange = "{4583, 27}";
+			sepNavVisRange = "{4327, 730}";
+			sepNavWindowFrame = "{{613, 82}, {820, 839}}";
+		};
+	};
+	F59DC6E30353ED1201AEEDB1 /* Application icon.icns */ = {
+		uiCtxt = {
+			sepNavWindowFrame = "{{153, 349}, {889, 698}}";
+		};
+	};
+	F59DC6FC0354188F01AEEDB1 /* NJRDateFormatter.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 876}}";
+			sepNavSelRange = "{310, 74}";
+			sepNavVisRange = "{0, 390}";
+			sepNavWindowFrame = "{{61, 196}, {872, 935}}";
+		};
+	};
+	F59DC6FD0354188F01AEEDB1 /* NJRDateFormatter.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 2376}}";
+			sepNavSelRange = "{5530, 0}";
+			sepNavVisRange = "{3665, 2352}";
+			sepNavWindowFrame = "{{90, 149}, {872, 935}}";
+		};
+	};
+	F59DD4500358ABF4017318D0 /* English */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 876}}";
+			sepNavSelRange = "{412, 0}";
+			sepNavVisRange = "{0, 412}";
+			sepNavWindowFrame = "{{61, 196}, {872, 935}}";
+		};
+	};
+	F59E47A7036AAA6D016B311C /* PSAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 891}}";
+			sepNavSelRange = "{1898, 0}";
+			sepNavVisRange = "{145, 1875}";
+			sepNavWindowFrame = "{{641, 165}, {820, 839}}";
+		};
+	};
+	F59E47A8036AAA6D016B311C /* PSAlert.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 385}}";
+			sepNavSelRange = "{350, 33}";
+			sepNavVisRange = "{325, 97}";
+		};
+	};
+	F59E47AB036AAC1E016B311C /* PSBeepAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {1297, 781}}";
+			sepNavSelRange = "{1311, 0}";
+			sepNavVisRange = "{0, 1940}";
+			sepNavWindowFrame = "{{360, 78}, {1356, 780}}";
+		};
+	};
+	F59E47AC036AAC1E016B311C /* PSBeepAlert.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {1297, 721}}";
+			sepNavSelRange = "{200, 12}";
+			sepNavVisRange = "{0, 370}";
+			sepNavWindowFrame = "{{360, 78}, {1356, 780}}";
+		};
+	};
+	F59E47B0036AACD1016B311C /* PSNotifierAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 780}}";
+			sepNavSelRange = "{900, 0}";
+			sepNavVisRange = "{0, 900}";
+			sepNavWindowFrame = "{{199, 166}, {820, 839}}";
+		};
+	};
+	F59E47B4036AAD55016B311C /* PSAlarmAlertController.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {813, 1045}}";
+			sepNavSelRange = "{1054, 0}";
+			sepNavVisRange = "{358, 2980}";
+			sepNavWindowFrame = "{{38, 217}, {872, 935}}";
+		};
+	};
+	F59E47B5036AAD55016B311C /* PSAlarmAlertController.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 307}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 586}";
+		};
+	};
+	F59E47B8036AB217016B311C /* PSDockBounceAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 780}}";
+			sepNavSelRange = "{873, 17}";
+			sepNavVisRange = "{0, 1126}";
+			sepNavWindowFrame = "{{130, 229}, {820, 839}}";
+		};
+	};
+	F59E47BC036AB228016B311C /* PSMovieAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 1522}}";
+			sepNavSelRange = "{1930, 0}";
+			sepNavVisRange = "{1829, 2467}";
+			sepNavWindowFrame = "{{84, 177}, {1052, 933}}";
+		};
+	};
+	F59E47BD036AB228016B311C /* PSMovieAlert.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 874}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 598}";
+		};
+	};
+	F59E4BE6036B2E5E016B311C /* PSScriptAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 1251}}";
+			sepNavSelRange = "{919, 0}";
+			sepNavVisRange = "{0, 2284}";
+			sepNavWindowFrame = "{{337, 181}, {889, 698}}";
+		};
+	};
+	F59E4C0D036B43D2016B311C /* NSMovie-NJRExtensions.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 780}}";
+			sepNavSelRange = "{601, 18}";
+			sepNavVisRange = "{0, 1083}";
+			sepNavWindowFrame = "{{590, 339}, {820, 839}}";
+		};
+	};
+	F59F6CB40354E7DA01AEEDB1 /* PSAlarm.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 913}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 2636}";
+			sepNavWindowFrame = "{{15, 240}, {1052, 933}}";
+		};
+	};
+	F59F6CB50354E7DA01AEEDB1 /* PSAlarm.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 6490}}";
+			sepNavSelRange = "{20070, 0}";
+			sepNavVisRange = "{17193, 2959}";
+			sepNavWindowFrame = "{{466, 241}, {1052, 933}}";
+		};
+	};
+	F5B79836035F64F80136A8DD /* NJRTableView.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 1210}}";
+			sepNavSelRange = "{899, 0}";
+			sepNavVisRange = "{0, 2747}";
+		};
+	};
+	F5B79837035F64F80136A8DD /* NJRTableView.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 897}}";
+			sepNavSelRange = "{646, 0}";
+			sepNavVisRange = "{0, 646}";
+			sepNavWindowFrame = "{{835, 245}, {1052, 933}}";
+		};
+	};
+	F5F1986C036B813101EB0372 /* PSSpeechAlert.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {920, 1056}}";
+			sepNavSelRange = "{1881, 0}";
+			sepNavVisRange = "{1560, 905}";
+			sepNavWindowFrame = "{{454, 61}, {1052, 933}}";
+		};
+	};
+	F5F1986D036B813101EB0372 /* PSSpeechAlert.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {817, 264}}";
+			sepNavSelRange = "{172, 0}";
+			sepNavVisRange = "{0, 264}";
+		};
+	};
+	F5F19870036B822E01EB0372 /* NJRVoicePopUpButton.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 1441}}";
+			sepNavSelRange = "{976, 0}";
+			sepNavVisRange = "{0, 1846}";
+			sepNavWindowFrame = "{{981, 73}, {889, 698}}";
+		};
+	};
+	F5F19871036B822E01EB0372 /* NJRVoicePopUpButton.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {830, 662}}";
+			sepNavSelRange = "{250, 0}";
+			sepNavVisRange = "{0, 583}";
+		};
+	};
+	F5F198D0036BE57401EB0372 /* NJRTableDelegate.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 2970}}";
+			sepNavSelRange = "{8161, 0}";
+			sepNavVisRange = "{6950, 2655}";
+			sepNavWindowFrame = "{{236, 118}, {1052, 933}}";
+		};
+	};
+	F5F198D1036BE57401EB0372 /* NJRTableDelegate.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {993, 897}}";
+			sepNavSelRange = "{495, 46}";
+			sepNavVisRange = "{0, 546}";
+			sepNavWindowFrame = "{{130, 135}, {1052, 933}}";
+		};
+	};
+	F5F240B5036A862101FE7503 /* NJRFSObjectSelector.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {869, 3575}}";
+			sepNavSelRange = "{2553, 75}";
+			sepNavVisRange = "{2223, 704}";
+			sepNavWindowFrame = "{{996, 92}, {820, 839}}";
+		};
+	};
+	F5F240B6036A862101FE7503 /* NJRFSObjectSelector.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {761, 780}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 653}";
+		};
+	};
+	F5F240CC036A877301FE7503 /* NJRQTMediaPopUpButton.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {940, 7022}}";
+			sepNavSelRange = "{1783, 11}";
+			sepNavVisRange = "{159, 3769}";
+			sepNavWindowFrame = "{{14, 50}, {999, 1128}}";
+		};
+	};
+	F5F240CD036A877301FE7503 /* NJRQTMediaPopUpButton.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {940, 1069}}";
+			sepNavSelRange = "{461, 0}";
+			sepNavVisRange = "{0, 827}";
+			sepNavWindowFrame = "{{14, 50}, {999, 1128}}";
+		};
+	};
+}
Index: releases/Pester/1.1b6/Source/Pester.xcodeproj/project.pbxproj
===================================================================
--- releases/Pester/1.1b6/Source/Pester.xcodeproj/project.pbxproj	(revision 365)
+++ releases/Pester/1.1b6/Source/Pester.xcodeproj/project.pbxproj	(revision 365)
@@ -0,0 +1,1042 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 42;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		E15044670CFCDC7E00DE47E9 /* NJRValidatingField.m in Sources */ = {isa = PBXBuildFile; fileRef = E15044660CFCDC7E00DE47E9 /* NJRValidatingField.m */; };
+		E17562620CF8C50B00FC5057 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E17562610CF8C50B00FC5057 /* Sparkle.framework */; };
+		E17562720CF8C53F00FC5057 /* Sparkle.framework in Copy Sparkle.framework */ = {isa = PBXBuildFile; fileRef = E17562610CF8C50B00FC5057 /* Sparkle.framework */; };
+		E17CA7BB0CF7D08C00018B34 /* DynaLoader.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E17CA7BA0CF7D08C00018B34 /* DynaLoader.a */; };
+		E17CA9AB0CF7F8CB00018B34 /* Manip.pm in Copy Perl Modules */ = {isa = PBXBuildFile; fileRef = E17CA9A90CF7F8C200018B34 /* Manip.pm */; };
+		E1B11F270CF5F30F004975AA /* Notifier.nib in Resources */ = {isa = PBXBuildFile; fileRef = F59DC6DF0353DF5E01AEEDB1 /* Notifier.nib */; };
+		E1B11F280CF5F30F004975AA /* Application icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = F59DC6E30353ED1201AEEDB1 /* Application icon.icns */; };
+		E1B11F290CF5F30F004975AA /* Alarms.nib in Resources */ = {isa = PBXBuildFile; fileRef = F560E2EE03574F8A01A4E466 /* Alarms.nib */; };
+		E1B11F2A0CF5F30F004975AA /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; };
+		E1B11F2B0CF5F30F004975AA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
+		E1B11F2C0CF5F30F004975AA /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = F59DD44F0358ABF4017318D0 /* Credits.html */; };
+		E1B11F2D0CF5F30F004975AA /* Read Me.rtfd in Resources */ = {isa = PBXBuildFile; fileRef = F589C9C6035AA12B0155F4B2 /* Read Me.rtfd */; };
+		E1B11F2E0CF5F30F004975AA /* Movie alert.nib in Resources */ = {isa = PBXBuildFile; fileRef = F59E4BDE036B1D94016B311C /* Movie alert.nib */; };
+		E1B11F2F0CF5F30F004975AA /* Popup triangle.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F5243DA40378681101AEEDB1 /* Popup triangle.tiff */; };
+		E1B11F300CF5F30F004975AA /* Calendar.nib in Resources */ = {isa = PBXBuildFile; fileRef = E17F2F4D03FDFB3100050002 /* Calendar.nib */; };
+		E1B11F310CF5F30F004975AA /* Calendar.tiff in Resources */ = {isa = PBXBuildFile; fileRef = E17F2F6B03FDFE2C00050002 /* Calendar.tiff */; };
+		E1B11F320CF5F30F004975AA /* OARightArrowPressed.tiff in Resources */ = {isa = PBXBuildFile; fileRef = E1DEF0EA03FE1BF100050002 /* OARightArrowPressed.tiff */; };
+		E1B11F330CF5F30F004975AA /* OALeftArrow.tiff in Resources */ = {isa = PBXBuildFile; fileRef = E1DEF0EB03FE1BF100050002 /* OALeftArrow.tiff */; };
+		E1B11F340CF5F30F004975AA /* OARightArrow.tiff in Resources */ = {isa = PBXBuildFile; fileRef = E1DEF0EC03FE1BF100050002 /* OARightArrow.tiff */; };
+		E1B11F350CF5F30F004975AA /* OALeftArrowPressed.tiff in Resources */ = {isa = PBXBuildFile; fileRef = E1DEF0ED03FE1BF100050002 /* OALeftArrowPressed.tiff */; };
+		E1B11F360CF5F30F004975AA /* Snooze until.nib in Resources */ = {isa = PBXBuildFile; fileRef = E1DEF10603FF83AC00050002 /* Snooze until.nib */; };
+		E1B11F370CF5F30F004975AA /* Read Me.nib in Resources */ = {isa = PBXBuildFile; fileRef = E10F2A3B0402E61C00050002 /* Read Me.nib */; };
+		E1B11F380CF5F30F004975AA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E10E71F3040C335800050002 /* Localizable.strings */; };
+		E1B11F390CF5F30F004975AA /* Preferences.nib in Resources */ = {isa = PBXBuildFile; fileRef = E11CB6E0043025F500050002 /* Preferences.nib */; };
+		E1B11F3A0CF5F30F004975AA /* Volume 2.png in Resources */ = {isa = PBXBuildFile; fileRef = E127900104429F9300050002 /* Volume 2.png */; };
+		E1B11F3B0CF5F30F004975AA /* Volume 0.png in Resources */ = {isa = PBXBuildFile; fileRef = E127900204429F9300050002 /* Volume 0.png */; };
+		E1B11F3C0CF5F30F004975AA /* Volume 3.png in Resources */ = {isa = PBXBuildFile; fileRef = E127900304429F9300050002 /* Volume 3.png */; };
+		E1B11F3D0CF5F30F004975AA /* Volume 1.png in Resources */ = {isa = PBXBuildFile; fileRef = E127900404429F9300050002 /* Volume 1.png */; };
+		E1B11F3E0CF5F30F004975AA /* Volume.nib in Resources */ = {isa = PBXBuildFile; fileRef = E12790090442A5B100050002 /* Volume.nib */; };
+		E1B11F400CF5F30F004975AA /* BDAlias.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F240CA036A869401FE7503 /* BDAlias.m */; };
+		E1B11F410CF5F30F004975AA /* NJRDateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F59DC6FD0354188F01AEEDB1 /* NJRDateFormatter.m */; settings = {COMPILER_FLAGS = "-Wno-unused-function"; }; };
+		E1B11F420CF5F30F004975AA /* NJRFSObjectSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F240B5036A862101FE7503 /* NJRFSObjectSelector.m */; };
+		E1B11F430CF5F30F004975AA /* NJRHistoryTrackingComboBox.m in Sources */ = {isa = PBXBuildFile; fileRef = F560E3160357895001A4E466 /* NJRHistoryTrackingComboBox.m */; };
+		E1B11F440CF5F30F004975AA /* NJRIntegerFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = F59DC6E80353F58B01AEEDB1 /* NJRIntegerFilter.m */; };
+		E1B11F450CF5F30F004975AA /* NJRQTMediaPopUpButton.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F240CC036A877301FE7503 /* NJRQTMediaPopUpButton.m */; };
+		E1B11F460CF5F30F004975AA /* NJRScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = F552A8AC0358E8B201AEEDB1 /* NJRScrollView.m */; };
+		E1B11F470CF5F30F004975AA /* NJRTableDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F198D0036BE57401EB0372 /* NJRTableDelegate.m */; };
+		E1B11F480CF5F30F004975AA /* NJRTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B79836035F64F80136A8DD /* NJRTableView.m */; };
+		E1B11F490CF5F30F004975AA /* NJRUnfocusableMovieView.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F0B14B03AD8CEF00AEFBE0 /* NJRUnfocusableMovieView.m */; };
+		E1B11F4A0CF5F30F004975AA /* NJRVoicePopUpButton.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F19870036B822E01EB0372 /* NJRVoicePopUpButton.m */; };
+		E1B11F4B0CF5F30F004975AA /* NSAttributedString-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F0B26903AEDE8600AEFBE0 /* NSAttributedString-NJRExtensions.m */; };
+		E1B11F4C0CF5F30F004975AA /* NSCalendarDate-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E162698A03B6190B00000028 /* NSCalendarDate-NJRExtensions.m */; };
+		E1B11F4D0CF5F30F004975AA /* NSCharacterSet-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B79865038865B30136A8DD /* NSCharacterSet-NJRExtensions.m */; };
+		E1B11F4E0CF5F30F004975AA /* NSDictionary-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F5EE8DB9038A76AC0134BEAC /* NSDictionary-NJRExtensions.m */; };
+		E1B11F500CF5F30F004975AA /* NSMovie-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E4C0D036B43D2016B311C /* NSMovie-NJRExtensions.m */; };
+		E1B11F510CF5F30F004975AA /* NSString-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F0B14F03ADBE3800AEFBE0 /* NSString-NJRExtensions.m */; };
+		E1B11F520CF5F30F004975AA /* NSTableView-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E162698303B6189000000028 /* NSTableView-NJRExtensions.m */; };
+		E1B11F530CF5F30F004975AA /* PSAlarm.m in Sources */ = {isa = PBXBuildFile; fileRef = F59F6CB50354E7DA01AEEDB1 /* PSAlarm.m */; };
+		E1B11F540CF5F30F004975AA /* PSAlarmAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47B4036AAD55016B311C /* PSAlarmAlertController.m */; };
+		E1B11F550CF5F30F004975AA /* PSAlarmNotifierController.m in Sources */ = {isa = PBXBuildFile; fileRef = F59DC6DC0353DE5101AEEDB1 /* PSAlarmNotifierController.m */; };
+		E1B11F560CF5F30F004975AA /* PSAlarmSetController.m in Sources */ = {isa = PBXBuildFile; fileRef = F59DC6D40353C9E601AEEDB1 /* PSAlarmSetController.m */; };
+		E1B11F570CF5F30F004975AA /* PSAlarms.m in Sources */ = {isa = PBXBuildFile; fileRef = F517D6420356B8CC016D68B9 /* PSAlarms.m */; };
+		E1B11F580CF5F30F004975AA /* PSAlarmsController.m in Sources */ = {isa = PBXBuildFile; fileRef = F517D63E0356B80C016D68B9 /* PSAlarmsController.m */; };
+		E1B11F590CF5F30F004975AA /* PSAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47A7036AAA6D016B311C /* PSAlert.m */; };
+		E1B11F5A0CF5F30F004975AA /* PSAlerts.m in Sources */ = {isa = PBXBuildFile; fileRef = E1A18CAC03B4344C00000028 /* PSAlerts.m */; };
+		E1B11F5B0CF5F30F004975AA /* PSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = F560E2F10357506401A4E466 /* PSApplication.m */; };
+		E1B11F5C0CF5F30F004975AA /* PSBeepAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47AB036AAC1E016B311C /* PSBeepAlert.m */; };
+		E1B11F5D0CF5F30F004975AA /* PSDockBounceAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47B8036AB217016B311C /* PSDockBounceAlert.m */; };
+		E1B11F5E0CF5F30F004975AA /* PSMovieAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47BC036AB228016B311C /* PSMovieAlert.m */; };
+		E1B11F5F0CF5F30F004975AA /* PSMovieAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47C0036B1ADD016B311C /* PSMovieAlertController.m */; };
+		E1B11F600CF5F30F004975AA /* PSNotifierAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E47B0036AACD1016B311C /* PSNotifierAlert.m */; };
+		E1B11F610CF5F30F004975AA /* PSPowerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E130B9AD03B7C59700000028 /* PSPowerManager.m */; };
+		E1B11F620CF5F30F004975AA /* PSScriptAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F59E4BE6036B2E5E016B311C /* PSScriptAlert.m */; };
+		E1B11F630CF5F30F004975AA /* PSSpeechAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F1986C036B813101EB0372 /* PSSpeechAlert.m */; };
+		E1B11F640CF5F30F004975AA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
+		E1B11F650CF5F30F004975AA /* NJRIntervalField.m in Sources */ = {isa = PBXBuildFile; fileRef = E13919E303B9E4E100000028 /* NJRIntervalField.m */; };
+		E1B11F660CF5F30F004975AA /* PSError.m in Sources */ = {isa = PBXBuildFile; fileRef = E1ECA00703C002B300F54C66 /* PSError.m */; };
+		E1B11F670CF5F30F004975AA /* NJRCenteringMovieView.m in Sources */ = {isa = PBXBuildFile; fileRef = E1D358BA03C60CE900C8A18E /* NJRCenteringMovieView.m */; };
+		E1B11F690CF5F30F004975AA /* PSTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = E1BF6E9103C928D200050002 /* PSTimer.m */; };
+		E1B11F6A0CF5F30F004975AA /* PSWakeAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = E19FD39F03C98645001509F3 /* PSWakeAlert.m */; };
+		E1B11F6B0CF5F30F004975AA /* PSCalendarController.m in Sources */ = {isa = PBXBuildFile; fileRef = E17F2F3303FDF2F000050002 /* PSCalendarController.m */; };
+		E1B11F6C0CF5F30F004975AA /* OACalendarView.m in Sources */ = {isa = PBXBuildFile; fileRef = E17F2F4103FDF67700050002 /* OACalendarView.m */; };
+		E1B11F6D0CF5F30F004975AA /* NSImage-OAExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E17F2F4303FDF67700050002 /* NSImage-OAExtensions.m */; };
+		E1B11F6E0CF5F30F004975AA /* NSCalendarDate-OFExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E17F2F4503FDF67800050002 /* NSCalendarDate-OFExtensions.m */; };
+		E1B11F6F0CF5F30F004975AA /* NJROffCenterPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = E1DEF0FA03FE9F2C00050002 /* NJROffCenterPanel.m */; };
+		E1B11F700CF5F30F004975AA /* PSSnoozeUntilController.m in Sources */ = {isa = PBXBuildFile; fileRef = E1DEF10303FF816300050002 /* PSSnoozeUntilController.m */; };
+		E1B11F710CF5F30F004975AA /* PSTimeDateEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = E1DEF117040014EB00050002 /* PSTimeDateEditor.m */; };
+		E1B11F720CF5F30F004975AA /* NJRReadMeController.m in Sources */ = {isa = PBXBuildFile; fileRef = E10F2A3F0402E71400050002 /* NJRReadMeController.m */; };
+		E1B11F730CF5F30F004975AA /* NJRSplitView.m in Sources */ = {isa = PBXBuildFile; fileRef = E1B46FE904057E3400050002 /* NJRSplitView.m */; };
+		E1B11F740CF5F30F004975AA /* NJRHotKeyField.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F993C20436280200050002 /* NJRHotKeyField.m */; };
+		E1B11F750CF5F30F004975AA /* PSPreferencesController.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F993C70436310F00050002 /* PSPreferencesController.m */; };
+		E1B11F760CF5F30F004975AA /* NSFont-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E169592A0437A4A400050002 /* NSFont-NJRExtensions.m */; };
+		E1B11F770CF5F30F004975AA /* NJRHotKeyFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E14DEC830438969400050002 /* NJRHotKeyFieldCell.m */; };
+		E1B11F780CF5F30F004975AA /* NJRHotKey.m in Sources */ = {isa = PBXBuildFile; fileRef = E11373150439FCC700050002 /* NJRHotKey.m */; };
+		E1B11F790CF5F30F004975AA /* NJRHotKeyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E145383F043A1DB200050002 /* NJRHotKeyManager.m */; };
+		E1B11F7A0CF5F30F004975AA /* PSVolumeController.m in Sources */ = {isa = PBXBuildFile; fileRef = E127900D0442A92900050002 /* PSVolumeController.m */; };
+		E1B11F7B0CF5F30F004975AA /* PSMediaAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = E18DA4090442C56D00050002 /* PSMediaAlert.m */; };
+		E1B11F7C0CF5F30F004975AA /* NJRSoundManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E18DAAEB0442CC3E00050002 /* NJRSoundManager.m */; };
+		E1B11F7D0CF5F30F004975AA /* NJRNonCenteringWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = E1CDEC3E0444053F00AEEDB1 /* NJRNonCenteringWindow.m */; };
+		E1B11F7F0CF5F30F004975AA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
+		E1B11F800CF5F30F004975AA /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5F240D0036A87E301FE7503 /* Carbon.framework */; };
+		E1B11F820CF5F30F004975AA /* QuickTime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F59E4BEA036B33EC016B311C /* QuickTime.framework */; };
+		E1B11F830CF5F30F004975AA /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E14DB31203B8038300000028 /* IOKit.framework */; };
+		E1B11F840CF5F30F004975AA /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E18DAAEE0442D02500050002 /* CoreAudio.framework */; };
+		E1B121970CF62FD1004975AA /* NSMenuItem-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E1B121960CF62FD1004975AA /* NSMenuItem-NJRExtensions.m */; };
+		E1B1219E0CF63858004975AA /* NSImage-NJRExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = E1B1219C0CF63858004975AA /* NSImage-NJRExtensions.m */; };
+		E1B121B40CF639A1004975AA /* QTKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E1B121B30CF639A1004975AA /* QTKit.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		E17562730CF8C55D00FC5057 /* Copy Sparkle.framework */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+				E17562720CF8C53F00FC5057 /* Sparkle.framework in Copy Sparkle.framework */,
+			);
+			name = "Copy Sparkle.framework";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E17CA9A80CF7F89900018B34 /* Copy Perl Modules */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = Date;
+			dstSubfolderSpec = 7;
+			files = (
+				E17CA9AB0CF7F8CB00018B34 /* Manip.pm in Copy Perl Modules */,
+			);
+			name = "Copy Perl Modules";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
+		29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
+		29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
+		29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
+		E10E71F4040C335800050002 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = "<group>"; };
+		E10F2A3C0402E61C00050002 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = "English.lproj/Read Me.nib"; sourceTree = "<group>"; };
+		E10F2A3E0402E71400050002 /* NJRReadMeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRReadMeController.h; sourceTree = "<group>"; };
+		E10F2A3F0402E71400050002 /* NJRReadMeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRReadMeController.m; sourceTree = "<group>"; };
+		E11373140439FCC700050002 /* NJRHotKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRHotKey.h; sourceTree = "<group>"; };
+		E11373150439FCC700050002 /* NJRHotKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRHotKey.m; sourceTree = "<group>"; };
+		E11CB6E1043025F500050002 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Preferences.nib; sourceTree = "<group>"; };
+		E127900104429F9300050002 /* Volume 2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Volume 2.png"; sourceTree = "<group>"; };
+		E127900204429F9300050002 /* Volume 0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Volume 0.png"; sourceTree = "<group>"; };
+		E127900304429F9300050002 /* Volume 3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Volume 3.png"; sourceTree = "<group>"; };
+		E127900404429F9300050002 /* Volume 1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Volume 1.png"; sourceTree = "<group>"; };
+		E127900A0442A5B100050002 /* Volume.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Volume.nib; path = English.lproj/Volume.nib; sourceTree = "<group>"; };
+		E127900C0442A92900050002 /* PSVolumeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSVolumeController.h; sourceTree = "<group>"; };
+		E127900D0442A92900050002 /* PSVolumeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSVolumeController.m; sourceTree = "<group>"; };
+		E130B9AC03B7C59700000028 /* PSPowerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSPowerManager.h; sourceTree = "<group>"; };
+		E130B9AD03B7C59700000028 /* PSPowerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSPowerManager.m; sourceTree = "<group>"; };
+		E13919E203B9E4E100000028 /* NJRIntervalField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRIntervalField.h; sourceTree = "<group>"; };
+		E13919E303B9E4E100000028 /* NJRIntervalField.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRIntervalField.m; sourceTree = "<group>"; };
+		E145383E043A1DB200050002 /* NJRHotKeyManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRHotKeyManager.h; sourceTree = "<group>"; };
+		E145383F043A1DB200050002 /* NJRHotKeyManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRHotKeyManager.m; sourceTree = "<group>"; };
+		E14DB31203B8038300000028 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = "<absolute>"; };
+		E14DEC820438969400050002 /* NJRHotKeyFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRHotKeyFieldCell.h; sourceTree = "<group>"; };
+		E14DEC830438969400050002 /* NJRHotKeyFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRHotKeyFieldCell.m; sourceTree = "<group>"; };
+		E15044650CFCDC7E00DE47E9 /* NJRValidatingField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRValidatingField.h; sourceTree = "<group>"; };
+		E15044660CFCDC7E00DE47E9 /* NJRValidatingField.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRValidatingField.m; sourceTree = "<group>"; };
+		E162698303B6189000000028 /* NSTableView-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = "NSTableView-NJRExtensions.m"; sourceTree = "<group>"; };
+		E162698403B6189000000028 /* NSTableView-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = "NSTableView-NJRExtensions.h"; sourceTree = "<group>"; };
+		E162698903B6190B00000028 /* NSCalendarDate-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSCalendarDate-NJRExtensions.h"; sourceTree = "<group>"; };
+		E162698A03B6190B00000028 /* NSCalendarDate-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSCalendarDate-NJRExtensions.m"; sourceTree = "<group>"; };
+		E16959290437A4A400050002 /* NSFont-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFont-NJRExtensions.h"; sourceTree = "<group>"; };
+		E169592A0437A4A400050002 /* NSFont-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFont-NJRExtensions.m"; sourceTree = "<group>"; };
+		E17562610CF8C50B00FC5057 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Sparkle/build/Release/Sparkle.framework; sourceTree = "<group>"; };
+		E17562BD0CF8E7D000FC5057 /* updates.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = updates.xml; path = ../Updates/updates.xml; sourceTree = SOURCE_ROOT; };
+		E17562BE0CF8E7D000FC5057 /* release-notes.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = "release-notes.html"; path = "../Updates/release-notes.html"; sourceTree = SOURCE_ROOT; };
+		E17562BF0CF8E7D000FC5057 /* release-notes.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = "release-notes.css"; path = "../Updates/release-notes.css"; sourceTree = SOURCE_ROOT; };
+		E17562C00CF8E7D000FC5057 /* Application icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Application icon.png"; path = "../Updates/Application icon.png"; sourceTree = SOURCE_ROOT; };
+		E17CA7BA0CF7D08C00018B34 /* DynaLoader.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = DynaLoader.a; sourceTree = "<group>"; };
+		E17CA9A90CF7F8C200018B34 /* Manip.pm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = Manip.pm; sourceTree = SOURCE_ROOT; };
+		E17F2F3203FDF2F000050002 /* PSCalendarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSCalendarController.h; sourceTree = "<group>"; };
+		E17F2F3303FDF2F000050002 /* PSCalendarController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSCalendarController.m; sourceTree = "<group>"; };
+		E17F2F4003FDF67700050002 /* NSCalendarDate-OFExtensions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = "NSCalendarDate-OFExtensions.h"; sourceTree = "<group>"; };
+		E17F2F4103FDF67700050002 /* OACalendarView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OACalendarView.m; sourceTree = "<group>"; };
+		E17F2F4203FDF67700050002 /* OACalendarView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OACalendarView.h; sourceTree = "<group>"; };
+		E17F2F4303FDF67700050002 /* NSImage-OAExtensions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = "NSImage-OAExtensions.m"; sourceTree = "<group>"; };
+		E17F2F4403FDF67800050002 /* NSImage-OAExtensions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = "NSImage-OAExtensions.h"; sourceTree = "<group>"; };
+		E17F2F4503FDF67800050002 /* NSCalendarDate-OFExtensions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = "NSCalendarDate-OFExtensions.m"; sourceTree = "<group>"; };
+		E17F2F4E03FDFB3100050002 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Calendar.nib; sourceTree = "<group>"; };
+		E17F2F6B03FDFE2C00050002 /* Calendar.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Calendar.tiff; sourceTree = "<group>"; };
+		E18DA4080442C56D00050002 /* PSMediaAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSMediaAlert.h; sourceTree = "<group>"; };
+		E18DA4090442C56D00050002 /* PSMediaAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSMediaAlert.m; sourceTree = "<group>"; };
+		E18DAAEA0442CC3E00050002 /* NJRSoundManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRSoundManager.h; sourceTree = "<group>"; };
+		E18DAAEB0442CC3E00050002 /* NJRSoundManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRSoundManager.m; sourceTree = "<group>"; };
+		E18DAAEE0442D02500050002 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = /System/Library/Frameworks/CoreAudio.framework; sourceTree = "<absolute>"; };
+		E191700B03AD24A600AEFBE0 /* NSDisclosureButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSDisclosureButtonCell.h; sourceTree = "<group>"; };
+		E19DCBD203BE4AA200B934BF /* Pester_Prefix.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pester_Prefix.h; sourceTree = "<group>"; };
+		E19FD39E03C98645001509F3 /* PSWakeAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSWakeAlert.h; sourceTree = "<group>"; };
+		E19FD39F03C98645001509F3 /* PSWakeAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSWakeAlert.m; sourceTree = "<group>"; };
+		E1A18CAB03B4344C00000028 /* PSAlerts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSAlerts.h; sourceTree = "<group>"; };
+		E1A18CAC03B4344C00000028 /* PSAlerts.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSAlerts.m; sourceTree = "<group>"; };
+		E1A18CB203B439CF00000028 /* PSPropertyListSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSPropertyListSerialization.h; sourceTree = SOURCE_ROOT; };
+		E1B11F8B0CF5F30F004975AA /* Info-Pester.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Pester.plist"; sourceTree = "<group>"; };
+		E1B11F8C0CF5F30F004975AA /* Pester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pester.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		E1B121950CF62FD1004975AA /* NSMenuItem-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMenuItem-NJRExtensions.h"; sourceTree = "<group>"; };
+		E1B121960CF62FD1004975AA /* NSMenuItem-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMenuItem-NJRExtensions.m"; sourceTree = "<group>"; };
+		E1B1219C0CF63858004975AA /* NSImage-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage-NJRExtensions.m"; sourceTree = "<group>"; };
+		E1B1219D0CF63858004975AA /* NSImage-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSImage-NJRExtensions.h"; sourceTree = "<group>"; };
+		E1B121B30CF639A1004975AA /* QTKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QTKit.framework; path = /System/Library/Frameworks/QTKit.framework; sourceTree = "<absolute>"; };
+		E1B46FE804057E3400050002 /* NJRSplitView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRSplitView.h; sourceTree = "<group>"; };
+		E1B46FE904057E3400050002 /* NJRSplitView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRSplitView.m; sourceTree = "<group>"; };
+		E1BF6E9003C928D200050002 /* PSTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSTimer.h; sourceTree = "<group>"; };
+		E1BF6E9103C928D200050002 /* PSTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTimer.m; sourceTree = "<group>"; };
+		E1CDEC3D0444053F00AEEDB1 /* NJRNonCenteringWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRNonCenteringWindow.h; sourceTree = "<group>"; };
+		E1CDEC3E0444053F00AEEDB1 /* NJRNonCenteringWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRNonCenteringWindow.m; sourceTree = "<group>"; };
+		E1D358B903C60CE900C8A18E /* NJRCenteringMovieView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRCenteringMovieView.h; sourceTree = "<group>"; };
+		E1D358BA03C60CE900C8A18E /* NJRCenteringMovieView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRCenteringMovieView.m; sourceTree = "<group>"; };
+		E1DEF0EA03FE1BF100050002 /* OARightArrowPressed.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = OARightArrowPressed.tiff; sourceTree = "<group>"; };
+		E1DEF0EB03FE1BF100050002 /* OALeftArrow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = OALeftArrow.tiff; sourceTree = "<group>"; };
+		E1DEF0EC03FE1BF100050002 /* OARightArrow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = OARightArrow.tiff; sourceTree = "<group>"; };
+		E1DEF0ED03FE1BF100050002 /* OALeftArrowPressed.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = OALeftArrowPressed.tiff; sourceTree = "<group>"; };
+		E1DEF0F903FE9F2C00050002 /* NJROffCenterPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJROffCenterPanel.h; sourceTree = "<group>"; };
+		E1DEF0FA03FE9F2C00050002 /* NJROffCenterPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJROffCenterPanel.m; sourceTree = "<group>"; };
+		E1DEF10203FF816300050002 /* PSSnoozeUntilController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSSnoozeUntilController.h; sourceTree = "<group>"; };
+		E1DEF10303FF816300050002 /* PSSnoozeUntilController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSSnoozeUntilController.m; sourceTree = "<group>"; };
+		E1DEF10703FF83AC00050002 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = "English.lproj/Snooze until.nib"; sourceTree = "<group>"; };
+		E1DEF116040014EB00050002 /* PSTimeDateEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSTimeDateEditor.h; sourceTree = "<group>"; };
+		E1DEF117040014EB00050002 /* PSTimeDateEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTimeDateEditor.m; sourceTree = "<group>"; };
+		E1ECA00603C002B300F54C66 /* PSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSError.h; sourceTree = SOURCE_ROOT; };
+		E1ECA00703C002B300F54C66 /* PSError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSError.m; sourceTree = SOURCE_ROOT; };
+		E1F0B14A03AD8CEF00AEFBE0 /* NJRUnfocusableMovieView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRUnfocusableMovieView.h; sourceTree = "<group>"; };
+		E1F0B14B03AD8CEF00AEFBE0 /* NJRUnfocusableMovieView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRUnfocusableMovieView.m; sourceTree = "<group>"; };
+		E1F0B14E03ADBE3800AEFBE0 /* NSString-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString-NJRExtensions.h"; sourceTree = "<group>"; };
+		E1F0B14F03ADBE3800AEFBE0 /* NSString-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString-NJRExtensions.m"; sourceTree = "<group>"; };
+		E1F0B26803AEDE8600AEFBE0 /* NSAttributedString-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString-NJRExtensions.h"; sourceTree = "<group>"; };
+		E1F0B26903AEDE8600AEFBE0 /* NSAttributedString-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString-NJRExtensions.m"; sourceTree = "<group>"; };
+		E1F993C10436280200050002 /* NJRHotKeyField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NJRHotKeyField.h; sourceTree = "<group>"; };
+		E1F993C20436280200050002 /* NJRHotKeyField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRHotKeyField.m; sourceTree = "<group>"; };
+		E1F993C60436310F00050002 /* PSPreferencesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSPreferencesController.h; sourceTree = "<group>"; };
+		E1F993C70436310F00050002 /* PSPreferencesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSPreferencesController.m; sourceTree = "<group>"; };
+		F517D63E0356B80C016D68B9 /* PSAlarmsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSAlarmsController.m; sourceTree = "<group>"; };
+		F517D63F0356B80C016D68B9 /* PSAlarmsController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlarmsController.h; sourceTree = "<group>"; };
+		F517D6420356B8CC016D68B9 /* PSAlarms.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSAlarms.m; sourceTree = "<group>"; };
+		F517D6430356B8CC016D68B9 /* PSAlarms.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlarms.h; sourceTree = "<group>"; };
+		F5243DA40378681101AEEDB1 /* Popup triangle.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Popup triangle.tiff"; sourceTree = "<group>"; };
+		F552A8AB0358E8B201AEEDB1 /* NJRScrollView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRScrollView.h; sourceTree = "<group>"; };
+		F552A8AC0358E8B201AEEDB1 /* NJRScrollView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRScrollView.m; sourceTree = "<group>"; };
+		F560E2EF03574F8A01A4E466 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Alarms.nib; sourceTree = "<group>"; };
+		F560E2F10357506401A4E466 /* PSApplication.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSApplication.m; sourceTree = "<group>"; };
+		F560E2F20357506401A4E466 /* PSApplication.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSApplication.h; sourceTree = "<group>"; };
+		F560E3150357895001A4E466 /* NJRHistoryTrackingComboBox.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRHistoryTrackingComboBox.h; sourceTree = "<group>"; };
+		F560E3160357895001A4E466 /* NJRHistoryTrackingComboBox.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRHistoryTrackingComboBox.m; sourceTree = "<group>"; };
+		F562F809037E5D0101AEEDB1 /* VERSION */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; };
+		F589C9C6035AA12B0155F4B2 /* Read Me.rtfd */ = {isa = PBXFileReference; lastKnownFileType = wrapper.rtfd; path = "Read Me.rtfd"; sourceTree = "<group>"; };
+		F59DC6D30353C9E601AEEDB1 /* PSAlarmSetController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlarmSetController.h; sourceTree = "<group>"; };
+		F59DC6D40353C9E601AEEDB1 /* PSAlarmSetController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSAlarmSetController.m; sourceTree = "<group>"; };
+		F59DC6DB0353DE5101AEEDB1 /* PSAlarmNotifierController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlarmNotifierController.h; sourceTree = "<group>"; };
+		F59DC6DC0353DE5101AEEDB1 /* PSAlarmNotifierController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSAlarmNotifierController.m; sourceTree = "<group>"; };
+		F59DC6E00353DF5E01AEEDB1 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Notifier.nib; sourceTree = "<group>"; };
+		F59DC6E30353ED1201AEEDB1 /* Application icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "Application icon.icns"; sourceTree = "<group>"; };
+		F59DC6E80353F58B01AEEDB1 /* NJRIntegerFilter.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRIntegerFilter.m; sourceTree = "<group>"; };
+		F59DC6E90353F58B01AEEDB1 /* NJRIntegerFilter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRIntegerFilter.h; sourceTree = "<group>"; };
+		F59DC6FC0354188F01AEEDB1 /* NJRDateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRDateFormatter.h; sourceTree = "<group>"; };
+		F59DC6FD0354188F01AEEDB1 /* NJRDateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRDateFormatter.m; sourceTree = "<group>"; };
+		F59DD4500358ABF4017318D0 /* English */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.html; name = English; path = English.lproj/Credits.html; sourceTree = "<group>"; };
+		F59E47A7036AAA6D016B311C /* PSAlert.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSAlert.m; sourceTree = "<group>"; };
+		F59E47A8036AAA6D016B311C /* PSAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlert.h; sourceTree = "<group>"; };
+		F59E47AB036AAC1E016B311C /* PSBeepAlert.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSBeepAlert.m; sourceTree = "<group>"; };
+		F59E47AC036AAC1E016B311C /* PSBeepAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSBeepAlert.h; sourceTree = "<group>"; };
+		F59E47B0036AACD1016B311C /* PSNotifierAlert.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSNotifierAlert.m; sourceTree = "<group>"; };
+		F59E47B1036AACD1016B311C /* PSNotifierAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSNotifierAlert.h; sourceTree = "<group>"; };
+		F59E47B4036AAD55016B311C /* PSAlarmAlertController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSAlarmAlertController.m; sourceTree = "<group>"; };
+		F59E47B5036AAD55016B311C /* PSAlarmAlertController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlarmAlertController.h; sourceTree = "<group>"; };
+		F59E47B8036AB217016B311C /* PSDockBounceAlert.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSDockBounceAlert.m; sourceTree = "<group>"; };
+		F59E47B9036AB217016B311C /* PSDockBounceAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSDockBounceAlert.h; sourceTree = "<group>"; };
+		F59E47BC036AB228016B311C /* PSMovieAlert.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSMovieAlert.m; sourceTree = "<group>"; };
+		F59E47BD036AB228016B311C /* PSMovieAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSMovieAlert.h; sourceTree = "<group>"; };
+		F59E47C0036B1ADD016B311C /* PSMovieAlertController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSMovieAlertController.m; sourceTree = "<group>"; };
+		F59E47C1036B1ADD016B311C /* PSMovieAlertController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSMovieAlertController.h; sourceTree = "<group>"; };
+		F59E4BDF036B1D94016B311C /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = "English.lproj/Movie alert.nib"; sourceTree = "<group>"; };
+		F59E4BE6036B2E5E016B311C /* PSScriptAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSScriptAlert.m; sourceTree = "<group>"; };
+		F59E4BE7036B2E5E016B311C /* PSScriptAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSScriptAlert.h; sourceTree = "<group>"; };
+		F59E4BEA036B33EC016B311C /* QuickTime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickTime.framework; path = /System/Library/Frameworks/QuickTime.framework; sourceTree = "<absolute>"; };
+		F59E4C0D036B43D2016B311C /* NSMovie-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = "NSMovie-NJRExtensions.m"; sourceTree = "<group>"; };
+		F59E4C0E036B43D2016B311C /* NSMovie-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = "NSMovie-NJRExtensions.h"; sourceTree = "<group>"; };
+		F59F6CB40354E7DA01AEEDB1 /* PSAlarm.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSAlarm.h; sourceTree = "<group>"; };
+		F59F6CB50354E7DA01AEEDB1 /* PSAlarm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSAlarm.m; sourceTree = "<group>"; };
+		F5B79836035F64F80136A8DD /* NJRTableView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRTableView.m; sourceTree = "<group>"; };
+		F5B79837035F64F80136A8DD /* NJRTableView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRTableView.h; sourceTree = "<group>"; };
+		F5B79865038865B30136A8DD /* NSCharacterSet-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = "NSCharacterSet-NJRExtensions.m"; sourceTree = "<group>"; };
+		F5B79866038865B30136A8DD /* NSCharacterSet-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = "NSCharacterSet-NJRExtensions.h"; sourceTree = "<group>"; };
+		F5EE8DB9038A76AC0134BEAC /* NSDictionary-NJRExtensions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary-NJRExtensions.m"; sourceTree = "<group>"; };
+		F5EE8DBA038A76AC0134BEAC /* NSDictionary-NJRExtensions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = "NSDictionary-NJRExtensions.h"; sourceTree = "<group>"; };
+		F5F1986C036B813101EB0372 /* PSSpeechAlert.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PSSpeechAlert.m; sourceTree = "<group>"; };
+		F5F1986D036B813101EB0372 /* PSSpeechAlert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PSSpeechAlert.h; sourceTree = "<group>"; };
+		F5F19870036B822E01EB0372 /* NJRVoicePopUpButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NJRVoicePopUpButton.m; sourceTree = "<group>"; };
+		F5F19871036B822E01EB0372 /* NJRVoicePopUpButton.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRVoicePopUpButton.h; sourceTree = "<group>"; };
+		F5F198D0036BE57401EB0372 /* NJRTableDelegate.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRTableDelegate.m; sourceTree = "<group>"; };
+		F5F198D1036BE57401EB0372 /* NJRTableDelegate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRTableDelegate.h; sourceTree = "<group>"; };
+		F5F240B5036A862101FE7503 /* NJRFSObjectSelector.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRFSObjectSelector.m; sourceTree = "<group>"; };
+		F5F240B6036A862101FE7503 /* NJRFSObjectSelector.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRFSObjectSelector.h; sourceTree = "<group>"; };
+		F5F240B9036A867901FE7503 /* BDAlias.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BDAlias.h; sourceTree = "<group>"; };
+		F5F240CA036A869401FE7503 /* BDAlias.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = BDAlias.m; sourceTree = "<group>"; };
+		F5F240CC036A877301FE7503 /* NJRQTMediaPopUpButton.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NJRQTMediaPopUpButton.m; sourceTree = "<group>"; };
+		F5F240CD036A877301FE7503 /* NJRQTMediaPopUpButton.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NJRQTMediaPopUpButton.h; sourceTree = "<group>"; };
+		F5F240D0036A87E301FE7503 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = "<absolute>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		E1B11F7E0CF5F30F004975AA /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E1B11F7F0CF5F30F004975AA /* Cocoa.framework in Frameworks */,
+				E1B11F800CF5F30F004975AA /* Carbon.framework in Frameworks */,
+				E1B11F820CF5F30F004975AA /* QuickTime.framework in Frameworks */,
+				E1B11F830CF5F30F004975AA /* IOKit.framework in Frameworks */,
+				E1B11F840CF5F30F004975AA /* CoreAudio.framework in Frameworks */,
+				E1B121B40CF639A1004975AA /* QTKit.framework in Frameworks */,
+				E17CA7BB0CF7D08C00018B34 /* DynaLoader.a in Frameworks */,
+				E17562620CF8C50B00FC5057 /* Sparkle.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		080E96DDFE201D6D7F000001 /* Classes */ = {
+			isa = PBXGroup;
+			children = (
+				F560E2F20357506401A4E466 /* PSApplication.h */,
+				F560E2F10357506401A4E466 /* PSApplication.m */,
+				F560E311035787D501A4E466 /* Controllers */,
+				F560E314035787F901A4E466 /* Views */,
+				F59E47AF036AAC31016B311C /* Alerts */,
+				F560E312035787E001A4E466 /* Alarms */,
+				F560E313035787ED01A4E466 /* Utilities */,
+				E162698703B6189D00000028 /* Categories */,
+			);
+			name = Classes;
+			sourceTree = "<group>";
+		};
+		1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				E17562610CF8C50B00FC5057 /* Sparkle.framework */,
+				1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
+				F5F240D0036A87E301FE7503 /* Carbon.framework */,
+				E18DAAEE0442D02500050002 /* CoreAudio.framework */,
+				E1B121B30CF639A1004975AA /* QTKit.framework */,
+				F59E4BEA036B33EC016B311C /* QuickTime.framework */,
+				E14DB31203B8038300000028 /* IOKit.framework */,
+			);
+			name = "Linked Frameworks";
+			sourceTree = "<group>";
+		};
+		1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				29B97325FDCFA39411CA2CEA /* Foundation.framework */,
+				29B97324FDCFA39411CA2CEA /* AppKit.framework */,
+			);
+			name = "Other Frameworks";
+			sourceTree = "<group>";
+		};
+		19C28FACFE9D520D11CA2CBB /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				F562F809037E5D0101AEEDB1 /* VERSION */,
+				E1B11F8C0CF5F30F004975AA /* Pester.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		29B97314FDCFA39411CA2CEA /* Pester */ = {
+			isa = PBXGroup;
+			children = (
+				080E96DDFE201D6D7F000001 /* Classes */,
+				29B97315FDCFA39411CA2CEA /* Other Sources */,
+				29B97317FDCFA39411CA2CEA /* Resources */,
+				29B97323FDCFA39411CA2CEA /* Frameworks */,
+				E19DCBE503BE516000B934BF /* Documentation */,
+				E17562BC0CF8E7A800FC5057 /* Sparkle */,
+				19C28FACFE9D520D11CA2CBB /* Products */,
+			);
+			name = Pester;
+			sourceTree = "<group>";
+		};
+		29B97315FDCFA39411CA2CEA /* Other Sources */ = {
+			isa = PBXGroup;
+			children = (
+				E19DCBD203BE4AA200B934BF /* Pester_Prefix.h */,
+				29B97316FDCFA39411CA2CEA /* main.m */,
+			);
+			name = "Other Sources";
+			sourceTree = "<group>";
+		};
+		29B97317FDCFA39411CA2CEA /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				E18830410442B5FC00050002 /* Calendar icons */,
+				E18830420442B60000050002 /* Volume icons */,
+				F5243DA40378681101AEEDB1 /* Popup triangle.tiff */,
+				29B97318FDCFA39411CA2CEA /* MainMenu.nib */,
+				F560E2EE03574F8A01A4E466 /* Alarms.nib */,
+				F59DC6DF0353DF5E01AEEDB1 /* Notifier.nib */,
+				F59E4BDE036B1D94016B311C /* Movie alert.nib */,
+				E12790090442A5B100050002 /* Volume.nib */,
+				E17F2F4D03FDFB3100050002 /* Calendar.nib */,
+				E1DEF10603FF83AC00050002 /* Snooze until.nib */,
+				E10F2A3B0402E61C00050002 /* Read Me.nib */,
+				E11CB6E0043025F500050002 /* Preferences.nib */,
+				E1B11F8B0CF5F30F004975AA /* Info-Pester.plist */,
+				089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
+				E10E71F3040C335800050002 /* Localizable.strings */,
+				F59DD44F0358ABF4017318D0 /* Credits.html */,
+				F59DC6E30353ED1201AEEDB1 /* Application icon.icns */,
+			);
+			name = Resources;
+			sourceTree = "<group>";
+		};
+		29B97323FDCFA39411CA2CEA /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
+				1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
+				E162698803B618BD00000028 /* Private Headers */,
+				E17CA7B60CF7D07400018B34 /* Perl */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		E162698703B6189D00000028 /* Categories */ = {
+			isa = PBXGroup;
+			children = (
+				E1F0B26803AEDE8600AEFBE0 /* NSAttributedString-NJRExtensions.h */,
+				E1F0B26903AEDE8600AEFBE0 /* NSAttributedString-NJRExtensions.m */,
+				E162698903B6190B00000028 /* NSCalendarDate-NJRExtensions.h */,
+				E162698A03B6190B00000028 /* NSCalendarDate-NJRExtensions.m */,
+				F5B79866038865B30136A8DD /* NSCharacterSet-NJRExtensions.h */,
+				F5B79865038865B30136A8DD /* NSCharacterSet-NJRExtensions.m */,
+				F5EE8DBA038A76AC0134BEAC /* NSDictionary-NJRExtensions.h */,
+				F5EE8DB9038A76AC0134BEAC /* NSDictionary-NJRExtensions.m */,
+				E16959290437A4A400050002 /* NSFont-NJRExtensions.h */,
+				E169592A0437A4A400050002 /* NSFont-NJRExtensions.m */,
+				E1B1219C0CF63858004975AA /* NSImage-NJRExtensions.m */,
+				E1B1219D0CF63858004975AA /* NSImage-NJRExtensions.h */,
+				F59E4C0E036B43D2016B311C /* NSMovie-NJRExtensions.h */,
+				F59E4C0D036B43D2016B311C /* NSMovie-NJRExtensions.m */,
+				E1F0B14E03ADBE3800AEFBE0 /* NSString-NJRExtensions.h */,
+				E1F0B14F03ADBE3800AEFBE0 /* NSString-NJRExtensions.m */,
+				E162698303B6189000000028 /* NSTableView-NJRExtensions.m */,
+				E162698403B6189000000028 /* NSTableView-NJRExtensions.h */,
+				E1B121950CF62FD1004975AA /* NSMenuItem-NJRExtensions.h */,
+				E1B121960CF62FD1004975AA /* NSMenuItem-NJRExtensions.m */,
+			);
+			name = Categories;
+			sourceTree = "<group>";
+		};
+		E162698803B618BD00000028 /* Private Headers */ = {
+			isa = PBXGroup;
+			children = (
+				E191700B03AD24A600AEFBE0 /* NSDisclosureButtonCell.h */,
+			);
+			name = "Private Headers";
+			sourceTree = "<group>";
+		};
+		E17562BC0CF8E7A800FC5057 /* Sparkle */ = {
+			isa = PBXGroup;
+			children = (
+				E17562BD0CF8E7D000FC5057 /* updates.xml */,
+				E17562BE0CF8E7D000FC5057 /* release-notes.html */,
+				E17562BF0CF8E7D000FC5057 /* release-notes.css */,
+				E17562C00CF8E7D000FC5057 /* Application icon.png */,
+			);
+			name = Sparkle;
+			sourceTree = "<group>";
+		};
+		E17CA7B60CF7D07400018B34 /* Perl */ = {
+			isa = PBXGroup;
+			children = (
+				E17CA9A90CF7F8C200018B34 /* Manip.pm */,
+				E17CA7BA0CF7D08C00018B34 /* DynaLoader.a */,
+			);
+			name = Perl;
+			sourceTree = "<group>";
+		};
+		E17F2F4C03FDF67D00050002 /* OACalendarView */ = {
+			isa = PBXGroup;
+			children = (
+				E17F2F4003FDF67700050002 /* NSCalendarDate-OFExtensions.h */,
+				E17F2F4503FDF67800050002 /* NSCalendarDate-OFExtensions.m */,
+				E17F2F4203FDF67700050002 /* OACalendarView.h */,
+				E17F2F4103FDF67700050002 /* OACalendarView.m */,
+				E17F2F4403FDF67800050002 /* NSImage-OAExtensions.h */,
+				E17F2F4303FDF67700050002 /* NSImage-OAExtensions.m */,
+				E1DEF0F903FE9F2C00050002 /* NJROffCenterPanel.h */,
+				E1DEF0FA03FE9F2C00050002 /* NJROffCenterPanel.m */,
+			);
+			name = OACalendarView;
+			sourceTree = "<group>";
+		};
+		E18830410442B5FC00050002 /* Calendar icons */ = {
+			isa = PBXGroup;
+			children = (
+				E17F2F6B03FDFE2C00050002 /* Calendar.tiff */,
+				E1DEF0EA03FE1BF100050002 /* OARightArrowPressed.tiff */,
+				E1DEF0EB03FE1BF100050002 /* OALeftArrow.tiff */,
+				E1DEF0EC03FE1BF100050002 /* OARightArrow.tiff */,
+				E1DEF0ED03FE1BF100050002 /* OALeftArrowPressed.tiff */,
+			);
+			name = "Calendar icons";
+			sourceTree = "<group>";
+		};
+		E18830420442B60000050002 /* Volume icons */ = {
+			isa = PBXGroup;
+			children = (
+				E127900204429F9300050002 /* Volume 0.png */,
+				E127900404429F9300050002 /* Volume 1.png */,
+				E127900104429F9300050002 /* Volume 2.png */,
+				E127900304429F9300050002 /* Volume 3.png */,
+			);
+			name = "Volume icons";
+			sourceTree = "<group>";
+		};
+		E19DCBE503BE516000B934BF /* Documentation */ = {
+			isa = PBXGroup;
+			children = (
+				F589C9C6035AA12B0155F4B2 /* Read Me.rtfd */,
+			);
+			name = Documentation;
+			sourceTree = "<group>";
+		};
+		F560E311035787D501A4E466 /* Controllers */ = {
+			isa = PBXGroup;
+			children = (
+				F517D63F0356B80C016D68B9 /* PSAlarmsController.h */,
+				F517D63E0356B80C016D68B9 /* PSAlarmsController.m */,
+				F59DC6D30353C9E601AEEDB1 /* PSAlarmSetController.h */,
+				F59DC6D40353C9E601AEEDB1 /* PSAlarmSetController.m */,
+				F59E47B5036AAD55016B311C /* PSAlarmAlertController.h */,
+				F59E47B4036AAD55016B311C /* PSAlarmAlertController.m */,
+				F59DC6DB0353DE5101AEEDB1 /* PSAlarmNotifierController.h */,
+				F59DC6DC0353DE5101AEEDB1 /* PSAlarmNotifierController.m */,
+				F59E47C1036B1ADD016B311C /* PSMovieAlertController.h */,
+				F59E47C0036B1ADD016B311C /* PSMovieAlertController.m */,
+				E17F2F3203FDF2F000050002 /* PSCalendarController.h */,
+				E17F2F3303FDF2F000050002 /* PSCalendarController.m */,
+				E127900C0442A92900050002 /* PSVolumeController.h */,
+				E127900D0442A92900050002 /* PSVolumeController.m */,
+				E1F993C60436310F00050002 /* PSPreferencesController.h */,
+				E1F993C70436310F00050002 /* PSPreferencesController.m */,
+				E1DEF10203FF816300050002 /* PSSnoozeUntilController.h */,
+				E1DEF10303FF816300050002 /* PSSnoozeUntilController.m */,
+				E10F2A3E0402E71400050002 /* NJRReadMeController.h */,
+				E10F2A3F0402E71400050002 /* NJRReadMeController.m */,
+				F5F198D1036BE57401EB0372 /* NJRTableDelegate.h */,
+				F5F198D0036BE57401EB0372 /* NJRTableDelegate.m */,
+			);
+			name = Controllers;
+			sourceTree = "<group>";
+		};
+		F560E312035787E001A4E466 /* Alarms */ = {
+			isa = PBXGroup;
+			children = (
+				F517D6430356B8CC016D68B9 /* PSAlarms.h */,
+				F517D6420356B8CC016D68B9 /* PSAlarms.m */,
+				F59F6CB40354E7DA01AEEDB1 /* PSAlarm.h */,
+				F59F6CB50354E7DA01AEEDB1 /* PSAlarm.m */,
+				E1BF6E9003C928D200050002 /* PSTimer.h */,
+				E1BF6E9103C928D200050002 /* PSTimer.m */,
+			);
+			name = Alarms;
+			sourceTree = "<group>";
+		};
+		F560E313035787ED01A4E466 /* Utilities */ = {
+			isa = PBXGroup;
+			children = (
+				F59E47A6036AA5FC016B311C /* BDAlias */,
+				E1DEF116040014EB00050002 /* PSTimeDateEditor.h */,
+				E1DEF117040014EB00050002 /* PSTimeDateEditor.m */,
+				E1ECA00603C002B300F54C66 /* PSError.h */,
+				E1ECA00703C002B300F54C66 /* PSError.m */,
+				E1A18CB203B439CF00000028 /* PSPropertyListSerialization.h */,
+				E130B9AC03B7C59700000028 /* PSPowerManager.h */,
+				E130B9AD03B7C59700000028 /* PSPowerManager.m */,
+				F59DC6E90353F58B01AEEDB1 /* NJRIntegerFilter.h */,
+				F59DC6E80353F58B01AEEDB1 /* NJRIntegerFilter.m */,
+				F59DC6FC0354188F01AEEDB1 /* NJRDateFormatter.h */,
+				F59DC6FD0354188F01AEEDB1 /* NJRDateFormatter.m */,
+				E11373140439FCC700050002 /* NJRHotKey.h */,
+				E11373150439FCC700050002 /* NJRHotKey.m */,
+				E145383E043A1DB200050002 /* NJRHotKeyManager.h */,
+				E145383F043A1DB200050002 /* NJRHotKeyManager.m */,
+				E18DAAEA0442CC3E00050002 /* NJRSoundManager.h */,
+				E18DAAEB0442CC3E00050002 /* NJRSoundManager.m */,
+				E1CDEC3D0444053F00AEEDB1 /* NJRNonCenteringWindow.h */,
+				E1CDEC3E0444053F00AEEDB1 /* NJRNonCenteringWindow.m */,
+			);
+			name = Utilities;
+			sourceTree = "<group>";
+		};
+		F560E314035787F901A4E466 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				E17F2F4C03FDF67D00050002 /* OACalendarView */,
+				E1D358B903C60CE900C8A18E /* NJRCenteringMovieView.h */,
+				E1D358BA03C60CE900C8A18E /* NJRCenteringMovieView.m */,
+				E1F0B14A03AD8CEF00AEFBE0 /* NJRUnfocusableMovieView.h */,
+				E1F0B14B03AD8CEF00AEFBE0 /* NJRUnfocusableMovieView.m */,
+				F5B79837035F64F80136A8DD /* NJRTableView.h */,
+				F5B79836035F64F80136A8DD /* NJRTableView.m */,
+				F552A8AB0358E8B201AEEDB1 /* NJRScrollView.h */,
+				F552A8AC0358E8B201AEEDB1 /* NJRScrollView.m */,
+				E1B46FE804057E3400050002 /* NJRSplitView.h */,
+				E1B46FE904057E3400050002 /* NJRSplitView.m */,
+				F560E3150357895001A4E466 /* NJRHistoryTrackingComboBox.h */,
+				F560E3160357895001A4E466 /* NJRHistoryTrackingComboBox.m */,
+				E1F993C10436280200050002 /* NJRHotKeyField.h */,
+				E1F993C20436280200050002 /* NJRHotKeyField.m */,
+				E14DEC820438969400050002 /* NJRHotKeyFieldCell.h */,
+				E14DEC830438969400050002 /* NJRHotKeyFieldCell.m */,
+				E13919E203B9E4E100000028 /* NJRIntervalField.h */,
+				E13919E303B9E4E100000028 /* NJRIntervalField.m */,
+				F5F240B6036A862101FE7503 /* NJRFSObjectSelector.h */,
+				F5F240B5036A862101FE7503 /* NJRFSObjectSelector.m */,
+				F5F240CD036A877301FE7503 /* NJRQTMediaPopUpButton.h */,
+				F5F240CC036A877301FE7503 /* NJRQTMediaPopUpButton.m */,
+				E15044650CFCDC7E00DE47E9 /* NJRValidatingField.h */,
+				E15044660CFCDC7E00DE47E9 /* NJRValidatingField.m */,
+				F5F19871036B822E01EB0372 /* NJRVoicePopUpButton.h */,
+				F5F19870036B822E01EB0372 /* NJRVoicePopUpButton.m */,
+			);
+			name = Views;
+			sourceTree = "<group>";
+		};
+		F59E47A6036AA5FC016B311C /* BDAlias */ = {
+			isa = PBXGroup;
+			children = (
+				F5F240B9036A867901FE7503 /* BDAlias.h */,
+				F5F240CA036A869401FE7503 /* BDAlias.m */,
+			);
+			name = BDAlias;
+			sourceTree = "<group>";
+		};
+		F59E47AF036AAC31016B311C /* Alerts */ = {
+			isa = PBXGroup;
+			children = (
+				E1A18CAB03B4344C00000028 /* PSAlerts.h */,
+				E1A18CAC03B4344C00000028 /* PSAlerts.m */,
+				F59E47A8036AAA6D016B311C /* PSAlert.h */,
+				F59E47A7036AAA6D016B311C /* PSAlert.m */,
+				F59E47B9036AB217016B311C /* PSDockBounceAlert.h */,
+				F59E47B8036AB217016B311C /* PSDockBounceAlert.m */,
+				F59E4BE7036B2E5E016B311C /* PSScriptAlert.h */,
+				F59E4BE6036B2E5E016B311C /* PSScriptAlert.m */,
+				F59E47B1036AACD1016B311C /* PSNotifierAlert.h */,
+				F59E47B0036AACD1016B311C /* PSNotifierAlert.m */,
+				E18DA4080442C56D00050002 /* PSMediaAlert.h */,
+				E18DA4090442C56D00050002 /* PSMediaAlert.m */,
+				F59E47AC036AAC1E016B311C /* PSBeepAlert.h */,
+				F59E47AB036AAC1E016B311C /* PSBeepAlert.m */,
+				F59E47BD036AB228016B311C /* PSMovieAlert.h */,
+				F59E47BC036AB228016B311C /* PSMovieAlert.m */,
+				F5F1986D036B813101EB0372 /* PSSpeechAlert.h */,
+				F5F1986C036B813101EB0372 /* PSSpeechAlert.m */,
+				E19FD39E03C98645001509F3 /* PSWakeAlert.h */,
+				E19FD39F03C98645001509F3 /* PSWakeAlert.m */,
+			);
+			name = Alerts;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		E1B11EE30CF5F30F004975AA /* Pester */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = E1B11F870CF5F30F004975AA /* Build configuration list for PBXNativeTarget "Pester" */;
+			buildPhases = (
+				E1B11F3F0CF5F30F004975AA /* Sources */,
+				E1B11F7E0CF5F30F004975AA /* Frameworks */,
+				E1B11F260CF5F30F004975AA /* Resources */,
+				E17CA9A80CF7F89900018B34 /* Copy Perl Modules */,
+				E17562730CF8C55D00FC5057 /* Copy Sparkle.framework */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Pester;
+			productInstallPath = /;
+			productName = Pester;
+			productReference = E1B11F8C0CF5F30F004975AA /* Pester.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		29B97313FDCFA39411CA2CEA /* Project object */ = {
+			isa = PBXProject;
+			buildConfigurationList = E1B11EDA0CF5F2EB004975AA /* Build configuration list for PBXProject "Pester" */;
+			compatibilityVersion = "Xcode 2.4";
+			hasScannedForEncodings = 1;
+			mainGroup = 29B97314FDCFA39411CA2CEA /* Pester */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				E1B11EE30CF5F30F004975AA /* Pester */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		E1B11F260CF5F30F004975AA /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E1B11F270CF5F30F004975AA /* Notifier.nib in Resources */,
+				E1B11F280CF5F30F004975AA /* Application icon.icns in Resources */,
+				E1B11F290CF5F30F004975AA /* Alarms.nib in Resources */,
+				E1B11F2A0CF5F30F004975AA /* MainMenu.nib in Resources */,
+				E1B11F2B0CF5F30F004975AA /* InfoPlist.strings in Resources */,
+				E1B11F2C0CF5F30F004975AA /* Credits.html in Resources */,
+				E1B11F2D0CF5F30F004975AA /* Read Me.rtfd in Resources */,
+				E1B11F2E0CF5F30F004975AA /* Movie alert.nib in Resources */,
+				E1B11F2F0CF5F30F004975AA /* Popup triangle.tiff in Resources */,
+				E1B11F300CF5F30F004975AA /* Calendar.nib in Resources */,
+				E1B11F310CF5F30F004975AA /* Calendar.tiff in Resources */,
+				E1B11F320CF5F30F004975AA /* OARightArrowPressed.tiff in Resources */,
+				E1B11F330CF5F30F004975AA /* OALeftArrow.tiff in Resources */,
+				E1B11F340CF5F30F004975AA /* OARightArrow.tiff in Resources */,
+				E1B11F350CF5F30F004975AA /* OALeftArrowPressed.tiff in Resources */,
+				E1B11F360CF5F30F004975AA /* Snooze until.nib in Resources */,
+				E1B11F370CF5F30F004975AA /* Read Me.nib in Resources */,
+				E1B11F380CF5F30F004975AA /* Localizable.strings in Resources */,
+				E1B11F390CF5F30F004975AA /* Preferences.nib in Resources */,
+				E1B11F3A0CF5F30F004975AA /* Volume 2.png in Resources */,
+				E1B11F3B0CF5F30F004975AA /* Volume 0.png in Resources */,
+				E1B11F3C0CF5F30F004975AA /* Volume 3.png in Resources */,
+				E1B11F3D0CF5F30F004975AA /* Volume 1.png in Resources */,
+				E1B11F3E0CF5F30F004975AA /* Volume.nib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		E1B11F3F0CF5F30F004975AA /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				E1B11F400CF5F30F004975AA /* BDAlias.m in Sources */,
+				E1B11F410CF5F30F004975AA /* NJRDateFormatter.m in Sources */,
+				E1B11F420CF5F30F004975AA /* NJRFSObjectSelector.m in Sources */,
+				E1B11F430CF5F30F004975AA /* NJRHistoryTrackingComboBox.m in Sources */,
+				E1B11F440CF5F30F004975AA /* NJRIntegerFilter.m in Sources */,
+				E1B11F450CF5F30F004975AA /* NJRQTMediaPopUpButton.m in Sources */,
+				E1B11F460CF5F30F004975AA /* NJRScrollView.m in Sources */,
+				E1B11F470CF5F30F004975AA /* NJRTableDelegate.m in Sources */,
+				E1B11F480CF5F30F004975AA /* NJRTableView.m in Sources */,
+				E1B11F490CF5F30F004975AA /* NJRUnfocusableMovieView.m in Sources */,
+				E1B11F4A0CF5F30F004975AA /* NJRVoicePopUpButton.m in Sources */,
+				E1B11F4B0CF5F30F004975AA /* NSAttributedString-NJRExtensions.m in Sources */,
+				E1B11F4C0CF5F30F004975AA /* NSCalendarDate-NJRExtensions.m in Sources */,
+				E1B11F4D0CF5F30F004975AA /* NSCharacterSet-NJRExtensions.m in Sources */,
+				E1B11F4E0CF5F30F004975AA /* NSDictionary-NJRExtensions.m in Sources */,
+				E1B11F500CF5F30F004975AA /* NSMovie-NJRExtensions.m in Sources */,
+				E1B11F510CF5F30F004975AA /* NSString-NJRExtensions.m in Sources */,
+				E1B11F520CF5F30F004975AA /* NSTableView-NJRExtensions.m in Sources */,
+				E1B11F530CF5F30F004975AA /* PSAlarm.m in Sources */,
+				E1B11F540CF5F30F004975AA /* PSAlarmAlertController.m in Sources */,
+				E1B11F550CF5F30F004975AA /* PSAlarmNotifierController.m in Sources */,
+				E1B11F560CF5F30F004975AA /* PSAlarmSetController.m in Sources */,
+				E1B11F570CF5F30F004975AA /* PSAlarms.m in Sources */,
+				E1B11F580CF5F30F004975AA /* PSAlarmsController.m in Sources */,
+				E1B11F590CF5F30F004975AA /* PSAlert.m in Sources */,
+				E1B11F5A0CF5F30F004975AA /* PSAlerts.m in Sources */,
+				E1B11F5B0CF5F30F004975AA /* PSApplication.m in Sources */,
+				E1B11F5C0CF5F30F004975AA /* PSBeepAlert.m in Sources */,
+				E1B11F5D0CF5F30F004975AA /* PSDockBounceAlert.m in Sources */,
+				E1B11F5E0CF5F30F004975AA /* PSMovieAlert.m in Sources */,
+				E1B11F5F0CF5F30F004975AA /* PSMovieAlertController.m in Sources */,
+				E1B11F600CF5F30F004975AA /* PSNotifierAlert.m in Sources */,
+				E1B11F610CF5F30F004975AA /* PSPowerManager.m in Sources */,
+				E1B11F620CF5F30F004975AA /* PSScriptAlert.m in Sources */,
+				E1B11F630CF5F30F004975AA /* PSSpeechAlert.m in Sources */,
+				E1B11F640CF5F30F004975AA /* main.m in Sources */,
+				E1B11F650CF5F30F004975AA /* NJRIntervalField.m in Sources */,
+				E1B11F660CF5F30F004975AA /* PSError.m in Sources */,
+				E1B11F670CF5F30F004975AA /* NJRCenteringMovieView.m in Sources */,
+				E1B11F690CF5F30F004975AA /* PSTimer.m in Sources */,
+				E1B11F6A0CF5F30F004975AA /* PSWakeAlert.m in Sources */,
+				E1B11F6B0CF5F30F004975AA /* PSCalendarController.m in Sources */,
+				E1B11F6C0CF5F30F004975AA /* OACalendarView.m in Sources */,
+				E1B11F6D0CF5F30F004975AA /* NSImage-OAExtensions.m in Sources */,
+				E1B11F6E0CF5F30F004975AA /* NSCalendarDate-OFExtensions.m in Sources */,
+				E1B11F6F0CF5F30F004975AA /* NJROffCenterPanel.m in Sources */,
+				E1B11F700CF5F30F004975AA /* PSSnoozeUntilController.m in Sources */,
+				E1B11F710CF5F30F004975AA /* PSTimeDateEditor.m in Sources */,
+				E1B11F720CF5F30F004975AA /* NJRReadMeController.m in Sources */,
+				E1B11F730CF5F30F004975AA /* NJRSplitView.m in Sources */,
+				E1B11F740CF5F30F004975AA /* NJRHotKeyField.m in Sources */,
+				E1B11F750CF5F30F004975AA /* PSPreferencesController.m in Sources */,
+				E1B11F760CF5F30F004975AA /* NSFont-NJRExtensions.m in Sources */,
+				E1B11F770CF5F30F004975AA /* NJRHotKeyFieldCell.m in Sources */,
+				E1B11F780CF5F30F004975AA /* NJRHotKey.m in Sources */,
+				E1B11F790CF5F30F004975AA /* NJRHotKeyManager.m in Sources */,
+				E1B11F7A0CF5F30F004975AA /* PSVolumeController.m in Sources */,
+				E1B11F7B0CF5F30F004975AA /* PSMediaAlert.m in Sources */,
+				E1B11F7C0CF5F30F004975AA /* NJRSoundManager.m in Sources */,
+				E1B11F7D0CF5F30F004975AA /* NJRNonCenteringWindow.m in Sources */,
+				E1B121970CF62FD1004975AA /* NSMenuItem-NJRExtensions.m in Sources */,
+				E1B1219E0CF63858004975AA /* NSImage-NJRExtensions.m in Sources */,
+				E15044670CFCDC7E00DE47E9 /* NJRValidatingField.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				089C165DFE840E0CC02AAC07 /* English */,
+			);
+			name = InfoPlist.strings;
+			sourceTree = "<group>";
+		};
+		29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				29B97319FDCFA39411CA2CEA /* English */,
+			);
+			name = MainMenu.nib;
+			sourceTree = "<group>";
+		};
+		E10E71F3040C335800050002 /* Localizable.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				E10E71F4040C335800050002 /* English */,
+			);
+			name = Localizable.strings;
+			sourceTree = "<group>";
+		};
+		E10F2A3B0402E61C00050002 /* Read Me.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				E10F2A3C0402E61C00050002 /* English */,
+			);
+			name = "Read Me.nib";
+			sourceTree = "<group>";
+		};
+		E11CB6E0043025F500050002 /* Preferences.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				E11CB6E1043025F500050002 /* English */,
+			);
+			name = Preferences.nib;
+			sourceTree = "<group>";
+		};
+		E12790090442A5B100050002 /* Volume.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				E127900A0442A5B100050002 /* Volume.nib */,
+			);
+			name = Volume.nib;
+			sourceTree = "<group>";
+		};
+		E17F2F4D03FDFB3100050002 /* Calendar.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				E17F2F4E03FDFB3100050002 /* English */,
+			);
+			name = Calendar.nib;
+			sourceTree = "<group>";
+		};
+		E1DEF10603FF83AC00050002 /* Snooze until.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				E1DEF10703FF83AC00050002 /* English */,
+			);
+			name = "Snooze until.nib";
+			sourceTree = "<group>";
+		};
+		F560E2EE03574F8A01A4E466 /* Alarms.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				F560E2EF03574F8A01A4E466 /* English */,
+			);
+			name = Alarms.nib;
+			sourceTree = "<group>";
+		};
+		F59DC6DF0353DF5E01AEEDB1 /* Notifier.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				F59DC6E00353DF5E01AEEDB1 /* English */,
+			);
+			name = Notifier.nib;
+			sourceTree = "<group>";
+		};
+		F59DD44F0358ABF4017318D0 /* Credits.html */ = {
+			isa = PBXVariantGroup;
+			children = (
+				F59DD4500358ABF4017318D0 /* English */,
+			);
+			name = Credits.html;
+			sourceTree = "<group>";
+		};
+		F59E4BDE036B1D94016B311C /* Movie alert.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				F59E4BDF036B1D94016B311C /* English */,
+			);
+			name = "Movie alert.nib";
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		E1B11ED40CF5F2EB004975AA /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_C_LANGUAGE_STANDARD = c99;
+				HEADER_SEARCH_PATHS = "/System/Library/Perl/5.8.8/darwin-thread-multi-2level/CORE";
+				LIBRARY_SEARCH_PATHS = "/System/Library/Perl/5.8.8/darwin-thread-multi-2level/CORE";
+				OTHER_LDFLAGS = "-lperl";
+				SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
+			};
+			name = Debug;
+		};
+		E1B11ED50CF5F2EB004975AA /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = (
+					i386,
+					ppc,
+				);
+				GCC_C_LANGUAGE_STANDARD = c99;
+				HEADER_SEARCH_PATHS = "/System/Library/Perl/5.8.8/darwin-thread-multi-2level/CORE";
+				LIBRARY_SEARCH_PATHS = "/System/Library/Perl/5.8.8/darwin-thread-multi-2level/CORE";
+				OTHER_LDFLAGS = "-lperl";
+				SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
+				STRIP_INSTALLED_PRODUCT = NO;
+			};
+			name = Release;
+		};
+		E1B11F880CF5F30F004975AA /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 22;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
+					"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_2)",
+				);
+				FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks\"";
+				FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_2 = "\"$(SRCROOT)/Sparkle/build/Release\"";
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_ENABLE_FIX_AND_CONTINUE = YES;
+				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = Pester_Prefix.h;
+				GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_USE_GCC3_PFE_SUPPORT = YES;
+				INFOPLIST_FILE = "Info-Pester.plist";
+				INSTALL_PATH = /;
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
+				);
+				LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SYSTEM_LIBRARY_DIR)/Perl/5.8.8/darwin-thread-multi-2level/auto/DynaLoader\"";
+				PRODUCT_NAME = Pester;
+				PRODUCT_VERSION = 1.1a4;
+				VERSIONING_SYSTEM = "apple-generic";
+				WARNING_CFLAGS = (
+					"-Wmost",
+					"-Wno-four-char-constants",
+					"-Wno-unknown-pragmas",
+				);
+			};
+			name = Debug;
+		};
+		E1B11F890CF5F30F004975AA /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				COPY_PHASE_STRIP = YES;
+				CURRENT_PROJECT_VERSION = 22;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
+					"$(FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_2)",
+				);
+				FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks\"";
+				FRAMEWORK_SEARCH_PATHS_QUOTED_FOR_TARGET_2 = "\"$(SRCROOT)/Sparkle/build/Release\"";
+				GCC_ENABLE_FIX_AND_CONTINUE = NO;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = Pester_Prefix.h;
+				GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_USE_GCC3_PFE_SUPPORT = YES;
+				INFOPLIST_FILE = "Info-Pester.plist";
+				INSTALL_PATH = /;
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)",
+				);
+				LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SYSTEM_LIBRARY_DIR)/Perl/5.8.8/darwin-thread-multi-2level/auto/DynaLoader\"";
+				PRODUCT_NAME = Pester;
+				PRODUCT_VERSION = 1.1a4;
+				VERSIONING_SYSTEM = "apple-generic";
+				WARNING_CFLAGS = (
+					"-Wmost",
+					"-Wno-four-char-constants",
+					"-Wno-unknown-pragmas",
+				);
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		E1B11EDA0CF5F2EB004975AA /* Build configuration list for PBXProject "Pester" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E1B11ED40CF5F2EB004975AA /* Debug */,
+				E1B11ED50CF5F2EB004975AA /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Debug;
+		};
+		E1B11F870CF5F30F004975AA /* Build configuration list for PBXNativeTarget "Pester" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E1B11F880CF5F30F004975AA /* Debug */,
+				E1B11F890CF5F30F004975AA /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Debug;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
+}
Index: releases/Pester/1.1b6/Source/Pester_Prefix.h
===================================================================
--- releases/Pester/1.1b6/Source/Pester_Prefix.h	(revision 365)
+++ releases/Pester/1.1b6/Source/Pester_Prefix.h	(revision 365)
@@ -0,0 +1,7 @@
+//
+// Prefix header for all source files of the 'Pester' target in the 'Pester' project
+//
+
+#ifdef __OBJC__
+    #import <Cocoa/Cocoa.h>
+#endif
Index: releases/Pester/1.1b6/Source/main.m
===================================================================
--- releases/Pester/1.1b6/Source/main.m	(revision 365)
+++ releases/Pester/1.1b6/Source/main.m	(revision 365)
@@ -0,0 +1,14 @@
+//
+//  main.m
+//  Pester
+//
+//  Created by Nicholas Riley on Tue Oct 08 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char *argv[])
+{
+    return NSApplicationMain(argc, argv);
+}
Index: releases/Pester/1.1b6/Updates/release-notes.css
===================================================================
--- releases/Pester/1.1b6/Updates/release-notes.css	(revision 365)
+++ releases/Pester/1.1b6/Updates/release-notes.css	(revision 365)
@@ -0,0 +1,129 @@
+body  {
+	margin: 12px;
+}
+
+table {
+	border: dotted 1px #ccc;
+	border-collapse: collapse;
+	width: 100%;
+}
+
+h1, h2, h3, th {
+	font-family: "Gill Sans", "Lucida Grande", sans-serif;
+}
+
+th {
+	background-color: #e6edff;
+	text-align: left;
+	padding-left: 6px;
+	padding-top: 2px;
+	padding-bottom: 2px
+}
+
+tr {
+	vertical-align: top;
+}
+
+td.icon {
+	width: 150px;
+	height: 128px;
+	background-position: top center;
+	background-repeat: no-repeat;
+	background-image: url('Application icon.png');
+	background-attachment: scroll;
+}
+
+h3, th {
+	font-size: 11pt;
+	font-weight: bold;
+	}
+	
+h3 {
+	margin-top: -4px;
+	margin-bottom: -4px
+}
+
+p, td, ol {
+	font-family: "Hoefler Text", serif;
+	font-size: 14px;
+	line-height: 16px;
+}
+
+p {
+	text-decoration: none
+	}
+
+
+ol {
+	list-style-position: outside;
+	margin-top: 12px;
+	margin-bottom: 12px;
+	margin-left: -18px;
+	padding-left: 40px
+	}
+
+
+ol li {
+	margin-top: 6px;
+	margin-bottom: 6px
+	}
+
+
+ol p {
+	margin-top: 6px;
+	margin-bottom: 6px
+	}
+
+
+ul {
+	font-size: 9pt;
+	font-family: "Lucida Grande", Arial, sans-serif;
+	line-height: 12pt;
+	list-style-type: square;
+	list-style-position: outside;
+	margin-top: 12px;
+	margin-bottom: 12px;
+	margin-left: -24px;
+	padding-left: 40px
+	}
+
+
+ul li {
+	margin-top: 6px;
+	margin-bottom: 6px
+	}
+
+
+ul p {
+	margin-top: 6px;
+	margin-bottom: 6px
+	}
+
+
+a {
+	color: #00f;
+	font-size: 9pt;
+	font-family: "Lucida Grande", Arial, sans-serif;
+	line-height: 12pt;
+	text-decoration: none
+	}
+
+
+a:hover {
+	color: #00f;
+	font-family: "Lucida Grande", Arial, sans-serif;
+	text-decoration: underline
+	}
+
+hr {
+	text-decoration: none;
+	border: solid 1px #bfbfbf
+	}
+
+td {
+	padding: 6px
+	}
+
+.mult  {
+	margin-top: -8px
+	}
Index: releases/Pester/1.1b6/Updates/release-notes.html
===================================================================
--- releases/Pester/1.1b6/Updates/release-notes.html	(revision 365)
+++ releases/Pester/1.1b6/Updates/release-notes.html	(revision 365)
@@ -0,0 +1,26 @@
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+	<title>What's new in Pester</title>
+	<meta name="robots" content="anchors">
+	<link href="release-notes.css" type="text/css" rel="stylesheet" media="all">
+</head>
+
+<body>
+<table>
+	<tr><th colspan="2">Pester 1.1 beta 6 (22)</th></tr>
+	<tr><td class="icon"></td>
+		<td>
+			<ul>
+			    <li>Repeating alarms continue to repeat if they didn’t expire while Pester was not running.</li>
+			    <li>(Re-)added speech alerts.</li>
+			    <li>Alert help tags in the alarm list appear correctly in Leopard.</li>
+			    <li>Changes to the field you’re editing are saved when switching between “in” and “at”.</li>
+			    <li>Corrected validation of the alert repetition field which allowed you to specify zero (blank) or over 99 repetitions.</li>
+			    <li>Pester 1.0 alarms no longer create duplicates of themselves on import.</li>
+			</ul>
+		</td>
+	</tr>
+</table>
+</body>
+</html>
Index: releases/Pester/1.1b6/Updates/updates.xml
===================================================================
--- releases/Pester/1.1b6/Updates/updates.xml	(revision 365)
+++ releases/Pester/1.1b6/Updates/updates.xml	(revision 365)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
+  <channel>
+    <title>Pester</title>
+    <link>http://web.sabi.net/nriley/software/#pester</link>
+    <description>Appcast for Pester updates.</description>
+    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
+    <language>en</language>
+    <pubDate>Tue, 27 Nov 2007 18:26:26 -0600</pubDate>
+    <lastBuildDate>Tue, 27 Nov 2007 18:26:26 -0600</lastBuildDate>
+    
+    <atom:link href="http://web.sabi.net/nriley/software/Pester/updates.xml" rel="self" type="application/rss+xml"/>
+    <item>
+      <title>Pester 1.1b6</title>
+      <link>http://web.sabi.net/nriley/software/#pester</link>
+      <author>pester@sabi.net (Nicholas Riley)</author>
+      <pubDate>Tue, 27 Nov 2007 18:26:26 -0600</pubDate>
+      <enclosure url="http://web.sabi.net/nriley/software/Pester-1.1b6.dmg" length="746933" type="application/x-apple-diskimage" sparkle:version="22" sparkle:shortVersionString="1.1b6" sparkle:dsaSignature="MC4CFQCKd5osaqHiFoGzDDTAHtBy+fHabgIVAJ9odNFRknU2/Czd3hVHu/htpvEu"/>
+      <guid isPermaLink="false">pester-11b6</guid>
+      <sparkle:releaseNotesLink>http://web.sabi.net/nriley/software/Pester/release-notes.html</sparkle:releaseNotesLink>
+    </item>
+    <item>
+      <title>Pester 1.1b5</title>
+      <link>http://web.sabi.net/nriley/software/#pester</link>
+      <author>pester@sabi.net (Nicholas Riley)</author>
+      <pubDate>Sat, 24 Nov 2007 17:14:47 -0600</pubDate>
+      <enclosure url="http://web.sabi.net/nriley/software/Pester-1.1b5.dmg" length="749389" type="application/x-apple-diskimage" sparkle:version="21" sparkle:shortVersionString="1.1b5" sparkle:dsaSignature="MCwCFFkK4zRnPAKoKDEz3h8/Ra4KDZT6AhQryVERDhVrDE1Q014MLJv/HKVc5g=="/>
+      <guid isPermaLink="false">pester-11b5</guid>
+      <sparkle:releaseNotesLink>http://web.sabi.net/nriley/software/Pester/release-notes.html</sparkle:releaseNotesLink>
+    </item>
+  </channel>
+</rss>
Index: releases/Pester/1.1b6/package-Pester.sh
===================================================================
--- releases/Pester/1.1b6/package-Pester.sh	(revision 365)
+++ releases/Pester/1.1b6/package-Pester.sh	(revision 365)
@@ -0,0 +1,54 @@
+#!/bin/zsh -e
+
+# set -x -v
+
+PACKAGEDIR="$PWD"
+PRODUCT="Pester"
+
+# gather information
+cd "$PACKAGEDIR"/Source
+VERSION=`agvtool mvers -terse1`
+BUILD=`agvtool vers -terse`
+DMG="$PRODUCT-$VERSION.dmg" VOL="$PRODUCT $VERSION"
+DSTROOT="$PACKAGEDIR/$VOL"
+
+# clean and build
+sudo rm -rf "$DSTROOT"
+find . -name \*~ -exec rm '{}' \;
+rm -rf build/ Sparkle/build/
+cd Sparkle
+xcodebuild -target Sparkle -configuration Release
+cd ..
+xcodebuild -target Pester -configuration Release "DSTROOT=$DSTROOT" \
+    DEPLOYMENT_LOCATION=YES install
+rm -rf build/Release # or Xcode gets confused next time because of the symlink
+find "$DSTROOT" \( -name ".svn" -or -name "Headers" \) \
+    -exec sudo /bin/rm -rf "{}" \; || true
+
+# create disk image
+cd "$PACKAGEDIR"
+rm -f $DMG
+hdiutil create $DMG -megabytes 5 -ov -layout NONE -fs 'HFS+' -volname $VOL
+MOUNT=`hdiutil attach $DMG`
+DISK=`echo $MOUNT | sed -ne ' s|^/dev/\([^ ]*\).*$|\1|p'`
+MOUNTPOINT=`echo $MOUNT | sed -ne 's|^.*\(/Volumes/.*\)$|\1|p'`
+ditto -rsrc "$DSTROOT" "$MOUNTPOINT"
+chmod -R a+rX,u+w "$MOUNTPOINT"
+hdiutil detach $DISK
+hdiutil resize -sectors min $DMG
+hdiutil convert $DMG -format UDBZ -o z$DMG
+mv z$DMG $DMG
+hdiutil internet-enable $DMG
+zmodload zsh/stat
+SIZE=$(stat -L +size $DMG)
+
+# update Web presence
+DIGEST=`openssl dgst -sha1 -binary < $DMG | openssl dgst -dss1 -sign ~/Documents/Development/DSA/dsa_priv.pem | openssl enc -base64`
+perl -pi -e 's|(<enclosure url=".+'$DMG'").+/>|\1 length="'$SIZE'" type="application/x-apple-diskimage" sparkle:version="'$BUILD'" sparkle:shortVersionString="'$VERSION'" sparkle:dsaSignature="'$DIGEST'"/>|' Updates/updates.xml
+scp $DMG ainaz:web/nriley/software/$DMG.new
+ssh ainaz chmod go+r web/nriley/software/$DMG.new
+ssh ainaz mv web/nriley/software/$DMG{.new,}
+rsync -avz --exclude='.*' Updates/ ainaz:web/nriley/software/$PRODUCT/
+# cd "$PACKAGEDIR"/Source
+# agvtool bump -all
+
