source: trunk/Cocoa/Pester/Source/PSAlarms.m@ 468

Last change on this file since 468 was 364, checked in by Nicholas Riley, 17 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.