Changeset 53 for trunk/Cocoa/Pester/Source/PSAlarmSetController.m
- Timestamp:
- 01/02/03 05:30:03 (21 years ago)
- Location:
- trunk/Cocoa/Pester/Source
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Cocoa/Pester/Source
- Property svn:ignore
-
old new 1 1 build 2 .gdb_history
-
- Property svn:ignore
-
trunk/Cocoa/Pester/Source/PSAlarmSetController.m
r51 r53 9 9 #import "PSAlarmSetController.h" 10 10 #import "PSAlarmAlertController.h" 11 #import "PSPowerManager.h" 11 12 #import "NJRDateFormatter.h" 12 13 #import "NJRFSObjectSelector.h" 14 #import "NJRIntervalField.h" 13 15 #import "NJRQTMediaPopUpButton.h" 14 16 #import "NJRVoicePopUpButton.h" 17 #import "NSString-NJRExtensions.h" 18 #import "NSAttributedString-NJRExtensions.h" 19 #import "NSCalendarDate-NJRExtensions.h" 15 20 #import <Carbon/Carbon.h> 16 21 22 #import "PSAlerts.h" 17 23 #import "PSDockBounceAlert.h" 18 24 #import "PSScriptAlert.h" … … 38 44 ¥ If you feed NSCalendarDate dateWithNaturalLanguageString: an " AM"/" PM" locale, it doesn't accept that date format. 39 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. 40 47 ¥ too hard to implement date-only or time-only formatters 41 48 ¥ should be able to specify that natural language favors date or time (10 = 10th of month, not 10am) … … 44 51 */ 45 52 53 static NSString * const PSAlertsSelected = @"Pester alerts selected"; // NSUserDefaults key 54 static NSString * const PSAlertsEditing = @"Pester alerts editing"; // NSUserDefaults key 55 46 56 @interface PSAlarmSetController (Private) 47 57 58 - (void)_readAlerts:(PSAlerts *)alerts; 59 - (BOOL)_setAlerts; 48 60 - (void)_stopUpdateTimer; 49 61 … … 54 66 - (void)awakeFromNib; 55 67 { 68 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 56 69 alarm = [[PSAlarm alloc] init]; 57 70 [[self window] center]; … … 60 73 [timeDate setFormatter: [[NJRDateFormatter alloc] initWithDateFormat: [NJRDateFormatter localizedDateFormatIncludingWeekday: NO] allowNaturalLanguage: YES]]; 61 74 { 62 NSArray *dayNames = [ [NSUserDefaults standardUserDefaults]arrayForKey:75 NSArray *dayNames = [defaults arrayForKey: 63 76 NSWeekDayNameArray]; 64 77 NSArray *completions = [timeDateCompletions itemTitles]; … … 83 96 } 84 97 } 98 [editAlert setIntValue: [defaults boolForKey: PSAlertsEditing]]; 99 { 100 NSDictionary *plAlerts = [defaults dictionaryForKey: PSAlertsSelected]; 101 PSAlerts *alerts; 102 if (plAlerts == nil) { 103 alerts = [[PSAlerts alloc] initWithPesterVersion1Alerts]; 104 } else { 105 NS_DURING 106 alerts = [[PSAlerts alloc] initWithPropertyList: plAlerts]; 107 NS_HANDLER 108 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]); 109 alerts = [[PSAlerts alloc] initWithPesterVersion1Alerts]; 110 NS_ENDHANDLER 111 } 112 [self _readAlerts: alerts]; 113 } 85 114 [timeDate setObjectValue: [NSDate date]]; 86 [self inAtChanged: nil]; 115 [self inAtChanged: nil]; // by convention, if sender is nil, we're initializing 87 116 [self playSoundChanged: nil]; 88 117 [self doScriptChanged: nil]; 89 118 [self doSpeakChanged: nil]; 119 [self editAlertChanged: nil]; 90 120 [script setFileTypes: [NSArray arrayWithObjects: @"applescript", @"script", NSFileTypeForHFSTypeCode(kOSAFileType), NSFileTypeForHFSTypeCode('TEXT'), nil]]; 91 121 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(silence:) name: PSAlarmAlertStopNotification object: nil]; 92 122 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(playSoundChanged:) name: NJRQTMediaPopUpButtonMovieChangedNotification object: sound]; 93 [voice setDelegate: self]; 94 // XXX still broken under 10.2, check 10.1 behavior and see if subclassing NSComboBox will help 95 // if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1) { 96 // XXX workaround for 10.1.x bug which sets the first responder to the wrong field, but it works if I set the initial first responder to nil... go figure. 97 [[self window] setInitialFirstResponder: nil]; 98 // } 123 [voice setDelegate: self]; // XXX why don't we do this in IB? It should use the accessor... 124 [wakeUp setEnabled: [PSPowerManager autoWakeSupported]]; 125 // 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. 126 [[self window] setInitialFirstResponder: nil]; 99 127 [[self window] makeKeyAndOrderFront: nil]; 100 128 } … … 110 138 } 111 139 140 // XXX with -[NSControl currentEditor] don't need to compare? Also check -[NSControl validateEditing] 112 141 - (id)objectValueForTextField:(NSTextField *)field whileEditing:(id)sender; 113 142 { … … 124 153 } 125 154 155 #pragma mark date/interval setting 156 126 157 - (void)setAlarmDateAndInterval:(id)sender; 127 158 { 128 159 if (isInterval) { 129 [alarm setInterval: 130 [[self objectValueForTextField: timeInterval whileEditing: sender] intValue] * 131 [timeIntervalUnits selectedTag]]; 160 [alarm setInterval: [timeInterval interval]]; 132 161 } else { 133 162 [alarm setForDate: [self objectValueForTextField: timeDate whileEditing: sender] … … 141 170 } 142 171 143 // XXX use OACalendar ?172 // XXX use OACalendar in popup like Palm Desktop? 144 173 145 174 - (IBAction)updateDateDisplay:(id)sender; … … 193 222 [timeDateCompletions setEnabled: !isInterval]; 194 223 if (sender != nil) 195 [[self window] makeFirstResponder: isInterval ? timeInterval : timeOfDay];224 [[self window] makeFirstResponder: isInterval ? (NSTextField *)timeInterval : timeOfDay]; 196 225 // NSLog(@"UPDATING FROM inAtChanged"); 197 226 [self update: nil]; 198 227 } 228 229 - (IBAction)dateCompleted:(NSPopUpButton *)sender; 230 { 231 [timeDate setStringValue: [sender titleOfSelectedItem]]; 232 [self update: sender]; 233 } 234 235 #pragma mark alert editing 236 237 - (IBAction)editAlertChanged:(id)sender; 238 { 239 BOOL editAlertSelected = [editAlert intValue]; 240 NSView *editAlertControl = [editAlert controlView]; 241 NSWindow *window = [self window]; 242 NSRect frame = [window frame]; 243 if (editAlertSelected) { 244 NSSize editWinSize = [window maxSize]; 245 [editAlertControl setNextKeyView: [displayMessage controlView]]; 246 frame.origin.y += frame.size.height - editWinSize.height; 247 frame.size = editWinSize; 248 [window setFrame: frame display: (sender != nil) animate: (sender != nil)]; 249 [self updateDateDisplay: sender]; 250 [alertTabs selectTabViewItemWithIdentifier: @"edit"]; 251 } else { 252 NSSize viewWinSize = [window minSize]; 253 NSRect textFrame = [alertView frame]; 254 float textHeight; 255 if (![self _setAlerts]) { 256 [alertView setStringValue: [NSString stringWithFormat: @"CouldnÕt process alert information.\n%@", status]]; 257 } else { 258 NSAttributedString *string = [[alarm alerts] prettyList]; 259 if (string == nil) { 260 [alertView setStringValue: @"Do nothing. Click the button labeled ÒEditÓ to add an alert."]; 261 } else { 262 [alertView setAttributedStringValue: string]; 263 [self updateDateDisplay: sender]; 264 } 265 } 266 if (sender != nil) { // nil == we're initializing, don't mess with focus 267 NSResponder *oldResponder = [window firstResponder]; 268 // make sure focus doesn't get stuck in the edit tab: it is confusing and leaves behind artifacts 269 if (oldResponder == editAlertControl || [oldResponder isKindOfClass: [NSView class]] && [(NSView *)oldResponder isDescendantOf: alertTabs]) 270 [window makeFirstResponder: messageField]; // would use editAlertControl, but can't get it to display anomaly-free. 271 [self silence: sender]; 272 } 273 // allow height to expand, though not arbitrarily (should still fit on an 800x600 screen) 274 textHeight = [[alertView cell] cellSizeForBounds: NSMakeRect(0, 0, textFrame.size.width, 400)].height; 275 textFrame.origin.y += textFrame.size.height - textHeight; 276 textFrame.size.height = textHeight; 277 [alertView setFrame: textFrame]; 278 viewWinSize.height += textHeight; 279 [alertTabs selectTabViewItemWithIdentifier: @"view"]; 280 frame.origin.y += frame.size.height - viewWinSize.height; 281 frame.size = viewWinSize; 282 [window setFrame: frame display: (sender != nil) animate: (sender != nil)]; 283 [editAlertControl setNextKeyView: cancelButton]; 284 } 285 if (sender != nil) { 286 [[NSUserDefaults standardUserDefaults] setBool: editAlertSelected forKey: PSAlertsEditing]; 287 } 288 } 289 199 290 200 291 - (IBAction)playSoundChanged:(id)sender; … … 206 297 [soundRepetitionStepper setEnabled: canRepeat]; 207 298 [soundRepetitionsLabel setTextColor: canRepeat ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]]; 208 if (playSoundSelected && sender != nil)299 if (playSoundSelected && sender == playSound) { 209 300 [[self window] makeFirstResponder: sound]; 301 } 210 302 } 211 303 … … 234 326 [script setEnabled: doScriptSelected]; 235 327 [scriptSelectButton setEnabled: doScriptSelected]; 236 if (doScriptSelected && sender != nil) 328 if (doScriptSelected && sender != nil) { 237 329 [[self window] makeFirstResponder: scriptSelectButton]; 330 if ([script alias] == nil) [scriptSelectButton performClick: sender]; 331 } 238 332 } 239 333 240 334 - (IBAction)doSpeakChanged:(id)sender; 241 335 { 242 BOOL doSpeakSelected = [doSpeak intValue];336 BOOL doSpeakSelected = [doSpeak state] == NSOnState; 243 337 [voice setEnabled: doSpeakSelected]; 244 338 if (doSpeakSelected && sender != nil) … … 246 340 } 247 341 248 - (IBAction)dateCompleted:(NSPopUpButton *)sender; 249 { 250 [timeDate setStringValue: [sender titleOfSelectedItem]]; 251 [self update: sender]; 252 } 342 - (void)_readAlerts:(PSAlerts *)alerts; 343 { 344 NSEnumerator *e = [alerts alertEnumerator]; 345 PSAlert *alert; 346 347 [alarm setAlerts: alerts]; 348 349 // turn off all alerts 350 [bounceDockIcon setState: NSOffState]; 351 [doScript setIntValue: NO]; 352 [displayMessage setIntValue: NO]; 353 [playSound setIntValue: NO]; 354 [doSpeak setIntValue: NO]; 355 356 while ( (alert = [e nextObject]) != nil) { 357 if ([alert isKindOfClass: [PSDockBounceAlert class]]) { 358 [bounceDockIcon setState: NSOnState]; 359 } else if ([alert isKindOfClass: [PSScriptAlert class]]) { 360 [doScript setIntValue: YES]; 361 [script setAlias: [(PSScriptAlert *)alert scriptFileAlias]]; 362 } else if ([alert isKindOfClass: [PSNotifierAlert class]]) { 363 [displayMessage setIntValue: YES]; 364 } else if ([alert isKindOfClass: [PSBeepAlert class]]) { 365 unsigned int repetitions = [(PSBeepAlert *)alert repetitions]; 366 [playSound setIntValue: YES]; 367 [sound setAlias: nil]; 368 [soundRepetitions setIntValue: repetitions]; 369 [soundRepetitionStepper setIntValue: repetitions]; 370 } else if ([alert isKindOfClass: [PSMovieAlert class]]) { 371 unsigned int repetitions = [(PSMovieAlert *)alert repetitions]; 372 [playSound setIntValue: YES]; 373 [sound setAlias: [(PSMovieAlert *)alert movieFileAlias]]; 374 [soundRepetitions setIntValue: repetitions]; 375 [soundRepetitionStepper setIntValue: repetitions]; 376 } else if ([alert isKindOfClass: [PSSpeechAlert class]]) { 377 [doSpeak setIntValue: YES]; 378 [voice setVoice: [(PSSpeechAlert *)alert voice]]; 379 } 380 } 381 } 382 383 - (BOOL)_setAlerts; 384 { 385 PSAlerts *alerts = [alarm alerts]; 386 387 [alerts removeAlerts]; 388 NS_DURING 389 // dock bounce alert 390 if ([bounceDockIcon state] == NSOnState) 391 [alerts addAlert: [PSDockBounceAlert alert]]; 392 // script alert 393 if ([doScript intValue]) { 394 BDAlias *scriptFileAlias = [script alias]; 395 if (scriptFileAlias == nil) { 396 [self setStatus: @"Unable to set script alert (no script specified?)"]; 397 return NO; 398 } 399 [alerts addAlert: [PSScriptAlert alertWithScriptFileAlias: scriptFileAlias]]; 400 } 401 // notifier alert 402 if ([displayMessage intValue]) 403 [alerts addAlert: [PSNotifierAlert alert]]; 404 // sound alerts 405 if ([playSound intValue]) { 406 BDAlias *soundAlias = [sound selectedAlias]; 407 unsigned short numReps = [soundRepetitions intValue]; 408 if (soundAlias == nil) // beep alert 409 [alerts addAlert: [PSBeepAlert alertWithRepetitions: numReps]]; 410 else // movie alert 411 [alerts addAlert: [PSMovieAlert alertWithMovieFileAlias: soundAlias repetitions: numReps]]; 412 } 413 // speech alert 414 if ([doSpeak intValue]) 415 [alerts addAlert: [PSSpeechAlert alertWithVoice: [voice titleOfSelectedItem]]]; 416 [[NSUserDefaults standardUserDefaults] setObject: [alerts propertyListRepresentation] forKey: PSAlertsSelected]; 417 NS_HANDLER 418 [self setStatus: [localException reason]]; 419 NS_VALUERETURN(NO, BOOL); 420 NS_ENDHANDLER 421 return YES; 422 } 423 424 #pragma mark actions 253 425 254 426 // to ensure proper updating of interval, this should be the only method by which the window is shown (e.g. from the Alarm menu) … … 256 428 { 257 429 if (![[self window] isVisible]) { 430 NSDate *today = [NSCalendarDate dateForDay: [NSDate date]]; 431 if ([(NSDate *)[timeDate objectValue] compare: today] == NSOrderedAscending) { 432 [timeDate setObjectValue: today]; 433 } 258 434 [self update: self]; 259 // 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. :(435 // 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. :( 260 436 [[self window] makeFirstResponder: [[self window] initialFirstResponder]]; 261 437 } … … 266 442 { 267 443 // set alerts before setting alarm... 268 [alarm removeAlerts]; 269 // dock bounce alert 270 if ([bounceDockIcon state] == NSOnState) 271 [alarm addAlert: [PSDockBounceAlert alert]]; 272 // script alert 273 if ([doScript intValue]) { 274 BDAlias *scriptFileAlias = [script alias]; 275 if (scriptFileAlias == nil) { 276 [self setStatus: @"Unable to set script alert (no script specified?)"]; 277 return; 278 } 279 [alarm addAlert: [PSScriptAlert alertWithScriptFileAlias: scriptFileAlias]]; 280 } 281 // notifier alert 282 if ([displayMessage intValue]) 283 [alarm addAlert: [PSNotifierAlert alert]]; 284 // sound alerts 285 if ([playSound intValue]) { 286 BDAlias *soundAlias = [sound selectedAlias]; 287 unsigned short numReps = [soundRepetitions intValue]; 288 if (soundAlias == nil) // beep alert 289 [alarm addAlert: [PSBeepAlert alertWithRepetitions: numReps]]; 290 else // movie alert 291 [alarm addAlert: [PSMovieAlert alertWithMovieFileAlias: soundAlias repetitions: numReps]]; 292 } 293 // speech alert 294 if ([doSpeak intValue]) 295 [alarm addAlert: [PSSpeechAlert alertWithVoice: [voice titleOfSelectedItem]]]; 444 if (![self _setAlerts]) return; 296 445 297 446 // set alarm 298 447 [self setAlarmDateAndInterval: sender]; 448 [alarm setRepeating: [timeIntervalRepeats state] == NSOnState]; 299 449 [alarm setMessage: [messageField stringValue]]; 300 450 if (![alarm setTimer]) { … … 319 469 @implementation PSAlarmSetController (NSControlSubclassDelegate) 320 470 471 - (BOOL)control:(NSControl *)control didFailToFormatString:(NSString *)string errorDescription:(NSString *)error; 472 { 473 if (control == timeInterval) 474 [timeInterval handleDidFailToFormatString: string errorDescription: error label: @"alarm interval"]; 475 return NO; 476 } 477 321 478 - (void)control:(NSControl *)control didFailToValidatePartialString:(NSString *)string errorDescription:(NSString *)error; 322 479 { 323 unichar c;324 int tag;325 unsigned length = [string length];326 if (control != timeInterval || length == 0) return;327 c = [string characterAtIndex: length - 1];328 switch (c) {329 case 's': case 'S': tag = 1; break;330 case 'm': case 'M': tag = 60; break;331 case 'h': case 'H': tag = 60 * 60; break;332 default: return;333 }334 [timeIntervalUnits selectItemAtIndex:335 [timeIntervalUnits indexOfItemWithTag: tag]];336 480 // NSLog(@"UPDATING FROM validation"); 337 [self update: timeInterval]; // make sure we still examine the field editor, otherwise if the existing numeric string is invalid, it'll be cleared481 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 338 482 } 339 483 … … 347 491 [self silence: nil]; 348 492 [self _stopUpdateTimer]; 493 [self _setAlerts]; 349 494 } 350 495 … … 367 512 - (NSString *)voicePopUpButton:(NJRVoicePopUpButton *)sender previewStringForVoice:(NSString *)voice; 368 513 { 369 return [messageField stringValue]; 514 NSString *message = [messageField stringValue]; 515 if (message == nil || [message length] == 0) 516 message = [alarm message]; 517 return message; 370 518 } 371 519
Note:
See TracChangeset
for help on using the changeset viewer.