source: trunk/Cocoa/Pester/Source/PSAlarmSetController.m@ 39

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

Lots of wonderful changes for which I composed a detailed svn commit
message, which it proceeded to mangle yet AGAIN on failed commit.

File size: 11.9 KB
Line 
1//
2// PSAlarmSetController.m
3// Pester
4//
5// Created by Nicholas Riley on Tue Oct 08 2002.
6// Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "PSAlarmSetController.h"
10#import "PSAlarmAlertController.h"
11#import "NJRDateFormatter.h"
12#import "NJRFSObjectSelector.h"
13#import "NJRQTMediaPopUpButton.h"
14#import "NJRVoicePopUpButton.h"
15#import <Carbon/Carbon.h>
16
17#import "PSDockBounceAlert.h"
18#import "PSScriptAlert.h"
19#import "PSNotifierAlert.h"
20#import "PSBeepAlert.h"
21#import "PSMovieAlert.h"
22#import "PSSpeechAlert.h"
23
24/* Bugs to file:
25
26¥ any trailing spaces: -> exception for +[NSCalendarDate dateWithNaturalLanguageString]:
27 > NSCalendarDate dateWithNaturalLanguageString: '12 '
28 format error: internal error
29
30¥ NSDate natural language stuff in NSCalendarDate (why?), misspelled category name
31¥ NSCalendarDate natural language stuff behaves differently from NSDateFormatter (AM/PM has no effect, shouldn't they share code?)
32¥ NSDateFormatter doc class description gives two examples for natural language that are incorrect, no link to NSDate doc that describes exactly how natural language dates are parsed
33¥ NSTimeFormatString does not include %p when it should, meaning that AM/PM is stripped yet 12-hour time is still used
34¥ NSNextDayDesignations, NSNextNextDayDesignations are noted as 'a string' in NSUserDefaults docs, but maybe they are actually an array, or either an array or a string, given their names?
35¥ "Setting the Format for Dates" does not document how to get 1:15 AM, the answer is %1I - strftime has no exact equivalent; the closest is %l. strftime does not permit numeric prefixes. It also refers to "NSCalendar" when no such class exists.
36¥ none of many mentions of NSAMPMDesignation indicates that they include the leading spaces (" AM", " PM"). In "Setting the Format for Dates", needs to mention that the leading spaces are not included in %p with strftime. But if you use the NSCalendarDate stuff, it appears %p doesn't include the space.
37¥ descriptions for %X and %x are reversed (time zone is in %X, not %x)
38¥ too hard to implement date-only or time-only formatters
39¥ should be able to specify that natural language favors date or time (10 = 10th of month, not 10am)
40¥ please expose the iCal controls!
41
42*/
43
44@interface PSAlarmSetController (Private)
45
46- (void)_stopUpdateTimer;
47
48@end
49
50@implementation PSAlarmSetController
51
52- (void)awakeFromNib;
53{
54 // XXX bugs prevent this code from working properly on Jaguar
55 /* NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
56 [timeOfDay setFormatter: [[NJRDateFormatter alloc] initWithDateFormat: [defaults objectForKey: NSTimeFormatString] allowNaturalLanguage: YES]];
57 [timeDate setFormatter: [[NJRDateFormatter alloc] initWithDateFormat: [defaults objectForKey: NSShortDateFormatString] allowNaturalLanguage: YES]]; */
58 alarm = [[PSAlarm alloc] init];
59 [[self window] center];
60 [self inAtChanged: nil];
61 [self playSoundChanged: nil];
62 [self doScriptChanged: nil];
63 [self doSpeakChanged: nil];
64 [script setFileTypes: [NSArray arrayWithObjects: @"applescript", @"script", NSFileTypeForHFSTypeCode(kOSAFileType), NSFileTypeForHFSTypeCode('TEXT'), nil]];
65 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(silence:) name: PSAlarmAlertStopNotification object: nil];
66 [voice setDelegate: self];
67 [[self window] makeKeyAndOrderFront: nil];
68}
69
70- (void)setStatus:(NSString *)aString;
71{
72 // NSLog(@"%@", alarm);
73 if (aString != status) {
74 [status release]; status = nil;
75 status = [aString retain];
76 [timeSummary setStringValue: status];
77 }
78}
79
80- (id)objectValueForTextField:(NSTextField *)field whileEditing:(id)sender;
81{
82 if (sender == field) {
83 NSString *stringValue = [[[self window] fieldEditor: NO forObject: field] string];
84 id obj = nil;
85 [[field formatter] getObjectValue: &obj forString: stringValue errorDescription: NULL];
86 // NSLog(@"from field editor: %@", obj);
87 return obj;
88 } else {
89 // NSLog(@"from field: %@", [field objectValue]);
90 return [field objectValue];
91 }
92}
93
94- (void)setAlarmDateAndInterval:(id)sender;
95{
96 if (isInterval) {
97 [alarm setInterval:
98 [[self objectValueForTextField: timeInterval whileEditing: sender] intValue] *
99 [timeIntervalUnits selectedTag]];
100 } else {
101 [alarm setForDate: [self objectValueForTextField: timeDate whileEditing: sender]
102 atTime: [self objectValueForTextField: timeOfDay whileEditing: sender]];
103 }
104}
105
106- (void)_stopUpdateTimer;
107{
108 [updateTimer invalidate]; [updateTimer release]; updateTimer = nil;
109}
110
111// XXX use OACalendar?
112
113- (IBAction)updateDateDisplay:(id)sender;
114{
115 // NSLog(@"updateDateDisplay: %@", sender);
116 if ([alarm isValid]) {
117 [self setStatus: [[alarm date] descriptionWithCalendarFormat: @"Alarm will be set for %X on %x" timeZone: nil locale: nil]];
118 [setButton setEnabled: YES];
119 if (updateTimer == nil || ![updateTimer isValid]) {
120 // XXX this logic (and the timer) should really go into PSAlarm, to send notifications for status updates instead. Timer starts when people are watching, stops when people aren't.
121 // NSLog(@"setting timer");
122 if (isInterval) {
123 updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateDateDisplay:) userInfo: nil repeats: YES];
124 } else {
125 updateTimer = [NSTimer scheduledTimerWithTimeInterval: [alarm interval] target: self selector: @selector(updateDateDisplay:) userInfo: nil repeats: NO];
126 }
127 [updateTimer retain];
128 }
129 } else {
130 [setButton setEnabled: NO];
131 [self setStatus: [alarm invalidMessage]];
132 [self _stopUpdateTimer];
133 }
134}
135
136// Be careful not to hook up any of the text fields' actions to update: because we handle them in controlTextDidChange: instead. If we could get the active text field somehow via public API (guess we could use controlTextDidBegin/controlTextDidEndEditing) then we'd not need to overload the update sender for this purpose. Or, I guess, we could use another method other than update. It should not be this hard to implement what is essentially standard behavior. Sigh.
137
138- (IBAction)update:(id)sender;
139{
140 // NSLog(@"update: %@", sender);
141 [self setAlarmDateAndInterval: sender];
142 [self updateDateDisplay: sender];
143}
144
145- (IBAction)inAtChanged:(id)sender;
146{
147 isInterval = ([inAtMatrix selectedTag] == 0);
148 [timeInterval setEnabled: isInterval];
149 [timeIntervalUnits setEnabled: isInterval];
150 [timeIntervalRepeats setEnabled: isInterval];
151 [timeOfDay setEnabled: !isInterval];
152 [timeDate setEnabled: !isInterval];
153 [timeDateCompletions setEnabled: !isInterval];
154 if (sender != nil)
155 [[self window] makeFirstResponder: isInterval ? timeInterval : timeOfDay];
156 // NSLog(@"UPDATING FROM inAtChanged");
157 [self update: nil];
158}
159
160- (IBAction)playSoundChanged:(id)sender;
161{
162 BOOL playSoundSelected = [playSound intValue];
163 [sound setEnabled: playSoundSelected];
164 [soundRepetitions setEnabled: playSoundSelected];
165 [soundRepetitionStepper setEnabled: playSoundSelected];
166 [soundRepetitionsLabel setTextColor: playSoundSelected ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]];
167 if (playSoundSelected && sender != nil)
168 [[self window] makeFirstResponder: sound];
169}
170
171// XXX should check the 'Do script:' button when someone drops a script on the button
172
173- (IBAction)doScriptChanged:(id)sender;
174{
175 BOOL doScriptSelected = [doScript intValue];
176 [script setEnabled: doScriptSelected];
177 [scriptSelectButton setEnabled: doScriptSelected];
178 if (doScriptSelected && sender != nil)
179 [[self window] makeFirstResponder: scriptSelectButton];
180}
181
182- (IBAction)doSpeakChanged:(id)sender;
183{
184 BOOL doSpeakSelected = [doSpeak intValue];
185 [voice setEnabled: doSpeakSelected];
186 if (doSpeakSelected && sender != nil)
187 [[self window] makeFirstResponder: voice];
188}
189
190- (IBAction)dateCompleted:(NSPopUpButton *)sender;
191{
192 [timeDate setStringValue: [sender titleOfSelectedItem]];
193 [self update: sender];
194}
195
196// to ensure proper updating of interval, this should be the only method by which the window is shown (e.g. from the Alarm menu)
197- (IBAction)showWindow:(id)sender;
198{
199 if (![[self window] isVisible]) {
200 [self update: self];
201 // XXX otherwise, first responder appears to alternate every time the window is shown?! And if you set the initial first responder, you can't tab in the window. :(
202 [[self window] makeFirstResponder: [[self window] initialFirstResponder]];
203 }
204 [super showWindow: sender];
205}
206
207- (IBAction)setAlarm:(NSButton *)sender;
208{
209 // set alarm
210 [self setAlarmDateAndInterval: sender];
211 [alarm setMessage: [messageField stringValue]];
212 if (![alarm setTimer]) {
213 [self setStatus: [@"Unable to set alarm. " stringByAppendingString: [alarm invalidMessage]]];
214 return;
215 }
216
217 [alarm removeAlerts];
218 // dock bounce alert
219 if ([bounceDockIcon state] == NSOnState)
220 [alarm addAlert: [PSDockBounceAlert alert]];
221 // script alert
222 if ([doScript intValue]) {
223 BDAlias *scriptFileAlias = [script alias];
224 if (scriptFileAlias == nil) {
225 [self setStatus: @"Unable to set script alert (no script specified?)"];
226 return;
227 }
228 [alarm addAlert: [PSScriptAlert alertWithScriptFileAlias: scriptFileAlias]];
229 }
230 // notifier alert
231 if ([displayMessage intValue])
232 [alarm addAlert: [PSNotifierAlert alert]];
233 // sound alerts
234 if ([playSound intValue]) {
235 BDAlias *soundAlias = [sound selectedAlias];
236 unsigned short numReps = [soundRepetitions intValue];
237 if (soundAlias == nil) // beep alert
238 [alarm addAlert: [PSBeepAlert alertWithRepetitions: numReps]];
239 else // movie alert
240 [alarm addAlert: [PSMovieAlert alertWithMovieFileAlias: soundAlias repetitions: numReps]];
241 }
242 // speech alert
243 if ([doSpeak intValue])
244 [alarm addAlert: [PSSpeechAlert alertWithVoice: [voice titleOfSelectedItem]]];
245
246 [self setStatus: [[alarm date] descriptionWithCalendarFormat: @"Alarm set for %x at %X" timeZone: nil locale: nil]];
247 [[self window] close];
248 [alarm release];
249 alarm = [[PSAlarm alloc] init];
250}
251
252- (IBAction)silence:(id)sender;
253{
254 [sound stopSoundPreview: self];
255 [voice stopVoicePreview: self];
256}
257
258@end
259
260@implementation PSAlarmSetController (NSControlSubclassDelegate)
261
262- (void)control:(NSControl *)control didFailToValidatePartialString:(NSString *)string errorDescription:(NSString *)error;
263{
264 unichar c;
265 int tag;
266 unsigned length = [string length];
267 if (control != timeInterval || length == 0) return;
268 c = [string characterAtIndex: length - 1];
269 switch (c) {
270 case 's': case 'S': tag = 1; break;
271 case 'm': case 'M': tag = 60; break;
272 case 'h': case 'H': tag = 60 * 60; break;
273 default: return;
274 }
275 [timeIntervalUnits selectItemAtIndex:
276 [timeIntervalUnits indexOfItemWithTag: tag]];
277 // NSLog(@"UPDATING FROM validation");
278 [self update: timeInterval]; // make sure we still examine the field editor, otherwise if the existing numeric string is invalid, it'll be cleared
279}
280
281@end
282
283@implementation PSAlarmSetController (NSWindowNotifications)
284
285- (void)windowWillClose:(NSNotification *)notification;
286{
287 // NSLog(@"stopping update timer");
288 [self silence: nil];
289 [self _stopUpdateTimer];
290}
291
292@end
293
294@implementation PSAlarmSetController (NSControlSubclassNotifications)
295
296// called because we're the delegate
297
298- (void)controlTextDidChange:(NSNotification *)notification;
299{
300 // NSLog(@"UPDATING FROM controlTextDidChange");
301 [self update: [notification object]];
302}
303
304@end
305
306@implementation PSAlarmSetController (NJRVoicePopUpButtonDelegate)
307
308- (NSString *)voicePopUpButton:(NJRVoicePopUpButton *)sender previewStringForVoice:(NSString *)voice;
309{
310 return [messageField stringValue];
311}
312
313@end
Note: See TracBrowser for help on using the repository browser.