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

Last change on this file since 394 was 143, checked in by Nicholas Riley, 17 years ago

DockCam?.m: Updated for CURLHandle 1.7. Still leaking like a sieve.

DockCamStatus?.m: Removed an extraneous character which interfered with
compilation.

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.