Index: /trunk/Cocoa/DockCam/DockCam.h
===================================================================
--- /trunk/Cocoa/DockCam/DockCam.h	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCam.h	(revision 5)
@@ -0,0 +1,40 @@
+//
+//  DockCam.h
+//  DockCam
+//
+//  Created by Nicholas Riley on Wed Jun 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@class DockCamStatus;
+@class DockCamPrefs;
+@class NJRVendingImageView;
+@class CURLHandle;
+
+@interface DockCam : NSObject <NSURLHandleClient> {
+    NSMutableData *data;
+    NSImage *image;
+    NSSize imageSize, previousImageSize;
+    NSURL *location;
+    CURLHandle *imageHandle;
+    NSTimer *delayTimer;
+    NSTimeInterval timeLeft;
+    DockCamPrefs *preferencesController;
+    IBOutlet DockCamStatus *status;
+    IBOutlet NSWindow *imageWindow;
+    IBOutlet NJRVendingImageView *imageView;
+    IBOutlet NSMenuItem *pauseResumeItem; // workaround for dock sender bug in 10.1
+}
+
+- (void)scheduleLoading;
+- (IBAction)reloadImage:(id)sender;
+- (IBAction)cancelLoading:(id)sender;
+
+- (IBAction)pauseTimer:(NSMenuItem *)sender;
+- (IBAction)resumeTimer:(NSMenuItem *)sender;
+
+- (IBAction)orderFrontPreferencesPanel:(id)sender;
+
+@end
Index: /trunk/Cocoa/DockCam/DockCam.m
===================================================================
--- /trunk/Cocoa/DockCam/DockCam.m	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCam.m	(revision 5)
@@ -0,0 +1,373 @@
+//
+//  DockCam.m
+//  DockCam
+//
+//  Created by Nicholas Riley on Wed Jun 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+/*
+ Done in 1.0b1:
+    Add refresh command
+    Add pause command
+    Add configuration interface
+    Add show/hide status window
+    Add UI element validation: cancel/load
+    Dock menu
+    Add drag&drop / copy-paste
+    Fix window resizing to do it from the top left
+    Fix combo box items not saving
+    Fix status palette not saving position
+ Done in 1.0b2:
+    Fix missing image problem with autosave (Fix/report bug with NaN)
+    Fix window resizing to happen only when underlying image changes size
+    Hand cursor for dragging (modulo Cocoa bug)
+    Fix every other time you open Preferences, the focus changes
+ Done in 1.0b3:
+    Switch to CURLHandle
+ Done in 1.0b4:
+    Lazily create (NS|C)URLHandle
+    Fix return in combo box not triggering default button
+    Misc. bug fixes
+ To do:
+    Fix memory leak in libcurl/CURLHandle (emailed Dan Wood 2002.06.30)
+    Report bug with aspect ratio being useless
+    Report bug (and post on weblog) with dragging images: if it's the same image as in an image view, then it draws at the top left of the screen briefly
+
+*/
+
+// debugging: change 1 to 0 to disable features
+#define DOCKCAM_DRAW_IMAGE 1
+#define DOCKCAM_CREATE_IMAGE 1
+#define DOCKCAM_DISPLAY_STATUS 1
+#define DOCKCAM_ACCEPT_DATA 1
+#define DOCKCAM_LOAD_DATA 1
+
+#import <CURLHandle/CURLHandle.h>
+#import <CURLHandle/CURLHandle+extras.h>
+
+#import "DockCam.h"
+#import "DockCamStatus.h"
+#import "DockCamPrefs.h"
+#import "NJRVendingImageView.h"
+#import "NJRWindowMagnetism.h"
+
+BOOL successfulLoad;
+
+@implementation DockCam
+
+#pragma mark interface validation
+
+- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem;
+{
+    SEL action = [anItem action];
+    BOOL loadInProgress = ([imageHandle status] == NSURLHandleLoadInProgress);
+    if (action == @selector(reloadImage:)) return !loadInProgress;
+    else if (action == @selector(cancelLoading:)) return loadInProgress;
+    else if (action == @selector(pauseTimer:) || action == @selector(resumeTimer:)) return delayTimer != nil;
+    return YES;
+}
+
+#pragma mark timers
+
+- (void)loadImage:(NSTimer *)timer;
+{
+    static NSAutoreleasePool *pool = nil;
+
+    NSLog(@"loading...");
+    delayTimer = nil;
+#if DOCKCAM_ACCEPT_DATA
+    [data release]; data = nil;
+#endif
+    pool = [[NSAutoreleasePool alloc] init];
+#if DOCKCAM_ACCEPT_DATA
+    data = [[NSMutableData alloc] init];
+#endif
+    [status setStatus: @"Retrieving imageÉ"];
+#if DOCKCAM_LOAD_DATA
+    [imageHandle loadInBackground]; // retains imageHandle until load is complete
+#else
+    // fake completion of loading
+    [self URLHandleResourceDidFinishLoading: imageHandle];
+#endif
+    [pool release]; pool = nil;
+}
+
+- (void)scheduleLoadingWithInterval:(NSTimeInterval)interval;
+{
+    delayTimer = [NSTimer scheduledTimerWithTimeInterval: interval
+                                                  target: self selector: @selector(loadImage:) userInfo: nil
+                                                 repeats: NO];
+}
+
+- (void)scheduleLoading;
+{
+    [delayTimer invalidate]; delayTimer = nil;
+    if ([DockCamPrefs boolForPref: DCImageRefreshEnabled]) {
+        if ([imageHandle status] == NSURLHandleLoadInProgress) return;
+        [self scheduleLoadingWithInterval: [DockCamPrefs intForPref: DCImageRefreshInterval]];
+    }
+}
+
+#pragma mark actions
+
+- (IBAction)reloadImage:(id)sender;
+{
+    if ([imageHandle status] == NSURLHandleLoadInProgress) return;
+    [delayTimer invalidate]; delayTimer = nil;
+    [self loadImage: nil];
+}
+
+- (IBAction)cancelLoading:(id)sender;
+{
+    if ([imageHandle status] != NSURLHandleLoadInProgress) return;
+    [imageHandle cancelLoadInBackground];
+}
+
+- (IBAction)pauseTimer:(NSMenuItem *)sender;
+{
+    timeLeft = [[delayTimer fireDate] timeIntervalSinceDate: [NSDate date]];
+    [delayTimer invalidate];
+    [status setStatus: [NSString stringWithFormat: @"Timer paused (%.0f %@)", timeLeft,
+          round(timeLeft) == 1 ? @"second remains" : @"seconds remain"]];
+    [imageWindow setTitle: @"DockCam (paused)"];
+    // XXX pauseResumeItem would be 'sender' if dock menu senders weren't all NSApp
+    [pauseResumeItem setAction: @selector(resumeTimer:)];
+    [pauseResumeItem setTitle: @"Resume Timer"];
+}
+
+- (IBAction)resumeTimer:(NSMenuItem *)sender;
+{
+    // XXX pauseResumeItem would be 'sender' if dock menu senders weren't all NSApp
+    [self scheduleLoadingWithInterval: timeLeft];
+    timeLeft = 0;
+    [status setStatus: @"Timer resumed"];
+    [imageWindow setTitle: @"DockCam"];
+    [pauseResumeItem setAction: @selector(pauseTimer:)];
+    [pauseResumeItem setTitle: @"Pause Timer"];
+}
+
+#pragma mark preference changes
+
+- (void)locationChanged:(NSNotification *)notification;
+{
+    NSString *locationString = [DockCamPrefs stringForPref: DCImageLocation];
+    [self cancelLoading: nil];
+    [location release]; location = nil;
+    if (locationString == nil || [locationString isEqualToString: @""]) {
+        [status setStatus: @"No image location specified in Preferences"];
+        return;
+    }
+    locationString = (NSString *)CFURLCreateStringByAddingPercentEscapes(
+        NULL, (CFStringRef) locationString, NULL, NULL,
+        CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
+    location = [[NSURL URLWithString: locationString] retain];
+    [locationString release];
+    [status setLocation: location];
+    [imageHandle release]; imageHandle = nil;
+    imageHandle = (CURLHandle *)[location URLHandleUsingCache: NO];
+    if (imageHandle != nil) {
+        [imageHandle retain];
+        [imageHandle addClient: self];
+        NSLog(@"handle is %@", imageHandle);
+        [self loadImage: nil];
+    } else {
+        // permanent failure, explicitly do NOT restart timer here
+        [status setFailedWithReason: @"Image location (URL) not found, check Preferences"];
+    }
+}
+
+- (void)intervalChanged:(NSNotification *)notification;
+{
+    [self scheduleLoading];
+}
+
+- (void)showInBackgroundChanged:(NSNotification *)notification;
+{
+    [imageWindow setHidesOnDeactivate: ![DockCamPrefs boolForPref: DCShowImageInBackground]];
+}
+
+#pragma mark startup
+
+- (void)startUp;
+{
+    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+    [status setStatus: @"Starting upÉ"];
+    previousImageSize = [DockCamPrefs sizeForPref: DCImageSize];
+    [notificationCenter addObserver: self selector: @selector(locationChanged:)
+                               name: DCPrefChangedNotification
+                             object: DCImageLocation];
+    [notificationCenter addObserver: self selector: @selector(intervalChanged:)
+                               name: DCPrefChangedNotification
+                             object: DCImageRefreshEnabled];
+    [notificationCenter addObserver: self selector: @selector(intervalChanged:)
+                               name: DCPrefChangedNotification
+                             object: DCImageRefreshInterval];
+    [notificationCenter addObserver: self selector: @selector(showInBackgroundChanged:)
+                               name: DCPrefChangedNotification
+                             object: DCShowImageInBackground];
+    [self locationChanged: nil];
+    if (location == nil) [self orderFrontPreferencesPanel: self];
+    [self showInBackgroundChanged: nil];
+}
+
+- (void)awakeFromNib;
+{
+    NSArray *autosaveFrame = [[[NSUserDefaults standardUserDefaults] stringForKey: @"NSWindow Frame DockCam"] componentsSeparatedByString: @" "];
+    NSRect frame;
+#if !DOCKCAM_DISPLAY_STATUS
+    status = nil;
+#endif
+    // XXX workaround for bug in 10.1.5 (and earlier?): loading from autosave corrupts frame
+    if (autosaveFrame != nil && [autosaveFrame count] == 9) {
+        frame.origin.x = [[autosaveFrame objectAtIndex: 0] floatValue];
+        frame.origin.y = [[autosaveFrame objectAtIndex: 1] floatValue];
+        frame.size.width = [[autosaveFrame objectAtIndex: 2] floatValue];
+        frame.size.height = [[autosaveFrame objectAtIndex: 3] floatValue];
+        [imageWindow setFrame: frame display: NO];
+    }
+    [[NJRWindowMagnetism alloc] initForWindow: imageWindow];
+    [self performSelector: @selector(startUp) withObject: nil afterDelay: 0];
+}
+
+// XXX workaround for bug in 10.1.5 (and earlier?): loading from autosave corrupts frame
+- (void)windowDidMove:(NSNotification *)aNotification
+{
+    [imageWindow saveFrameUsingName: @"DockCam"];
+}
+
+- (void)windowDidResize:(NSNotification *)notification;
+{
+    [self windowDidMove: notification];
+}
+
+#pragma mark preferences
+
+- (IBAction)orderFrontPreferencesPanel:(id)sender;
+{
+    if (!preferencesController)  {
+        preferencesController = [[DockCamPrefs alloc] init];
+    }
+    [preferencesController showWindow:self];
+}
+
+#pragma mark URL loading
+
+- (void)URLHandle:(NSURLHandle *)sender resourceDataDidBecomeAvailable:(NSData *)newBytes;
+{
+#if DOCKCAM_ACCEPT_DATA
+    [data appendData: newBytes];
+#endif
+}
+
+- (void)URLHandleResourceDidBeginLoading:(NSURLHandle *)sender; {}
+
+- (void)URLHandleResourceDidFinishLoading:(NSURLHandle *)sender;
+{
+#if DOCKCAM_CREATE_IMAGE
+    NSImage *scaledImage = nil;
+    NSSize scaledSize;
+    unsigned int styleMask = [imageWindow styleMask];
+    NSRect contentRect = [NSWindow contentRectForFrameRect: [imageWindow frame] styleMask: styleMask];
+    NSRect frame;
+    image = [[NSImage alloc] initWithData: data];
+#endif
+    [data release]; data = nil;
+#if DOCKCAM_CREATE_IMAGE
+    if (image == nil) {
+        [status setFailedWithReason: @"Image format unrecognized"];
+        [self scheduleLoading];
+        return;
+    }
+    scaledSize = imageSize = [image size];
+#if DOCKCAM_DRAW_IMAGE
+    if (abs(imageSize.height - imageSize.width) < 1) scaledImage = [image retain];
+    else {
+        if (imageSize.height > imageSize.width) scaledSize.width = imageSize.height;
+        else scaledSize.height = imageSize.width;
+        scaledImage = [[NSImage alloc] initWithSize: scaledSize];
+        [scaledImage lockFocus];
+        [image compositeToPoint: NSMakePoint((scaledSize.width - imageSize.width) / 2.,
+                                             (scaledSize.height - imageSize.height) / 2.)
+                      operation: NSCompositeCopy];
+        [scaledImage unlockFocus];
+    }
+    [imageView setImage: nil]; // autoreleases image
+    contentRect.origin.y += contentRect.size.height - imageSize.height;
+    contentRect.size = imageSize;
+    frame = [NSWindow frameRectForContentRect: contentRect styleMask: styleMask];
+    if (!NSEqualSizes(imageSize, previousImageSize)) {
+        [imageWindow setFrame: frame display: NO];
+        previousImageSize = imageSize;
+        [DockCamPrefs setSize: imageSize forPref: DCImageSize];
+    }
+    [imageWindow setMaxSize: frame.size];
+    [imageView setImage: image];
+    [imageView setToolTip: [location description]];
+    [NSApp setApplicationIconImage: scaledImage];
+#endif /* DOCKCAM_DRAW_IMAGE */
+    [image release];
+    [scaledImage release];
+    [status setRetrievedWithSize: imageSize];
+#endif /* DOCKCAM_CREATE_IMAGE */
+    if (!successfulLoad || ![imageWindow isVisible]) {
+        [imageWindow makeKeyAndOrderFront: self];
+        successfulLoad = YES;
+    }
+    [self scheduleLoading];
+}
+
+- (void)URLHandleResourceDidCancelLoading:(NSURLHandle *)sender;
+{
+    [status setStatus: @"Image retrieval cancelled"];
+    [self scheduleLoading];
+}
+
+- (void)URLHandle:(NSURLHandle *)sender resourceDidFailLoadingWithReason:(NSString *)reason;
+{
+    [status setFailedWithReason: reason];
+    [self scheduleLoading];
+}
+
+@end
+
+@implementation DockCam (NSWindowDelegate)
+
+- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize;
+{
+    NSRect rect = {NSZeroPoint, proposedFrameSize};
+    NSSize contentSize = [NSWindow contentRectForFrameRect: rect styleMask: [sender styleMask]].size;
+    contentSize.height = contentSize.width * (imageSize.height / imageSize.width);
+    rect.size = contentSize;
+    return [NSWindow frameRectForContentRect: rect styleMask: [sender styleMask]].size;
+}
+
+- (IBAction)copy:(id)sender;
+{
+    [imageView putImageOnPasteboard: [NSPasteboard generalPasteboard]];
+}
+
+@end
+
+@implementation DockCam (NSApplicationNotifications)
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
+{
+    [CURLHandle curlHelloSignature:'XxXx' acceptAll:YES];
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification;
+{
+    [CURLHandle curlGoodbye];
+}
+
+@end
+
+@implementation DockCam (NSApplicationDelegate)
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender;
+{
+    return successfulLoad; // window hidden until first succesful load
+}
+
+@end
Index: /trunk/Cocoa/DockCam/DockCam.pbproj/nicholas.pbxuser
===================================================================
--- /trunk/Cocoa/DockCam/DockCam.pbproj/nicholas.pbxuser	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCam.pbproj/nicholas.pbxuser	(revision 5)
@@ -0,0 +1,392 @@
+// !$*UTF8*$!
+{
+	29B97313FDCFA39411CA2CEA = {
+		activeBuildStyle = 4A9504CCFFE6A4B311CA0CBA;
+		activeExecutable = F5C2947702C9824601AEEDB1;
+		activeTarget = 29B97326FDCFA39411CA2CEA;
+		addToTargets = (
+			29B97326FDCFA39411CA2CEA,
+		);
+		breakpoints = (
+			F5C2947E02C99BC301AEEDB1,
+			F5643F9702CEF87001AEEDB1,
+			F5EE2B5702CF16EE01AEEDB1,
+		);
+		executables = (
+			F5C2947702C9824601AEEDB1,
+		);
+		perUserDictionary = {
+			PBXPerProjectTemplateStateSaveDate = 56183221;
+			"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 = {
+							Debugger = {
+								Collapsed = NO;
+								Frame = "{{0, 0}, {484, 208}}";
+								Split0 = {
+									Frame = "{{0, 24}, {484, 184}}";
+									Split0 = {
+										Frame = "{{0, 0}, {236, 184}}";
+									};
+									Split1 = {
+										DebugVariablesTableConfiguration = (
+											Name,
+											123,
+											Value,
+											85,
+											Summary,
+											62.123,
+										);
+										Frame = "{{245, 0}, {239, 184}}";
+									};
+									SplitCount = 2;
+								};
+								SplitCount = 1;
+								Tab0 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								Tab1 = {
+									Frame = "{{0, 0}, {100, 50}}";
+								};
+								TabCount = 2;
+								TabsVisible = YES;
+							};
+							Frame = "{{0, 0}, {484, 208}}";
+							LauncherConfigVersion = 7;
+						};
+						Tab1 = {
+							Frame = "{{0, 0}, {664, 208}}";
+							LauncherConfigVersion = 3;
+							Runner = {
+								Frame = "{{0, 0}, {664, 208}}";
+							};
+						};
+						Tab2 = {
+							BuildMessageFrame = "{{0, 0}, {666, 43}}";
+							BuildTranscriptFrame = "{{0, 52}, {666, 0}}";
+							Frame = "{{0, 0}, {664, 50}}";
+						};
+						Tab3 = {
+							Frame = "{{0, 0}, {612, 295}}";
+						};
+						TabCount = 4;
+						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;
+				};
+				StatusViewVisible = NO;
+				Template = F5CA7ECB015C094F0DCA290F;
+				ToolbarVisible = NO;
+				WindowLocation = "{482, 114}";
+			};
+			PBXWorkspaceContents = (
+				{
+					LeftSlideOut = {
+						Split0 = {
+							Split0 = {
+								NavCount = 1;
+								NavGeometry0 = {
+									Frame = "{{0, 0}, {150, 530}}";
+									NavBarVisible = YES;
+								};
+							};
+							SplitCount = 1;
+							Tab0 = {
+								Debugger = {
+									Split0 = {
+										SplitCount = 2;
+									};
+									SplitCount = 1;
+									TabCount = 2;
+								};
+								LauncherConfigVersion = 7;
+							};
+							Tab1 = {
+								LauncherConfigVersion = 3;
+								Runner = {
+								};
+							};
+							TabCount = 4;
+						};
+						SplitCount = 1;
+						Tab1 = {
+							OptionsSetName = "Hierarchy, all classes";
+						};
+						TabCount = 5;
+					};
+				},
+			);
+			PBXWorkspaceGeometries = (
+				{
+					ContentSize = "{352, 553}";
+					LeftSlideOut = {
+						ActiveTab = 0;
+						Collapsed = YES;
+						Frame = "{{0, 23}, {352, 530}}";
+						Split0 = {
+							Collapsed = NO;
+							Frame = "{{1e+06, 1e+06}, {0, 530}}";
+							Split0 = {
+								Frame = "{{0, 0}, {0, 530}}";
+							};
+							SplitCount = 1;
+							Tab0 = {
+								Debugger = {
+									Collapsed = NO;
+									Frame = "{{0, 0}, {484, 208}}";
+									Split0 = {
+										Frame = "{{0, 24}, {484, 184}}";
+										Split0 = {
+											Frame = "{{0, 0}, {236, 184}}";
+										};
+										Split1 = {
+											DebugVariablesTableConfiguration = (
+												Name,
+												48.80299,
+												Value,
+												71.07401,
+												Summary,
+												93.123,
+											);
+											Frame = "{{245, 0}, {239, 184}}";
+										};
+										SplitCount = 2;
+									};
+									SplitCount = 1;
+									Tab0 = {
+										Frame = "{{0, 0}, {100, 50}}";
+									};
+									Tab1 = {
+										Frame = "{{0, 0}, {100, 50}}";
+									};
+									TabCount = 2;
+									TabsVisible = YES;
+								};
+								Frame = "{{0, 0}, {484, 208}}";
+								LauncherConfigVersion = 7;
+							};
+							Tab1 = {
+								Frame = "{{0, 0}, {664, 208}}";
+								LauncherConfigVersion = 3;
+								Runner = {
+									Frame = "{{0, 0}, {664, 208}}";
+								};
+							};
+							Tab2 = {
+								BuildMessageFrame = "{{0, 0}, {666, 43}}";
+								BuildTranscriptFrame = "{{0, 52}, {666, 0}}";
+								Frame = "{{0, 0}, {664, 50}}";
+							};
+							Tab3 = {
+								Frame = "{{0, 0}, {612, 295}}";
+							};
+							TabCount = 4;
+							TabsVisible = NO;
+						};
+						SplitCount = 1;
+						Tab0 = {
+							Frame = "{{0, 0}, {328, 530}}";
+							GroupTreeTableConfiguration = (
+								TargetStatusColumn,
+								18,
+								MainColumn,
+								295,
+							);
+						};
+						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}, {328, 530}}";
+							TargetTableConfiguration = (
+								ActiveObject,
+								16,
+								ObjectNames,
+								295.296,
+							);
+						};
+						Tab4 = {
+							BreakpointsTreeTableConfiguration = (
+								breakpointColumn,
+								266,
+								enabledColumn,
+								31,
+							);
+							Frame = "{{0, 0}, {319, 576}}";
+						};
+						TabCount = 5;
+						TabsVisible = YES;
+					};
+					StatusViewVisible = YES;
+					Template = F5E465990156DFB90DCA290F;
+					ToolbarVisible = YES;
+					WindowLocation = "{11, 546}";
+				},
+			);
+			PBXWorkspaceStateSaveDate = 56183221;
+		};
+		projectwideBuildSettings = {
+		};
+		userBookmarkGroup = F5E40F1D02CADE3701AEEDB1;
+		wantsIndex = 1;
+		wantsSCM = -1;
+	};
+	29B97326FDCFA39411CA2CEA = {
+		activeExec = 0;
+		executables = (
+			F5C2947702C9824601AEEDB1,
+		);
+	};
+	F5643F9702CEF87001AEEDB1 = {
+		isa = PBXSymbolicBreakpoint;
+		state = 1;
+		symbolName = "-[CURLHandle dealloc]";
+	};
+	F5C2947702C9824601AEEDB1 = {
+		activeArgIndex = 2147483647;
+		argumentStrings = (
+		);
+		debuggerPlugin = GDBDebugging;
+		dylibVariantSuffix = "";
+		enableDebugStr = 1;
+		environmentEntries = (
+			{
+				active = YES;
+				name = NSZombieEnabled;
+				value = YES;
+			},
+		);
+		isa = PBXExecutable;
+		name = DockCam;
+		shlibInfoDictList = (
+		);
+		sourceDirectories = (
+		);
+	};
+	F5C2947E02C99BC301AEEDB1 = {
+		isa = PBXSymbolicBreakpoint;
+		state = 1;
+		symbolName = "-[NSException raise]";
+	};
+	F5D224B902CC7D1801AEEDB1 = {
+		fRef = F5C2947B02C98F3D01AEEDB1;
+		isa = PBXTextBookmark;
+		name = "DockCam.h: 26";
+		rLen = 9;
+		rLoc = 651;
+		rType = 0;
+		vrLen = 877;
+		vrLoc = 0;
+	};
+	F5D224BA02CC84AC01AEEDB1 = {
+		fRef = F5C2947A02C98F3D01AEEDB1;
+		isa = PBXTextBookmark;
+		name = "DockCam.m: 1";
+		rLen = 12684;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 1887;
+		vrLoc = 2312;
+	};
+	F5E40F1D02CADE3701AEEDB1 = {
+		children = (
+			F5E40F4102CB0F5A01AEEDB1,
+			F5D224B902CC7D1801AEEDB1,
+			F5D224BA02CC84AC01AEEDB1,
+		);
+		isa = PBXBookmarkGroup;
+		name = Root;
+	};
+	F5E40F4102CB0F5A01AEEDB1 = {
+		fRef = F5E40F3E02CB0BCE01AEEDB1;
+		isa = PBXTextBookmark;
+		name = "DockCamStatus.h: 25";
+		rLen = 0;
+		rLoc = 711;
+		rType = 0;
+		vrLen = 592;
+		vrLoc = 0;
+	};
+	F5EE2B5702CF16EE01AEEDB1 = {
+		fileReference = F51D694A02CC26BA01AEEDB1;
+		isa = PBXFileBreakpoint;
+		lineNumber = 74;
+		state = 1;
+	};
+}
Index: /trunk/Cocoa/DockCam/DockCam.pbproj/project.pbxproj
===================================================================
--- /trunk/Cocoa/DockCam/DockCam.pbproj/project.pbxproj	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCam.pbproj/project.pbxproj	(revision 5)
@@ -0,0 +1,607 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 38;
+	objects = {
+		080E96DCFE201CFB7F000001 = {
+			fileRef = 29B97318FDCFA39411CA2CEA;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		080E96DDFE201D6D7F000001 = {
+			children = (
+				F5C2947B02C98F3D01AEEDB1,
+				F5C2947A02C98F3D01AEEDB1,
+				F5E40F3E02CB0BCE01AEEDB1,
+				F5E40F3D02CB0BCE01AEEDB1,
+				F5DAC40502CBE5B101AEEDB1,
+				F5DAC40402CBE5B101AEEDB1,
+				F51D694B02CC26BA01AEEDB1,
+				F51D694A02CC26BA01AEEDB1,
+				F5D224A702CC709501AEEDB1,
+				F5D224A602CC709501AEEDB1,
+				F58CD71502CDA0D401AEEDB1,
+				F58CD71602CDA0D401AEEDB1,
+			);
+			isa = PBXGroup;
+			name = Classes;
+			refType = 4;
+		};
+		089C165CFE840E0CC02AAC07 = {
+			children = (
+				089C165DFE840E0CC02AAC07,
+			);
+			isa = PBXVariantGroup;
+			name = InfoPlist.strings;
+			refType = 4;
+		};
+		089C165DFE840E0CC02AAC07 = {
+			fileEncoding = 10;
+			isa = PBXFileReference;
+			name = English;
+			path = English.lproj/InfoPlist.strings;
+			refType = 4;
+		};
+		089C165EFE840E0CC02AAC07 = {
+			fileRef = 089C165CFE840E0CC02AAC07;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+//080
+//081
+//082
+//083
+//084
+//100
+//101
+//102
+//103
+//104
+		1058C7A0FEA54F0111CA2CBB = {
+			children = (
+				F5EC504302F7A7D201AEEDB1,
+				1058C7A1FEA54F0111CA2CBB,
+			);
+			isa = PBXGroup;
+			name = "Linked Frameworks";
+			refType = 4;
+		};
+		1058C7A1FEA54F0111CA2CBB = {
+			isa = PBXFrameworkReference;
+			name = Cocoa.framework;
+			path = /System/Library/Frameworks/Cocoa.framework;
+			refType = 0;
+		};
+		1058C7A2FEA54F0111CA2CBB = {
+			children = (
+				29B97325FDCFA39411CA2CEA,
+				29B97324FDCFA39411CA2CEA,
+			);
+			isa = PBXGroup;
+			name = "Other Frameworks";
+			refType = 4;
+		};
+		1058C7A3FEA54F0111CA2CBB = {
+			fileRef = 1058C7A1FEA54F0111CA2CBB;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+//100
+//101
+//102
+//103
+//104
+//170
+//171
+//172
+//173
+//174
+		17587328FF379C6511CA2CBB = {
+			isa = PBXApplicationReference;
+			path = DockCam.app;
+			refType = 3;
+		};
+//170
+//171
+//172
+//173
+//174
+//190
+//191
+//192
+//193
+//194
+		19C28FACFE9D520D11CA2CBB = {
+			children = (
+				17587328FF379C6511CA2CBB,
+			);
+			isa = PBXGroup;
+			name = Products;
+			refType = 4;
+		};
+//190
+//191
+//192
+//193
+//194
+//290
+//291
+//292
+//293
+//294
+		29B97313FDCFA39411CA2CEA = {
+			buildStyles = (
+				4A9504CCFFE6A4B311CA0CBA,
+				4A9504CDFFE6A4B311CA0CBA,
+			);
+			isa = PBXProject;
+			mainGroup = 29B97314FDCFA39411CA2CEA;
+			projectDirPath = "";
+			targets = (
+				29B97326FDCFA39411CA2CEA,
+			);
+		};
+		29B97314FDCFA39411CA2CEA = {
+			children = (
+				080E96DDFE201D6D7F000001,
+				29B97315FDCFA39411CA2CEA,
+				29B97317FDCFA39411CA2CEA,
+				29B97323FDCFA39411CA2CEA,
+				19C28FACFE9D520D11CA2CBB,
+			);
+			isa = PBXGroup;
+			name = DockCam;
+			path = "";
+			refType = 4;
+		};
+		29B97315FDCFA39411CA2CEA = {
+			children = (
+				29B97316FDCFA39411CA2CEA,
+			);
+			isa = PBXGroup;
+			name = "Other Sources";
+			path = "";
+			refType = 4;
+		};
+		29B97316FDCFA39411CA2CEA = {
+			isa = PBXFileReference;
+			path = main.m;
+			refType = 4;
+		};
+		29B97317FDCFA39411CA2CEA = {
+			children = (
+				F5E40F2802CAE11E01AEEDB1,
+				F5C2421502CE877B01000211,
+				F5C2421602CE877B01000211,
+				29B97318FDCFA39411CA2CEA,
+				F5DAC40802CBE6D001AEEDB1,
+				F5BA54E302CEB99C01000211,
+				089C165CFE840E0CC02AAC07,
+			);
+			isa = PBXGroup;
+			name = Resources;
+			path = "";
+			refType = 4;
+		};
+		29B97318FDCFA39411CA2CEA = {
+			children = (
+				29B97319FDCFA39411CA2CEA,
+			);
+			isa = PBXVariantGroup;
+			name = MainMenu.nib;
+			path = "";
+			refType = 4;
+		};
+		29B97319FDCFA39411CA2CEA = {
+			isa = PBXFileReference;
+			name = English;
+			path = English.lproj/MainMenu.nib;
+			refType = 4;
+		};
+		29B97323FDCFA39411CA2CEA = {
+			children = (
+				1058C7A0FEA54F0111CA2CBB,
+				1058C7A2FEA54F0111CA2CBB,
+			);
+			isa = PBXGroup;
+			name = Frameworks;
+			path = "";
+			refType = 4;
+		};
+		29B97324FDCFA39411CA2CEA = {
+			isa = PBXFrameworkReference;
+			name = AppKit.framework;
+			path = /System/Library/Frameworks/AppKit.framework;
+			refType = 0;
+		};
+		29B97325FDCFA39411CA2CEA = {
+			isa = PBXFrameworkReference;
+			name = Foundation.framework;
+			path = /System/Library/Frameworks/Foundation.framework;
+			refType = 0;
+		};
+		29B97326FDCFA39411CA2CEA = {
+			buildPhases = (
+				29B97327FDCFA39411CA2CEA,
+				29B97328FDCFA39411CA2CEA,
+				29B9732BFDCFA39411CA2CEA,
+				29B9732DFDCFA39411CA2CEA,
+				F58FF72E02CED48101000211,
+			);
+			buildSettings = {
+				FRAMEWORK_SEARCH_PATHS = "\"/Volumes/GrayExtra/Extra/Framework sources/CURLHandle/CURLHandleSource/build\" \"/Volumes/GrayExtra/Extra/Framework sources/CURLHandle\"";
+				HEADER_SEARCH_PATHS = "/sw/include /usr/local/fink/include /usr/local/include";
+				INSTALL_PATH = "$(HOME)/Applications";
+				LIBRARY_SEARCH_PATHS = "";
+				PRODUCT_NAME = DockCam;
+				SECTORDER_FLAGS = "";
+				WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+				WRAPPER_EXTENSION = app;
+			};
+			dependencies = (
+			);
+			isa = PBXApplicationTarget;
+			name = DockCam;
+			productInstallPath = "$(HOME)/Applications";
+			productName = DockCam;
+			productReference = 17587328FF379C6511CA2CBB;
+			productSettingsXML = "<?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>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>DockCam</string>
+	<key>CFBundleGetInfoString</key>
+	<string>1.0b4, ©2002 Nicholas Riley</string>
+	<key>CFBundleIconFile</key>
+	<string>DockCam.icns</string>
+	<key>CFBundleIdentifier</key>
+	<string>net.sabi.DockCam</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>DockCam</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0b4</string>
+	<key>CFBundleSignature</key>
+	<string>DkCm</string>
+	<key>CFBundleVersion</key>
+	<string>1.0b4</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
+";
+			shouldUseHeadermap = 1;
+		};
+		29B97327FDCFA39411CA2CEA = {
+			buildActionMask = 2147483647;
+			files = (
+				F5C2947D02C98F3D01AEEDB1,
+				F5E40F4002CB0BCE01AEEDB1,
+				F5DAC40702CBE5B101AEEDB1,
+				F51D694D02CC26BA01AEEDB1,
+				F5D224A902CC709501AEEDB1,
+				F58CD71702CDA0D401AEEDB1,
+			);
+			isa = PBXHeadersBuildPhase;
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		29B97328FDCFA39411CA2CEA = {
+			buildActionMask = 2147483647;
+			files = (
+				080E96DCFE201CFB7F000001,
+				F5DAC40A02CBE6D001AEEDB1,
+				089C165EFE840E0CC02AAC07,
+				F5E40F2902CAE11E01AEEDB1,
+				F5C2421702CE877B01000211,
+				F5C2421802CE877B01000211,
+				F5BA54E402CEB99C01000211,
+			);
+			isa = PBXResourcesBuildPhase;
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		29B9732BFDCFA39411CA2CEA = {
+			buildActionMask = 2147483647;
+			files = (
+				29B9732CFDCFA39411CA2CEA,
+				F5C2947C02C98F3D01AEEDB1,
+				F5E40F3F02CB0BCE01AEEDB1,
+				F5DAC40602CBE5B101AEEDB1,
+				F51D694C02CC26BA01AEEDB1,
+				F5D224A802CC709501AEEDB1,
+				F58CD71802CDA0D401AEEDB1,
+			);
+			isa = PBXSourcesBuildPhase;
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		29B9732CFDCFA39411CA2CEA = {
+			fileRef = 29B97316FDCFA39411CA2CEA;
+			isa = PBXBuildFile;
+			settings = {
+				ATTRIBUTES = (
+				);
+			};
+		};
+		29B9732DFDCFA39411CA2CEA = {
+			buildActionMask = 2147483647;
+			files = (
+				1058C7A3FEA54F0111CA2CBB,
+				F5EC504902F7A7D201AEEDB1,
+			);
+			isa = PBXFrameworksBuildPhase;
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+//290
+//291
+//292
+//293
+//294
+//4A0
+//4A1
+//4A2
+//4A3
+//4A4
+		4A9504CCFFE6A4B311CA0CBA = {
+			buildRules = (
+			);
+			buildSettings = {
+				COPY_PHASE_STRIP = NO;
+				OPTIMIZATION_CFLAGS = "-O0";
+			};
+			isa = PBXBuildStyle;
+			name = Development;
+		};
+		4A9504CDFFE6A4B311CA0CBA = {
+			buildRules = (
+			);
+			buildSettings = {
+				COPY_PHASE_STRIP = YES;
+			};
+			isa = PBXBuildStyle;
+			name = Deployment;
+		};
+//4A0
+//4A1
+//4A2
+//4A3
+//4A4
+//F50
+//F51
+//F52
+//F53
+//F54
+		F51D694A02CC26BA01AEEDB1 = {
+			isa = PBXFileReference;
+			path = NJRHistoryTrackingComboBox.m;
+			refType = 4;
+		};
+		F51D694B02CC26BA01AEEDB1 = {
+			isa = PBXFileReference;
+			path = NJRHistoryTrackingComboBox.h;
+			refType = 4;
+		};
+		F51D694C02CC26BA01AEEDB1 = {
+			fileRef = F51D694A02CC26BA01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F51D694D02CC26BA01AEEDB1 = {
+			fileRef = F51D694B02CC26BA01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F58CD71502CDA0D401AEEDB1 = {
+			isa = PBXFileReference;
+			name = NJRWindowMagnetism.h;
+			path = /Users/nicholas/Documents/Development/HostLauncher/NJRWindowMagnetism.h;
+			refType = 0;
+		};
+		F58CD71602CDA0D401AEEDB1 = {
+			isa = PBXFileReference;
+			name = NJRWindowMagnetism.m;
+			path = /Users/nicholas/Documents/Development/HostLauncher/NJRWindowMagnetism.m;
+			refType = 0;
+		};
+		F58CD71702CDA0D401AEEDB1 = {
+			fileRef = F58CD71502CDA0D401AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F58CD71802CDA0D401AEEDB1 = {
+			fileRef = F58CD71602CDA0D401AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F58FF72E02CED48101000211 = {
+			buildActionMask = 12;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			isa = PBXCopyFilesBuildPhase;
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F5BA54E302CEB99C01000211 = {
+			isa = PBXFileReference;
+			path = Credits.rtf;
+			refType = 4;
+		};
+		F5BA54E402CEB99C01000211 = {
+			fileRef = F5BA54E302CEB99C01000211;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5C2421502CE877B01000211 = {
+			isa = PBXFileReference;
+			path = "Open hand.png";
+			refType = 4;
+		};
+		F5C2421602CE877B01000211 = {
+			isa = PBXFileReference;
+			path = "Closed hand.png";
+			refType = 4;
+		};
+		F5C2421702CE877B01000211 = {
+			fileRef = F5C2421502CE877B01000211;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5C2421802CE877B01000211 = {
+			fileRef = F5C2421602CE877B01000211;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5C2947A02C98F3D01AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCam.m;
+			refType = 4;
+		};
+		F5C2947B02C98F3D01AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCam.h;
+			refType = 4;
+		};
+		F5C2947C02C98F3D01AEEDB1 = {
+			fileRef = F5C2947A02C98F3D01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5C2947D02C98F3D01AEEDB1 = {
+			fileRef = F5C2947B02C98F3D01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5D224A602CC709501AEEDB1 = {
+			isa = PBXFileReference;
+			path = NJRVendingImageView.m;
+			refType = 4;
+		};
+		F5D224A702CC709501AEEDB1 = {
+			isa = PBXFileReference;
+			path = NJRVendingImageView.h;
+			refType = 4;
+		};
+		F5D224A802CC709501AEEDB1 = {
+			fileRef = F5D224A602CC709501AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5D224A902CC709501AEEDB1 = {
+			fileRef = F5D224A702CC709501AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5DAC40402CBE5B101AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCamPrefs.m;
+			refType = 4;
+		};
+		F5DAC40502CBE5B101AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCamPrefs.h;
+			refType = 4;
+		};
+		F5DAC40602CBE5B101AEEDB1 = {
+			fileRef = F5DAC40402CBE5B101AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5DAC40702CBE5B101AEEDB1 = {
+			fileRef = F5DAC40502CBE5B101AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5DAC40802CBE6D001AEEDB1 = {
+			children = (
+				F5DAC40902CBE6D001AEEDB1,
+			);
+			isa = PBXVariantGroup;
+			name = Preferences.nib;
+			path = "";
+			refType = 4;
+		};
+		F5DAC40902CBE6D001AEEDB1 = {
+			isa = PBXFileReference;
+			name = English;
+			path = English.lproj/Preferences.nib;
+			refType = 4;
+		};
+		F5DAC40A02CBE6D001AEEDB1 = {
+			fileRef = F5DAC40802CBE6D001AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5E40F2802CAE11E01AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCam.icns;
+			refType = 4;
+		};
+		F5E40F2902CAE11E01AEEDB1 = {
+			fileRef = F5E40F2802CAE11E01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5E40F3D02CB0BCE01AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCamStatus.m;
+			refType = 4;
+		};
+		F5E40F3E02CB0BCE01AEEDB1 = {
+			isa = PBXFileReference;
+			path = DockCamStatus.h;
+			refType = 4;
+		};
+		F5E40F3F02CB0BCE01AEEDB1 = {
+			fileRef = F5E40F3D02CB0BCE01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5E40F4002CB0BCE01AEEDB1 = {
+			fileRef = F5E40F3E02CB0BCE01AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+		F5EC504302F7A7D201AEEDB1 = {
+			isa = PBXFrameworkReference;
+			name = CURLHandle.framework;
+			path = "/Volumes/GrayExtra/Extra/Framework sources/CURLHandle/CURLHandle.framework";
+			refType = 0;
+		};
+		F5EC504902F7A7D201AEEDB1 = {
+			fileRef = F5EC504302F7A7D201AEEDB1;
+			isa = PBXBuildFile;
+			settings = {
+			};
+		};
+	};
+	rootObject = 29B97313FDCFA39411CA2CEA;
+}
Index: /trunk/Cocoa/DockCam/DockCamPrefs.h
===================================================================
--- /trunk/Cocoa/DockCam/DockCamPrefs.h	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCamPrefs.h	(revision 5)
@@ -0,0 +1,41 @@
+//
+//  DockCamPrefs.h
+//  DockCam
+//
+//  Created by Nicholas Riley on Thu Jun 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+extern NSString * const DCImageLocation;
+extern NSString * const DCImageSize;
+extern NSString * const DCImageRefreshEnabled;
+extern NSString * const DCImageRefreshInterval;
+extern NSString * const DCShowImageInBackground;
+extern NSString * const DCShowStatus;
+
+extern NSString * const DCPrefChangedNotification;
+
+@interface DockCamPrefs : NSWindowController {
+    IBOutlet NSComboBox *imageLocation;
+    IBOutlet NSMatrix *retrieveImageAutomatically;
+    IBOutlet NSTextField *imageRefreshInterval;
+    IBOutlet NSPopUpButton *imageRefreshIntervalUnits;
+    IBOutlet NSButton *showImageInBackground;
+}
+
++ (BOOL)boolForPref:(NSString *)pref;
++ (int)intForPref:(NSString *)pref;
++ (NSSize)sizeForPref:(NSString *)pref;
++ (NSString *)stringForPref:(NSString *)pref;
+
++ (void)setBool:(BOOL)value forPref:(NSString *)pref;
++ (void)setInt:(int)value forPref:(NSString *)pref;
++ (void)setString:(NSString *)value forPref:(NSString *)pref;
++ (void)setSize:(NSSize)value forPref:(NSString *)pref;
+
+- (IBAction)writeToPrefs:(id)sender;
+- (IBAction)retrieveImageAutomatically:(id)sender;
+
+@end
Index: /trunk/Cocoa/DockCam/DockCamPrefs.m
===================================================================
--- /trunk/Cocoa/DockCam/DockCamPrefs.m	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCamPrefs.m	(revision 5)
@@ -0,0 +1,208 @@
+//
+//  DockCamPrefs.m
+//  DockCam
+//
+//  Created by Nicholas Riley on Thu Jun 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "DockCamPrefs.h"
+
+NSString * const DCImageLocation = @"DCImageLocation";
+NSString * const DCImageSize = @"DCImageSize";
+NSString * const DCImageRefreshEnabled = @"DCImageRefreshEnabled";
+NSString * const DCImageRefreshInterval = @"DCImageRefreshInterval";
+NSString * const DCShowImageInBackground = @"DCShowImageInBackground";
+NSString * const DCShowStatus = @"DCShowStatus";
+
+NSString * const DCPrefChangedNotification = @"DCPrefChangedNotification";
+
+const NSTimeInterval DCDefaultImageRefreshInterval = 30;
+
+enum {
+    DCImageRetrieveOnOpen = 2000, DCImageRetrieveOnTimer = 2001
+};
+
+enum {
+    DCImageRefreshIntervalSeconds = 1, DCImageRefreshIntervalMinutes = 60, DCImageRefreshIntervalHours = 3600
+};
+
+NSUserDefaults *defaults;
+
+@implementation DockCamPrefs
+
++ (void)initialize;
+{
+    NSDictionary *appDefaults =
+        [NSDictionary dictionaryWithObjectsAndKeys:
+            @"", DCImageLocation,
+            [NSNumber numberWithInt: DCDefaultImageRefreshInterval], DCImageRefreshInterval,
+            [NSNumber numberWithBool: NO], DCShowImageInBackground,
+            [NSNumber numberWithBool: YES], DCShowStatus,
+           nil];
+    defaults = [NSUserDefaults standardUserDefaults];
+    [defaults registerDefaults: appDefaults];
+}
+
++ (void)_prefChanged:(NSString *)pref;
+{
+    [[NSNotificationCenter defaultCenter] postNotificationName: DCPrefChangedNotification object: pref];
+}
+
+#pragma mark preference getters
+
++ (BOOL)boolForPref:(NSString *)pref;
+{
+    return [defaults boolForKey: pref];
+}
+
++ (int)intForPref:(NSString *)pref;
+{
+    return [defaults integerForKey: pref];
+}
+
++ (NSString *)stringForPref:(NSString *)pref;
+{
+    return [defaults stringForKey: pref];
+}
+
++ (NSSize)sizeForPref:(NSString *)pref;
+{
+    return NSSizeFromString([defaults stringForKey: pref]);
+}
+
+#pragma mark preference setters
+
++ (void)setBool:(BOOL)value forPref:(NSString *)pref;
+{
+    BOOL oldValue = [self boolForPref: pref];
+    if (oldValue != value) {
+        [defaults setBool: value forKey: pref];
+        [self _prefChanged: pref];
+    }
+}
+
++ (void)setInt:(int)value forPref:(NSString *)pref;
+{
+    int oldValue = [self intForPref: pref];
+    if (oldValue != value) {
+        [defaults setInteger: value forKey: pref];
+        [self _prefChanged: pref];
+    }
+}
+
++ (void)setString:(NSString *)value forPref:(NSString *)pref;
+{
+    NSString *oldValue = [self stringForPref: pref];
+    if (![oldValue isEqualToString: value]) {
+        [defaults setObject: value forKey: pref];
+        [self _prefChanged: pref];
+    }
+}
+
++ (void)setSize:(NSSize)value forPref:(NSString *)pref;
+{
+    NSSize oldValue = [self sizeForPref: pref];
+    if (!NSEqualSizes(oldValue, value)) {
+        [defaults setObject: NSStringFromSize(value) forKey: pref];
+        [self _prefChanged: pref];
+    }
+}
+
+#pragma mark complex interface-preference mapping
+
+- (BOOL)retrieveOnTimer;
+{
+    return [[retrieveImageAutomatically selectedCell] tag] == DCImageRetrieveOnTimer;
+}
+
+- (NSTimeInterval)interval;
+{
+    return [imageRefreshInterval intValue] * [[imageRefreshIntervalUnits selectedItem] tag];
+}
+
+- (void)setInterval:(NSTimeInterval)interval;
+{
+    // we assume that the tags are in ascending order in the array
+    NSEnumerator *e = [[imageRefreshIntervalUnits itemArray] reverseObjectEnumerator];
+    NSMenuItem *i;
+    int multiplierTag;
+    
+    while ( (i = [e nextObject]) != nil) {
+        multiplierTag = [i tag];
+        if (((int)interval % multiplierTag) == 0) {
+            [imageRefreshInterval setIntValue: interval / multiplierTag];
+            [imageRefreshIntervalUnits selectItem: i];
+            return;
+        }
+    }
+    [imageRefreshIntervalUnits selectItemAtIndex: 0];
+    [imageRefreshInterval setIntValue: DCDefaultImageRefreshInterval];
+}
+
+#pragma mark preference I/O
+
+- (void)readFromPrefs;
+{
+    [imageLocation setStringValue: [DockCamPrefs stringForPref: DCImageLocation]];
+    [retrieveImageAutomatically selectCellWithTag:
+        [DockCamPrefs boolForPref: DCImageRefreshEnabled] ? DCImageRetrieveOnTimer : DCImageRetrieveOnOpen];
+    [self setInterval: [DockCamPrefs intForPref: DCImageRefreshInterval]];
+    [showImageInBackground setState:
+        [DockCamPrefs boolForPref: DCShowImageInBackground] ? NSOnState : NSOffState];
+}
+
+- (IBAction)writeToPrefs:(id)sender;
+{
+    [DockCamPrefs setString: [imageLocation stringValue] forPref: DCImageLocation];
+    [DockCamPrefs setBool: [self retrieveOnTimer] forPref: DCImageRefreshEnabled];
+    [DockCamPrefs setInt: [self interval] forPref: DCImageRefreshInterval];
+    [DockCamPrefs setBool: [showImageInBackground state] == NSOnState forPref: DCShowImageInBackground];
+    [defaults synchronize];
+}
+
+#pragma mark interface maintenance
+
+- (IBAction)showWindow:(id)sender;
+{
+    // XXX workaround for weird bug where combo box and text field alternately become first responder
+    [super showWindow: sender];
+    [[[self window] initialFirstResponder] becomeFirstResponder];
+}
+
+- (void)update;
+{
+    BOOL enabled = [self retrieveOnTimer];
+    [imageRefreshInterval setEnabled: enabled];
+    [imageRefreshIntervalUnits setEnabled: enabled];
+}
+
+- (IBAction)retrieveImageAutomatically:(id)sender;
+{
+    [self update];
+    if ([self retrieveOnTimer]) [imageRefreshInterval becomeFirstResponder];
+    else [sender becomeFirstResponder];
+}
+
+#pragma mark initialize-release
+
+- (id)init {
+    self = [super initWithWindowNibName:@"Preferences"];
+    [self window]; // connect outlets
+    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowWillClose:)
+                                                 name: NSWindowWillCloseNotification object: [self window]];
+    [self readFromPrefs];
+    [self update];
+    return self;
+}
+
+@end
+
+@implementation DockCamPrefs (NSWindowNotifications)
+
+- (void)windowWillClose:(NSNotification *)notification;
+{
+    [self writeToPrefs: self];
+}
+
+@end
Index: /trunk/Cocoa/DockCam/DockCamStatus.h
===================================================================
--- /trunk/Cocoa/DockCam/DockCamStatus.h	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCamStatus.h	(revision 5)
@@ -0,0 +1,28 @@
+//
+//  DockCamStatus.h
+//  DockCam
+//
+//  Created by Nicholas Riley on Thu Jun 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+
+@interface DockCamStatus : NSWindowController {
+    IBOutlet NSForm *statusForm;
+    IBOutlet NSFormCell *locationCell;
+    IBOutlet NSFormCell *actualSize;
+    IBOutlet NSFormCell *retrievedDate;
+    IBOutlet NSFormCell *failedDate;
+    IBOutlet NSFormCell *failedReason;
+    IBOutlet NSFormCell *statusCell;
+    IBOutlet id<NSMenuItem> showHideStatusItem;
+}
+
+- (void)setLocation:(NSURL *)aLocation;
+- (void)setStatus:(NSString *)aStatus;
+- (void)setRetrievedWithSize:(NSSize)size;
+- (void)setFailedWithReason:(NSString *)reason;
+
+@end
Index: /trunk/Cocoa/DockCam/DockCamStatus.m
===================================================================
--- /trunk/Cocoa/DockCam/DockCamStatus.m	(revision 5)
+++ /trunk/Cocoa/DockCam/DockCamStatus.m	(revision 5)
@@ -0,0 +1,87 @@
+//
+//  DockCamStatus.m
+//  DockCam
+//
+//  Created by Nicholas Riley on Thu Jun 27 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "DockCamStatus.h"
+#import "DockCamPrefs.h"
+
+@implementation DockCamStatus
+
+- (void)awakeFromNib;
+{
+    NSEnumerator *e = [[statusForm cells] objectEnumerator];
+    NSCell *cell;
+    while ( (cell = [e nextObject]) != nil) {
+        [cell setFont: [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]];
+    }
+
+    // XXX workaround for bug in 10.1.5 (and earlier?): autosave name set in IB doesn't show up
+    [self setWindowFrameAutosaveName: @"DockCam Status"];
+    [[self window] setHidesOnDeactivate: YES];
+        
+    if ([DockCamPrefs boolForPref: DCShowStatus])
+        [self showWindow: self];
+}
+
+- (IBAction)showWindow:(id)sender;
+{
+    [DockCamPrefs setBool: YES forPref: DCShowStatus];
+    [showHideStatusItem setAction: @selector(hideWindow:)];
+    [showHideStatusItem setTitle: @"Hide Status"];
+    // No need to subscribe to notifications, as the delegate we're already subscribed
+    [[self window] orderFront: self];
+}
+
+- (IBAction)hideWindow:(id)sender;
+{
+    [[self window] orderOut: sender];
+    [DockCamPrefs setBool: NO forPref: DCShowStatus];
+    [showHideStatusItem setAction: @selector(showWindow:)];
+    [showHideStatusItem setTitle: @"Show Status"];
+}
+
+w
+- (void)windowWillClose:(NSNotification *)aNotification;
+{
+    [self hideWindow: self];
+}
+
+#pragma mark status changes
+
+- (void)setLocation:(NSURL *)location;
+{
+    [locationCell setStringValue: [location absoluteString]];
+    [actualSize setStringValue: @""];
+    [retrievedDate setStringValue: @"never"];
+    [failedDate setStringValue: @"never"];
+    [failedReason setStringValue: @""];
+}
+
+- (void)setStatus:(NSString *)status;
+{
+    [statusCell setStringValue: status];
+}
+
+- (void)setRetrievedWithSize:(NSSize)size;
+{
+    static const unichar timesUnichar = 0x00d7;
+    [retrievedDate setObjectValue: [NSDate date]];
+    [self setStatus: @"Image retrieved successfully"];
+    [actualSize setStringValue: [NSString stringWithFormat: @"%.0f %@ %.0f pixels",
+        size.width, [NSString stringWithCharacters: &timesUnichar length: 1], size.height]];
+}
+
+- (void)setFailedWithReason:(NSString *)reason;
+{
+    [failedDate setObjectValue: [NSDate date]];
+    [failedReason setStringValue: reason];
+    [self setStatus: @"Image retrieval failed"];
+    if (![[self window] isVisible])
+        [self showWindow: self];
+}
+
+@end
Index: /trunk/Cocoa/DockCam/English.lproj/MainMenu.nib/classes.nib
===================================================================
--- /trunk/Cocoa/DockCam/English.lproj/MainMenu.nib/classes.nib	(revision 5)
+++ /trunk/Cocoa/DockCam/English.lproj/MainMenu.nib/classes.nib	(revision 5)
@@ -0,0 +1,45 @@
+{
+    IBClasses = (
+        {
+            ACTIONS = {
+                cancelLoading = id; 
+                orderFrontPreferencesPanel = id; 
+                pauseTimer = id; 
+                reloadImage = id; 
+                resumeTimer = id; 
+            }; 
+            CLASS = DockCam; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                imageView = NJRVendingImageView; 
+                imageWindow = NSWindow; 
+                pauseResumeItem = NSMenuItem; 
+                status = DockCamStatus; 
+            }; 
+            SUPERCLASS = NSObject; 
+        }, 
+        {
+            CLASS = DockCamStatus; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                actualSize = NSFormCell; 
+                failedDate = NSFormCell; 
+                failedReason = NSFormCell; 
+                locationCell = NSFormCell; 
+                retrievedDate = NSFormCell; 
+                showHideStatusItem = id; 
+                statusCell = NSFormCell; 
+                statusForm = NSForm; 
+            }; 
+            SUPERCLASS = NSWindowController; 
+        }, 
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {
+            CLASS = NJRVendingImageView; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {contextMenu = NSMenu; }; 
+            SUPERCLASS = NSImageView; 
+        }
+    ); 
+    IBVersion = 1; 
+}
Index: /trunk/Cocoa/DockCam/English.lproj/MainMenu.nib/info.nib
===================================================================
--- /trunk/Cocoa/DockCam/English.lproj/MainMenu.nib/info.nib	(revision 5)
+++ /trunk/Cocoa/DockCam/English.lproj/MainMenu.nib/info.nib	(revision 5)
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>12 74 356 240 0 0 1600 1178 </string>
+	<key>IBEditorPositions</key>
+	<dict>
+		<key>29</key>
+		<string>164 373 260 44 0 0 1600 1178 </string>
+	</dict>
+	<key>IBFramework Version</key>
+	<string>263.2</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>269</integer>
+		<integer>29</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>5S66</string>
+</dict>
+</plist>
Index: /trunk/Cocoa/DockCam/English.lproj/Preferences.nib/classes.nib
===================================================================
--- /trunk/Cocoa/DockCam/English.lproj/Preferences.nib/classes.nib	(revision 5)
+++ /trunk/Cocoa/DockCam/English.lproj/Preferences.nib/classes.nib	(revision 5)
@@ -0,0 +1,25 @@
+{
+    IBClasses = (
+        {
+            ACTIONS = {retrieveImageAutomatically = id; writeToPrefs = id; }; 
+            CLASS = DockCamPrefs; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                imageLocation = NSComboBox; 
+                imageRefreshInterval = NSTextField; 
+                imageRefreshIntervalUnits = NSPopUpButton; 
+                retrieveImageAutomatically = NSMatrix; 
+                showImageInBackground = NSButton; 
+            }; 
+            SUPERCLASS = NSWindowController; 
+        }, 
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {
+            ACTIONS = {clearAllEntries = id; removeEntry = id; }; 
+            CLASS = NJRHistoryTrackingComboBox; 
+            LANGUAGE = ObjC; 
+            SUPERCLASS = NSComboBox; 
+        }
+    ); 
+    IBVersion = 1; 
+}
Index: /trunk/Cocoa/DockCam/English.lproj/Preferences.nib/info.nib
===================================================================
--- /trunk/Cocoa/DockCam/English.lproj/Preferences.nib/info.nib	(revision 5)
+++ /trunk/Cocoa/DockCam/English.lproj/Preferences.nib/info.nib	(revision 5)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+	<key>IBDocumentLocation</key>
+	<string>100 33 356 240 0 0 1600 1178 </string>
+	<key>IBFramework Version</key>
+	<string>263.2</string>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>8</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>5S66</string>
+</dict>
+</plist>
Index: /trunk/Cocoa/DockCam/NJRHistoryTrackingComboBox.h
===================================================================
--- /trunk/Cocoa/DockCam/NJRHistoryTrackingComboBox.h	(revision 5)
+++ /trunk/Cocoa/DockCam/NJRHistoryTrackingComboBox.h	(revision 5)
@@ -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: /trunk/Cocoa/DockCam/NJRHistoryTrackingComboBox.m
===================================================================
--- /trunk/Cocoa/DockCam/NJRHistoryTrackingComboBox.m	(revision 5)
+++ /trunk/Cocoa/DockCam/NJRHistoryTrackingComboBox.m	(revision 5)
@@ -0,0 +1,75 @@
+//
+//  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, @"CanÕt track history for combo box with tag 0: please set a tag");
+    return [NSString stringWithFormat: @"NJRHistoryTrackingComboBox tag %d", [self tag]];
+}
+
+- (void)awakeFromNib;
+{
+    NSAssert([self tag] != 0, @"CanÕt track history for combo box with tag 0: please set a tag");
+    [[NSNotificationCenter defaultCenter] addObserver: self
+                                             selector: @selector(textDidEndEditing:)
+                                                 name: NSTextDidEndEditingNotification
+                                               object: self];
+    [self removeAllItems];
+    [self addItemsWithObjectValues: [[NSUserDefaults standardUserDefaults] stringArrayForKey: [self _defaultKey]]];
+    [self setItemHeight: [self itemHeight] + 2];
+}
+
+- (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];
+    [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: /trunk/Cocoa/DockCam/NJRVendingImageView.h
===================================================================
--- /trunk/Cocoa/DockCam/NJRVendingImageView.h	(revision 5)
+++ /trunk/Cocoa/DockCam/NJRVendingImageView.h	(revision 5)
@@ -0,0 +1,17 @@
+//
+//  NJRVendingImageView.h
+//  DockCam
+//
+//  Created by Nicholas Riley on Fri Jun 28 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface NJRVendingImageView : NSImageView {
+}
+
+- (void)putImageOnPasteboard:(NSPasteboard *)pboard;
+
+@end
Index: /trunk/Cocoa/DockCam/NJRVendingImageView.m
===================================================================
--- /trunk/Cocoa/DockCam/NJRVendingImageView.m	(revision 5)
+++ /trunk/Cocoa/DockCam/NJRVendingImageView.m	(revision 5)
@@ -0,0 +1,68 @@
+//
+//  NJRVendingImageView.m
+//  DockCam
+//
+//  Created by Nicholas Riley on Fri Jun 28 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import "NJRVendingImageView.h"
+
+@implementation NJRVendingImageView
+
+NSCursor *openHandCursor = nil, *closedHandCursor = nil;
+
++ (void)initialize;
+{
+    openHandCursor = [[NSCursor alloc] initWithImage: [NSImage imageNamed: @"Open hand"] hotSpot: NSMakePoint(7, 7)];
+    closedHandCursor = [[NSCursor alloc] initWithImage: [NSImage imageNamed: @"Closed hand"] hotSpot: NSMakePoint(7, 7)];
+}
+
+- (void)putImageOnPasteboard:(NSPasteboard *)pboard;
+{
+    [pboard declareTypes: [NSArray arrayWithObject: NSTIFFPboardType] owner: self];
+    [pboard setData: [[self image] TIFFRepresentation] forType: NSTIFFPboardType];
+}
+
+- (void)mouseDown:(NSEvent *)event;
+{
+    NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
+    NSImage *image = [self image];
+    NSRect viewRect = { NSZeroPoint, [self bounds].size };
+    NSRect imageRect = { NSZeroPoint, [image size] };
+    NSImage *draggingImage = [[NSImage alloc] initWithSize: viewRect.size];
+
+    [draggingImage lockFocus];
+    [image drawInRect: viewRect fromRect: imageRect operation: NSCompositeCopy fraction: 0.6];
+    [draggingImage unlockFocus];
+
+    [self putImageOnPasteboard: pboard];
+
+    [NSApp currentEvent];
+
+    [closedHandCursor push]; // XXX doesn't always work, bug 2976315
+
+    [self dragImage: draggingImage at: NSZeroPoint offset: NSZeroSize event: event pasteboard: pboard source: self slideBack: YES];
+
+    [closedHandCursor pop];
+}
+
+// XXX guess at size of resize corner
+#define RESIZE_CORNER_EDGE 15
+
+// XXX assumes that the view takes up the entire window, fine for this app but not in general
+- (void)resetCursorRects;
+{
+    NSRect rect = [self bounds];
+
+    rect.origin.y += RESIZE_CORNER_EDGE; 
+    rect.size.height -= RESIZE_CORNER_EDGE;
+    [self addCursorRect: rect cursor: openHandCursor];
+    
+    rect.origin.y -= RESIZE_CORNER_EDGE;
+    rect.size.height = RESIZE_CORNER_EDGE;
+    rect.size.width -= RESIZE_CORNER_EDGE;
+    [self addCursorRect: rect cursor: openHandCursor];
+}
+
+@end
Index: /trunk/Cocoa/DockCam/main.m
===================================================================
--- /trunk/Cocoa/DockCam/main.m	(revision 5)
+++ /trunk/Cocoa/DockCam/main.m	(revision 5)
@@ -0,0 +1,14 @@
+//
+//  main.m
+//  DockCam
+//
+//  Created by Nicholas Riley on Wed Jun 26 2002.
+//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char *argv[])
+{
+    return NSApplicationMain(argc, argv);
+}
