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

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

Popup triangle.tiff: Needed display component of NJRFSObjectSelector.
This one appears to use transparency unlike the one I prepared for
Process Exhibits, so it should be preferred.

NSMovie-NJRExtensions: Added -isStatic to identify whether the movie
contains dynamic components. If the movie is static, it most likely
contains only an image and shouldn't be 'played' as such, otherwise
the duration will be so short that the image won't be useful.

NJRFSObjectSelector: Fixed -drawRect: to draw the drag feedback
rectangle inside the bounds of the control, not inside whatever dirty
rectangle is passed to the method (often they are the same, but not
always). Only draw the popup arrow if the control is enabled.
Properly draw the popup arrow with transparency. Display the 'make
alias' cursor as additional drag feedback.

PSMovieAlertController: Only repeat movie and auto-close after movie
finished if it contains time-based media, otherwise just display the
movie (an image) until the window is closed or alarms are cancelled.

NSImage-NJRExtensions: Include code to actually scale icons from
F-Script Anywhere, otherwise the menu ends up with 32x32 (or
potentially larger) icons if smaller variations are not provided.
There's some more code in FSA that chooses which representation to
select; this code may still not be properly handling representations,
but it works better now. A good test case is the icon for Tex-Edit
Plus documents.

NJRQTMediaPopUpButton: Added notification for movie change (needed to
update interface). Changed -_validatePreview to
-_validateWithPreview: - preview is now optional. This will be needed
when I add archiving support for the selected item; we'll need to
validate it before updating the interface, but we don't want sounds to
play. Added some #pragma mark lines to separate methods by
functionality. Call -validateWithPreview: NO in awakeFromNib (again,
this logic will become more sophisticated later). Commented some
debugging logic since I'm pretty happy with the code. Added
-canRepeat accessor and setters, notification support in the
validation method. Added drag feedback.

PSAlarmSetController: Notification support, -setSoundRepetitionCount:
to avoid flashing with repetition text field control as NSStepper is
adjusted

English.lproj/MainMenu.nib: Switched action method for the NSStepper
stuff discussed above.

AM /Users/nicholas/Documents/Development/Cocoa/Pester/Source/Popup triangle.tiff
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/NSMovie-NJRExtensions.m
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/NJRFSObjectSelector.m
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/PSMovieAlertController.m
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/NSImage-NJRExtensions.m
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/NJRQTMediaPopUpButton.h
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/NJRQTMediaPopUpButton.m
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/.DS_Store
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/English.lproj/MainMenu.nib/objects.nib
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/English.lproj/MainMenu.nib/info.nib
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/English.lproj/MainMenu.nib/classes.nib
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/Pester.pbproj/nicholas.pbxuser
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/Pester.pbproj/project.pbxproj
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/PSAlarmSetController.h
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/PSAlarmSetController.m
M /Users/nicholas/Documents/Development/Cocoa/Pester/Source/NSMovie-NJRExtensions.h

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