source: trunk/Cocoa/Pester/Source/PSTimer.m @ 516

Last change on this file since 516 was 364, checked in by Nicholas Riley, 13 years ago

English.lproj/Alarms.nib: Specify alternating row coloring in the nib,
now we're 10.4+.

English.lproj/InfoPlist.strings: Updated for 1.1b6.

English.lproj/Localizable.strings: Quote alarm message in pretty
description (used in tooltip). Change voice error now it no longer
incorporates OSStatus.

English.lproj/MainMenu.nib: Add speech prefs again; turn repetitions
field into a NJRValidatingField and hook up its delegate.

Info-Pester.plist: Updated for 1.1b6.

NJRHotKey.m: Switch to new Objective-C exception style.

NJRIntervalField.[hm]: Now a subclass of NJRValidatingField.

NJRTableDelegate.m: Get rid of our own tooltip support as NSTableView
now supports them (though with a minor visual glitch on the first
tooltip).

NJRTableView.[hm]: Remove tooltip support. Remove alternating row
coloring support.

NJRValidatingField.[hm]: Contains validation sheet stuff from
NJRIntervalField.

NJRVoicePopUpButton.[hm]: Switch to NSSpeechSynthesizer.

PSAlarm.m: Quote alarm message in pretty description (used in
tooltip). Fix repeating alarms not restoring as repeating if they
didn't expire while Pester was not running. No longer set timer on
Pester 1.0 alarm import, to help make importing atomic.

PSAlarmSetController.[hm]: Use NJRValidatingField for repetitions
field. Switch to new Objective-C exception style. Fix validation
issues on in/at changing. Temporary changes to restore speech support
and allow the sound popup to be removed entirely from the nib (rather
than being dragged out of the visible area, as it was in 1.1b5).
Changes for NSSpeechSynthesizer, which uses different voice names.

PSAlarms.m: Switch to new Objective-C exception style. Fix
duplication and error handling in Pester 1.0 alarm import, making
atomic.

PSAlarmsController.m: Use new tooltip support (since it's implemented
in the delegate rather than the data source, we have to proxy it).

PSAlerts.m: Wrap initialization in exception block so we don't leak.

PSApplication.m: Switch to new Objective-C exception style.

PSMediaAlert.m: Clamp repetitions at 1..99 so the user can't type an
invalid value, then quit and have it saved.

PSSpeechAlert.[hm]: Switch to NSSpeechSynthesizer. Throw an
intelligible exception if the voice is unavailable.

PSTimer.m: Switch to new Objective-C exception style.

Pester.xcodeproj: Remove VERSION generation; rename targets to be more
understandable.

Read Me.rtfd: Updated for 1.1b6.

SUSpeaker.[hm]: Gone in switch to NSSpeechSynthesizer.

VERSION: Gone - we use agvtool for everything now.

Updates/release-notes.html: Updated for 1.1b6.

Updates/updates.xml: Updated for 1.1b6.

package-Pester.sh: Use agvtool to get version. Atomically update
file on Web server to avoid partial downloads.

File size: 7.3 KB
Line 
1//
2//  PSTimer.m
3//  Pester
4//
5//  Created by Nicholas Riley on Sun Jan 05 2003.
6//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
7//
8
9#import "PSTimer.h"
10#import "PSAlarm.h"
11#import "PSPowerManager.h"
12
13NSTimer *PSTimerCurrent = nil;
14PSTimer *PSTimerOnWake = nil;
15NSMutableArray *PSTimerAllTimers = nil;
16
17@interface PSTimer (Private)
18+ (void)_schedule;
19@end
20
21@implementation PSTimer
22
23+ (void)setUp;
24{
25    static PSPowerManager *powerManager;
26
27    if (powerManager == nil) {
28        powerManager = [[PSPowerManager alloc] initWithDelegate: self];
29        PSTimerAllTimers = [[NSMutableArray alloc] init];
30    }
31}
32
33+ (void)_schedule;
34{
35    NSDate *aboutNow = [NSDate dateWithTimeIntervalSinceNow: 0.1];
36    [PSTimerCurrent invalidate]; [PSTimerCurrent release]; PSTimerCurrent = nil;
37    PSTimerOnWake = nil;
38    if ([PSTimerAllTimers count] > 0) {
39        PSTimer *timer = nil;
40        NSEnumerator *e;
41        [PSTimerAllTimers sortUsingSelector: @selector(compare:)];
42        // NSLog(@"_schedule: timers %@", [PSTimerAllTimers description]);
43        e = [PSTimerAllTimers objectEnumerator];
44        while ( (timer = [e nextObject]) != nil) {
45            if ([timer isWakeUp]) {
46                PSTimerOnWake = timer;
47                // NSLog(@"scheduling wake timer %@", timer);
48                break;
49            }
50        }
51        e = [PSTimerAllTimers objectEnumerator];
52        while ( (timer = [e nextObject]) != nil) {
53            if ([[timer fireDate] compare: aboutNow] != NSOrderedDescending) {
54                [timer performSelector: @selector(_timerExpired) withObject: nil afterDelay: 0];
55                return;
56            } else {
57                NSTimeInterval ti = [[timer fireDate] timeIntervalSinceNow];
58                if (ti > 0.1) {
59                    PSTimerCurrent = [[NSTimer scheduledTimerWithTimeInterval: ti target: timer selector: @selector(_timerExpired) userInfo: nil repeats: NO] retain];
60                    // NSLog(@"setting timer: %@", PSTimerCurrent);
61                } else {
62                    // NSLog(@"timer would have been too fast, setting: %@", timer);
63                    [timer performSelector: @selector(_timerExpired) withObject: nil afterDelay: 0];
64                }
65                return;
66            }
67        }
68        NSAssert(NO, @"shouldn't get here");
69    } else {
70        // NSLog(@"_schedule: no timers");
71    }
72}
73
74+ (void)_timerAdded:(PSTimer *)timer;
75{
76    NSAssert1([PSTimerAllTimers indexOfObject: timer] == NSNotFound, @"PSTimerAllTimers already contains %@", timer);
77    [PSTimerAllTimers addObject: timer];
78    [self _schedule];
79}
80
81+ (void)_timerDeleted:(PSTimer *)timer;
82{
83    NSAssert1([PSTimerAllTimers indexOfObject: timer] != NSNotFound, @"PSTimerAllTimers does not contain %@", timer);
84    [PSTimerAllTimers removeObject: timer];
85    [self _schedule];
86}
87
88#pragma mark private
89
90- (void)_setFireDate:(NSDate *)date;
91{
92    if (fireDate != date) {
93        [fireDate release];
94        fireDate = [date retain];
95        if (fireDate != nil) {
96            isValid = YES;
97        }
98    }
99}
100
101- (void)_setFireDateFromInterval;
102{
103    [self _setFireDate: [NSDate dateWithTimeIntervalSinceNow: timeInterval]];
104}
105
106- (void)_invalidate;
107{
108    if (isValid) {
109        isValid = NO;
110        [[self class] _timerDeleted: self];
111    }
112}
113
114- (void)_timerExpired;
115{
116    if (!isValid) return; // in case the timer went off after we were invalidated
117    [self retain]; // make sure we’re still accessible during the invocation
118    // NSLog(@"timer expired: %@", self);
119    if (repeats) {
120        [invocation invoke];
121        if (isValid) {
122            [self _setFireDateFromInterval];
123            [[self class] _schedule];
124            // NSLog(@"timer repeats: %@", self);
125        }
126    } else {
127        [self _invalidate];
128        [invocation invoke];
129    }
130    [self release];
131}
132
133#pragma mark initialize-release
134
135- (id)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)anObject repeats:(BOOL)yesOrNo;
136{
137    if ( (self = [self init]) != nil) {
138        invocation = [[NSInvocation invocationWithMethodSignature:
139            [aTarget methodSignatureForSelector: aSelector]] retain];
140        [invocation setSelector: aSelector];
141        [invocation setTarget: aTarget];
142        [invocation setArgument: &self atIndex: 2];
143        userInfo = [anObject retain];
144        repeats = yesOrNo;
145        timeInterval = ti;
146        // don't do this or we leak: [invocation retainArguments];
147        [aTarget retain]; // mimics retain behavior
148        [self _setFireDateFromInterval];
149        [[self class] _timerAdded: self]; // mimics runloop retention behavior
150    }
151    return self;
152}
153
154+ (PSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)anObject repeats:(BOOL)yesOrNo;
155{
156    PSTimer *timer = [[self alloc] initWithTimeInterval: ti target: aTarget selector: aSelector userInfo: anObject repeats: yesOrNo];
157    [timer release];
158    return timer;
159}
160
161- (void)dealloc;
162{
163    // NSLog(@"DEALLOC %@", self);
164    isValid = NO;
165    [fireDate release]; fireDate = nil;
166    [[invocation target] release];
167    [invocation release]; invocation = nil;
168    [userInfo release]; userInfo = nil;
169    [super dealloc];
170}
171
172#pragma mark accessing
173
174- (NSDate *)fireDate;
175{
176    return fireDate;
177}
178
179- (void)invalidate;
180{
181    repeats = NO;
182    [self _invalidate];
183}
184
185- (BOOL)isValid;
186{
187    return isValid;
188}
189
190- (id)userInfo;
191{
192    return userInfo;
193}
194
195- (BOOL)isWakeUp;
196{
197    return isWakeUp;
198}
199
200- (void)setWakeUp:(BOOL)doWake;
201{
202    isWakeUp = doWake;
203}
204
205- (NSComparisonResult)compare:(PSTimer *)other;
206{
207    return [fireDate compare: [other fireDate]];
208}
209
210- (NSString *)description;
211{
212    return [NSString stringWithFormat: @"%@: at %@ do %@ on %@", [super description], [self fireDate], NSStringFromSelector([invocation selector]), [[invocation target] class]];
213}
214
215@end
216
217@implementation PSTimer (PSPowerManagerDelegate)
218
219+ (void)_runScheduledWakeErrorPanel:(NSString *)error;
220{
221    NSRunAlertPanel(NSLocalizedString(@"Can't schedule wake from sleep", "Wake timer set failure panel title"), NSLocalizedString(@"Pester is unable to set this computer to wake up at a later date (%@)", "Wake timer set failure panel message"), NSLocalizedString(@"Sleep", "Wake timer set failure panel button"), nil, nil, error);
222}
223
224+ (BOOL)powerManagerShouldIdleSleep:(PSPowerManager *)powerManager;
225{
226    [PSTimerCurrent invalidate];
227    if (PSTimerOnWake != nil) {
228        NSDate *date = [PSTimerOnWake fireDate];
229        // NSLog(@"%lf sec remain until alarm", [date timeIntervalSinceNow]);
230        if ([date timeIntervalSinceNow] > 30) {
231            // NSLog(@"going to sleep, setting timer %@", PSTimerOnWake);
232            @try {
233                [PSPowerManager setWakeTime: [[PSTimerOnWake fireDate] addTimeInterval: -15]];
234            } @catch (NSException *exception) {
235                [self performSelectorOnMainThread: @selector(_runScheduledWakeErrorPanel:)
236                                       withObject: [exception description] waitUntilDone: YES];
237            }
238            return YES;
239        } else {
240            // NSLog(@"not setting timer, will attempt to prevent idle sleep");
241            return NO;
242        }
243    }
244    return YES;
245}
246
247+ (void)powerManagerWillDemandSleep:(PSPowerManager *)powerManager;
248{
249    [self powerManagerShouldIdleSleep: powerManager];
250}
251
252+ (void)powerManagerDidWake:(PSPowerManager *)powerManager;
253{
254    if (PSTimerCurrent != nil) {
255        [self _schedule];
256    }
257}
258
259@end
Note: See TracBrowser for help on using the repository browser.