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

Last change on this file since 60 was 60, checked in by Nicholas Riley, 22 years ago

PSScriptAlert.m: Removed reference to NDAppleScriptObject.

PSMovieAlertController.[hm]: Added minimum sizing.

NJRCenteringMovieView.[hm]: Support for centering movie inside its
frame if the frame is too large for the movie; sizes movie
proportionally if necessary (not used in Pester).

NSImage-NJRExtensions.[hm]: Fixed copyright.

Read Me.rtfd: Updated with bug fixes.

PSMovieAlert.m: Note bug in NSMovieView, currently doesn't affect us.

NJRQTMediaPopUpButton.m: Work around background processor use bug when
NSMovieView has movie set but not playing.

NJRVoicePopUpButton.m: Removed obsolete comment.

PSApplication.m: Only show alarm set window on rapp if alerts aren't
expiring.

PSAlarms.[hm]: Added -alarmsExpiring for support of conditional
rapp window open feature in PSApplication.

PSAlarmSetController.m: Stop window update timer and movie playback on
hide, restart timer on activate - fixes background processor usage.

File size: 22.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 "PSPowerManager.h"
12#import "NJRDateFormatter.h"
13#import "NJRFSObjectSelector.h"
14#import "NJRIntervalField.h"
15#import "NJRQTMediaPopUpButton.h"
16#import "NJRVoicePopUpButton.h"
17#import "NSString-NJRExtensions.h"
18#import "NSAttributedString-NJRExtensions.h"
19#import "NSCalendarDate-NJRExtensions.h"
20#import <Carbon/Carbon.h>
21
22#import "PSAlerts.h"
23#import "PSDockBounceAlert.h"
24#import "PSScriptAlert.h"
25#import "PSNotifierAlert.h"
26#import "PSBeepAlert.h"
27#import "PSMovieAlert.h"
28#import "PSSpeechAlert.h"
29
30/* Bugs to file:
31
32¥ any trailing spaces: -> exception for +[NSCalendarDate dateWithNaturalLanguageString]:
33 > NSCalendarDate dateWithNaturalLanguageString: '12 '
34 format error: internal error
35
36¥ NSDate natural language stuff in NSCalendarDate (why?), misspelled category name
37¥ NSCalendarDate natural language stuff behaves differently from NSDateFormatter (AM/PM has no effect, shouldn't they share code?)
38¥ descriptionWithCalendarFormat:, dateWithNaturalLanguageString: does not default to current locale, instead it defaults to US unless you tell it otherwise
39¥ 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
40¥ NSTimeFormatString does not include %p when it should, meaning that AM/PM is stripped yet 12-hour time is still used
41¥ 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?
42¥ "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.
43¥ 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 (because it doesn't use the locale dictionary).
44¥ If you feed NSCalendarDate dateWithNaturalLanguageString: an " AM"/" PM" locale, it doesn't accept that date format.
45¥ descriptions for %X and %x are reversed (time zone is in %X, not %x)
46¥ NSComboBox data source issues, canÕt have it appear as ÒtodayÓ because the formatter doesnÕt like that. Should be able to enter text into the data source and have the formatter process it without altering it.
47¥ too hard to implement date-only or time-only formatters
48¥ should be able to specify that natural language favors date or time (10 = 10th of month, not 10am)
49¥ please expose the iCal controls!
50
51*/
52
53static NSString * const PSAlertsSelected = @"Pester alerts selected"; // NSUserDefaults key
54static NSString * const PSAlertsEditing = @"Pester alerts editing"; // NSUserDefaults key
55
56@interface PSAlarmSetController (Private)
57
58- (void)_readAlerts:(PSAlerts *)alerts;
59- (BOOL)_setAlerts;
60- (void)_stopUpdateTimer;
61
62@end
63
64@implementation PSAlarmSetController
65
66- (void)awakeFromNib;
67{
68 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
69 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
70 alarm = [[PSAlarm alloc] init];
71 [[self window] center];
72 // XXX excessive retention of formatters? check later...
73 [timeOfDay setFormatter: [[NJRDateFormatter alloc] initWithDateFormat: [NJRDateFormatter localizedTimeFormatIncludingSeconds: NO] allowNaturalLanguage: YES]];
74 [timeDate setFormatter: [[NJRDateFormatter alloc] initWithDateFormat: [NJRDateFormatter localizedDateFormatIncludingWeekday: NO] allowNaturalLanguage: YES]];
75 {
76 NSArray *dayNames = [defaults arrayForKey:
77 NSWeekDayNameArray];
78 NSArray *completions = [timeDateCompletions itemTitles];
79 NSEnumerator *e = [completions objectEnumerator];
80 NSString *title;
81 int itemIndex = 0;
82 NSRange matchingRange;
83 while ( (title = [e nextObject]) != nil) {
84 matchingRange = [title rangeOfString: @"ÇdayÈ"];
85 if (matchingRange.location != NSNotFound) {
86 NSMutableString *format = [title mutableCopy];
87 NSEnumerator *we = [dayNames objectEnumerator];
88 NSString *dayName;
89 [format deleteCharactersInRange: matchingRange];
90 [format insertString: @"%@" atIndex: matchingRange.location];
91 [timeDateCompletions removeItemAtIndex: itemIndex];
92 while ( (dayName = [we nextObject]) != nil) {
93 [timeDateCompletions insertItemWithTitle: [NSString stringWithFormat: format, dayName] atIndex: itemIndex];
94 itemIndex++;
95 }
96 } else itemIndex++;
97 }
98 }
99 [editAlert setIntValue: [defaults boolForKey: PSAlertsEditing]];
100 {
101 NSDictionary *plAlerts = [defaults dictionaryForKey: PSAlertsSelected];
102 PSAlerts *alerts;
103 if (plAlerts == nil) {
104 alerts = [[PSAlerts alloc] initWithPesterVersion1Alerts];
105 } else {
106 NS_DURING
107 alerts = [[PSAlerts alloc] initWithPropertyList: plAlerts];
108 NS_HANDLER
109 NSRunAlertPanel(@"Unable to restore alerts", @"Pester could not restore recent alert information for one or more alerts in the Set Alarm window. The default set of alerts will be used instead.\n\n%@", nil, nil, nil, [localException reason]);
110 alerts = [[PSAlerts alloc] initWithPesterVersion1Alerts];
111 NS_ENDHANDLER
112 }
113 [self _readAlerts: alerts];
114 }
115 [timeDate setObjectValue: [NSDate date]];
116 [self inAtChanged: nil]; // by convention, if sender is nil, we're initializing
117 [self playSoundChanged: nil];
118 [self doScriptChanged: nil];
119 [self doSpeakChanged: nil];
120 [self editAlertChanged: nil];
121 [script setFileTypes: [NSArray arrayWithObjects: @"applescript", @"script", NSFileTypeForHFSTypeCode(kOSAFileType), NSFileTypeForHFSTypeCode('TEXT'), nil]];
122 [notificationCenter addObserver: self selector: @selector(silence:) name: PSAlarmAlertStopNotification object: nil];
123 [notificationCenter addObserver: self selector: @selector(playSoundChanged:) name: NJRQTMediaPopUpButtonMovieChangedNotification object: sound];
124 [notificationCenter addObserver: self selector: @selector(applicationWillHide:) name: NSApplicationWillHideNotification object: NSApp];
125 [notificationCenter addObserver: self selector: @selector(applicationDidUnhide:) name: NSApplicationDidUnhideNotification object: NSApp];
126 [voice setDelegate: self]; // XXX why don't we do this in IB? It should use the accessor...
127 [wakeUp setEnabled: [PSPowerManager autoWakeSupported]];
128 // XXX workaround for 10.1.x and 10.2.x bug which sets the first responder to the wrong field alternately, but it works if I set the initial first responder to nil... go figure.
129 [[self window] setInitialFirstResponder: nil];
130 [[self window] makeKeyAndOrderFront: nil];
131}
132
133- (void)setStatus:(NSString *)aString;
134{
135 // NSLog(@"%@", alarm);
136 if (aString != status) {
137 [status release]; status = nil;
138 status = [aString retain];
139 [timeSummary setStringValue: status];
140 }
141}
142
143// XXX with -[NSControl currentEditor] don't need to compare? Also check -[NSControl validateEditing]
144- (id)objectValueForTextField:(NSTextField *)field whileEditing:(id)sender;
145{
146 if (sender == field) {
147 NSString *stringValue = [[[self window] fieldEditor: NO forObject: field] string];
148 id obj = nil;
149 [[field formatter] getObjectValue: &obj forString: stringValue errorDescription: NULL];
150 // NSLog(@"from field editor: %@", obj);
151 return obj;
152 } else {
153 // NSLog(@"from field: %@", [field objectValue]);
154 return [field objectValue];
155 }
156}
157
158#pragma mark date/interval setting
159
160- (void)setAlarmDateAndInterval:(id)sender;
161{
162 if (isInterval) {
163 [alarm setInterval: [timeInterval interval]];
164 } else {
165 [alarm setForDate: [self objectValueForTextField: timeDate whileEditing: sender]
166 atTime: [self objectValueForTextField: timeOfDay whileEditing: sender]];
167 }
168}
169
170- (void)_stopUpdateTimer;
171{
172 [updateTimer invalidate]; [updateTimer release]; updateTimer = nil;
173}
174
175// XXX use OACalendar in popup like Palm Desktop?
176
177- (IBAction)updateDateDisplay:(id)sender;
178{
179 // NSLog(@"updateDateDisplay: %@", sender);
180 if ([alarm isValid]) {
181 [self setStatus: [NSString stringWithFormat: @"Alarm will be set for %@ on %@", [alarm timeString], [alarm dateString]]];
182 [setButton setEnabled: YES];
183 if (updateTimer == nil || ![updateTimer isValid]) {
184 // 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.
185 // NSLog(@"setting timer");
186 if (isInterval) {
187 updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateDateDisplay:) userInfo: nil repeats: YES];
188 } else {
189 updateTimer = [NSTimer scheduledTimerWithTimeInterval: [alarm interval] target: self selector: @selector(updateDateDisplay:) userInfo: nil repeats: NO];
190 }
191 [updateTimer retain];
192 }
193 } else {
194 [setButton setEnabled: NO];
195 [self setStatus: [alarm invalidMessage]];
196 [self _stopUpdateTimer];
197 }
198}
199
200// 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.
201// Note: finding out whether a given control is editing is easier. See: <http://cocoa.mamasam.com/COCOADEV/2002/03/2/28501.php>.
202
203- (IBAction)update:(id)sender;
204{
205 // NSLog(@"update: %@", sender);
206 [self setAlarmDateAndInterval: sender];
207 [self updateDateDisplay: sender];
208}
209
210- (IBAction)inAtChanged:(id)sender;
211{
212 NSButtonCell *new = [inAtMatrix selectedCell], *old;
213 isInterval = ([inAtMatrix selectedTag] == 0);
214 old = [inAtMatrix cellWithTag: isInterval];
215 NSAssert(new != old, @"in and at buttons should be distinct!");
216 [old setKeyEquivalent: [new keyEquivalent]];
217 [old setKeyEquivalentModifierMask: [new keyEquivalentModifierMask]];
218 [new setKeyEquivalent: @""];
219 [new setKeyEquivalentModifierMask: 0];
220 [timeInterval setEnabled: isInterval];
221 [timeIntervalUnits setEnabled: isInterval];
222 [timeIntervalRepeats setEnabled: isInterval];
223 [timeOfDay setEnabled: !isInterval];
224 [timeDate setEnabled: !isInterval];
225 [timeDateCompletions setEnabled: !isInterval];
226 if (sender != nil)
227 [[self window] makeFirstResponder: isInterval ? (NSTextField *)timeInterval : timeOfDay];
228 // NSLog(@"UPDATING FROM inAtChanged");
229 [self update: nil];
230}
231
232- (IBAction)dateCompleted:(NSPopUpButton *)sender;
233{
234 [timeDate setStringValue: [sender titleOfSelectedItem]];
235 [self update: sender];
236}
237
238#pragma mark alert editing
239
240- (IBAction)editAlertChanged:(id)sender;
241{
242 BOOL editAlertSelected = [editAlert intValue];
243 NSView *editAlertControl = [editAlert controlView];
244 NSWindow *window = [self window];
245 NSRect frame = [window frame];
246 if (editAlertSelected) {
247 NSSize editWinSize = [window maxSize];
248 [editAlertControl setNextKeyView: [displayMessage controlView]];
249 frame.origin.y += frame.size.height - editWinSize.height;
250 frame.size = editWinSize;
251 [window setFrame: frame display: (sender != nil) animate: (sender != nil)];
252 [self updateDateDisplay: sender];
253 [alertTabs selectTabViewItemWithIdentifier: @"edit"];
254 } else {
255 NSSize viewWinSize = [window minSize];
256 NSRect textFrame = [alertView frame];
257 float textHeight;
258 if (![self _setAlerts]) {
259 [alertView setStringValue: [NSString stringWithFormat: @"CouldnÕt process alert information.\n%@", status]];
260 } else {
261 NSAttributedString *string = [[alarm alerts] prettyList];
262 if (string == nil) {
263 [alertView setStringValue: @"Do nothing. Click the button labeled ÒEditÓ to add an alert."];
264 } else {
265 [alertView setAttributedStringValue: string];
266 [self updateDateDisplay: sender];
267 }
268 }
269 if (sender != nil) { // nil == we're initializing, don't mess with focus
270 NSResponder *oldResponder = [window firstResponder];
271 // make sure focus doesn't get stuck in the edit tab: it is confusing and leaves behind artifacts
272 if (oldResponder == editAlertControl || [oldResponder isKindOfClass: [NSView class]] && [(NSView *)oldResponder isDescendantOf: alertTabs])
273 [window makeFirstResponder: messageField]; // would use editAlertControl, but can't get it to display anomaly-free.
274 [self silence: sender];
275 }
276 // allow height to expand, though not arbitrarily (should still fit on an 800x600 screen)
277 textHeight = [[alertView cell] cellSizeForBounds: NSMakeRect(0, 0, textFrame.size.width, 400)].height;
278 textFrame.origin.y += textFrame.size.height - textHeight;
279 textFrame.size.height = textHeight;
280 [alertView setFrame: textFrame];
281 viewWinSize.height += textHeight;
282 [alertTabs selectTabViewItemWithIdentifier: @"view"];
283 frame.origin.y += frame.size.height - viewWinSize.height;
284 frame.size = viewWinSize;
285 [window setFrame: frame display: (sender != nil) animate: (sender != nil)];
286 [editAlertControl setNextKeyView: cancelButton];
287 }
288 if (sender != nil) {
289 [[NSUserDefaults standardUserDefaults] setBool: editAlertSelected forKey: PSAlertsEditing];
290 }
291}
292
293
294- (IBAction)playSoundChanged:(id)sender;
295{
296 BOOL playSoundSelected = [playSound intValue];
297 BOOL canRepeat = playSoundSelected ? [sound canRepeat] : NO;
298 [sound setEnabled: playSoundSelected];
299 [soundRepetitions setEnabled: canRepeat];
300 [soundRepetitionStepper setEnabled: canRepeat];
301 [soundRepetitionsLabel setTextColor: canRepeat ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]];
302 if (playSoundSelected && sender == playSound) {
303 [[self window] makeFirstResponder: sound];
304 }
305}
306
307- (IBAction)setSoundRepetitionCount:(id)sender;
308{
309 NSTextView *fieldEditor = (NSTextView *)[soundRepetitions currentEditor];
310 BOOL isEditing = (fieldEditor != nil);
311 int newReps = [sender intValue], oldReps;
312 if (isEditing) {
313 // XXX work around bug where if you ask soundRepetitions for its intValue too often while it's editing, the field begins to flash
314 oldReps = [[[fieldEditor textStorage] string] intValue];
315 } else oldReps = [soundRepetitions intValue];
316 if (newReps != oldReps) {
317 [soundRepetitions setIntValue: newReps];
318 // NSLog(@"updating: new value %d, old value %d%@", newReps, oldReps, isEditing ? @", is editing" : @"");
319 // XXX work around 10.1 bug, otherwise field only displays every second value
320 if (isEditing) [soundRepetitions selectText: self];
321 }
322}
323
324// XXX should check the 'Do script:' button when someone drops a script on the button
325
326- (IBAction)doScriptChanged:(id)sender;
327{
328 BOOL doScriptSelected = [doScript intValue];
329 [script setEnabled: doScriptSelected];
330 [scriptSelectButton setEnabled: doScriptSelected];
331 if (doScriptSelected && sender != nil) {
332 [[self window] makeFirstResponder: scriptSelectButton];
333 if ([script alias] == nil) [scriptSelectButton performClick: sender];
334 }
335}
336
337- (IBAction)doSpeakChanged:(id)sender;
338{
339 BOOL doSpeakSelected = [doSpeak state] == NSOnState;
340 [voice setEnabled: doSpeakSelected];
341 if (doSpeakSelected && sender != nil)
342 [[self window] makeFirstResponder: voice];
343}
344
345- (void)_readAlerts:(PSAlerts *)alerts;
346{
347 NSEnumerator *e = [alerts alertEnumerator];
348 PSAlert *alert;
349
350 [alarm setAlerts: alerts];
351
352 // turn off all alerts
353 [bounceDockIcon setState: NSOffState];
354 [doScript setIntValue: NO];
355 [displayMessage setIntValue: NO];
356 [playSound setIntValue: NO];
357 [doSpeak setIntValue: NO];
358
359 while ( (alert = [e nextObject]) != nil) {
360 if ([alert isKindOfClass: [PSDockBounceAlert class]]) {
361 [bounceDockIcon setState: NSOnState];
362 } else if ([alert isKindOfClass: [PSScriptAlert class]]) {
363 [doScript setIntValue: YES];
364 [script setAlias: [(PSScriptAlert *)alert scriptFileAlias]];
365 } else if ([alert isKindOfClass: [PSNotifierAlert class]]) {
366 [displayMessage setIntValue: YES];
367 } else if ([alert isKindOfClass: [PSBeepAlert class]]) {
368 unsigned int repetitions = [(PSBeepAlert *)alert repetitions];
369 [playSound setIntValue: YES];
370 [sound setAlias: nil];
371 [soundRepetitions setIntValue: repetitions];
372 [soundRepetitionStepper setIntValue: repetitions];
373 } else if ([alert isKindOfClass: [PSMovieAlert class]]) {
374 unsigned int repetitions = [(PSMovieAlert *)alert repetitions];
375 [playSound setIntValue: YES];
376 [sound setAlias: [(PSMovieAlert *)alert movieFileAlias]];
377 [soundRepetitions setIntValue: repetitions];
378 [soundRepetitionStepper setIntValue: repetitions];
379 } else if ([alert isKindOfClass: [PSSpeechAlert class]]) {
380 [doSpeak setIntValue: YES];
381 [voice setVoice: [(PSSpeechAlert *)alert voice]];
382 }
383 }
384}
385
386- (BOOL)_setAlerts;
387{
388 PSAlerts *alerts = [alarm alerts];
389
390 [alerts removeAlerts];
391 NS_DURING
392 // dock bounce alert
393 if ([bounceDockIcon state] == NSOnState)
394 [alerts addAlert: [PSDockBounceAlert alert]];
395 // script alert
396 if ([doScript intValue]) {
397 BDAlias *scriptFileAlias = [script alias];
398 if (scriptFileAlias == nil) {
399 [self setStatus: @"Unable to set script alert (no script specified?)"];
400 return NO;
401 }
402 [alerts addAlert: [PSScriptAlert alertWithScriptFileAlias: scriptFileAlias]];
403 }
404 // notifier alert
405 if ([displayMessage intValue])
406 [alerts addAlert: [PSNotifierAlert alert]];
407 // sound alerts
408 if ([playSound intValue]) {
409 BDAlias *soundAlias = [sound selectedAlias];
410 unsigned short numReps = [soundRepetitions intValue];
411 if (soundAlias == nil) // beep alert
412 [alerts addAlert: [PSBeepAlert alertWithRepetitions: numReps]];
413 else // movie alert
414 [alerts addAlert: [PSMovieAlert alertWithMovieFileAlias: soundAlias repetitions: numReps]];
415 }
416 // speech alert
417 if ([doSpeak intValue])
418 [alerts addAlert: [PSSpeechAlert alertWithVoice: [voice titleOfSelectedItem]]];
419 [[NSUserDefaults standardUserDefaults] setObject: [alerts propertyListRepresentation] forKey: PSAlertsSelected];
420 NS_HANDLER
421 [self setStatus: [localException reason]];
422 NS_VALUERETURN(NO, BOOL);
423 NS_ENDHANDLER
424 return YES;
425}
426
427#pragma mark actions
428
429// to ensure proper updating of interval, this should be the only method by which the window is shown (e.g. from the Alarm menu)
430- (IBAction)showWindow:(id)sender;
431{
432 if (![[self window] isVisible]) {
433 NSDate *today = [NSCalendarDate dateForDay: [NSDate date]];
434 if ([(NSDate *)[timeDate objectValue] compare: today] == NSOrderedAscending) {
435 [timeDate setObjectValue: today];
436 }
437 [self update: self];
438 // XXX bug workaround - 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. :(
439 [[self window] makeFirstResponder: [[self window] initialFirstResponder]];
440 }
441 [super showWindow: sender];
442}
443
444- (IBAction)setAlarm:(NSButton *)sender;
445{
446 // set alerts before setting alarm...
447 if (![self _setAlerts]) return;
448
449 // set alarm
450 [self setAlarmDateAndInterval: sender];
451 [alarm setRepeating: [timeIntervalRepeats state] == NSOnState];
452 [alarm setMessage: [messageField stringValue]];
453 if (![alarm setTimer]) {
454 [self setStatus: [@"Unable to set alarm. " stringByAppendingString: [alarm invalidMessage]]];
455 return;
456 }
457
458 [self setStatus: [[alarm date] descriptionWithCalendarFormat: @"Alarm set for %x at %X" timeZone: nil locale: nil]];
459 [[self window] close];
460 [alarm release];
461 alarm = [[PSAlarm alloc] init];
462}
463
464- (IBAction)silence:(id)sender;
465{
466 [sound stopSoundPreview: self];
467 [voice stopVoicePreview: self];
468}
469
470@end
471
472@implementation PSAlarmSetController (NSControlSubclassDelegate)
473
474- (BOOL)control:(NSControl *)control didFailToFormatString:(NSString *)string errorDescription:(NSString *)error;
475{
476 if (control == timeInterval)
477 [timeInterval handleDidFailToFormatString: string errorDescription: error label: @"alarm interval"];
478 return NO;
479}
480
481- (void)control:(NSControl *)control didFailToValidatePartialString:(NSString *)string errorDescription:(NSString *)error;
482{
483 // NSLog(@"UPDATING FROM validation");
484 if (control == timeInterval) [self update: timeInterval]; // make sure we still examine the field editor, otherwise if the existing numeric string is invalid, it'll be cleared
485}
486
487@end
488
489@implementation PSAlarmSetController (NSWindowNotifications)
490
491- (void)windowWillClose:(NSNotification *)notification;
492{
493 // NSLog(@"stopping update timer");
494 [self silence: nil];
495 [self _stopUpdateTimer];
496 [self _setAlerts];
497}
498
499@end
500
501@implementation PSAlarmSetController (NSControlSubclassNotifications)
502
503// called because we're the delegate
504
505- (void)controlTextDidChange:(NSNotification *)notification;
506{
507 // NSLog(@"UPDATING FROM controlTextDidChange: %@", [notification object]);
508 [self update: [notification object]];
509}
510
511@end
512
513@implementation PSAlarmSetController (NJRVoicePopUpButtonDelegate)
514
515- (NSString *)voicePopUpButton:(NJRVoicePopUpButton *)sender previewStringForVoice:(NSString *)voice;
516{
517 NSString *message = [messageField stringValue];
518 if (message == nil || [message length] == 0)
519 message = [alarm message];
520 return message;
521}
522
523@end
524
525@implementation PSAlarmSetController (NSApplicationNotifications)
526
527- (void)applicationWillHide:(NSNotification *)notification;
528{
529 if ([[self window] isVisible]) {
530 NSLog(@"hide");
531 [self silence: nil];
532 [self _stopUpdateTimer];
533 }
534}
535
536- (void)applicationDidUnhide:(NSNotification *)notification;
537{
538 if ([[self window] isVisible]) {
539 NSLog(@"unhide");
540 [self update: self];
541 }
542}
543
544@end
Note: See TracBrowser for help on using the repository browser.