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

Last change on this file since 60 was 60, checked in by Nicholas Riley, 17 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.