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

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

Localization, bug fixes

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