source: trunk/Cocoa/DockCam/DockCam.m@ 135

Last change on this file since 135 was 5, checked in by Nicholas Riley, 22 years ago

Initial import.

File size: 12.4 KB
Line 
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
55BOOL 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
Note: See TracBrowser for help on using the repository browser.