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 |
---|