source: releases/Pester/1.1b4/Source/PSApplication.m@ 148

Last change on this file since 148 was 133, checked in by Nicholas Riley, 22 years ago

VERSION: Updated for 1.1b4.

PSMovieAlertController.[hm]: Set output volume from alert; save and
restore output volume (bug 27).

PSBeepAlert.[hm]: Save and set volume, disabled pending a
synchronous equivalent to NSBeep().

NJRQTMediaPopUpButton.[hm]: Save movieHasAudio (does not include
beeps, pending the above fix, since they don't permit volume
adjustment). savedVolume indicates whether the volume has been saved
but not restored yet; outputVolume stores the set volume (eventually
incorporated into a PSMediaAlert).

PSMediaAlert.[hm]: Stores repetitions and volume information for audio
alerts. New superclass of PSBeepAlert and PSMovieAlert.

PSPreferencesController.m: Fixed bug where hot keys still appeared
even after they couldn't be set - last of bug 29. Add Command-comma
to the list of disallowed equivalents, as it's reserved for
Preferences now (still a bug - it'll show the entire set key
equivalent at the left side of the window when you press
command-comma; ah well.)

Volume [0123].png: Volume-indicating small icons from QuickTime 6.

NJRSoundManager.[hm]: Interface to volume saving, restoring, setting -
necessary because the QuickTime call to SetDefaultOutputVolume sets
the right channel volume only (bug 27).

PSVolumeController.h: Controller for volume popup window (bug 27).
Not your average NSWindowController, but it works. Keyboard control
of volume is still necessary; filed as bug 31.

PSAlarmSetController.[hm]: Added references to sound volume button and
showVolume: action. Added volume setting support (bug 27); mostly
similar interface to calendar, though we need direct calls to
NJRSoundManager to restore sound volume at times. Only enable sound
volume button if selected media file has audio component (and isn't the
system alert sound, which I discussed above). Take advantage of
PSMediaAlert to factor some code in _readAlerts:. Save and restore
volume as part of alerts.

Read Me.rtfd: Updated release notes; fixed some bizarre text
formatting problems; search/replace "* " bullet-space with "*\t"
bullet-tab to improve alignment in release notes.

PSMovieAlert.[hm]: Factored code into PSMediaAlert. Describe output
volume as percentage of maximum.

NJRHotKey.m: Fixed some odd spacing left over from Ecky's code.

PSApplication.m: Restore saved output volume on quit.

English.lproj/MainMenu.nib: Added volume button.

English.lproj/Volume.nib: Volume nib (bug 27).

PSCalendarController.m: Removed casts from a copy/paste error. Fixed
variable names in some code inherited from my TextExtras incremental
search modifications - it's not always a text field now.

File size: 10.0 KB
Line 
1//
2// PSApplication.m
3// Pester
4//
5// Created by Nicholas Riley on Fri Oct 11 2002.
6// Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "PSApplication.h"
10#import "PSAlarmSetController.h"
11#import "PSAlarmAlertController.h"
12#import "PSAlarmsController.h"
13#import "PSPreferencesController.h"
14#import "NJRReadMeController.h"
15#import "NJRSoundManager.h"
16#import "PSAlarm.h"
17#import "PSAlarms.h"
18#import "PSTimer.h"
19#import "NJRHotKey.h"
20
21#import <Carbon/Carbon.h>
22
23@implementation PSApplication
24
25- (void)finishLaunching;
26{
27 appIconImage = [[NSImage imageNamed: @"NSApplicationIcon"] retain];
28 [[NSNotificationCenter defaultCenter] addObserver: [PSAlarmAlertController class] selector: @selector(controllerWithTimerExpiredNotification:) name: PSAlarmTimerExpiredNotification object: nil];
29 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(nextAlarmDidChange:) name: PSAlarmsNextAlarmDidChangeNotification object: nil];
30 // XXX exception handling
31 [PSAlarms setUp];
32 [self setDelegate: self];
33 [PSPreferencesController readPreferences];
34 [super finishLaunching];
35}
36
37#pragma mark actions
38
39- (IBAction)showHelp:(id)sender;
40{
41 [NJRReadMeController readMeControllerWithRTFDocument: [[NSBundle mainBundle] pathForResource: @"Read Me" ofType: @"rtfd"]];
42}
43
44- (IBAction)stopAlerts:(id)sender;
45{
46 [PSAlarmAlertController stopAlerts: sender];
47}
48
49- (IBAction)orderFrontSetAlarmPanel:(id)sender;
50{
51 [NSApp activateIgnoringOtherApps: YES];
52 [alarmSetController showWindow: self];
53}
54
55- (IBAction)orderFrontAlarmsPanel:(id)sender;
56{
57 [NSApp activateIgnoringOtherApps: YES];
58 if (alarmsController == nil) {
59 alarmsController = [[PSAlarmsController alloc] init];
60 }
61 [alarmsController showWindow: self];
62}
63
64- (IBAction)orderFrontPreferencesPanel:(id)sender;
65{
66 if (!preferencesController) {
67 preferencesController = [[PSPreferencesController alloc] init];
68 }
69 [preferencesController showWindow: self];
70}
71
72#pragma mark update timer
73
74- (void)_resetUpdateTimer;
75{
76 if (dockUpdateTimer != nil) {
77 [dockUpdateTimer invalidate];
78 [dockUpdateTimer release];
79 dockUpdateInterval = 0;
80 dockUpdateTimer = nil;
81 }
82}
83
84- (void)_setUpdateTimerForInterval:(NSTimeInterval)interval alarm:(PSAlarm *)alarm repeats:(BOOL)repeats;
85{
86 dockUpdateTimer = [PSTimer scheduledTimerWithTimeInterval: interval target: self selector: @selector(_updateDockTile:) userInfo: alarm repeats: repeats];
87 [dockUpdateTimer retain];
88 dockUpdateInterval = interval; // because [timer timeInterval] always returns 0 once set
89}
90
91- (void)_updateDockTile:(PSTimer *)timer;
92{
93 PSAlarm *alarm = [timer userInfo];
94 NSTimeInterval timeRemaining;
95 NSString *tileString;
96 if (timer == nil) alarm = [[PSAlarms allAlarms] nextAlarm];
97 if (alarm == nil) return;
98 tileString = [alarm timeRemainingString];
99 timeRemaining = [alarm timeRemaining]; // want to err on the side of timeRemaining being smaller, otherwise ÇexpiredÈ can appear
100 {
101 NSMutableDictionary *atts = [NSMutableDictionary dictionary];
102 NSSize imageSize = [appIconImage size];
103 NSImage *tile = [[NSImage alloc] initWithSize: imageSize];
104 NSSize textSize;
105 NSPoint textOrigin;
106 NSRect frameRect;
107 float fontSize = 37;
108
109 do {
110 fontSize -= 1;
111 [atts setObject: [NSFont boldSystemFontOfSize: fontSize] forKey: NSFontAttributeName];
112 textSize = [tileString sizeWithAttributes: atts];
113 } while (textSize.width > imageSize.width - 8);
114
115 textOrigin = NSMakePoint(imageSize.width / 2 - textSize.width / 2,
116 imageSize.height / 2 - textSize.height / 2);
117 frameRect = NSInsetRect(NSMakeRect(textOrigin.x, textOrigin.y, textSize.width, textSize.height), -4, -2);
118
119 [tile lockFocus];
120 // draw the grayed-out app icon
121 [appIconImage dissolveToPoint: NSZeroPoint fraction: 0.5];
122 // draw the frame
123 [[NSColor colorWithCalibratedWhite: 0.1 alpha: 0.5] set];
124 NSRectFill(frameRect);
125 // draw a gray two-pixel text shadow
126 [atts setObject: [NSColor grayColor] forKey: NSForegroundColorAttributeName];
127 textOrigin.x++; textOrigin.y--;
128 [tileString drawAtPoint: textOrigin withAttributes: atts];
129 textOrigin.x++; textOrigin.y--;
130 [tileString drawAtPoint: textOrigin withAttributes: atts];
131 // draw white text
132 textOrigin.x -= 2; textOrigin.y += 2;
133 [atts setObject: [NSColor whiteColor] forKey: NSForegroundColorAttributeName];
134 [tileString drawAtPoint: textOrigin withAttributes: atts];
135
136 [tile unlockFocus];
137 [NSApp setApplicationIconImage: tile];
138 [tile release];
139 }
140 // NSLog(@"_updateDockTile > time remaining %@ (%.6lf), last time interval %.6lf", tileString, timeRemaining, dockUpdateInterval);
141 if (timeRemaining > 61) {
142 NSTimeInterval nextUpdate = ((unsigned long long)timeRemaining) % 60;
143 if (nextUpdate <= 1) nextUpdate = 60;
144 [self _resetUpdateTimer];
145 [self _setUpdateTimerForInterval: nextUpdate alarm: alarm repeats: NO];
146 // NSLog(@"_updateDockTile > set timer for %.0lf seconds", nextUpdate);
147 } else if (timer == nil || dockUpdateInterval > 1) {
148 [self _resetUpdateTimer];
149 [self _setUpdateTimerForInterval: 1 alarm: alarm repeats: YES];
150 // NSLog(@"_updateDockTile > set timer for 1 second");
151 } else if (timeRemaining <= 1) {
152 [self _resetUpdateTimer];
153 }
154}
155
156- (void)nextAlarmDidChange:(NSNotification *)notification;
157{
158 PSAlarm *nextAlarm = [notification object];
159 // NSLog(@"nextAlarmDidChange: %@", nextAlarm);
160 [self _resetUpdateTimer];
161 if (nextAlarm == nil) {
162 [NSApp setApplicationIconImage: appIconImage];
163 } else {
164 [self _updateDockTile: nil];
165 }
166}
167
168@end
169
170@implementation PSApplication (NSApplicationDelegate)
171
172- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag;
173{
174 // XXX sometimes alarmsExpiring is NO (?), and we display the alarm set controller on top of an expiring alarm, try to reproduce
175 if (!flag && ![[PSAlarms allAlarms] alarmsExpiring] && [NSApp modalWindow] == nil)
176 [alarmSetController showWindow: self];
177 return YES;
178}
179
180- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
181{
182 NSMenu *dockMenu = [[NSMenu alloc] initWithTitle: @""];
183 PSAlarms *alarms = [PSAlarms allAlarms];
184 PSAlarm *nextAlarm = [alarms nextAlarm];
185 NSMenuItem *item;
186 if (nextAlarm == nil) {
187 [dockMenu addItemWithTitle: @"No Pending Alarms" action: nil keyEquivalent: @""];
188 } else {
189 [dockMenu addItemWithTitle: @"Next Alarm" action: nil keyEquivalent: @""];
190 [dockMenu addItemWithTitle: [NSString stringWithFormat: @" %@", [nextAlarm message]] action: nil keyEquivalent: @""];
191 [dockMenu addItemWithTitle: [NSString stringWithFormat: @" %@ %@", [nextAlarm shortDateString], [nextAlarm timeString]] action: nil keyEquivalent: @""];
192 [dockMenu addItemWithTitle: [NSString stringWithFormat: @" Remaining: %@", [nextAlarm timeRemainingString]] action: nil keyEquivalent: @""];
193 }
194 [dockMenu addItem: [NSMenuItem separatorItem]];
195 item = [dockMenu addItemWithTitle: NSLocalizedString(@"Set Alarm...", "Dock menu item") action: @selector(orderFrontSetAlarmPanel:) keyEquivalent: @""];
196 [item setTarget: self];
197 item = [dockMenu addItemWithTitle: [NSString stringWithFormat: NSLocalizedString(@"All Alarms (%d)...", "Dock menu item (%d replaced by number of alarms)"), [alarms alarmCount]] action: @selector(orderFrontAlarmsPanel:) keyEquivalent: @""];
198 [item setTarget: self];
199 return [dockMenu autorelease];
200}
201
202@end
203
204@implementation PSApplication (NSApplicationNotifications)
205
206- (void)applicationDidFinishLaunching:(NSNotification *)notification;
207{
208 // XXX import panel will not be frontmost window if you switch to another app while Pester is launching; Mac OS X bug?
209 PSAlarms *allAlarms = [PSAlarms allAlarms];
210 unsigned version1AlarmCount = [allAlarms countOfVersion1Alarms];
211 if (version1AlarmCount > 0) {
212 int answer = NSRunAlertPanel(@"Import alarms from older Pester version?", @"Pester found %u alarm%@ created with an older version. These alarms must be converted for use with this version of Pester, and will be unavailable in previous versions after conversion. New alarms created with this version of Pester will not appear in Pester version 1.1a3 or earlier.",
213 @"Import", @"Discard", NSLocalizedString(@"Don't Import", "Pester <= 1.1a3 format alarms button"),
214 version1AlarmCount, version1AlarmCount == 1 ? @"" : @"s");
215 switch (answer) {
216 case NSAlertDefaultReturn:
217 NS_DURING
218 [allAlarms importVersion1Alarms];
219 NS_HANDLER
220 NSRunAlertPanel(@"Error occurred importing alarms", @"Pester was unable to convert some alarms created with an older version. Those alarms which could be read have been converted. The previous-format alarms have been retained; try using an older version of Pester to read them.\n\n%@", nil, nil, nil, [localException reason]);
221 NS_VOIDRETURN;
222 NS_ENDHANDLER
223 case NSAlertAlternateReturn:
224 [allAlarms discardVersion1Alarms];
225 break;
226 case NSAlertOtherReturn:
227 break;
228 }
229 }
230}
231
232- (void)applicationWillTerminate:(NSNotification *)notification;
233{
234 [NJRSoundManager restoreSavedDefaultOutputVolume];
235 [NSApp setApplicationIconImage: appIconImage];
236}
237
238// calendar window (running in modal session) will appear even when app is in background; shouldn't
239- (void)applicationWillBecomeActive:(NSNotification *)notification;
240{
241 NSWindow *modalWindow = [NSApp modalWindow];
242 if (modalWindow != nil) [modalWindow makeKeyAndOrderFront: nil];
243}
244
245- (void)applicationWillResignActive:(NSNotification *)notification;
246{
247 NSWindow *modalWindow = [NSApp modalWindow];
248 if (modalWindow != nil) [modalWindow orderOut: nil];
249}
250
251@end
Note: See TracBrowser for help on using the repository browser.