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

Last change on this file since 601 was 601, checked in by Nicholas Riley, 10 years ago

Float vs. double fixes to pacify GCC.

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