[5] | 1 | //
|
---|
| 2 | // DockCam.m
|
---|
| 3 | // DockCam
|
---|
| 4 | //
|
---|
| 5 | // Created by Nicholas Riley on Wed Jun 26 2002.
|
---|
| 6 | // Copyright (c) 2002 Nicholas Riley. All rights reserved.
|
---|
| 7 | //
|
---|
| 8 |
|
---|
| 9 | /*
|
---|
| 10 | Done in 1.0b1:
|
---|
| 11 | Add refresh command
|
---|
| 12 | Add pause command
|
---|
| 13 | Add configuration interface
|
---|
| 14 | Add show/hide status window
|
---|
| 15 | Add UI element validation: cancel/load
|
---|
| 16 | Dock menu
|
---|
| 17 | Add drag&drop / copy-paste
|
---|
| 18 | Fix window resizing to do it from the top left
|
---|
| 19 | Fix combo box items not saving
|
---|
| 20 | Fix status palette not saving position
|
---|
| 21 | Done in 1.0b2:
|
---|
| 22 | Fix missing image problem with autosave (Fix/report bug with NaN)
|
---|
| 23 | Fix window resizing to happen only when underlying image changes size
|
---|
| 24 | Hand cursor for dragging (modulo Cocoa bug)
|
---|
| 25 | Fix every other time you open Preferences, the focus changes
|
---|
| 26 | Done in 1.0b3:
|
---|
| 27 | Switch to CURLHandle
|
---|
| 28 | Done in 1.0b4:
|
---|
| 29 | Lazily create (NS|C)URLHandle
|
---|
| 30 | Fix return in combo box not triggering default button
|
---|
| 31 | Misc. bug fixes
|
---|
| 32 | To do:
|
---|
| 33 | Fix memory leak in libcurl/CURLHandle (emailed Dan Wood 2002.06.30)
|
---|
| 34 | Report bug with aspect ratio being useless
|
---|
| 35 | 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
|
---|
| 36 |
|
---|
| 37 | */
|
---|
| 38 |
|
---|
| 39 | // debugging: change 1 to 0 to disable features
|
---|
| 40 | #define DOCKCAM_DRAW_IMAGE 1
|
---|
| 41 | #define DOCKCAM_CREATE_IMAGE 1
|
---|
| 42 | #define DOCKCAM_DISPLAY_STATUS 1
|
---|
| 43 | #define DOCKCAM_ACCEPT_DATA 1
|
---|
| 44 | #define DOCKCAM_LOAD_DATA 1
|
---|
| 45 |
|
---|
| 46 | #import <CURLHandle/CURLHandle.h>
|
---|
| 47 | #import <CURLHandle/CURLHandle+extras.h>
|
---|
| 48 |
|
---|
| 49 | #import "DockCam.h"
|
---|
| 50 | #import "DockCamStatus.h"
|
---|
| 51 | #import "DockCamPrefs.h"
|
---|
| 52 | #import "NJRVendingImageView.h"
|
---|
| 53 | #import "NJRWindowMagnetism.h"
|
---|
| 54 |
|
---|
| 55 | BOOL successfulLoad;
|
---|
| 56 |
|
---|
| 57 | @implementation DockCam
|
---|
| 58 |
|
---|
| 59 | #pragma mark interface validation
|
---|
| 60 |
|
---|
| 61 | - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem;
|
---|
| 62 | {
|
---|
| 63 | SEL action = [anItem action];
|
---|
| 64 | BOOL loadInProgress = ([imageHandle status] == NSURLHandleLoadInProgress);
|
---|
| 65 | if (action == @selector(reloadImage:)) return !loadInProgress;
|
---|
| 66 | else if (action == @selector(cancelLoading:)) return loadInProgress;
|
---|
| 67 | else if (action == @selector(pauseTimer:) || action == @selector(resumeTimer:)) return delayTimer != nil;
|
---|
| 68 | return YES;
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | #pragma mark timers
|
---|
| 72 |
|
---|
| 73 | - (void)loadImage:(NSTimer *)timer;
|
---|
| 74 | {
|
---|
| 75 | static NSAutoreleasePool *pool = nil;
|
---|
| 76 |
|
---|
| 77 | NSLog(@"loading...");
|
---|
| 78 | delayTimer = nil;
|
---|
| 79 | #if DOCKCAM_ACCEPT_DATA
|
---|
| 80 | [data release]; data = nil;
|
---|
| 81 | #endif
|
---|
| 82 | pool = [[NSAutoreleasePool alloc] init];
|
---|
| 83 | #if DOCKCAM_ACCEPT_DATA
|
---|
| 84 | data = [[NSMutableData alloc] init];
|
---|
| 85 | #endif
|
---|
| 86 | [status setStatus: @"Retrieving imageÉ"];
|
---|
| 87 | #if DOCKCAM_LOAD_DATA
|
---|
| 88 | [imageHandle loadInBackground]; // retains imageHandle until load is complete
|
---|
| 89 | #else
|
---|
| 90 | // fake completion of loading
|
---|
| 91 | [self URLHandleResourceDidFinishLoading: imageHandle];
|
---|
| 92 | #endif
|
---|
| 93 | [pool release]; pool = nil;
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | - (void)scheduleLoadingWithInterval:(NSTimeInterval)interval;
|
---|
| 97 | {
|
---|
| 98 | delayTimer = [NSTimer scheduledTimerWithTimeInterval: interval
|
---|
| 99 | target: self selector: @selector(loadImage:) userInfo: nil
|
---|
| 100 | repeats: NO];
|
---|
| 101 | }
|
---|
| 102 |
|
---|
| 103 | - (void)scheduleLoading;
|
---|
| 104 | {
|
---|
| 105 | [delayTimer invalidate]; delayTimer = nil;
|
---|
| 106 | if ([DockCamPrefs boolForPref: DCImageRefreshEnabled]) {
|
---|
| 107 | if ([imageHandle status] == NSURLHandleLoadInProgress) return;
|
---|
| 108 | [self scheduleLoadingWithInterval: [DockCamPrefs intForPref: DCImageRefreshInterval]];
|
---|
| 109 | }
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | #pragma mark actions
|
---|
| 113 |
|
---|
| 114 | - (IBAction)reloadImage:(id)sender;
|
---|
| 115 | {
|
---|
| 116 | if ([imageHandle status] == NSURLHandleLoadInProgress) return;
|
---|
| 117 | [delayTimer invalidate]; delayTimer = nil;
|
---|
| 118 | [self loadImage: nil];
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | - (IBAction)cancelLoading:(id)sender;
|
---|
| 122 | {
|
---|
| 123 | if ([imageHandle status] != NSURLHandleLoadInProgress) return;
|
---|
| 124 | [imageHandle cancelLoadInBackground];
|
---|
| 125 | }
|
---|
| 126 |
|
---|
| 127 | - (IBAction)pauseTimer:(NSMenuItem *)sender;
|
---|
| 128 | {
|
---|
| 129 | timeLeft = [[delayTimer fireDate] timeIntervalSinceDate: [NSDate date]];
|
---|
| 130 | [delayTimer invalidate];
|
---|
| 131 | [status setStatus: [NSString stringWithFormat: @"Timer paused (%.0f %@)", timeLeft,
|
---|
| 132 | round(timeLeft) == 1 ? @"second remains" : @"seconds remain"]];
|
---|
| 133 | [imageWindow setTitle: @"DockCam (paused)"];
|
---|
| 134 | // XXX pauseResumeItem would be 'sender' if dock menu senders weren't all NSApp
|
---|
| 135 | [pauseResumeItem setAction: @selector(resumeTimer:)];
|
---|
| 136 | [pauseResumeItem setTitle: @"Resume Timer"];
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | - (IBAction)resumeTimer:(NSMenuItem *)sender;
|
---|
| 140 | {
|
---|
| 141 | // XXX pauseResumeItem would be 'sender' if dock menu senders weren't all NSApp
|
---|
| 142 | [self scheduleLoadingWithInterval: timeLeft];
|
---|
| 143 | timeLeft = 0;
|
---|
| 144 | [status setStatus: @"Timer resumed"];
|
---|
| 145 | [imageWindow setTitle: @"DockCam"];
|
---|
| 146 | [pauseResumeItem setAction: @selector(pauseTimer:)];
|
---|
| 147 | [pauseResumeItem setTitle: @"Pause Timer"];
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | #pragma mark preference changes
|
---|
| 151 |
|
---|
| 152 | - (void)locationChanged:(NSNotification *)notification;
|
---|
| 153 | {
|
---|
| 154 | NSString *locationString = [DockCamPrefs stringForPref: DCImageLocation];
|
---|
| 155 | [self cancelLoading: nil];
|
---|
| 156 | [location release]; location = nil;
|
---|
| 157 | if (locationString == nil || [locationString isEqualToString: @""]) {
|
---|
| 158 | [status setStatus: @"No image location specified in Preferences"];
|
---|
| 159 | return;
|
---|
| 160 | }
|
---|
| 161 | locationString = (NSString *)CFURLCreateStringByAddingPercentEscapes(
|
---|
| 162 | NULL, (CFStringRef) locationString, NULL, NULL,
|
---|
| 163 | CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
|
---|
| 164 | location = [[NSURL URLWithString: locationString] retain];
|
---|
| 165 | [locationString release];
|
---|
| 166 | [status setLocation: location];
|
---|
| 167 | [imageHandle release]; imageHandle = nil;
|
---|
| 168 | imageHandle = (CURLHandle *)[location URLHandleUsingCache: NO];
|
---|
| 169 | if (imageHandle != nil) {
|
---|
| 170 | [imageHandle retain];
|
---|
| 171 | [imageHandle addClient: self];
|
---|
| 172 | NSLog(@"handle is %@", imageHandle);
|
---|
| 173 | [self loadImage: nil];
|
---|
| 174 | } else {
|
---|
| 175 | // permanent failure, explicitly do NOT restart timer here
|
---|
| 176 | [status setFailedWithReason: @"Image location (URL) not found, check Preferences"];
|
---|
| 177 | }
|
---|
| 178 | }
|
---|
| 179 |
|
---|
| 180 | - (void)intervalChanged:(NSNotification *)notification;
|
---|
| 181 | {
|
---|
| 182 | [self scheduleLoading];
|
---|
| 183 | }
|
---|
| 184 |
|
---|
| 185 | - (void)showInBackgroundChanged:(NSNotification *)notification;
|
---|
| 186 | {
|
---|
| 187 | [imageWindow setHidesOnDeactivate: ![DockCamPrefs boolForPref: DCShowImageInBackground]];
|
---|
| 188 | }
|
---|
| 189 |
|
---|
| 190 | #pragma mark startup
|
---|
| 191 |
|
---|
| 192 | - (void)startUp;
|
---|
| 193 | {
|
---|
| 194 | NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
---|
| 195 | [status setStatus: @"Starting upÉ"];
|
---|
| 196 | previousImageSize = [DockCamPrefs sizeForPref: DCImageSize];
|
---|
| 197 | [notificationCenter addObserver: self selector: @selector(locationChanged:)
|
---|
| 198 | name: DCPrefChangedNotification
|
---|
| 199 | object: DCImageLocation];
|
---|
| 200 | [notificationCenter addObserver: self selector: @selector(intervalChanged:)
|
---|
| 201 | name: DCPrefChangedNotification
|
---|
| 202 | object: DCImageRefreshEnabled];
|
---|
| 203 | [notificationCenter addObserver: self selector: @selector(intervalChanged:)
|
---|
| 204 | name: DCPrefChangedNotification
|
---|
| 205 | object: DCImageRefreshInterval];
|
---|
| 206 | [notificationCenter addObserver: self selector: @selector(showInBackgroundChanged:)
|
---|
| 207 | name: DCPrefChangedNotification
|
---|
| 208 | object: DCShowImageInBackground];
|
---|
| 209 | [self locationChanged: nil];
|
---|
| 210 | if (location == nil) [self orderFrontPreferencesPanel: self];
|
---|
| 211 | [self showInBackgroundChanged: nil];
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | - (void)awakeFromNib;
|
---|
| 215 | {
|
---|
| 216 | NSArray *autosaveFrame = [[[NSUserDefaults standardUserDefaults] stringForKey: @"NSWindow Frame DockCam"] componentsSeparatedByString: @" "];
|
---|
| 217 | NSRect frame;
|
---|
| 218 | #if !DOCKCAM_DISPLAY_STATUS
|
---|
| 219 | status = nil;
|
---|
| 220 | #endif
|
---|
| 221 | // XXX workaround for bug in 10.1.5 (and earlier?): loading from autosave corrupts frame
|
---|
| 222 | if (autosaveFrame != nil && [autosaveFrame count] == 9) {
|
---|
| 223 | frame.origin.x = [[autosaveFrame objectAtIndex: 0] floatValue];
|
---|
| 224 | frame.origin.y = [[autosaveFrame objectAtIndex: 1] floatValue];
|
---|
| 225 | frame.size.width = [[autosaveFrame objectAtIndex: 2] floatValue];
|
---|
| 226 | frame.size.height = [[autosaveFrame objectAtIndex: 3] floatValue];
|
---|
| 227 | [imageWindow setFrame: frame display: NO];
|
---|
| 228 | }
|
---|
| 229 | [[NJRWindowMagnetism alloc] initForWindow: imageWindow];
|
---|
| 230 | [self performSelector: @selector(startUp) withObject: nil afterDelay: 0];
|
---|
| 231 | }
|
---|
| 232 |
|
---|
| 233 | // XXX workaround for bug in 10.1.5 (and earlier?): loading from autosave corrupts frame
|
---|
| 234 | - (void)windowDidMove:(NSNotification *)aNotification
|
---|
| 235 | {
|
---|
| 236 | [imageWindow saveFrameUsingName: @"DockCam"];
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | - (void)windowDidResize:(NSNotification *)notification;
|
---|
| 240 | {
|
---|
| 241 | [self windowDidMove: notification];
|
---|
| 242 | }
|
---|
| 243 |
|
---|
| 244 | #pragma mark preferences
|
---|
| 245 |
|
---|
| 246 | - (IBAction)orderFrontPreferencesPanel:(id)sender;
|
---|
| 247 | {
|
---|
| 248 | if (!preferencesController) {
|
---|
| 249 | preferencesController = [[DockCamPrefs alloc] init];
|
---|
| 250 | }
|
---|
| 251 | [preferencesController showWindow:self];
|
---|
| 252 | }
|
---|
| 253 |
|
---|
| 254 | #pragma mark URL loading
|
---|
| 255 |
|
---|
| 256 | - (void)URLHandle:(NSURLHandle *)sender resourceDataDidBecomeAvailable:(NSData *)newBytes;
|
---|
| 257 | {
|
---|
| 258 | #if DOCKCAM_ACCEPT_DATA
|
---|
| 259 | [data appendData: newBytes];
|
---|
| 260 | #endif
|
---|
| 261 | }
|
---|
| 262 |
|
---|
| 263 | - (void)URLHandleResourceDidBeginLoading:(NSURLHandle *)sender; {}
|
---|
| 264 |
|
---|
| 265 | - (void)URLHandleResourceDidFinishLoading:(NSURLHandle *)sender;
|
---|
| 266 | {
|
---|
| 267 | #if DOCKCAM_CREATE_IMAGE
|
---|
| 268 | NSImage *scaledImage = nil;
|
---|
| 269 | NSSize scaledSize;
|
---|
| 270 | unsigned int styleMask = [imageWindow styleMask];
|
---|
| 271 | NSRect contentRect = [NSWindow contentRectForFrameRect: [imageWindow frame] styleMask: styleMask];
|
---|
| 272 | NSRect frame;
|
---|
| 273 | image = [[NSImage alloc] initWithData: data];
|
---|
| 274 | #endif
|
---|
| 275 | [data release]; data = nil;
|
---|
| 276 | #if DOCKCAM_CREATE_IMAGE
|
---|
| 277 | if (image == nil) {
|
---|
| 278 | [status setFailedWithReason: @"Image format unrecognized"];
|
---|
| 279 | [self scheduleLoading];
|
---|
| 280 | return;
|
---|
| 281 | }
|
---|
| 282 | scaledSize = imageSize = [image size];
|
---|
| 283 | #if DOCKCAM_DRAW_IMAGE
|
---|
| 284 | if (abs(imageSize.height - imageSize.width) < 1) scaledImage = [image retain];
|
---|
| 285 | else {
|
---|
| 286 | if (imageSize.height > imageSize.width) scaledSize.width = imageSize.height;
|
---|
| 287 | else scaledSize.height = imageSize.width;
|
---|
| 288 | scaledImage = [[NSImage alloc] initWithSize: scaledSize];
|
---|
| 289 | [scaledImage lockFocus];
|
---|
| 290 | [image compositeToPoint: NSMakePoint((scaledSize.width - imageSize.width) / 2.,
|
---|
| 291 | (scaledSize.height - imageSize.height) / 2.)
|
---|
| 292 | operation: NSCompositeCopy];
|
---|
| 293 | [scaledImage unlockFocus];
|
---|
| 294 | }
|
---|
| 295 | [imageView setImage: nil]; // autoreleases image
|
---|
| 296 | contentRect.origin.y += contentRect.size.height - imageSize.height;
|
---|
| 297 | contentRect.size = imageSize;
|
---|
| 298 | frame = [NSWindow frameRectForContentRect: contentRect styleMask: styleMask];
|
---|
| 299 | if (!NSEqualSizes(imageSize, previousImageSize)) {
|
---|
| 300 | [imageWindow setFrame: frame display: NO];
|
---|
| 301 | previousImageSize = imageSize;
|
---|
| 302 | [DockCamPrefs setSize: imageSize forPref: DCImageSize];
|
---|
| 303 | }
|
---|
| 304 | [imageWindow setMaxSize: frame.size];
|
---|
| 305 | [imageView setImage: image];
|
---|
| 306 | [imageView setToolTip: [location description]];
|
---|
| 307 | [NSApp setApplicationIconImage: scaledImage];
|
---|
| 308 | #endif /* DOCKCAM_DRAW_IMAGE */
|
---|
| 309 | [image release];
|
---|
| 310 | [scaledImage release];
|
---|
| 311 | [status setRetrievedWithSize: imageSize];
|
---|
| 312 | #endif /* DOCKCAM_CREATE_IMAGE */
|
---|
| 313 | if (!successfulLoad || ![imageWindow isVisible]) {
|
---|
| 314 | [imageWindow makeKeyAndOrderFront: self];
|
---|
| 315 | successfulLoad = YES;
|
---|
| 316 | }
|
---|
| 317 | [self scheduleLoading];
|
---|
| 318 | }
|
---|
| 319 |
|
---|
| 320 | - (void)URLHandleResourceDidCancelLoading:(NSURLHandle *)sender;
|
---|
| 321 | {
|
---|
| 322 | [status setStatus: @"Image retrieval cancelled"];
|
---|
| 323 | [self scheduleLoading];
|
---|
| 324 | }
|
---|
| 325 |
|
---|
| 326 | - (void)URLHandle:(NSURLHandle *)sender resourceDidFailLoadingWithReason:(NSString *)reason;
|
---|
| 327 | {
|
---|
| 328 | [status setFailedWithReason: reason];
|
---|
| 329 | [self scheduleLoading];
|
---|
| 330 | }
|
---|
| 331 |
|
---|
| 332 | @end
|
---|
| 333 |
|
---|
| 334 | @implementation DockCam (NSWindowDelegate)
|
---|
| 335 |
|
---|
| 336 | - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize;
|
---|
| 337 | {
|
---|
| 338 | NSRect rect = {NSZeroPoint, proposedFrameSize};
|
---|
| 339 | NSSize contentSize = [NSWindow contentRectForFrameRect: rect styleMask: [sender styleMask]].size;
|
---|
| 340 | contentSize.height = contentSize.width * (imageSize.height / imageSize.width);
|
---|
| 341 | rect.size = contentSize;
|
---|
| 342 | return [NSWindow frameRectForContentRect: rect styleMask: [sender styleMask]].size;
|
---|
| 343 | }
|
---|
| 344 |
|
---|
| 345 | - (IBAction)copy:(id)sender;
|
---|
| 346 | {
|
---|
| 347 | [imageView putImageOnPasteboard: [NSPasteboard generalPasteboard]];
|
---|
| 348 | }
|
---|
| 349 |
|
---|
| 350 | @end
|
---|
| 351 |
|
---|
| 352 | @implementation DockCam (NSApplicationNotifications)
|
---|
| 353 |
|
---|
| 354 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
---|
| 355 | {
|
---|
| 356 | [CURLHandle curlHelloSignature:'XxXx' acceptAll:YES];
|
---|
| 357 | }
|
---|
| 358 |
|
---|
| 359 | - (void)applicationWillTerminate:(NSNotification *)aNotification;
|
---|
| 360 | {
|
---|
| 361 | [CURLHandle curlGoodbye];
|
---|
| 362 | }
|
---|
| 363 |
|
---|
| 364 | @end
|
---|
| 365 |
|
---|
| 366 | @implementation DockCam (NSApplicationDelegate)
|
---|
| 367 |
|
---|
| 368 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender;
|
---|
| 369 | {
|
---|
| 370 | return successfulLoad; // window hidden until first succesful load
|
---|
| 371 | }
|
---|
| 372 |
|
---|
| 373 | @end |
---|