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

Last change on this file since 131 was 131, checked in by Nicholas Riley, 19 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.