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

Last change on this file since 61 was 61, checked in by Nicholas Riley, 17 years ago

Pester 1.1b1.

PSPowerManager: Fixed delegate method selectors to better reflect what
is going on (Apple's docs in IOKit Fundamentals help with this; the
kIOMessage*Sleep constants are really poorly named).

VERSION: Updated for 1.1b1.

PSSpeechAlert.h: Fixed company name.

PSAlert.[hm]: Added -prepareForAlarm: to support PSWakeAlert.

PSTimer.[hm]: Replacement for NSTimer that works properly across
sleep/wake cycles and will schedule wake timers.

PSAlerts.[hm]: Added -prepareForAlarm: to support PSWakeAlert.

Read Me.rtfd: Updated for 1.1b1.

PSAlarm.[hm]: Added -setWakeUp:, invoke -[PSAlerts prepareForAlarm],
replaced alarm timer NSTimer with PSTimer.

PSApplication.[hm]: Replaced dock update timer NSTimer with PSTimer.
Uncovered some issues, need to fix later. Enable alarm discard for
beta release.

PSWakeAlert.[hm]: Shared alert implementation for wakeup. Doesn't do
anything at trigger time, but uses new preparation interface to work
at alarm set time (should work for repeating alarms too, but I didn't
bother to test...)

PSAlarmSetController.m: Added support for PSWakeAlert. Save default
alert information on quit. Removed debug statements on hide/unhide;
it works fine regardless of whether the app is explicitly hidden or
the window hides itself.

PSAlarms.m: PSTimer support - invoke +[PSTimer setUp] to initialize
timer list.

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