source: trunk/Cocoa/Pester/Source/PSAlarm.m @ 43

Last change on this file since 43 was 43, checked in by Nicholas Riley, 19 years ago

Pester 1.1a1.

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

English.lproj/MainMenu.nib: Placeholder for day names in popup menu, fixed up by code (this means you can still edit it from IB though). Added command-shift-T to both in/at cells (required, code removes one or the other as appropriate). Fixed up sizes of fields. Default to today (this will need fixing when we localized the word "today", but it's fine for now...).

English.lproj/Notifier.nib: Remove date formatter because we set a string directly now instead (could set formatter from code, but we don't).

NJRDateFormatter: many workarounds for Cocoa bugs: missing AM/PM, incorrect results with space before AM/PM, etc. Added class methods to do format manipulation and return localized formats which work for output (though not always for input; this class has an internal workaround for the AM/PM problem).

NJRFSObjectSelector: properly handle enabled attribute, store internally and report externally as appropriate. Previously, the button would become enabled if you dropped something on it even if it was supposed to be disabled.

NJRQTMediaPopUpButton: stop sound preview when button disabled.

NJRVoicePopUpButton: stop voice preview when button disabled.

PSAlarm: new method -dateString returns long date string. Maintain local copy of long date, short date and time formats, and locale, using NJRDateFormatter.

PSAlarmNotifierController: update to use -[PSAlarm dateString], -[PSAlarm timeString] for localization instead of using broken formatter.

PSAlarmSetController: update documentation for some more Cocoa bugs I need to file. Set time of day and date formatters with localized date formats from NJRDateFormatter (retain/release issue here?) Localize weekday popup for predefined dates. Localize static date display with NJRDateFormatter. Note a solution (thanks to Douglas Davidson) for figuring out which control is editing. Added command-shift-T key equivalent to toggle in/at. Properly work around bugs witih soundRepetitionCount flashing, except where it's impossible to do anything else.

Read Me.rtfd: Updated for 1.1a1.

VERSION: Updated for 1.1a1.

File size: 9.6 KB
Line 
1//
2//  PSAlarm.m
3//  Pester
4//
5//  Created by Nicholas Riley on Wed Oct 09 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "PSAlarm.h"
10#import "PSAlert.h"
11#import "NJRDateFormatter.h"
12
13NSString * const PSAlarmTimerSetNotification = @"PSAlarmTimerSetNotification";
14NSString * const PSAlarmTimerExpiredNotification = @"PSAlarmTimerExpiredNotification";
15
16static NSString *dateFormat, *shortDateFormat, *timeFormat;
17static NSDictionary *locale;
18
19// XXX need to reset pending alarms after sleep, they "freeze" and never expire.
20
21@implementation PSAlarm
22
23+ (void)initialize; // XXX change on locale modification, subscribe to NSNotifications
24{
25    dateFormat = [[NJRDateFormatter localizedDateFormatIncludingWeekday: YES] retain];
26    shortDateFormat = [[NJRDateFormatter localizedShortDateFormatIncludingWeekday: NO] retain];
27    timeFormat = [[NJRDateFormatter localizedTimeFormatIncludingSeconds: YES] retain];
28    locale = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] retain];
29}
30
31- (void)dealloc;
32{
33    // NSLog(@"DEALLOC %@", self);
34    alarmType = PSAlarmInvalid;
35    [alarmDate release]; alarmDate = nil;
36    [alarmMessage release]; alarmMessage = nil;
37    [invalidMessage release]; invalidMessage = nil;
38    [timer invalidate]; [timer release]; timer = nil;
39    [alerts release]; alerts = nil;
40    [super dealloc];
41}
42
43- (void)_setAlarmDate:(NSCalendarDate *)aDate;
44{
45    if (alarmDate != aDate) {
46        [alarmDate release];
47        alarmDate = nil;
48        alarmDate = [aDate retain];
49    }
50}
51
52- (void)_invalidate:(NSString *)aMessage;
53{
54    alarmType = PSAlarmInvalid;
55    if (aMessage != invalidMessage) {
56        [invalidMessage release];
57        invalidMessage = nil;
58        [self _setAlarmDate: nil];
59        alarmInterval = 0;
60        invalidMessage = [aMessage retain];
61    }
62}
63
64- (void)_validateForType:(PSAlarmType)type;
65{
66    if (alarmType == PSAlarmSet) return; // already valid
67    [invalidMessage release];
68    invalidMessage = nil;
69    alarmType = type;
70}
71
72- (void)_setDateFromInterval;
73{
74    [alarmDate release]; alarmDate = nil;
75    alarmDate = [NSCalendarDate dateWithTimeIntervalSinceNow: alarmInterval];
76    [alarmDate retain];
77    [self _validateForType: PSAlarmInterval];
78}
79
80- (void)setInterval:(NSTimeInterval)anInterval;
81{
82    alarmInterval = anInterval;
83    if (alarmInterval <= 0) {
84        [self _invalidate: @"Please specify an alarm interval."]; return;
85    }
86    [self _setDateFromInterval];
87}
88
89- (void)_setIntervalFromDate;
90{
91    alarmInterval = [alarmDate timeIntervalSinceNow] + 1;
92    if (alarmInterval <= 0) {
93        [self _invalidate: @"Please specify an alarm time in the future."];
94        return;
95    }
96    [self _validateForType: PSAlarmDate];
97}
98
99- (void)setForDateAtTime:(NSCalendarDate *)dateTime;
100{
101    [self _setAlarmDate: dateTime];
102    [self _setIntervalFromDate];
103}
104
105- (void)setForDate:(NSDate *)date atTime:(NSDate *)time;
106{
107    NSCalendarDate *calTime, *calDate;
108    if (time == nil && date == nil) {
109        [self _invalidate: @"Please specify an alarm date and time."]; return;
110    }
111    if (time == nil) {
112        [self _invalidate: @"Please specify an alarm time."]; return;
113    }
114    if (date == nil) {
115        [self _invalidate: @"Please specify an alarm date."]; return;
116    }
117    // XXX if calTime's date is different from the default date, complain
118    calTime = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate: [time timeIntervalSinceReferenceDate]];
119    calDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate: [date timeIntervalSinceReferenceDate]];
120    if (calTime == nil || calDate == nil) {
121        [self _invalidate: @"Please specify a reasonable date and time."];
122    }
123    [self setForDateAtTime:
124        [[[NSCalendarDate alloc] initWithYear: [calDate yearOfCommonEra]
125                                        month: [calDate monthOfYear]
126                                          day: [calDate dayOfMonth]
127                                         hour: [calTime hourOfDay]
128                                       minute: [calTime minuteOfHour]
129                                       second: [calTime secondOfMinute]
130                                     timeZone: nil] autorelease]];
131}
132
133- (BOOL)isValid;
134{
135    if (alarmType == PSAlarmDate) [self _setIntervalFromDate];
136    return (alarmType != PSAlarmInvalid);
137}
138
139- (void)setMessage:(NSString *)aMessage;
140{
141    if (aMessage != alarmMessage) {
142        [alarmMessage release];
143        alarmMessage = nil;
144        alarmMessage = [aMessage retain];
145    }
146}
147
148- (NSString *)message;
149{
150    if (alarmMessage == nil || [alarmMessage isEqualToString: @""])
151        return @"Alarm!";
152    return alarmMessage;   
153}
154
155- (NSString *)invalidMessage;
156{
157    if (invalidMessage == nil) return @"";
158    return invalidMessage;
159}
160
161- (NSCalendarDate *)date;
162{
163    if (alarmType == PSAlarmInterval) [self _setDateFromInterval];
164    return alarmDate;
165}
166
167- (NSString *)dateString;
168{
169    return [[self date] descriptionWithCalendarFormat: dateFormat locale: locale];
170}
171
172- (NSString *)shortDateString;
173{
174    return [[self date] descriptionWithCalendarFormat: shortDateFormat locale: locale];
175}
176
177- (NSString *)timeString;
178{
179    return [[self date] descriptionWithCalendarFormat: timeFormat locale: locale];
180}
181
182- (NSString *)timeRemainingString;
183{
184    static const unsigned long long minute = 60, hour = minute * 60, day = hour * 24, year = day * 365.26;
185    unsigned long long interval = [self interval];
186    // +[NSString stringWithFormat:] in 10.1 does not support long longs: work around it by converting to unsigned ints or longs for display
187    if (interval == 0) return @"ÇexpiredÈ";
188    if (interval < minute) return [NSString stringWithFormat: @"%us", (unsigned)interval];
189    if (interval < day) return [NSString stringWithFormat: @"%uh %um", (unsigned)(interval / hour), (unsigned)((interval % hour) / minute)];
190    if (interval < year) return [NSString stringWithFormat: @"%u days", (unsigned)(interval / day)];
191    if (interval < 2 * year) return @"One year";
192    return [NSString stringWithFormat: @"%lu years", (unsigned long)(interval / year)];
193}
194
195- (NSTimeInterval)interval;
196{
197    if (alarmType == PSAlarmSet || alarmType == PSAlarmDate) [self _setIntervalFromDate];
198    return alarmInterval;
199}
200
201- (BOOL)setTimer;
202{
203    switch (alarmType) {
204        case PSAlarmDate: if (![self isValid]) return NO;
205        case PSAlarmInterval:
206            timer = [NSTimer scheduledTimerWithTimeInterval: alarmInterval
207                                                     target: self
208                                                   selector: @selector(_timerExpired:)
209                                                   userInfo: nil
210                                                    repeats: NO];
211            if (timer != nil) {
212                [timer retain];
213                alarmType = PSAlarmSet;
214                [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmTimerSetNotification object: self];
215                return YES;
216            }
217        default:
218            return NO;
219    }
220}
221
222- (void)cancel;
223{
224    [timer invalidate]; [timer release]; timer = nil;
225}
226
227- (void)_timerExpired:(NSTimer *)aTimer;
228{
229    [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmTimerExpiredNotification object: self];
230    [timer release]; timer = nil;
231}
232
233- (NSString *)_alarmTypeString;
234{
235    switch (alarmType) {
236        case PSAlarmDate: return @"PSAlarmDate";
237        case PSAlarmInterval: return @"PSAlarmInterval";
238        case PSAlarmSet: return @"PSAlarmSet";
239        case PSAlarmInvalid: return @"PSAlarmInvalid";
240        default: return [NSString stringWithFormat: @"<unknown: %u>", alarmType];
241    }
242}
243
244- (NSComparisonResult)compare:(PSAlarm *)otherAlarm;
245{
246    return [[self date] compare: [otherAlarm date]];
247}
248
249- (void)addAlert:(PSAlert *)alert;
250{
251    if (alerts == nil) alerts = [[NSMutableArray alloc] initWithCapacity: 4];
252    [alerts addObject: alert];
253}
254
255- (void)removeAlerts;
256{
257    [alerts removeAllObjects];
258}
259
260- (NSArray *)alerts;
261{
262    return [[alerts copy] autorelease];
263}
264
265- (NSString *)description;
266{
267    return [NSString stringWithFormat: @"%@: type %@ date %@ interval %.1f%@",
268        [super description], [self _alarmTypeString], alarmDate, alarmInterval,
269        (alarmType == PSAlarmInvalid ?
270         [NSString stringWithFormat: @"\ninvalid message: %@", invalidMessage]
271        : (alarmType == PSAlarmSet ?
272           [NSString stringWithFormat: @"\ntimer: %@", timer] : @""))];
273}
274
275- (void)encodeWithCoder:(NSCoder *)coder;
276{
277    if (![self isValid]) return;
278    [coder encodeValueOfObjCType: @encode(PSAlarmType) at: &alarmType];
279    switch (alarmType) {
280        case PSAlarmDate:
281        case PSAlarmSet:
282            [coder encodeObject: alarmDate];
283            break;
284        case PSAlarmInterval:
285            [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &alarmInterval];
286            break;
287        default:
288            break;
289    }
290    [coder encodeObject: alarmMessage];
291    // NSLog(@"encoded: %@", self); // XXX happening twice, gdb refuses to show proper backtrace, grr
292    return;
293}
294
295- (id)initWithCoder:(NSCoder *)coder;
296{
297    if ( (self = [super init]) != nil) {
298        [coder decodeValueOfObjCType: @encode(PSAlarmType) at: &alarmType];
299        switch (alarmType) {
300            case PSAlarmDate:
301            case PSAlarmSet:
302                [self _setAlarmDate: [coder decodeObject]];
303                break;
304            case PSAlarmInterval:
305                [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &alarmInterval];
306                break;
307            default:
308                break;
309        }
310        [self setMessage: [coder decodeObject]];
311        if (alarmType == PSAlarmSet) {
312            alarmType = PSAlarmDate;
313            [self setTimer];
314        }
315    }
316    return self;
317}
318
319@end
Note: See TracBrowser for help on using the repository browser.