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

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

PSPreferencesController.[hm]: Added support for registering hot keys;
not the most elegant thing in the world, but much better than it was
in the prototype. Triggered by +readPreferences.

NJRHotKeyField.[hm]: Replaced model components (wow, was that ever
dumb) by NJRHotKey reference, eliminating cumbersome archiving model.
Added accessors for hot key.

NJRHotKeyManager.[hm]: Ported Quentin Carnicelli's HotKeyCenter code
to use NJRHotKey, cleaned up, and removed reverse-engineered pre-10.2
support.

NJRHotKey.[hm]: New. Provides Cocoa-centric storage for
three-component hot keys, mapping from Cocoa to Carbon modifiers.

PSApplication.m: Reorganized. Added invocation of
+[PSPreferencesController readPreferences].

Fixes bug 29.

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