source: trunk/Cocoa/Pester/Source/PSApplication.m@ 587

Last change on this file since 587 was 587, checked in by Nicholas Riley, 15 years ago

Fix some problems/deprecation identified by compiling with the 10.6 SDK.

File size: 11.1 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#import "NSWindowCollectionBehavior.h"
21
22@implementation PSApplication
23
24- (void)finishLaunching;
25{
26 appIconImage = [[NSImage imageNamed: @"NSApplicationIcon"] retain];
27 [[NSNotificationCenter defaultCenter] addObserver: [PSAlarmAlertController class] selector: @selector(controllerWithTimerExpiredNotification:) name: PSAlarmTimerExpiredNotification object: nil];
28 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(nextAlarmDidChange:) name: PSAlarmsNextAlarmDidChangeNotification object: nil];
29 // XXX exception handling
30 [PSAlarms setUp];
31 [self setDelegate: self];
32 [PSPreferencesController readPreferences];
33 [super finishLaunching];
34}
35
36#pragma mark actions
37
38- (IBAction)showHelp:(id)sender;
39{
40 [NJRReadMeController readMeControllerWithRTFDocument: [[NSBundle mainBundle] pathForResource: @"Read Me" ofType: @"rtfd"]];
41}
42
43- (IBAction)stopAlerts:(id)sender;
44{
45 [PSAlarmAlertController stopAlerts: sender];
46}
47
48- (IBAction)orderFrontSetAlarmPanel:(id)sender;
49{
50 NSWindow *window = [alarmSetController window];
51 if ([window respondsToSelector: @selector(setCollectionBehavior:)]) { // 10.5-only
52 // XXX bug workaround - NSWindowCollectionBehaviorMoveToActiveSpace is what we want, but it doesn't work correctly, probably because we have a "chicken and egg" problem as the panel isn't visible when the app is hidden
53 [window setCollectionBehavior: NSWindowCollectionBehaviorCanJoinAllSpaces];
54 [alarmSetController showWindow: self];
55 [window performSelector: @selector(setCollectionBehavior:) withObject:
56 (id)NSWindowCollectionBehaviorDefault afterDelay: 0];
57 [NSApp activateIgnoringOtherApps: YES]; // XXX causes title bar to flash
58 return;
59 }
60 [NSApp activateIgnoringOtherApps: YES];
61 [alarmSetController showWindow: self];
62}
63
64- (IBAction)orderFrontAlarmsPanel:(id)sender;
65{
66 [NSApp activateIgnoringOtherApps: YES];
67 if (alarmsController == nil) {
68 alarmsController = [[PSAlarmsController alloc] init];
69 }
70 [alarmsController showWindow: self];
71}
72
73- (IBAction)orderFrontPreferencesPanel:(id)sender;
74{
75 if (!preferencesController) {
76 preferencesController = [[PSPreferencesController alloc] init];
77 }
78 [preferencesController showWindow: self];
79}
80
81#pragma mark Spaces interaction
82
83- (void)orderOutSetAlarmPanelIfHidden;
84{
85 // prevent set alarm panel from "yanking" focus from an alarm notification, thereby obscuring the notification
86 if ([NSApp isActive])
87 return;
88
89 NSWindow *window = [alarmSetController window];
90 if (![window isVisible])
91 return;
92
93 [window orderOut: self];
94}
95
96#pragma mark update timer
97
98- (void)_resetUpdateTimer;
99{
100 if (dockUpdateTimer != nil) {
101 [dockUpdateTimer invalidate];
102 [dockUpdateTimer release];
103 dockUpdateInterval = 0;
104 dockUpdateTimer = nil;
105 }
106}
107
108- (void)_setUpdateTimerForInterval:(NSTimeInterval)interval alarm:(PSAlarm *)alarm repeats:(BOOL)repeats;
109{
110 dockUpdateTimer = [PSTimer scheduledTimerWithTimeInterval: interval target: self selector: @selector(_updateDockTile:) userInfo: alarm repeats: repeats];
111 [dockUpdateTimer retain];
112 dockUpdateInterval = interval; // because [timer timeInterval] always returns 0 once set
113}
114
115- (void)_updateDockTile:(PSTimer *)timer;
116{
117 PSAlarm *alarm = [timer userInfo];
118 NSTimeInterval timeRemaining;
119 NSString *tileString;
120 if (timer == nil) alarm = [[PSAlarms allAlarms] nextAlarm];
121 if (alarm == nil) return;
122 tileString = [alarm timeRemainingString];
123 timeRemaining = [alarm timeRemaining]; // want to err on the side of timeRemaining being smaller, otherwise ÇexpiredÈ can appear
124 {
125 NSMutableDictionary *atts = [NSMutableDictionary dictionary];
126 NSSize imageSize = [appIconImage size];
127 NSImage *tile = [[NSImage alloc] initWithSize: imageSize];
128 NSSize textSize;
129 NSPoint textOrigin;
130 NSRect frameRect;
131 float fontSize = 37;
132
133 do {
134 fontSize -= 1;
135 [atts setObject: [NSFont boldSystemFontOfSize: fontSize] forKey: NSFontAttributeName];
136 textSize = [tileString sizeWithAttributes: atts];
137 } while (textSize.width > imageSize.width - 8);
138
139 textOrigin = NSMakePoint(imageSize.width / 2 - textSize.width / 2,
140 imageSize.height / 2 - textSize.height / 2);
141 frameRect = NSInsetRect(NSMakeRect(textOrigin.x, textOrigin.y, textSize.width, textSize.height), -4, -2);
142
143 [tile lockFocus];
144 // draw the grayed-out app icon
145 [appIconImage dissolveToPoint: NSZeroPoint fraction: 0.5];
146 // draw the frame
147 [[NSColor colorWithCalibratedWhite: 0.1 alpha: 0.5] set];
148 NSRectFill(frameRect);
149 // draw a gray two-pixel text shadow
150 [atts setObject: [NSColor grayColor] forKey: NSForegroundColorAttributeName];
151 textOrigin.x++; textOrigin.y--;
152 [tileString drawAtPoint: textOrigin withAttributes: atts];
153 textOrigin.x++; textOrigin.y--;
154 [tileString drawAtPoint: textOrigin withAttributes: atts];
155 // draw white text
156 textOrigin.x -= 2; textOrigin.y += 2;
157 [atts setObject: [NSColor whiteColor] forKey: NSForegroundColorAttributeName];
158 [tileString drawAtPoint: textOrigin withAttributes: atts];
159
160 [tile unlockFocus];
161 [NSApp setApplicationIconImage: tile];
162 [tile release];
163 }
164 // NSLog(@"_updateDockTile > time remaining %@ (%.6lf), last time interval %.6lf", tileString, timeRemaining, dockUpdateInterval);
165 if (timeRemaining > 61) {
166 NSTimeInterval nextUpdate = ((unsigned long long)timeRemaining) % 60;
167 if (nextUpdate <= 1) nextUpdate = 60;
168 [self _resetUpdateTimer];
169 [self _setUpdateTimerForInterval: nextUpdate alarm: alarm repeats: NO];
170 // NSLog(@"_updateDockTile > set timer for %.0lf seconds", nextUpdate);
171 } else if (timer == nil || dockUpdateInterval > 1) {
172 [self _resetUpdateTimer];
173 [self _setUpdateTimerForInterval: 1 alarm: alarm repeats: YES];
174 // NSLog(@"_updateDockTile > set timer for 1 second");
175 } else if (timeRemaining <= 1) {
176 [self _resetUpdateTimer];
177 }
178}
179
180- (void)nextAlarmDidChange:(NSNotification *)notification;
181{
182 PSAlarm *nextAlarm = [notification object];
183 // NSLog(@"nextAlarmDidChange: %@", nextAlarm);
184 [self _resetUpdateTimer];
185 if (nextAlarm == nil) {
186 [NSApp setApplicationIconImage: appIconImage];
187 } else {
188 [self _updateDockTile: nil];
189 }
190}
191
192@end
193
194@implementation PSApplication (NSApplicationDelegate)
195
196- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag;
197{
198 // XXX sometimes alarmsExpiring is NO (?), and we display the alarm set controller on top of an expiring alarm, try to reproduce
199 if (!flag && ![[PSAlarms allAlarms] alarmsExpiring] && [NSApp modalWindow] == nil)
200 [alarmSetController showWindow: self];
201 return YES;
202}
203
204- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
205{
206 NSMenu *dockMenu = [[NSMenu alloc] initWithTitle: @""];
207 PSAlarms *alarms = [PSAlarms allAlarms];
208 PSAlarm *nextAlarm = [alarms nextAlarm];
209 NSMenuItem *item;
210 if (nextAlarm == nil) {
211 [dockMenu addItemWithTitle: @"No Pending Alarms" action: nil keyEquivalent: @""];
212 } else {
213 [dockMenu addItemWithTitle: @"Next Alarm" action: nil keyEquivalent: @""];
214 [dockMenu addItemWithTitle: [NSString stringWithFormat: @" %@", [nextAlarm message]] action: nil keyEquivalent: @""];
215 [dockMenu addItemWithTitle: [NSString stringWithFormat: @" %@ %@", [nextAlarm shortDateString], [nextAlarm timeString]] action: nil keyEquivalent: @""];
216 [dockMenu addItemWithTitle: [NSString stringWithFormat: @" Remaining: %@", [nextAlarm timeRemainingString]] action: nil keyEquivalent: @""];
217 }
218 [dockMenu addItem: [NSMenuItem separatorItem]];
219 item = [dockMenu addItemWithTitle: NSLocalizedString(@"Set Alarm...", "Dock menu item") action: @selector(orderFrontSetAlarmPanel:) keyEquivalent: @""];
220 [item setTarget: self];
221 item = [dockMenu addItemWithTitle: [NSString stringWithFormat: NSLocalizedString(@"All Alarms (%d)...", "Dock menu item (%d replaced by number of alarms)"), [alarms alarmCount]] action: @selector(orderFrontAlarmsPanel:) keyEquivalent: @""];
222 [item setTarget: self];
223 return [dockMenu autorelease];
224}
225
226@end
227
228@implementation PSApplication (NSApplicationNotifications)
229
230- (void)applicationDidFinishLaunching:(NSNotification *)notification;
231{
232 // XXX import panel will not be frontmost window if you switch to another app while Pester is launching; Mac OS X bug?
233 PSAlarms *allAlarms = [PSAlarms allAlarms];
234 unsigned version1AlarmCount = [allAlarms countOfVersion1Alarms];
235 if (version1AlarmCount > 0) {
236 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.",
237 @"Import", @"Discard", NSLocalizedString(@"Don't Import", "Pester <= 1.1a3 format alarms button"),
238 version1AlarmCount, version1AlarmCount == 1 ? @"" : @"s");
239 switch (answer) {
240 case NSAlertDefaultReturn:
241 @try {
242 [allAlarms importVersion1Alarms];
243 } @catch (NSException *exception) {
244 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, [exception reason]);
245 return;
246 }
247 case NSAlertAlternateReturn:
248 [allAlarms discardVersion1Alarms];
249 break;
250 case NSAlertOtherReturn:
251 break;
252 }
253 }
254}
255
256- (void)applicationWillTerminate:(NSNotification *)notification;
257{
258 [NJRSoundManager restoreSavedDefaultOutputVolume];
259 [NSApp setApplicationIconImage: appIconImage];
260}
261
262// calendar window (running in modal session) will appear even when app is in background; shouldn't
263- (void)applicationWillBecomeActive:(NSNotification *)notification;
264{
265 NSWindow *modalWindow = [NSApp modalWindow];
266 if (modalWindow != nil) [modalWindow makeKeyAndOrderFront: nil];
267}
268
269- (void)applicationWillResignActive:(NSNotification *)notification;
270{
271 NSWindow *modalWindow = [NSApp modalWindow];
272 if (modalWindow != nil) [modalWindow orderOut: nil];
273}
274
275@end
Note: See TracBrowser for help on using the repository browser.