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

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

PSPreferencesController.[hm]: Manage preferences window, just like in
HostLauncher (and DockCam, I guess, though I didn't check.)

NJRHotKeyField.[hm], NJRHotKeyFieldCell.[hm]: Implements a NSTextField
subclass which intercepts every keyboard event it can, and turns it
into a human-readable representation. Don't ask me how many hours of
work this was.

English.lproj/MainMenu.nib: Hook up Preferences menu item.

English.lproj/Preferences.nib: Simple Preferences panel. One
NJRHotKeyField, one button, a couple of static text fields.

NSString-NJRExtensions.[hm]: Added method from HostLauncher (modified
to output attributed string, as it's needed in order to get the right
mix of fonts), -keyEquivalentAttributedStringWithModifierMask:.
Greatly broadened the number of keys which this method can process to
pretty much the entire extended keyboard.

NSFont-NJRExtensions.[hm]: Provide a class method for obtaining a
theme font as a NSFont.

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