MenuNoteController.h

Copyright © 2003, 2004 Dave Bayer. Subject to the terms and conditions of the MIT License.

#import <Foundation/Foundation.h>
@class PreferenceController;

@interface MenuNoteController : NSObject
{
    NSStatusItem *statusItem;
    IBOutlet NSMenu *theMenu;
    PreferenceController *preferenceController;
    NSTimer *mainTimer;
    NSTimeInterval wakeDate, nextDate, wakeTime;
    BOOL awake;
}

- (void) setTimer;
- (void) freeTimer;
- (void) fireTimer;

- (IBAction) openPrefs: (id) sender;
- (IBAction) openHelp: (id) sender;

@end

MenuNoteController.m

Copyright © 2003, 2004 Dave Bayer. Subject to the terms and conditions of the MIT License.

#include <math.h>
#include <Carbon/Carbon.h>

#import "MenuNoteController.h"
#import "PreferenceController.h"

@implementation MenuNoteController

initialize

+ (void) initialize
{
    [PreferenceController registerDefaults];
    [PreferenceController restoreDefaults];
}

displayWillSleep

- (void) displayWillSleep
{
    NSLog(@"DisplayWillSleep");
    NSLog(@"");
    [self freeTimer];
    wakeTime += [NSDate timeIntervalSinceReferenceDate] - wakeDate;
    awake = NO;
}

displayDidWake

- (void) displayDidWake
{
    NSLog(@"DisplayDidWake");
    [self setTimer];
}

notifyProc

static void notifyProc(void *userData, short message, void *notifyData)
{
    MenuNoteController *self = userData;
    if (message == kDMNotifyDisplayWillSleep)
        [self displayWillSleep];
    else if (message == kDMNotifyDisplayDidWake)
        [self displayDidWake];
}

enableDisplayEvents

- (void) enableDisplayEvents
{
    ProcessSerialNumber serial;

    NSLog(@"enableDisplayEvents");
    GetCurrentProcess(&serial);
    DMRegisterExtendedNotifyProc(notifyProc, (void *) self, nil, &serial);
}

disableDisplayEvents

- (void) disableDisplayEvents
{
    ProcessSerialNumber serial;

    GetCurrentProcess(&serial);
    DMRemoveExtendedNotifyProc(notifyProc, nil, &serial, nil);
}

setTimer

- (void) setTimer
{
    mainTimer = [NSTimer
        scheduledTimerWithTimeInterval: 0.0
        target: self
        selector: @selector(timerAction:)
        userInfo: nil
        repeats: NO];
    [mainTimer retain];
}

freeTimer

- (void) freeTimer
{
    if (mainTimer != nil)
    {
        [mainTimer invalidate];
        [mainTimer release];
    }
    mainTimer = nil;
}

fireTimer

- (void) fireTimer
{
    [mainTimer fire];
}

resetTimer

- (void) resetTimer: (NSTimeInterval) fireDate
{
    [self freeTimer];
    mainTimer = [[NSTimer alloc]
        initWithFireDate: [NSDate dateWithTimeIntervalSinceReferenceDate: fireDate]
        interval: nil
        target: self
        selector: @selector(timerAction:)
        userInfo: nil
        repeats: NO];
    [mainTimer retain];
    [[NSRunLoop currentRunLoop] addTimer: mainTimer forMode: NSDefaultRunLoopMode];
}

setTitle

- (void) setTitle: (NSTimeInterval) date
{
    int wakeSeconds = wakeTime + date - wakeDate;

    NSMutableString *format = [NSMutableString stringWithCapacity: 64];
    [format setString: timeFormat];
    [format replaceOccurrencesOfString: @"%T"
        withString: [NSString stringWithFormat:@"%d", wakeSeconds/3600]
        options: NSLiteralSearch range: NSMakeRange(0, [format length])];
    [format replaceOccurrencesOfString: @"%U"
        withString: [NSString stringWithFormat:@"%02d", (wakeSeconds/60)%60]
        options: NSLiteralSearch range: NSMakeRange(0, [format length])];
    [format replaceOccurrencesOfString: @"%V"
        withString: [NSString stringWithFormat:@"%02d", wakeSeconds%60]
        options: NSLiteralSearch range: NSMakeRange(0, [format length])];

    NSCalendarDate *time = [[[NSDate dateWithTimeIntervalSinceReferenceDate: date]
            dateWithCalendarFormat: format timeZone: nil]
        dateByAddingYears: 0 months: 0 days: 0
        hours: hourOffset
        minutes: minuteOffset
        seconds: 0];
    NSString *title = [NSString stringWithFormat:@"%@", time];
    if (stripZeros)
    {
        NSMutableString *strip = [NSMutableString stringWithCapacity: 64];
        [strip setString: title];
        [strip replaceOccurrencesOfString: @"0" withString: @"" options: NSLiteralSearch range: NSMakeRange(0, 1)];
        [strip replaceOccurrencesOfString: @" 0" withString: @" " options: NSLiteralSearch range: NSMakeRange(0, [strip length])];
        title = strip;
    }
    [statusItem setTitle: title];
}

timerAction

- (void) timerAction: (NSTimer *) timer
{
    NSTimeInterval fireDate, roundedDate, fracPart;

    NSLog(@"timerAction");
    fireDate = [NSDate timeIntervalSinceReferenceDate];
    if (!awake)
    {
        wakeDate = fireDate;
        awake = YES;
    }
    else if (fireDate > nextDate + 1.0)
    {
        wakeDate += fireDate - nextDate;
    }
    NSLog(@"wakeDate = %@", [[NSDate dateWithTimeIntervalSinceReferenceDate: wakeDate]
        dateWithCalendarFormat: @"%H:%M:%S.%F" timeZone: nil]);
    NSLog(@"wakeTime = %.2f", wakeTime + fireDate - wakeDate);

    roundedDate = fireDate + roundTo/2;
    roundedDate -= fmod(roundedDate, roundTo);
    fireDate = roundedDate + roundTo;
    fracPart = fmod(fireDate, refreshRate);
    if (fracPart > 0) fireDate += refreshRate - fracPart;
    nextDate = fireDate;

    [self resetTimer: fireDate];
    [self setTitle: roundedDate];
}

awakeFromNib

- (void) awakeFromNib
{
    statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength] retain];
    [statusItem setMenu: theMenu];
    [statusItem setEnabled: YES];
    [self enableDisplayEvents];
    [self setTimer];
    wakeTime = 0.0;
    awake = NO;
}

openPrefs

- (IBAction) openPrefs: (id) sender
{
    if (!preferenceController)
    {
        preferenceController = [[PreferenceController alloc] init];
        [preferenceController setController: self];
    }
    [[preferenceController window] makeKeyAndOrderFront: nil];
}

openHelp

Bypass Apple Help, and instead open help using the user's preferred browser:

- (IBAction) openHelp: (id) sender
{
    static NSString *help = @"Contents/Resources/source/index.html";

     [[ NSWorkspace sharedWorkspace ] openFile:
        [ NSString pathWithComponents:
            [ NSArray arrayWithObjects:
                 [[ NSBundle mainBundle ] bundlePath ], help, nil ]]];
}

dealloc

- (void) dealloc
{
    NSLog(@"dealloc");
    [self freeTimer];
    [statusItem release];
    [preferenceController release];
    [super dealloc];
}

@end