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

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