source: trunk/Cocoa/Pester/Source/PSAlarms.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: 9.8 KB
Line 
1//
2//  PSAlarms.m
3//  Pester
4//
5//  Created by Nicholas Riley on Fri Oct 11 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "PSAlarms.h"
10#import "PSAlarm.h"
11#import "PSTimer.h"
12#import "NSDictionary-NJRExtensions.h"
13
14NSString * const PSAlarmImportException = @"PSAlarmImportException";
15
16NSString * const PSAlarmsDidChangeNotification = @"PSAlarmsDidChangeNotification";
17NSString * const PSAlarmsNextAlarmDidChangeNotification = @"PSAlarmsNextAlarmDidChangeNotification";
18
19// NSUserDefaults key
20static NSString * const PSPendingAlarms = @"Pester pending alarms"; // 1.0 Ð 1.1a3
21static NSString * const PSAllAlarms = @"Pester alarms"; // 1.1a4 Ð
22
23// property list keys
24static NSString * const PLAlarmsPending = @"pending";
25static NSString * const PLAlarmsExpired = @"expired";
26
27static PSAlarms *PSAlarmsAllAlarms = nil;
28
29@interface PSAlarms (Private)
30
31- (void)_updateNextAlarm;
32
33@end
34
35@implementation PSAlarms
36
37+ (void)setUp;
38{
39    [PSTimer setUp];
40    if (PSAlarmsAllAlarms == nil) {
41        NSDictionary *plAlarms = [[NSUserDefaults standardUserDefaults] objectForKey: PSAllAlarms];
42        if (plAlarms == nil) {
43            PSAlarmsAllAlarms = [[self alloc] init];
44        } else {
45            PSAlarmsAllAlarms = [[self alloc] initWithPropertyList: plAlarms];
46        }
47        [PSAlarmsAllAlarms _updateNextAlarm]; // only generate notifications after singleton established
48    }
49}
50
51+ (PSAlarms *)allAlarms;
52{
53    NSAssert(PSAlarmsAllAlarms != nil, @"Attempt to use +[PSAlarms allAlarms] before setup complete");
54    return PSAlarmsAllAlarms;
55}
56
57#pragma mark private
58
59- (void)_changed;
60{
61    NSMutableArray *alarmsData = [[NSMutableArray alloc] init];
62    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
63    [self _updateNextAlarm];
64    // NSLog(@"PSAlarms _changed:\n%@", alarms);
65    [defaults setObject: [self propertyListRepresentation] forKey: PSAllAlarms];
66    [defaults synchronize];
67    [alarmsData release];
68    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsDidChangeNotification object: self];
69}
70
71- (void)_alarmTimerExpired:(NSNotification *)notification;
72{
73    PSAlarm *alarm = [notification object];
74    NSLog(@"timer expired: %@ retainCount %d", alarm, [alarm retainCount]);
75    [expiredAlarms addObject: alarm];
76    NSLog(@"expired alarms: %@", [expiredAlarms description]);
77    [alarms removeObject: alarm];
78    [self _changed];
79}
80
81- (void)_alarmTimerSet:(NSNotification *)notification;
82{
83    PSAlarm *alarm = [notification object];
84    NSLog(@"timer set: %@ retainCount %d", alarm, [alarm retainCount]);
85    [alarms addObject: alarm];
86    [expiredAlarms removeObject: alarm];
87    [self _changed];
88}
89
90- (void)_alarmDied:(NSNotification *)notification;
91{
92    PSAlarm *alarm = [notification object];
93    // NSLog(@"alarm died: %@ retainCount %d", alarm, [alarm retainCount]);
94    [alarms removeObject: alarm];
95    [expiredAlarms removeObject: alarm];
96    [self _changed];
97}
98
99- (void)_updateNextAlarm;
100{
101    NSEnumerator *e;
102    PSAlarm *alarm, *oldNextAlarm = nextAlarm;
103    [nextAlarm release];
104    nextAlarm = nil;
105    // sort alarms so earliest is first
106    [alarms sortUsingSelector: @selector(compareDate:)];
107    // find first un-expired alarm
108    e = [alarms objectEnumerator];
109    while ( (alarm = [e nextObject]) != nil) {
110        if ([alarm isValid]) {
111            nextAlarm = [alarm retain];
112            break;
113        }
114    }
115    if (oldNextAlarm != nextAlarm)
116        [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsNextAlarmDidChangeNotification object: nextAlarm];
117}
118
119- (void)_setUpNotifications;
120{
121    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerSet:) name: PSAlarmTimerSetNotification object: nil];
122    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerExpired:) name: PSAlarmTimerExpiredNotification object: nil];
123    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmDied:) name: PSAlarmDiedNotification object: nil];
124}
125
126#pragma mark initialize-release
127
128- (id)init;
129{
130    if ( (self = [super init]) != nil) {
131        alarms = [[NSMutableArray alloc] init];
132        expiredAlarms = [[NSMutableSet alloc] init];
133        [self _setUpNotifications];
134    }
135    return self;
136}
137
138- (void)dealloc;
139{
140    [alarms release];
141    [expiredAlarms release];
142    [[NSNotificationCenter defaultCenter] removeObserver: self];
143    [super dealloc];
144}
145
146#pragma mark accessing
147
148- (NSArray *)alarms;
149{
150    return alarms;
151}
152
153- (PSAlarm *)nextAlarm;
154{
155    return nextAlarm;
156}
157
158- (int)alarmCount;
159{
160    return [alarms count];
161}
162
163- (PSAlarm *)alarmAtIndex:(int)index;
164{
165    return [alarms objectAtIndex: index];
166}
167
168- (void)removeAlarmAtIndex:(int)index;
169{
170    [(PSAlarm *)[alarms objectAtIndex: index] cancelTimer];
171    [alarms removeObjectAtIndex: index];
172}
173
174- (void)removeAlarmsAtIndices:(NSArray *)indices;
175{
176    NSEnumerator *e = [indices objectEnumerator];
177    NSNumber *n;
178    unsigned indexCount = [indices count], i = 0, alarmIndex;
179    unsigned *indexArray = (unsigned *)malloc(indexCount * sizeof(unsigned));
180    @try {
181        while ( (n = [e nextObject]) != nil) {
182            alarmIndex = [n intValue];
183            [(PSAlarm *)[alarms objectAtIndex: alarmIndex] cancelTimer];
184            indexArray[i] = alarmIndex;
185            i++;
186        }
187        [alarms removeObjectsFromIndices: indexArray numIndices: indexCount];
188    } @finally {
189        free(indexArray); indexArray = NULL;
190        [self _changed];
191    }
192}
193
194- (void)removeAlarms:(NSSet *)alarmsToRemove;
195{
196    NSEnumerator *e = [alarms objectEnumerator];
197    PSAlarm *alarm;
198    NSMutableArray *indices = [NSMutableArray arrayWithCapacity: [alarmsToRemove count]];
199    int alarmIndex = 0;
200
201    while ( (alarm = [e nextObject]) != nil) {
202        if ([alarmsToRemove containsObject: alarm])
203            [indices addObject: [NSNumber numberWithInt: alarmIndex]];
204        alarmIndex++;
205    }
206    [self removeAlarmsAtIndices: indices];
207}
208
209- (void)restoreAlarms:(NSSet *)alarmsToRestore;
210{
211    [alarmsToRestore makeObjectsPerformSelector: @selector(resetTimer)];
212}
213
214- (BOOL)alarmsExpiring;
215{
216    return [expiredAlarms count] != 0;
217}
218
219#pragma mark printing
220
221- (NSString *)description;
222{
223    return [NSString stringWithFormat: @"%@ pending %@\n%@\n",
224        [super description], alarms,
225        [expiredAlarms count] > 0 ? [NSString stringWithFormat: @"expired %@\n", expiredAlarms]
226                                  : @""];
227}
228
229#pragma mark property list serialization (Pester 1.1)
230
231- (NSDictionary *)propertyListRepresentation;
232{
233    NSMutableArray *plPendingAlarms = [[NSMutableArray alloc] initWithCapacity: [alarms count]];
234    NSMutableArray *plExpiredAlarms = [[NSMutableArray alloc] initWithCapacity: [expiredAlarms count]];
235    NSDictionary *plAllAlarms, *plAlarm;
236    NSEnumerator *e;
237    PSAlarm *alarm;
238
239    e = [alarms objectEnumerator];
240    while ( (alarm = [e nextObject]) != nil) {
241        plAlarm = [alarm propertyListRepresentation];
242        if (plAlarm != nil)
243            [plPendingAlarms addObject: plAlarm];
244    }
245
246    e = [expiredAlarms objectEnumerator];
247    while ( (alarm = [e nextObject]) != nil) {
248        plAlarm = [alarm propertyListRepresentation];
249        if (plAlarm != nil)
250            [plExpiredAlarms addObject: plAlarm];
251    }
252   
253    plAllAlarms = [NSDictionary dictionaryWithObjectsAndKeys:
254        plPendingAlarms, PLAlarmsPending, plExpiredAlarms, PLAlarmsExpired, nil];
255    [plPendingAlarms release];
256    [plExpiredAlarms release];
257
258    return plAllAlarms;
259}
260
261- (id)initWithPropertyList:(NSDictionary *)dict;
262{
263    if ( (self = [super init]) != nil) {
264        NSArray *plPendingAlarms = [dict objectForRequiredKey: PLAlarmsPending];
265        NSArray *plExpiredAlarms = [dict objectForRequiredKey: PLAlarmsExpired];
266        NSEnumerator *e;
267        NSDictionary *plAlarm;
268        PSAlarm *alarm;
269
270        alarms = [[NSMutableArray alloc] initWithCapacity: [plPendingAlarms count]];
271        e = [plPendingAlarms objectEnumerator];
272        while ( (plAlarm = [e nextObject]) != nil) {
273            alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm];
274            [alarms addObject: alarm];
275            [alarm release];
276        }
277
278        e = [plExpiredAlarms objectEnumerator];
279        while ( (plAlarm = [e nextObject]) != nil) {
280            // expired alarms may be ready for deletion, or may repeat - if the latter, PSAlarm will reschedule the alarm so the repeat interval begins at restoration time.
281            if ( (alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm]) != nil) {
282                [alarms addObject: alarm];
283                [alarm release];
284            }
285        }
286        expiredAlarms = [[NSMutableSet alloc] init];
287       
288        [self _setUpNotifications];
289    }
290    return self;
291}
292
293#pragma mark archiving (Pester 1.0)
294
295- (unsigned)countOfVersion1Alarms;
296{
297    return [[[NSUserDefaults standardUserDefaults] objectForKey: PSPendingAlarms] count];
298}
299
300- (void)discardVersion1Alarms;
301{
302    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
303    [defaults removeObjectForKey: PSPendingAlarms];
304    [defaults synchronize];
305}
306
307- (void)importVersion1Alarms;
308{
309    NSArray *alarmsData = [[NSUserDefaults standardUserDefaults] arrayForKey: PSPendingAlarms];
310    NSEnumerator *e = [alarmsData objectEnumerator];
311    NSData *alarmData;
312    PSAlarm *alarm;
313    NSMutableArray *importedAlarms = [[NSMutableArray alloc] initWithCapacity: [alarmsData count]];
314    @try {
315        while ( (alarmData = [e nextObject]) != nil) {
316            alarm = [NSUnarchiver unarchiveObjectWithData: alarmData];
317            if (alarm == nil)
318                @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: @"Failed to decode Pester 1.0 alarm." userInfo: nil];
319            [importedAlarms addObject: alarm];
320            if (![alarm setTimer]) // expired
321                [alarms addObject: alarm];
322        }
323    } @catch (NSException *exception) {
324        [self removeAlarms: [NSSet setWithArray: importedAlarms]];
325        @throw;
326    } @finally {
327        [importedAlarms release];
328    }
329}
330
331@end
Note: See TracBrowser for help on using the repository browser.