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