// // 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 #import #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 )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