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

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

Localization, bug fixes

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