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

Last change on this file since 51 was 51, checked in by Nicholas Riley, 21 years ago

Alarms.nib: Removed horizontal scroll bar. Turned on grid. Set delegate to NJRTableDelegate instead of PSAlarmSetController.

NJRTableDelegate: In general, made functional (was previously unused). Fixed MyCompanyName. Changed ORDER_BY_CONTEXT to use key-value coding instead of assuming data consists of a dictionary of dictionaries. Added sorting support (reorderedData, replaces oData) with autosave support for sort context. Added _positionTypeSelectDisplay, which adjusts position and justification of type select display control based on the current sort column. Added support for reverse sorting in type select string. Use table data source instead of sorted data so text matches as displayed (this will break with non-text cells...).

NJRTableView: Adapted from iTableView (Jaguar table alternate table background color), TableTester (most everything else) and NJROutlineView (keyDown, moveToBeginning/EndOfDocument). Support for type selection, delete shortcut for row deletion, and iTunes-alike background colors and frame.

NSCharacterSet-NJRExtensions: Moved _typeSelectSet from NJROutlineView as it's now shared with NJRTableView. Still need to factor NJROutlineView as embedded in HostLauncher some day.

PSAlarm: Reorganized, renamed and categorized methods. Added time accessor for the benefit of sorting. Renamed compare: to compareDate: for clarity. Added compareMessage:, though it's currently unused. Renamed cancel to cancelTimer for clarity.

PSAlarmSetController: More fun with initial first responder on window show/hide; still need to work around bug properly (subclass NSComboBox?) and fix it for real. As is, works for OS X 10.1.

PSAlarms: Added alarms accessor, returning alarm array. Fixed memory leak on successful alarm removal (oops). Added removeAlarms:, needed with sorted alarm list.

PSAlarmsController: Set window resize increment. Changes to table delegate methods to use reordered alarm list. Register for NSTableViewSelectionDidChangeNotification now we're no longer the table view delegate. Fixed autoselection in alarmsChanged by using data reordering support in NJRTableView. Implement NJRTableViewDataSource to permit deletion from table view.

Pester.pbproj: Added new files.

Read Me.rtfd: Added TableTester/iTableView acknowledgements. Updated release notes.

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