Initial revision
authorAlexander Strange <astrange@ithinksw.com>
Sun, 1 Dec 2002 06:58:09 +0000 (06:58 +0000)
committerAlexander Strange <astrange@ithinksw.com>
Sun, 1 Dec 2002 06:58:09 +0000 (06:58 +0000)
33 files changed:
English.lproj/InfoPlist.strings [new file with mode: 0755]
English.lproj/KeyCodes.plist [new file with mode: 0755]
English.lproj/MainMenu.nib/classes.nib [new file with mode: 0755]
English.lproj/MainMenu.nib/info.nib [new file with mode: 0755]
English.lproj/MainMenu.nib/objects.nib [new file with mode: 0755]
English.lproj/Preferences.nib/classes.nib [new file with mode: 0755]
English.lproj/Preferences.nib/info.nib [new file with mode: 0755]
English.lproj/Preferences.nib/objects.nib [new file with mode: 0755]
English.lproj/StatusWindow.nib/classes.nib [new file with mode: 0755]
English.lproj/StatusWindow.nib/info.nib [new file with mode: 0755]
English.lproj/StatusWindow.nib/objects.nib [new file with mode: 0755]
HotKeyCenter.h [new file with mode: 0755]
HotKeyCenter.m [new file with mode: 0755]
KeyBroadcaster.h [new file with mode: 0755]
KeyBroadcaster.m [new file with mode: 0755]
KeyCombo.h [new file with mode: 0755]
KeyCombo.m [new file with mode: 0755]
MTApplication.h [new file with mode: 0755]
MTApplication.m [new file with mode: 0755]
MenuInverted.tiff [new file with mode: 0755]
MenuNormal.tiff [new file with mode: 0755]
MenuTunes.h [new file with mode: 0755]
MenuTunes.m [new file with mode: 0755]
MenuTunesView.h [new file with mode: 0755]
MenuTunesView.m [new file with mode: 0755]
PreferencesController.h [new file with mode: 0755]
PreferencesController.m [new file with mode: 0755]
StatusWindow.h [new file with mode: 0755]
StatusWindow.m [new file with mode: 0755]
StatusWindowController.h [new file with mode: 0755]
StatusWindowController.m [new file with mode: 0755]
main.m [new file with mode: 0755]
submenu.tiff [new file with mode: 0755]

diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings
new file mode 100755 (executable)
index 0000000..d7798ba
Binary files /dev/null and b/English.lproj/InfoPlist.strings differ
diff --git a/English.lproj/KeyCodes.plist b/English.lproj/KeyCodes.plist
new file mode 100755 (executable)
index 0000000..7856ab6
--- /dev/null
@@ -0,0 +1,102 @@
+{
+       0 = "A";
+       1 = "S";
+       2 = "D";
+       3 = "F";
+       4 = "H";
+       5 = "G";
+       6 = "Z";
+       7 = "X";
+       8 = "C";
+       9 = "V";
+       11 = "B";
+       12 = "Q";
+       13 = "W";
+       14 = "E";
+       15 = "R";
+       16 = "Y";
+       17 = "T";
+       18 = "1";
+       19 = "2";
+       20 = "3";
+       21 = "4";
+       22 = "6";
+       23 = "5";
+       24 = "=";
+       25 = "9";
+       26 = "7";
+       27 = "-";
+       28 = "8";
+       29 = "0";
+       30 = "\]";
+       31 = "O";
+       32 = "U";
+       33 = "\[";
+       34 = "I";
+       35 = "P";
+       36 = "Return";
+       37 = "L";
+       38 = "J";
+       39 = "\'";
+       40 = "K";
+       41 = ";";
+       42 = "\\";
+       43 = "Pad ,";
+       44 = "Pad /";
+       45 = "N";
+       46 = "M";
+       47 = "Pad .";
+       48 = "Tab";
+       49 = "Space";
+       50 = "\`";
+       51 = "Delete";
+       53 = "ESC";
+       55 = "Command";
+       56 = "Shift";
+       57 = "Caps Lock";
+       58 = "Option";
+       59 = "Control";
+       65 = ".";
+       67 = "Pad *";
+       69 = "Pad +";
+       71 = "Clear";
+       75 = "\/";
+       76 = "Pad Enter";
+       78 = "Pad -";
+       81 = "Pad =";
+       82 = "Pad 0";
+       83 = "Pad 1";
+       84 = "Pad 2";
+       85 = "Pad 3";
+       86 = "Pad 4";
+       87 = "Pad 5";
+       88 = "Pad 6";
+       89 = "Pad 7";
+       91 = "Pad 8";
+       92 = "Pad 9";
+       96 = "F5";
+       97 = "F6";
+       98 = "F7";
+       99 = "F3";
+       100 = "F8";
+       101 = "F9";
+       103 = "F11";
+       105 = "F13";
+       107 = "F14";
+       109 = "F10";
+       111 = "F12";
+       113 = "F15";
+       114 = "Ins";
+       115 = "Home";
+       116 = "Page Up";
+       117 = "Del";
+       118 = "F4";
+       119 = "End";
+       120 = "F2";
+       121 = "Page Down";
+       122 = "F1";
+       123 = "Left Arrow";
+       124 = "Right Arrow";
+       125 = "Down Arrow";
+       126 = "Up Arrow";
+}
\ No newline at end of file
diff --git a/English.lproj/MainMenu.nib/classes.nib b/English.lproj/MainMenu.nib/classes.nib
new file mode 100755 (executable)
index 0000000..a5a8984
--- /dev/null
@@ -0,0 +1,8 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = MTApplication; LANGUAGE = ObjC; SUPERCLASS = NSApplication; }, 
+        {CLASS = MenuTunes; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
+    ); 
+    IBVersion = 1; 
+}
\ No newline at end of file
diff --git a/English.lproj/MainMenu.nib/info.nib b/English.lproj/MainMenu.nib/info.nib
new file mode 100755 (executable)
index 0000000..9b3242d
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>IBFramework Version</key>
+       <string>286.0</string>
+       <key>IBSystem Version</key>
+       <string>6F21</string>
+</dict>
+</plist>
diff --git a/English.lproj/MainMenu.nib/objects.nib b/English.lproj/MainMenu.nib/objects.nib
new file mode 100755 (executable)
index 0000000..50ca73e
Binary files /dev/null and b/English.lproj/MainMenu.nib/objects.nib differ
diff --git a/English.lproj/Preferences.nib/classes.nib b/English.lproj/Preferences.nib/classes.nib
new file mode 100755 (executable)
index 0000000..3c22618
--- /dev/null
@@ -0,0 +1,46 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = KeyBroadcaster; LANGUAGE = ObjC; SUPERCLASS = NSButton; }, 
+        {
+            ACTIONS = {
+                apply = id; 
+                cancel = id; 
+                cancelHotKey = id; 
+                clearHotKey = id; 
+                okHotKey = id; 
+                save = id; 
+                setCurrentTrackInfo = id; 
+                setNextTrack = id; 
+                setPlayPause = id; 
+                setPreviousTrack = id; 
+                setUpcomingSongs = id; 
+            }; 
+            CLASS = PreferencesController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {
+                albumCheckbox = NSButton; 
+                allTableView = NSTableView; 
+                artistCheckbox = NSButton; 
+                genreCheckbox = NSButton; 
+                keyComboField = NSTextField; 
+                keyComboPanel = NSPanel; 
+                menuTableView = NSTableView; 
+                nameCheckbox = NSButton; 
+                nextTrackButton = NSButton; 
+                playPauseButton = NSButton; 
+                previousTrackButton = NSButton; 
+                songRatingCheckbox = NSButton; 
+                songsInAdvance = NSTextField; 
+                trackInfoButton = NSButton; 
+                trackNumberCheckbox = NSButton; 
+                trackTimeCheckbox = NSButton; 
+                upcomingSongsButton = NSButton; 
+                window = NSWindow; 
+                yearCheckbox = NSButton; 
+            }; 
+            SUPERCLASS = NSObject; 
+        }
+    ); 
+    IBVersion = 1; 
+}
\ No newline at end of file
diff --git a/English.lproj/Preferences.nib/info.nib b/English.lproj/Preferences.nib/info.nib
new file mode 100755 (executable)
index 0000000..3f55daf
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>IBDocumentLocation</key>
+       <string>61 9 356 240 0 0 1152 746 </string>
+       <key>IBFramework Version</key>
+       <string>286.0</string>
+       <key>IBGroupedObjects</key>
+       <dict>
+               <key>0</key>
+               <array>
+                       <string>123</string>
+                       <string>124</string>
+               </array>
+       </dict>
+       <key>IBLastGroupID</key>
+       <string>1</string>
+       <key>IBOpenObjects</key>
+       <array>
+               <integer>6</integer>
+       </array>
+       <key>IBSystem Version</key>
+       <string>6F21</string>
+</dict>
+</plist>
diff --git a/English.lproj/Preferences.nib/objects.nib b/English.lproj/Preferences.nib/objects.nib
new file mode 100755 (executable)
index 0000000..bbf55df
Binary files /dev/null and b/English.lproj/Preferences.nib/objects.nib differ
diff --git a/English.lproj/StatusWindow.nib/classes.nib b/English.lproj/StatusWindow.nib/classes.nib
new file mode 100755 (executable)
index 0000000..7d510ed
--- /dev/null
@@ -0,0 +1,13 @@
+{
+    IBClasses = (
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {CLASS = StatusWindow; LANGUAGE = ObjC; SUPERCLASS = NSWindow; }, 
+        {
+            CLASS = StatusWindowController; 
+            LANGUAGE = ObjC; 
+            OUTLETS = {statusField = NSTextField; statusWindow = StatusWindow; }; 
+            SUPERCLASS = NSObject; 
+        }
+    ); 
+    IBVersion = 1; 
+}
\ No newline at end of file
diff --git a/English.lproj/StatusWindow.nib/info.nib b/English.lproj/StatusWindow.nib/info.nib
new file mode 100755 (executable)
index 0000000..93c48ef
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>IBFramework Version</key>
+       <string>286.0</string>
+       <key>IBOpenObjects</key>
+       <array>
+               <integer>5</integer>
+       </array>
+       <key>IBSystem Version</key>
+       <string>6F21</string>
+</dict>
+</plist>
diff --git a/English.lproj/StatusWindow.nib/objects.nib b/English.lproj/StatusWindow.nib/objects.nib
new file mode 100755 (executable)
index 0000000..6ea0e70
Binary files /dev/null and b/English.lproj/StatusWindow.nib/objects.nib differ
diff --git a/HotKeyCenter.h b/HotKeyCenter.h
new file mode 100755 (executable)
index 0000000..10ab9f8
--- /dev/null
@@ -0,0 +1,34 @@
+//
+//  HotKeyCenter.h
+//
+//  Created by Quentin D. Carnicelli on Thu Jun 06 2002.
+//  Copyright (c) 2002 Subband inc.. All rights reserved.
+//
+//  Feedback welcome at qdc@subband.com
+//  This code is provided AS IS, so don't hurt yourself with it...
+//
+
+#import <AppKit/AppKit.h>
+
+#import "KeyCombo.h"
+
+@interface HotKeyCenter : NSObject
+{
+    BOOL mEnabled;
+    NSMutableDictionary *mHotKeys;
+}
+
++ (id)sharedCenter;
+
+- (BOOL)addHotKey:(NSString *)name combo:(KeyCombo *)combo target:(id)target action:(SEL)action;
+- (void)removeHotKey:(NSString *)name;
+
+- (NSArray *)allNames;
+- (KeyCombo *)keyComboForName:(NSString *)name;
+
+- (void)setEnabled:(BOOL)enabled;
+- (BOOL)enabled;
+
+- (void)sendEvent:(NSEvent *)event;
+
+@end
diff --git a/HotKeyCenter.m b/HotKeyCenter.m
new file mode 100755 (executable)
index 0000000..9698fdb
--- /dev/null
@@ -0,0 +1,344 @@
+//
+//  HotKeyCenter.m
+//
+//  Created by Quentin D. Carnicelli on Thu Jun 06 2002.
+//  Copyright (c) 2002 Subband inc.. All rights reserved.
+//
+//  Feedback welcome at qdc@subband.com
+//  This code is provided AS IS, so don't hurt yourself with it...
+//
+
+#import "HotKeyCenter.h"
+#import "KeyCombo.h"
+
+#import <Carbon/Carbon.h>
+
+#define kHotKeyCenterSignature 'HKyC'
+
+//*** _HotKeyData
+@interface _HotKeyData : NSObject
+{
+    @public
+        BOOL mRegistered;
+        EventHotKeyRef mRef;
+        KeyCombo *mCombo;
+        id mTarget;
+        SEL mAction;
+}
+@end
+
+@implementation _HotKeyData
+@end
+
+//**** HotKeyCenter
+@interface HotKeyCenter (Private)
+    - (OSStatus)handleHotKeyEvent: (EventRef)inEvent;
+
+    - (BOOL)_registerHotKeyIfNeeded:(_HotKeyData *)hk;
+    - (void)_unregisterHotKeyIfNeeded:(_HotKeyData *)hk;
+
+    + (BOOL)_systemSupportsHotKeys;
+    - (void)_hotKeyUp:(_HotKeyData *)hotKey;
+    - (void)_hotKeyDown:(_HotKeyData *)hotKey;
+    - (void)_hotKeyDownWithRef:(EventHotKeyRef)ref;
+    - (void)_hotKeyUpWithRef:(EventHotKeyRef)ref;
+    - (_HotKeyData*)_findHotKeyWithRef:(EventHotKeyRef)ref;
+
+    pascal OSErr keyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *refCon);
+@end
+
+@implementation HotKeyCenter
+
+static id _sharedHKCenter = nil;
+
++ (id)sharedCenter
+{
+    if (_sharedHKCenter != nil)
+    {
+        return _sharedHKCenter;
+    }
+    
+    _sharedHKCenter = [[HotKeyCenter alloc] init];
+    
+    if ([self _systemSupportsHotKeys])
+    {
+        EventTypeSpec eventSpec[2] =
+        {
+            { kEventClassKeyboard, kEventHotKeyPressed },
+            { kEventClassKeyboard, kEventHotKeyReleased }
+        };    
+    
+        InstallEventHandler( GetEventDispatcherTarget(), NewEventHandlerUPP((EventHandlerProcPtr) keyEventHandler), 2, eventSpec, nil, nil);
+    }
+    
+    return _sharedHKCenter;
+}
+
+- (id)init
+{
+    if ( (self = [super init]) )
+    {
+            mEnabled = YES;
+            mHotKeys = [[NSMutableDictionary alloc] init];
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    [mHotKeys release];
+    [super dealloc];
+}
+
+- (BOOL)addHotKey:(NSString *)name combo:(KeyCombo *)combo target:(id)target action:(SEL)action
+{
+    _HotKeyData *oldHotKey;
+    _HotKeyData *newHotKey;
+    
+    NSParameterAssert(name != nil);
+    NSParameterAssert(combo != nil);
+    NSParameterAssert(target != nil);
+    NSParameterAssert(action != nil);
+    
+    //** Check if we have one of these yet
+    oldHotKey = [mHotKeys objectForKey:name];
+    
+    if (oldHotKey) //Registered already?
+    {
+        [self removeHotKey:name];
+    }
+    
+    //** Save the hot key to our own list
+    newHotKey = [[[_HotKeyData alloc] init] autorelease];
+    newHotKey->mRegistered = NO;
+    newHotKey->mRef = nil;
+    newHotKey->mCombo = [combo retain];
+    newHotKey->mTarget = target; //Retain this?
+    newHotKey->mAction = action;
+    
+    [mHotKeys setObject:newHotKey forKey:name];
+    
+    return [self _registerHotKeyIfNeeded:newHotKey];
+}
+
+- (void)removeHotKey:(NSString *)name;
+{
+    _HotKeyData *hotKey;
+    
+    hotKey = [mHotKeys objectForKey:name];
+    if (hotKey == nil) //Not registered
+        return;
+    
+    [self _unregisterHotKeyIfNeeded:hotKey];
+    [hotKey->mCombo release];
+    
+    //Drop it from our hot key list
+    [mHotKeys removeObjectForKey: name];
+}
+
+- (NSArray *)allNames
+{
+    return [mHotKeys allKeys];
+}
+
+- (KeyCombo *)keyComboForName:(NSString *)name
+{
+    _HotKeyData * hotKey;
+    
+    hotKey = [mHotKeys objectForKey:name];
+    if( hotKey == nil ) //Not registered
+        return nil;
+    
+    return hotKey->mCombo;
+}
+
+- (void)setEnabled:(BOOL)enabled
+{
+    NSEnumerator *enumerator;
+    _HotKeyData *hotKey;
+    
+    enumerator = [mHotKeys objectEnumerator];
+
+    while ((hotKey = [enumerator nextObject]) != nil)
+    {
+        if (enabled)
+            [self _registerHotKeyIfNeeded:hotKey];
+        else
+            [self _unregisterHotKeyIfNeeded:hotKey];
+    }
+    
+    mEnabled = enabled;
+}
+
+- (BOOL)enabled
+{
+    return mEnabled;
+}
+
+- (void)sendEvent:(NSEvent *)event;
+{
+    long subType;
+    EventHotKeyRef hotKeyRef;
+    
+    //We only have to intercept sendEvent to do hot keys on old system versions
+    if ([HotKeyCenter _systemSupportsHotKeys] == YES)
+            return;
+    
+    if ([event type] == NSSystemDefined)
+    {
+        subType = [event subtype];
+        
+        if (subType == 6) //6 is hot key down
+        {
+            hotKeyRef = (EventHotKeyRef)[event data1]; //data1 is our hot key ref
+            if (hotKeyRef != nil)
+            {
+                [self _hotKeyDownWithRef:hotKeyRef];
+            }
+        }
+        else if (subType == 9) //9 is hot key up
+        {
+            hotKeyRef = (EventHotKeyRef)[event data1];
+            if (hotKeyRef != nil)
+            {
+                [self _hotKeyUpWithRef:hotKeyRef];
+            }
+        }
+    }
+}
+
+- (OSStatus)handleHotKeyEvent:(EventRef)inEvent
+{
+    OSStatus err;
+    EventHotKeyID hotKeyID;
+    _HotKeyData *hk;
+    
+    //Shouldnt get here on non-hotkey supporting system versions
+    NSAssert([HotKeyCenter _systemSupportsHotKeys] == YES, @"");
+    NSAssert(GetEventClass(inEvent) == kEventClassKeyboard, @"Got unhandled event class");
+    
+    err = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, nil, sizeof(EventHotKeyID), nil, &hotKeyID);
+    
+    if (err)
+    {
+        return err;
+    }
+            
+    NSAssert(hotKeyID.signature == kHotKeyCenterSignature, @"Got unknown hot key");
+    
+    hk = (_HotKeyData *)hotKeyID.id;
+    NSAssert(hk != nil, @"Got bad hot key");
+    
+    switch (GetEventKind(inEvent))
+    {
+        case kEventHotKeyPressed:
+            [self _hotKeyDown:hk];
+        break;
+        
+        case kEventHotKeyReleased:
+            [self _hotKeyUp:hk];
+        break;
+        
+        default:
+        break;
+    }
+    
+    return noErr;
+}
+
++ (BOOL)_systemSupportsHotKeys
+{
+    SInt32 vers; 
+    Gestalt(gestaltSystemVersion,&vers); 
+    return (vers >= 0x00001020);
+}
+
+- (BOOL)_registerHotKeyIfNeeded:(_HotKeyData *)hk
+{
+    KeyCombo *combo;
+    
+    NSParameterAssert(hk != nil);
+    
+    combo = hk->mCombo;
+
+    if( mEnabled == YES && hk->mRegistered == NO && [combo isValid] == YES )
+    {
+        EventHotKeyID keyID;
+        OSStatus err;
+        
+        keyID.signature = kHotKeyCenterSignature;
+        keyID.id = (unsigned long)hk;
+        err = RegisterEventHotKey([combo keyCode], [combo modifiers], 
+                keyID, GetEventDispatcherTarget(), 0, &hk->mRef);
+        if (err)
+        {
+            return NO;
+        }
+        
+        hk->mRegistered = YES;
+    }
+    
+    return YES;
+}
+
+- (void)_unregisterHotKeyIfNeeded:(_HotKeyData *)hk
+{
+    NSParameterAssert(hk != nil);
+    
+    if (hk->mRegistered && hk->mRef != nil)
+    {
+        UnregisterEventHotKey(hk->mRef);
+    }
+}
+
+- (void)_hotKeyDown:(_HotKeyData *)hotKey
+{
+    id target = hotKey->mTarget;
+    SEL action = hotKey->mAction;
+    [target performSelector:action withObject:self];
+}
+
+- (void)_hotKeyUp: (_HotKeyData *)hotKey
+{
+}
+
+- (void)_hotKeyDownWithRef:(EventHotKeyRef)ref
+{
+    _HotKeyData *hotKey;
+    
+    hotKey = [self _findHotKeyWithRef:ref];
+    if (hotKey)
+    {
+        [self _hotKeyDown:hotKey];
+    }
+}
+
+- (void)_hotKeyUpWithRef:(EventHotKeyRef)ref
+{
+}
+
+- (_HotKeyData *)_findHotKeyWithRef:(EventHotKeyRef)ref
+{
+    NSEnumerator *enumerator;
+    _HotKeyData *hotKey;
+    
+    enumerator = [mHotKeys objectEnumerator];
+
+    while ((hotKey = [enumerator nextObject]) != nil)
+    {
+        if (hotKey->mRef == ref)
+        {
+            return hotKey;
+        }
+    }
+    
+    return nil;
+}
+
+pascal OSErr keyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *refCon)
+{
+    return [[HotKeyCenter sharedCenter] handleHotKeyEvent:inEvent];
+}
+
+@end
+
diff --git a/KeyBroadcaster.h b/KeyBroadcaster.h
new file mode 100755 (executable)
index 0000000..19df649
--- /dev/null
@@ -0,0 +1,9 @@
+/* KeyBroadcaster */
+
+#import <Cocoa/Cocoa.h>
+
+@interface KeyBroadcaster : NSButton
+{
+}
++ (long)cocoaToCarbonModifiers:(long)modifiers;
+@end
diff --git a/KeyBroadcaster.m b/KeyBroadcaster.m
new file mode 100755 (executable)
index 0000000..7b47d83
--- /dev/null
@@ -0,0 +1,58 @@
+#import "KeyBroadcaster.h"
+#import <Carbon/Carbon.h>
+
+@interface KeyBroadcaster (Private)
+- (void)_broadcastKeyCode:(short)keyCode andModifiers:(long)modifiers;
+@end
+
+@implementation KeyBroadcaster
+
+- (void)keyDown:(NSEvent *)event
+{
+    short keyCode;
+    long modifiers;
+    
+    keyCode = [event keyCode];
+    modifiers = [event modifierFlags];
+    
+    modifiers = [KeyBroadcaster cocoaToCarbonModifiers:modifiers];
+    [self _broadcastKeyCode:keyCode andModifiers:modifiers];
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent *)event
+{
+    [self keyDown:event];
+    return YES;
+}
+
+- (void)_broadcastKeyCode:(short)keyCode andModifiers:(long)modifiers
+{
+    NSNumber *keycodeNum = [NSNumber numberWithShort:keyCode];
+    NSNumber *modifiersNum = [NSNumber numberWithLong:modifiers];
+    NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
+                    keycodeNum, @"KeyCode", modifiersNum, @"Modifiers", nil, nil];
+    [[NSNotificationCenter defaultCenter] postNotificationName:@"KeyBroadcasterEvent" object:self userInfo:info];
+}
+
++ (long)cocoaToCarbonModifiers:(long)modifiers
+{
+    long carbonModifiers = 0;
+    int i;
+    static long cocoaToCarbon[6][2] = 
+    {
+        { NSCommandKeyMask, cmdKey },
+        { NSAlternateKeyMask, optionKey },
+        { NSControlKeyMask, controlKey },
+        { NSShiftKeyMask, shiftKey },
+    };
+    for (i = 0; i < 6; i++)
+    {
+        if (modifiers & cocoaToCarbon[i][0])
+        {
+            carbonModifiers += cocoaToCarbon[i][1];
+        }
+    }
+    return carbonModifiers;
+}
+
+@end
diff --git a/KeyCombo.h b/KeyCombo.h
new file mode 100755 (executable)
index 0000000..743c77b
--- /dev/null
@@ -0,0 +1,41 @@
+//
+//  KeyCombo.h
+//
+//  Created by Quentin D. Carnicelli on Tue Jun 18 2002.
+//  Copyright (c) 2001 Subband inc.. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface KeyCombo : NSObject <NSCopying, NSCoding>
+{
+    short mKeyCode;
+    long mModifiers;
+}
+
++ (id)keyCombo;
++ (id)clearKeyCombo;
++ (id)keyComboWithKeyCode:(short)keycode andModifiers:(long)modifiers;
+
+- (id)initWithKeyCode:(short)keycode andModifiers:(long)modifiers;
+
+- (id)copyWithZone:(NSZone *)zone;
+- (BOOL)isEqual:(id)object;
+
+- (short)keyCode;
+- (short)modifiers;
+
+- (BOOL)isValid;
+
+- (NSString *)userDisplayRep;
++ (NSDictionary *)keyCodesDictionary;
+
+@end
+
+@interface NSUserDefaults (KeyComboAdditions)
+
+- (void)setKeyCombo: (KeyCombo *)combo forKey: (NSString *)key;
+- (KeyCombo *)keyComboForKey: (NSString *)key;
+
+@end
diff --git a/KeyCombo.m b/KeyCombo.m
new file mode 100755 (executable)
index 0000000..716d4ae
--- /dev/null
@@ -0,0 +1,215 @@
+//
+//  KeyCombo.m
+//
+//  Created by Quentin D. Carnicelli on Tue Jun 18 2002.
+//  Copyright (c) 2001 Subband inc.. All rights reserved.
+//
+
+#import "KeyCombo.h"
+
+#import <AppKit/NSEvent.h>
+#import <Carbon/Carbon.h>
+
+@interface KeyCombo (Private)
+    + (NSString*)_stringForModifiers:(long)modifiers;
+    + (NSString*)_stringForKeyCode:(short)keyCode;
+@end
+
+
+@implementation KeyCombo
+
++ (id)keyCombo
+{
+    return [[[self alloc] init] autorelease];
+}
+
++ (id)clearKeyCombo
+{
+    return [self keyComboWithKeyCode:-1 andModifiers:-1];
+}
+
++ (id)keyComboWithKeyCode: (short)keycode andModifiers: (long)modifiers
+{
+    return [[[self alloc] initWithKeyCode:keycode andModifiers:modifiers] autorelease];
+}
+
+- (id)initWithKeyCode: (short)keycode andModifiers: (long)modifiers
+{
+    if ( (self = [super init]) )
+    {
+        mKeyCode = keycode;
+        mModifiers = modifiers;
+    }
+    return self;
+}
+
+- (id)init
+{
+    return [self initWithKeyCode: -1 andModifiers: -1];
+}
+
+- (id)copyWithZone:(NSZone *)zone;
+{
+    return [self retain];
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{      
+    self = [self init];
+    
+    if( self )
+    {
+        [aDecoder decodeValueOfObjCType: @encode(short) at: &mKeyCode];
+        [aDecoder decodeValueOfObjCType: @encode(long) at: &mModifiers];
+    }
+    
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{      
+    [aCoder encodeValueOfObjCType:@encode(short) at:&mKeyCode];
+    [aCoder encodeValueOfObjCType:@encode(long) at:&mModifiers];
+}
+
+- (BOOL)isEqual:(KeyCombo *)object
+{
+    return ([object isKindOfClass:[KeyCombo class]]) &&
+           ([object keyCode] == [self keyCode]) &&
+           ([object modifiers] == [self modifiers]);
+}
+
+- (NSString *)description
+{
+    return [self userDisplayRep];
+}
+
+- (short)keyCode
+{
+    return mKeyCode;
+}
+
+- (short)modifiers
+{
+    return mModifiers;
+}
+
+- (BOOL)isValid
+{
+    return ((mKeyCode >= 0) && (mModifiers >= 0));
+}
+
+- (NSString *)userDisplayRep
+{
+    if ([self isValid] == NO)
+    {
+        return @"None";
+    }
+    else
+    {
+        return [NSString stringWithFormat: @"%@%@",
+            [KeyCombo _stringForModifiers: mModifiers],
+            [KeyCombo _stringForKeyCode: mKeyCode]];
+    }
+}
+
++ (NSString *)_stringForModifiers: (long)modifiers
+{
+    static long modToChar[4][2] =
+    {
+            { cmdKey,  0x23180000 },
+            { optionKey,       0x23250000 },
+            { controlKey,      0x005E0000 },
+            { shiftKey,        0x21e70000 }
+    };
+    
+    NSString *str = [NSString string];
+    NSString *charStr;
+    long i;
+    
+    for (i = 0; i < 4; i++)
+    {
+        if (modifiers & modToChar[i][0])
+        {
+            charStr = [NSString stringWithCharacters:(const unichar *)&modToChar[i][1] length:1];
+            str = [str stringByAppendingString:charStr];
+        }
+    }
+    
+    return str;
+}
+
++ (NSString *)_stringForKeyCode:(short)keyCode
+{
+       NSDictionary *dict;
+       id key;
+       NSString *str;
+       
+       dict = [self keyCodesDictionary];
+       key = [NSString stringWithFormat: @"%d", keyCode];
+       str = [dict objectForKey: key];
+       
+       if( !str )
+               str = [NSString stringWithFormat: @"%X", keyCode];
+       
+       return str;
+}
+
++ (NSDictionary *)keyCodesDictionary
+{
+    static NSDictionary *keyCodes = nil;
+    
+    if (keyCodes == nil)
+    {
+        NSString *path;
+        NSString *contents;
+        
+        path = [[NSBundle bundleForClass: [KeyCombo class]] pathForResource: @"KeyCodes" ofType: @"plist"];
+        
+        contents = [NSString stringWithContentsOfFile: path];
+        keyCodes = [[contents propertyList] retain];
+    }
+    
+    return keyCodes;
+}
+
+@end
+
+@implementation NSUserDefaults (KeyComboAdditions)
+
+- (void)setKeyCombo:(KeyCombo *)combo forKey:(NSString *)key
+{
+    NSData *data;
+    if (combo)
+    {
+        data = [NSArchiver archivedDataWithRootObject:combo];
+    }
+    else
+    {
+        data = nil;
+    }
+    [self setObject:data forKey:key];
+}
+
+- (KeyCombo *)keyComboForKey:(NSString *)key
+{
+    NSData *data = [self objectForKey:key];
+    KeyCombo *combo;
+    if (data)
+    {
+        combo =  [[NSUnarchiver unarchiveObjectWithData:data] retain];
+    }
+    
+    if (combo == nil)
+    {
+        combo = [[KeyCombo alloc] init];
+    }
+    return combo;
+}
+
+@end
+
+
+
+
+
diff --git a/MTApplication.h b/MTApplication.h
new file mode 100755 (executable)
index 0000000..5692b39
--- /dev/null
@@ -0,0 +1,8 @@
+/* MTApplication */
+
+#import <Cocoa/Cocoa.h>
+
+@interface MTApplication : NSApplication
+{
+}
+@end
diff --git a/MTApplication.m b/MTApplication.m
new file mode 100755 (executable)
index 0000000..24ff6a4
--- /dev/null
@@ -0,0 +1,12 @@
+#import "MTApplication.h"
+#import "HotKeyCenter.h"
+
+@implementation MTApplication
+
+- (void)sendEvent:(NSEvent *)event
+{
+       [[HotKeyCenter sharedCenter] sendEvent:event];
+       [super sendEvent:event];
+}
+
+@end
diff --git a/MenuInverted.tiff b/MenuInverted.tiff
new file mode 100755 (executable)
index 0000000..37ed1b6
Binary files /dev/null and b/MenuInverted.tiff differ
diff --git a/MenuNormal.tiff b/MenuNormal.tiff
new file mode 100755 (executable)
index 0000000..8e94b1d
Binary files /dev/null and b/MenuNormal.tiff differ
diff --git a/MenuTunes.h b/MenuTunes.h
new file mode 100755 (executable)
index 0000000..e726be6
--- /dev/null
@@ -0,0 +1,54 @@
+/* MenuTunes */
+
+#import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+
+@class MenuTunesView, PreferencesController, StatusWindowController;
+
+@interface MenuTunes : NSObject
+{
+    NSStatusItem *statusItem;
+    NSMenu *menu;
+    MenuTunesView *view;
+    
+    //Used in updating the menu automatically
+    NSTimer *refreshTimer;
+    int curTrackIndex;
+    NSString *curPlaylist;
+    int trackInfoIndex;
+    
+    ProcessSerialNumber iTunesPSN;
+    bool didHaveAlbumName; //Helper variable for creating the menu
+    
+    //For upcoming songs
+    NSMenuItem *upcomingSongsItem;
+    NSMenu *upcomingSongsMenu;
+    
+    //For playlist selection
+    NSMenuItem *playlistItem;
+    NSMenu *playlistMenu;
+    
+    NSMenuItem *playPauseMenuItem; //Toggle between 'Play' and 'Pause'
+    
+    PreferencesController *prefsController;
+    StatusWindowController *statusController; //Shows track info and upcoming songs.
+}
+
+- (void)rebuildMenu;
+- (void)updateMenu;
+- (void)rebuildUpcomingSongsMenu;
+- (void)rebuildPlaylistMenu;
+
+- (void)clearHotKeys;
+- (void)setupHotKeys;
+
+- (NSString *)runScriptAndReturnResult:(NSString *)script;
+- (void)timerUpdate;
+
+- (ProcessSerialNumber)iTunesPSN;
+
+- (void)sendAEWithEventClass:(AEEventClass)eventClass andEventID:(AEEventID)eventID;
+
+- (void)closePreferences;
+
+@end
diff --git a/MenuTunes.m b/MenuTunes.m
new file mode 100755 (executable)
index 0000000..9879f8a
--- /dev/null
@@ -0,0 +1,677 @@
+//
+//  MenuTunes.m
+//
+//  iThink Software, Copyright 2002
+//
+//
+
+/*
+Things to do:
+Â¥ Radio mode makes things ugly
+Â¥ Add other options to the menu
+    - EQ sets
+    - set song rating
+Â¥ Make preferences window pretty
+Â¥ Hot Keys
+    - hot keys can't be set when NSBGOnly is on. The window is not key,
+      so the KeyBroadcaster does not pick up key combos. Bad...
+    - the hotkey classes are ugly, I didn't write them
+Â¥ Optimize code
+Â¥ Apple Events!
+*/
+
+#import "MenuTunes.h"
+#import "MenuTunesView.h"
+#import "PreferencesController.h"
+#import "HotKeyCenter.h"
+#import "StatusWindowController.h"
+
+@implementation MenuTunes
+
+- (void)applicationDidFinishLaunching:(NSNotification *)note
+{
+    menu = [[NSMenu alloc] initWithTitle:@""];
+    
+    if (![[NSUserDefaults standardUserDefaults] objectForKey:@"menu"])
+    {
+        [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"Play/Pause", @"Next Track", @"Previous Track", @"Fast Forward", @"Rewind", @"<separator>", @"Upcoming Songs", @"Playlists", @"<separator>", @"PreferencesÉ", @"Quit", @"<separator>", @"Current Track Info", nil] forKey:@"menu"];
+    }
+    
+    iTunesPSN = [self iTunesPSN]; //Get PSN of iTunes if it's running
+    [self rebuildMenu]; //Create the status item menu
+    
+    statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
+    [statusItem setImage:[NSImage imageNamed:@"menu.tiff"]];
+    [statusItem setHighlightMode:YES];
+    [statusItem setMenu:menu];
+    [statusItem retain];
+    
+    view = [[MenuTunesView alloc] initWithFrame:[[statusItem view] frame]];
+    //[statusItem setView:view];
+    
+    //If iTunes is running, start the timer
+    if (!((iTunesPSN.highLongOfPSN == kNoProcess) && (iTunesPSN.lowLongOfPSN == 0)))
+    {
+        refreshTimer = [NSTimer scheduledTimerWithTimeInterval:3.5 
+target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
+    }
+    else
+    {
+        NSMenu *menu2 = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+        
+        //Register for the workspace note
+        [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(iTunesLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
+        refreshTimer = NULL;
+        
+        [[menu2 addItemWithTitle:@"Open iTunes" action:@selector(openiTunes:) keyEquivalent:@""] setTarget:self];
+        [[menu2 addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""] setTarget:self];
+        [[menu2 addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""] setTarget:self];
+        [statusItem setMenu:menu2];
+    }
+}
+
+- (void)applicationWillTerminate:(NSNotification *)note
+{
+    [self clearHotKeys];
+    [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
+}
+
+- (void)dealloc
+{
+    if (refreshTimer)
+    {
+        [refreshTimer invalidate];
+    }
+    [statusItem release];
+    [menu release];
+    [view release];
+    [super dealloc];
+}
+
+//Recreate the status item menu
+- (void)rebuildMenu
+{
+    NSArray *myMenu = [[NSUserDefaults standardUserDefaults] arrayForKey:@"menu"];
+    int i;
+    trackInfoIndex = -1;
+    
+    if (!((iTunesPSN.highLongOfPSN == kNoProcess) && (iTunesPSN.lowLongOfPSN == 0)))
+    {
+        didHaveAlbumName = (([[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn album of current track\nend tell"] length] > 0) ? YES : NO);
+    }
+    else
+    {
+        didHaveAlbumName = NO;
+    }
+    
+    while ([menu numberOfItems] > 0)
+    {
+        [menu removeItemAtIndex:0];
+    }
+    
+    playPauseMenuItem = nil;
+    upcomingSongsItem = nil;
+    playlistItem = nil;
+    
+    for (i = 0; i < [myMenu count]; i++)
+    {
+        NSString *item = [myMenu objectAtIndex:i];
+        if ([item isEqualToString:@"Play/Pause"])
+        {
+            playPauseMenuItem = [menu addItemWithTitle:@"Play" action:@selector(playPause:) keyEquivalent:@""];
+            [playPauseMenuItem setTarget:self];
+        }
+        else if ([item isEqualToString:@"Next Track"])
+        {
+            [[menu addItemWithTitle:@"Next Track" action:@selector(nextSong:) keyEquivalent:@""] setTarget:self];
+        }
+        else if ([item isEqualToString:@"Previous Track"])
+        {
+            [[menu addItemWithTitle:@"Previous Track" action:@selector(prevSong:) keyEquivalent:@""] setTarget:self];
+        }
+        else if ([item isEqualToString:@"Fast Forward"])
+        {
+            [[menu addItemWithTitle:@"Fast Forward" action:@selector(fastForward:) keyEquivalent:@""] setTarget:self];
+        }
+        else if ([item isEqualToString:@"Rewind"])
+        {
+            [[menu addItemWithTitle:@"Rewind" action:@selector(rewind:) keyEquivalent:@""] setTarget:self];
+        }
+        else if ([item isEqualToString:@"Upcoming Songs"])
+        {
+            upcomingSongsItem = [menu addItemWithTitle:@"Upcoming Songs" action:NULL keyEquivalent:@""];
+        }
+        else if ([item isEqualToString:@"Playlists"])
+        {
+            playlistItem = [menu addItemWithTitle:@"Playlists" action:NULL keyEquivalent:@""];
+        }
+        else if ([item isEqualToString:@"PreferencesÉ"])
+        {
+            [[menu addItemWithTitle:@"PreferencesÉ" action:@selector(showPreferences:) keyEquivalent:@""] setTarget:self];
+        }
+        else if ([item isEqualToString:@"Quit"])
+        {
+            [[menu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""] setTarget:self];
+        }
+        else if ([item isEqualToString:@"Current Track Info"])
+        {
+            trackInfoIndex = [menu numberOfItems];
+            [menu addItemWithTitle:@"No Song" action:NULL keyEquivalent:@""];
+        }
+        else if ([item isEqualToString:@"<separator>"])
+        {
+            [menu addItem:[NSMenuItem separatorItem]];
+        }
+    }
+    curTrackIndex = -1; //Force update of everything
+    [self timerUpdate]; //Updates dynamic info in the menu
+    
+    [self clearHotKeys];
+    [self setupHotKeys];
+}
+
+//Updates the menu with current player state, song, and upcoming songs
+- (void)updateMenu
+{
+    NSString *curSongName, *curAlbumName;
+    NSMenuItem *menuItem;
+    
+    if ((iTunesPSN.highLongOfPSN == kNoProcess) && (iTunesPSN.lowLongOfPSN == 0))
+    {
+        return;
+    }
+    
+    //Get the current track name and album.
+    curSongName = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn name of current track\nend tell"];
+    curAlbumName = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn album of current track\nend tell"];
+    
+    if (upcomingSongsItem)
+    {
+        [self rebuildUpcomingSongsMenu];
+    }
+    if (playlistItem)
+    {
+        [self rebuildPlaylistMenu];
+    }
+    
+    if ([curSongName length] > 0)
+    {
+        int index = [menu indexOfItemWithTitle:@"Now Playing"];
+        
+        if (index > -1)
+        {
+            [menu removeItemAtIndex:index + 1];
+            if (didHaveAlbumName)
+            {
+                [menu removeItemAtIndex:index + 1];
+            }
+        }
+        
+        if ([curAlbumName length] > 0)
+        {
+            menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curAlbumName] action:NULL keyEquivalent:@""];
+            [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
+            [menuItem release];
+        }
+        
+        menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curSongName] action:NULL keyEquivalent:@""];
+        [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
+        [menuItem release];
+        
+        if (index == -1)
+        {
+            menuItem = [[NSMenuItem alloc] initWithTitle:@"Now Playing" action:NULL keyEquivalent:@""];
+            [menu removeItemAtIndex:[menu indexOfItemWithTitle:@"No Song"]];
+            [menu insertItem:menuItem atIndex:trackInfoIndex];
+            [menuItem release];
+        }
+    }
+    else if ([menu indexOfItemWithTitle:@"No Song"] == -1)
+    {
+        [menu removeItemAtIndex:trackInfoIndex];
+        [menu removeItemAtIndex:trackInfoIndex];
+        if (didHaveAlbumName)
+        {
+            [menu removeItemAtIndex:trackInfoIndex];
+        }
+        menuItem = [[NSMenuItem alloc] initWithTitle:@"No Song" action:NULL keyEquivalent:@""];
+        [menu insertItem:menuItem atIndex:trackInfoIndex];
+        [menuItem release];
+    }
+    
+    didHaveAlbumName = (([curAlbumName length] > 0) ? YES : NO);
+}
+
+//Rebuild the upcoming songs submenu. Can be improved a lot.
+- (void)rebuildUpcomingSongsMenu
+{
+    int numSongs = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn number of tracks in current playlist\nend tell"] intValue];
+    int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
+    
+    if (numSongs > 0)
+    {
+        int curTrack = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn index of current track\nend tell"] intValue];
+        int i;
+        
+        [upcomingSongsMenu release];
+        upcomingSongsMenu = [[NSMenu alloc] initWithTitle:@""];
+        
+        for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++)
+        {
+            if (i <= numSongs)
+            {
+                NSString *curSong = [self runScriptAndReturnResult:[NSString stringWithFormat:@"tell application \"iTunes\"\nreturn name of track %i of current playlist\nend tell", i]];
+                NSMenuItem *songItem;
+                songItem = [[NSMenuItem alloc] initWithTitle:curSong action:@selector(playTrack:) keyEquivalent:@""];
+                [songItem setTarget:self];
+                [songItem setRepresentedObject:[NSNumber numberWithInt:i]];
+                [upcomingSongsMenu addItem:songItem];
+                [songItem release];
+            }
+            else
+            {
+                [upcomingSongsMenu addItemWithTitle:@"End of playlist." action:NULL keyEquivalent:@""];
+                break;
+            }
+        }
+        [upcomingSongsItem setSubmenu:upcomingSongsMenu];
+    }
+}
+
+- (void)rebuildPlaylistMenu
+{
+    int numPlaylists = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn number of playlists\nend tell"] intValue];
+    int i;
+    
+    [playlistMenu release];
+    playlistMenu = [[NSMenu alloc] initWithTitle:@""];
+    
+    for (i = 1; i <= numPlaylists; i++)
+    {
+        NSString *playlistName = [self runScriptAndReturnResult:[NSString stringWithFormat:@"tell application \"iTunes\"\nreturn name of playlist %i\nend tell", i]];
+        NSMenuItem *tempItem;
+        tempItem = [[NSMenuItem alloc] initWithTitle:playlistName action:@selector(selectPlaylist:) keyEquivalent:@""];
+        [tempItem setTarget:self];
+        [tempItem setRepresentedObject:[NSNumber numberWithInt:i]];
+        [playlistMenu addItem:tempItem];
+        [tempItem release];
+    }
+    [playlistItem setSubmenu:playlistMenu];
+}
+
+- (void)clearHotKeys
+{
+    [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
+    [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
+    [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
+    [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
+    [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
+}
+
+- (void)setupHotKeys
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    
+    if ([defaults objectForKey:@"PlayPause"] != nil)
+    {
+        [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
+                combo:[defaults keyComboForKey:@"PlayPause"]
+                target:self action:@selector(playPause:)];
+    }
+    
+    if ([defaults objectForKey:@"NextTrack"] != nil)
+    {
+        [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
+                combo:[defaults keyComboForKey:@"NextTrack"]
+                target:self action:@selector(nextSong:)];
+    }
+    
+    if ([defaults objectForKey:@"PrevTrack"] != nil)
+    {
+        [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
+                combo:[defaults keyComboForKey:@"PrevTrack"]
+                target:self action:@selector(prevSong:)];
+    }
+    
+    if ([defaults objectForKey:@"TrackInfo"] != nil)
+    {
+        [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
+                combo:[defaults keyComboForKey:@"TrackInfo"]
+                target:self action:@selector(showCurrentTrackInfo)];
+    }
+    
+    if ([defaults objectForKey:@"UpcomingSongs"] != nil)
+    {
+        [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
+               combo:[defaults keyComboForKey:@"UpcomingSongs"]
+               target:self action:@selector(showUpcomingSongs)];
+    }
+}
+
+//Runs an AppleScript and returns the result as an NSString after stripping quotes, if needed.
+- (NSString *)runScriptAndReturnResult:(NSString *)script
+{
+    AEDesc scriptDesc, resultDesc;
+    Size length;
+    NSString *result;
+    Ptr buffer;
+    
+    AECreateDesc(typeChar, [script cString], [script cStringLength], 
+&scriptDesc);
+    
+    OSADoScript(OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype), &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
+    
+    length = AEGetDescDataSize(&resultDesc);
+    buffer = malloc(length);
+    
+    AEGetDescData(&resultDesc, buffer, length);
+    result = [NSString stringWithCString:buffer length:length];
+    if (![result isEqualToString:@""] &&
+        ([result characterAtIndex:0] == '\"') &&
+        ([result characterAtIndex:[result length] - 1] == '\"'))
+    {
+        result = [result substringWithRange:NSMakeRange(1, [result length] - 2)];
+    }
+    free(buffer);
+    buffer = NULL;
+    return result;
+}
+
+//Called when the timer fires.
+- (void)timerUpdate
+{
+    int pid;
+    if ((GetProcessPID(&iTunesPSN, &pid) == noErr) && (pid > 0))
+    {
+        int trackPlayingIndex = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn index of current track\nend tell"] intValue];
+        if (trackPlayingIndex != curTrackIndex)
+        {
+            [self updateMenu];
+            curTrackIndex = trackPlayingIndex;
+        }
+        /*else
+        {
+            NSString *playlist = [self runScriptAndReturnResult:@"tell application\n\"iTunes\"\nreturn name of current playlist\nend tell"];
+            
+            if (![playlist isEqualToString:curPlaylist])
+            {
+                [self updateMenu];
+                NSLog(@"update due to playlist change");
+                curPlaylist = [NSString stringWithString:playlist];
+            }
+        }*/
+        //Update Play/Pause menu item
+        if (playPauseMenuItem)
+        {
+            if ([[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn player state\nend tell"] isEqualToString:@"playing"])
+            {
+                [playPauseMenuItem setTitle:@"Pause"];
+            }
+            else
+            {
+                [playPauseMenuItem setTitle:@"Play"];
+            }
+        }
+    }
+    else
+    {
+        NSMenu *menu2 = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+        
+        [refreshTimer invalidate]; //Stop the timer
+        refreshTimer = NULL;
+        [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(iTunesLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
+        
+        [[menu2 addItemWithTitle:@"Open iTunes" 
+action:@selector(openiTunes:) keyEquivalent:@""] setTarget:self];
+        [[menu2 addItemWithTitle:@"Preferences" 
+action:@selector(showPreferences:) keyEquivalent:@""] setTarget:self];
+        [[menu2 addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) 
+keyEquivalent:@""] setTarget:self];
+        [statusItem setMenu:menu2];
+    }
+}
+
+- (void)iTunesLaunched:(NSNotification *)note
+{
+    NSDictionary *info = [note userInfo];
+    
+    iTunesPSN.highLongOfPSN = [[info objectForKey:@"NSApplicationProcessSerialNumberHigh"] longValue];
+    iTunesPSN.lowLongOfPSN = [[info objectForKey:@"NSApplicationProcessSerialNumberLow"] longValue];
+    
+    //Restart the timer
+    refreshTimer = [NSTimer scheduledTimerWithTimeInterval:3.5 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES]; 
+    
+    [self rebuildMenu]; //Rebuild the menu since no songs will be playing
+    [statusItem setMenu:menu]; //Set the menu back to the main one
+    
+    [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
+}
+
+//Return the PSN of iTunes, if it's running
+- (ProcessSerialNumber)iTunesPSN
+{
+    ProcessSerialNumber procNum;
+    procNum.highLongOfPSN = kNoProcess;
+    procNum.lowLongOfPSN = 0;
+    
+    while ( (GetNextProcess(&procNum) == noErr) ) 
+    {
+        CFStringRef procName;
+        
+        if ( (CopyProcessName(&procNum, &procName) == noErr) )
+        {
+            if ([(NSString *)procName isEqualToString:@"iTunes"])
+            {
+                return procNum;
+            }
+            [(NSString *)procName release];
+        }
+    }
+    return procNum;
+}
+
+//Send an AppleEvent with a given event ID
+- (void)sendAEWithEventClass:(AEEventClass)eventClass 
+andEventID:(AEEventID)eventID
+{
+    OSType iTunesType = 'hook';
+    AppleEvent event, reply;
+    
+    AEBuildAppleEvent(eventClass, eventID, typeApplSignature, &iTunesType, sizeof(iTunesType), kAutoGenerateReturnID, kAnyTransactionID, &event, NULL, "");
+    
+    AESend(&event, &reply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
+    AEDisposeDesc(&event);
+    AEDisposeDesc(&reply);
+}
+
+//
+// Selectors - called from status item menu
+//
+
+- (void)playTrack:(id)sender
+{
+    [self runScriptAndReturnResult:[NSString stringWithFormat:@"tell application \"iTunes\"\nplay track %i of current playlist\nend tell", [[sender representedObject] intValue]]];
+    [self updateMenu];
+}
+
+- (void)selectPlaylist:(id)sender
+{
+    [self runScriptAndReturnResult:[NSString stringWithFormat:@"tell application \"iTunes\"\nplay playlist %i\nend tell", [[sender representedObject] intValue]]];
+    [self updateMenu];
+}
+
+- (void)playPause:(id)sender
+{
+    NSString *state = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn player state\nend tell"];
+    if ([state isEqualToString:@"playing"])
+    {
+        [self sendAEWithEventClass:'hook' andEventID:'Paus'];
+        [playPauseMenuItem setTitle:@"Play"];
+    }
+    else if ([state isEqualToString:@"fast forwarding"] || [state 
+isEqualToString:@"rewinding"])
+    {
+        [self sendAEWithEventClass:'hook' andEventID:'Paus'];
+        [self sendAEWithEventClass:'hook' andEventID:'Play'];
+    }
+    else
+    {
+        [self sendAEWithEventClass:'hook' andEventID:'Play'];
+        [playPauseMenuItem setTitle:@"Pause"];
+    }
+}
+
+- (void)nextSong:(id)sender
+{
+    [self sendAEWithEventClass:'hook' andEventID:'Next'];
+}
+
+- (void)prevSong:(id)sender
+{
+    [self sendAEWithEventClass:'hook' andEventID:'Prev'];
+}
+
+- (void)fastForward:(id)sender
+{
+    [self sendAEWithEventClass:'hook' andEventID:'Fast'];
+}
+
+- (void)rewind:(id)sender
+{
+    [self sendAEWithEventClass:'hook' andEventID:'Rwnd'];
+}
+
+- (void)quitMenuTunes:(id)sender
+{
+    [NSApp terminate:self];
+}
+
+- (void)openiTunes:(id)sender
+{
+    [[NSWorkspace sharedWorkspace] launchApplication:@"iTunes"];
+}
+
+- (void)showPreferences:(id)sender
+{
+    if (!prefsController)
+    {
+        prefsController = [[PreferencesController alloc] initWithMenuTunes:self];
+        [self clearHotKeys];
+    }
+}
+
+
+- (void)closePreferences
+{
+    [self setupHotKeys];
+    [prefsController release];
+    prefsController = nil;
+}
+
+//
+//
+// Show Current Track Info And Show Upcoming Songs Floaters
+//
+//
+
+- (void)showCurrentTrackInfo
+{
+    NSString *trackName = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn name of current track\nend tell"];
+    if (!statusController && [trackName length])
+    {
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        NSString *stringToShow = @"";
+        int lines = 1;
+        
+        if ([defaults boolForKey:@"showName"])
+        {
+            stringToShow = [stringToShow stringByAppendingString:trackName];
+            stringToShow = [stringToShow stringByAppendingString:@"\n"];
+            lines++;
+        }
+        
+        if ([defaults boolForKey:@"showArtist"])
+        {
+            NSString *trackArtist = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn artist of current track\nend tell"];
+            stringToShow = [stringToShow stringByAppendingString:trackArtist];
+            stringToShow = [stringToShow stringByAppendingString:@"\n"];
+            lines++;
+        }
+        
+        if ([defaults boolForKey:@"showAlbum"])
+        {
+            NSString *trackAlbum = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn album of current track\nend tell"];
+            stringToShow = [stringToShow stringByAppendingString:trackAlbum];
+            stringToShow = [stringToShow stringByAppendingString:@"\n"];
+            lines++;
+        }
+        
+        //Rating - maybe
+        //Year - maybe
+        
+        if ([defaults boolForKey:@"showTime"])
+        {
+            NSString *trackLength = [self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn time of current track\nend tell"];
+            stringToShow = [stringToShow stringByAppendingString:trackLength];
+            stringToShow = [stringToShow stringByAppendingString:@"\n"];
+            lines++;
+        }
+        
+        {
+            int trackTimeLeft = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn (duration of current track) - player position\nend tell"] intValue];
+            int minutes = trackTimeLeft / 60, seconds = trackTimeLeft % 60;
+            if (seconds < 10)
+            {
+                stringToShow = [stringToShow stringByAppendingString:
+                            [NSString stringWithFormat:@"Time Remaining: %i:0%i", minutes, seconds]];
+            }
+            else
+            {
+                stringToShow = [stringToShow stringByAppendingString:
+                            [NSString stringWithFormat:@"Time Remaining: %i:%i", minutes, seconds]];
+            }
+        }
+        
+        statusController = [[StatusWindowController alloc] init];
+        [statusController setTrackInfo:stringToShow lines:lines];
+        [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(fadeAndCloseStatusWindow) userInfo:nil repeats:NO];
+    }
+}
+
+- (void)showUpcomingSongs
+{
+    if (!statusController)
+    {
+        int numSongs = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn number of tracks in current playlist\nend tell"] intValue];
+        
+        if (numSongs > 0)
+        {
+            int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
+            int curTrack = [[self runScriptAndReturnResult:@"tell application \"iTunes\"\nreturn index of current track\nend tell"] intValue];
+            int i;
+            NSString *songs = @"";
+            
+            statusController = [[StatusWindowController alloc] init];
+            for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++)
+            {
+                if (i <= numSongs)
+                {
+                    NSString *curSong = [self runScriptAndReturnResult:[NSString stringWithFormat:@"tell application \"iTunes\"\nreturn name of track %i of current playlist\nend tell", i]];
+                    songs = [songs stringByAppendingString:curSong];
+                    songs = [songs stringByAppendingString:@"\n"];
+                }
+            }
+            [statusController setUpcomingSongs:songs numSongs:numSongsInAdvance];
+            [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(fadeAndCloseStatusWindow) userInfo:nil repeats:NO];
+        }
+    }
+}
+
+- (void)fadeAndCloseStatusWindow
+{
+    [statusController fadeWindowOut];
+    [statusController release];
+    statusController = nil;
+}
+
+@end
diff --git a/MenuTunesView.h b/MenuTunesView.h
new file mode 100755 (executable)
index 0000000..1eeb4f4
--- /dev/null
@@ -0,0 +1,16 @@
+//
+//  MenuTunesView.h
+//  MenuTunes
+//
+//  Created by Kent Sutherland on Tue Nov 19 2002.
+//  Copyright (c) 2002 Kent Sutherland. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+
+@interface MenuTunesView : NSView
+{
+    NSImage *image, *altImage, *curImage;
+}
+
+@end
diff --git a/MenuTunesView.m b/MenuTunesView.m
new file mode 100755 (executable)
index 0000000..824683b
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  MenuTunesView.m
+//  MenuTunes
+//
+//  Created by Kent Sutherland on Tue Nov 19 2002.
+//  Copyright (c) 2002 Kent Sutherland. All rights reserved.
+//
+
+#import "MenuTunesView.h"
+
+
+@implementation MenuTunesView
+
+- (id)initWithFrame:(NSRect)frame
+{
+    if ( (self = [super initWithFrame:frame]) )
+    {
+        image = [NSImage imageNamed:@"menu"];
+        altImage = [NSImage imageNamed:@"selected_image"];
+       curImage = image;
+    }
+    return self;
+}
+
+- (void)drawRect:(NSRect)rect
+{
+    [curImage compositeToPoint:NSMakePoint(0, 0) operation:NSCompositeSourceOver];
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+    curImage = altImage;
+    [self setNeedsDisplay:YES];
+    [super mouseDown:event];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+    curImage = image;
+    [self setNeedsDisplay:YES];
+    [super mouseUp:event];
+}
+
+@end
diff --git a/PreferencesController.h b/PreferencesController.h
new file mode 100755 (executable)
index 0000000..f1e3be7
--- /dev/null
@@ -0,0 +1,52 @@
+/* PreferencesController */
+
+#import <Cocoa/Cocoa.h>
+
+@class MenuTunes, KeyCombo;
+
+@interface PreferencesController : NSObject
+{
+    IBOutlet NSButton *albumCheckbox;
+    IBOutlet NSTableView *allTableView;
+    IBOutlet NSButton *artistCheckbox;
+    IBOutlet NSTextField *keyComboField;
+    IBOutlet NSPanel *keyComboPanel;
+    IBOutlet NSTableView *menuTableView;
+    IBOutlet NSButton *nameCheckbox;
+    IBOutlet NSButton *nextTrackButton;
+    IBOutlet NSButton *playPauseButton;
+    IBOutlet NSButton *previousTrackButton;
+    IBOutlet NSButton *songRatingCheckbox;
+    IBOutlet NSTextField *songsInAdvance;
+    IBOutlet NSButton *trackInfoButton;
+    IBOutlet NSButton *trackNumberCheckbox;
+    IBOutlet NSButton *trackTimeCheckbox;
+    IBOutlet NSButton *upcomingSongsButton;
+    IBOutlet NSWindow *window;
+    IBOutlet NSButton *yearCheckbox;
+    
+    MenuTunes *mt;
+    NSMutableArray *availableItems, *myItems;
+    NSArray *submenuItems;
+    
+    KeyCombo *combo, *playPauseCombo, *nextTrackCombo,
+             *prevTrackCombo, *trackInfoCombo, *upcomingSongsCombo;
+    NSString *setHotKey;
+}
+- (id)initWithMenuTunes:(MenuTunes *)menutunes;
+
+- (IBAction)apply:(id)sender;
+- (IBAction)cancel:(id)sender;
+- (IBAction)cancelHotKey:(id)sender;
+- (IBAction)clearHotKey:(id)sender;
+- (IBAction)okHotKey:(id)sender;
+- (IBAction)save:(id)sender;
+- (IBAction)setCurrentTrackInfo:(id)sender;
+- (IBAction)setNextTrack:(id)sender;
+- (IBAction)setPlayPause:(id)sender;
+- (IBAction)setPreviousTrack:(id)sender;
+- (IBAction)setUpcomingSongs:(id)sender;
+
+- (void)setHotKey:(NSString *)key;
+- (void)setKeyCombo:(KeyCombo *)newCombo;
+@end
diff --git a/PreferencesController.m b/PreferencesController.m
new file mode 100755 (executable)
index 0000000..9568696
--- /dev/null
@@ -0,0 +1,546 @@
+#import "PreferencesController.h"
+#import "MenuTunes.h"
+#import "HotKeyCenter.h"
+
+@implementation PreferencesController
+
+- (id)initWithMenuTunes:(MenuTunes *)tunes;
+{
+    if ( (self = [super init]) )
+    {
+        int i;
+        NSImageCell *imgCell = [[[NSImageCell alloc] init] autorelease];
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        NSString *temp;
+        
+        mt = [tunes retain];
+        
+        //Load the nib
+        [NSBundle loadNibNamed:@"Preferences" owner:self];
+        
+        //Show our window
+        [window setLevel:NSStatusWindowLevel];
+        [window center];
+        [window makeKeyAndOrderFront:nil];
+        
+        //Set the table view cells up
+        [imgCell setImageScaling:NSScaleNone];
+        [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
+        [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
+        
+        //Register for drag and drop
+        [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", @"AllTableViewPboardType", nil]];
+        [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", @"AllTableViewPboardType", nil]];
+        
+        //Set the list of items you can have.
+        availableItems = [[NSMutableArray alloc] initWithObjects:@"Current Track Info",  @"Upcoming Songs", @"Playlists", @"Play/Pause", @"Next Track", @"Previous Track", @"Fast Forward", @"Rewind", @"<separator>", nil];
+        
+        //Get our preferred menu
+        myItems = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"menu"] mutableCopy];
+        if (myItems == nil)
+        {
+            myItems = [[NSMutableArray alloc] initWithObjects:@"Play/Pause", @"Next Track", @"Previous Track", @"Fast Forward", @"Rewind", @"<separator>", @"Upcoming Songs", @"Playlists", @"<separator>", @"PreferencesÉ", @"Quit", @"<separator>", @"Current Track Info", nil];
+            [[NSUserDefaults standardUserDefaults] setObject:myItems forKey:@"menu"];
+        }
+        
+        //Delete items in the availableItems array that are already part of the menu
+        for (i = 0; i < [myItems count]; i++)
+        {
+            NSString *item = [myItems objectAtIndex:i];
+            if (![item isEqualToString:@"<separator>"])
+            {
+                [availableItems removeObject:item];
+            }
+        }
+        
+        //Items that show should a submenu image
+        submenuItems = [[NSArray alloc] initWithObjects:@"Upcoming Songs", @"Playlists", nil];
+        
+        //Fill in the number of songs in advance to show field
+        if ([defaults integerForKey:@"SongsInAdvance"])
+        {
+            [songsInAdvance setIntValue:[defaults integerForKey:@"SongsInAdvance"]];
+        }
+        else
+        {
+            [songsInAdvance setIntValue:5];
+        }
+        
+        //Fill in hot key buttons
+        if ([defaults objectForKey:@"PlayPause"])
+        {
+            playPauseCombo = [defaults keyComboForKey:@"PlayPause"];
+            [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
+        }
+        else
+        {
+            playPauseCombo = [[KeyCombo alloc] init];
+        }
+        
+        if ([defaults objectForKey:@"NextTrack"])
+        {
+            nextTrackCombo = [defaults keyComboForKey:@"NextTrack"];
+            [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
+        }
+        else
+        {
+            nextTrackCombo = [[KeyCombo alloc] init];
+        }
+        
+        if ([defaults objectForKey:@"PrevTrack"])
+        {
+            prevTrackCombo = [defaults keyComboForKey:@"PrevTrack"];
+            [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
+        }
+        else
+        {
+            prevTrackCombo = [[KeyCombo alloc] init];
+        }
+        
+        if ([defaults objectForKey:@"TrackInfo"])
+        {
+            trackInfoCombo = [defaults keyComboForKey:@"TrackInfo"];
+            [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
+        }
+        else
+        {
+            trackInfoCombo = [[KeyCombo alloc] init];
+        }
+        
+        if ([defaults objectForKey:@"UpcomingSongs"])
+        {
+            upcomingSongsCombo = [defaults keyComboForKey:@"UpcomingSongs"];
+            [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
+        }
+        else
+        {
+            upcomingSongsCombo = [[KeyCombo alloc] init];
+        }
+        
+        //Check current track info buttons
+        
+        //Album and name get special treatment because they are defaults
+        if ( (temp = [defaults stringForKey:@"showAlbum"]) )
+        {
+            if ((temp == nil) || [temp isEqualToString:@"1"])
+            {
+                [albumCheckbox setState:NSOnState];
+            }
+            else
+            {
+                [albumCheckbox setState:NSOffState];
+            }
+        }
+        
+        if ( (temp = [defaults stringForKey:@"showName"]) )
+        {
+            if ((temp == nil) || [temp isEqualToString:@"1"])
+            {
+                [nameCheckbox setState:NSOnState];
+            }
+            else
+            {
+                [nameCheckbox setState:NSOffState];
+            }
+        }
+        
+        [artistCheckbox setState:[defaults boolForKey:@"showArtist"] ? NSOnState : NSOffState];
+        [songRatingCheckbox setState:[defaults boolForKey:@"showRating"] ? NSOnState : NSOffState];
+        [trackNumberCheckbox setState:[defaults boolForKey:@"showTrackNum"] ? NSOnState : NSOffState];
+        [trackTimeCheckbox setState:[defaults boolForKey:@"showTime"] ? NSOnState : NSOffState];
+        [yearCheckbox setState:[defaults boolForKey:@"showYear"] ? NSOnState : NSOffState];
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    [self setKeyCombo:nil];
+    [playPauseCombo release];
+    [nextTrackCombo release];
+    [prevTrackCombo release];
+    [trackInfoCombo release];
+    [upcomingSongsCombo release];
+    [keyComboPanel release];
+    [menuTableView setDataSource:nil];
+    [allTableView setDataSource:nil];
+    [mt release];
+    [availableItems release];
+    [submenuItems release];
+    [myItems release];
+}
+
+- (IBAction)apply:(id)sender
+{
+    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+    [defaults setObject:myItems forKey:@"menu"];
+    
+    //Set key combos
+    [defaults setKeyCombo:playPauseCombo forKey:@"PlayPause"];
+    [defaults setKeyCombo:nextTrackCombo forKey:@"NextTrack"];
+    [defaults setKeyCombo:prevTrackCombo forKey:@"PrevTrack"];
+    [defaults setKeyCombo:trackInfoCombo forKey:@"TrackInfo"];
+    [defaults setKeyCombo:upcomingSongsCombo forKey:@"UpcomingSongs"];
+    
+    //Set info checkboxes
+    [defaults setBool:[albumCheckbox state] forKey:@"showAlbum"];
+    [defaults setBool:[nameCheckbox state] forKey:@"showName"];
+    [defaults setBool:[artistCheckbox state] forKey:@"showArtist"];
+    [defaults setBool:[songRatingCheckbox state] forKey:@"showRating"];
+    [defaults setBool:[trackNumberCheckbox state] forKey:@"showTrackNum"];
+    [defaults setBool:[trackTimeCheckbox state] forKey:@"showTime"];
+    [defaults setBool:[yearCheckbox state] forKey:@"showYear"];
+    
+    //Set songs in advance
+    if ([songsInAdvance intValue])
+    {
+        [defaults setInteger:[songsInAdvance intValue] forKey:@"SongsInAdvance"];
+    }
+    else
+    {
+        [defaults setInteger:5 forKey:@"SongsInAdvance"];
+    }
+    
+    [mt rebuildMenu];
+    [mt clearHotKeys];
+}
+
+- (IBAction)cancel:(id)sender
+{
+    [window close];
+    [mt closePreferences];
+}
+
+- (IBAction)cancelHotKey:(id)sender
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [NSApp endSheet:keyComboPanel];
+    [keyComboPanel orderOut:nil];
+}
+
+- (IBAction)clearHotKey:(id)sender
+{
+    [self setKeyCombo:[KeyCombo clearKeyCombo]];
+}
+
+- (IBAction)okHotKey:(id)sender
+{
+    NSString *string;
+    if (([combo modifiers] <= 0) && ([combo keyCode] >= 0))
+    {
+        [window setLevel:NSNormalWindowLevel];
+        NSRunAlertPanel(@"Bad Key Combo", @"Please enter a valid key combo. A valid combo must have a modifier key in it. (Command, option, shift, control).", @"OK", nil, nil, nil);
+        [window setLevel:NSStatusWindowLevel];
+        return;
+    }
+    
+    string = [combo userDisplayRep];
+    
+    if (string == nil)
+    {
+        string = @"None";
+    }
+    if ([setHotKey isEqualToString:@"PlayPause"])
+    {
+        if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
+            [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
+            !(([combo modifiers] == -1) && ([combo keyCode] == -1)))
+        {
+            [window setLevel:NSNormalWindowLevel];
+            NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
+            [window setLevel:NSStatusWindowLevel];
+            return;
+        }
+        playPauseCombo = [combo copy];
+        [playPauseButton setTitle:string];
+    }
+    else if ([setHotKey isEqualToString:@"NextTrack"])
+    {
+        if (([combo isEqual:playPauseCombo] || [combo isEqual:prevTrackCombo] ||
+            [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
+            !(([combo modifiers] == -1) && ([combo keyCode] == -1)))
+        {
+            [window setLevel:NSNormalWindowLevel];
+            NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
+            [window setLevel:NSStatusWindowLevel];
+            return;
+        }
+        nextTrackCombo = [combo copy];
+        [nextTrackButton setTitle:string];
+    }
+    else if ([setHotKey isEqualToString:@"PrevTrack"])
+    {
+        if (([combo isEqual:nextTrackCombo] || [combo isEqual:playPauseCombo] ||
+            [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
+            !(([combo modifiers] == -1) && ([combo keyCode] == -1)))
+        {
+            [window setLevel:NSNormalWindowLevel];
+            NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
+            [window setLevel:NSStatusWindowLevel];
+            return;
+        }
+        prevTrackCombo = [combo copy];
+        [previousTrackButton setTitle:string];
+    }
+    else if ([setHotKey isEqualToString:@"TrackInfo"])
+    {
+        if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
+            [combo isEqual:playPauseCombo] || [combo isEqual:upcomingSongsCombo]) && 
+            !(([combo modifiers] == -1) && ([combo keyCode] == -1)))
+        {
+            [window setLevel:NSNormalWindowLevel];
+            NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
+            [window setLevel:NSStatusWindowLevel];
+            return;
+        }
+        trackInfoCombo = [combo copy];
+        [trackInfoButton setTitle:string];
+    }
+    else if ([setHotKey isEqualToString:@"UpcomingSongs"])
+    {
+        if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
+            [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo]) && 
+            !(([combo modifiers] == -1) && ([combo keyCode] == -1)))
+        {
+            [window setLevel:NSNormalWindowLevel];
+            NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
+            [window setLevel:NSStatusWindowLevel];
+            return;
+        }
+        upcomingSongsCombo = [combo copy];
+        [upcomingSongsButton setTitle:string];
+    }
+    [self cancelHotKey:sender];
+}
+
+- (IBAction)save:(id)sender
+{
+    [self apply:nil];
+    [window close];
+    [mt closePreferences];
+}
+
+- (IBAction)setCurrentTrackInfo:(id)sender
+{
+    [self setKeyCombo:trackInfoCombo];
+    [self setHotKey:@"TrackInfo"];
+}
+
+- (IBAction)setNextTrack:(id)sender
+{
+    [self setKeyCombo:nextTrackCombo];
+    [self setHotKey:@"NextTrack"];
+}
+
+- (IBAction)setPlayPause:(id)sender
+{
+    [self setKeyCombo:playPauseCombo];
+    [self setHotKey:@"PlayPause"];
+}
+
+- (IBAction)setPreviousTrack:(id)sender
+{
+    [self setKeyCombo:prevTrackCombo];
+    [self setHotKey:@"PrevTrack"];
+}
+
+- (IBAction)setUpcomingSongs:(id)sender
+{
+    [self setKeyCombo:upcomingSongsCombo];
+    [self setHotKey:@"UpcomingSongs"];
+}
+
+- (void)setHotKey:(NSString *)key
+{
+    setHotKey = key;
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
+    [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
+}
+
+- (void)keyEvent:(NSNotification *)note
+{
+    NSDictionary *info = [note userInfo];
+    short keyCode;
+    long modifiers;
+    KeyCombo *newCombo;
+    
+    keyCode = [[info objectForKey:@"KeyCode"] shortValue];
+    modifiers = [[info objectForKey:@"Modifiers"] longValue];
+    
+    newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
+    [self setKeyCombo:newCombo];
+}
+
+- (void)setKeyCombo:(KeyCombo *)newCombo
+{
+    NSString *string;
+    [combo release];
+    combo = [newCombo copy];
+    
+    string = [combo userDisplayRep];
+    if (string == nil)
+    {
+        string = @"";
+    }
+    [keyComboField setStringValue:string];
+}
+
+//
+//
+// Table View Datasource Methods
+//
+//
+
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+    if (aTableView == menuTableView)
+    {
+        return [myItems count];
+    }
+    else
+    {
+        return [availableItems count];
+    }
+}
+
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
+{
+    if (aTableView == menuTableView)
+    {
+        if ([[aTableColumn identifier] isEqualToString:@"name"])
+        {
+            return [myItems objectAtIndex:rowIndex];
+        }
+        else
+        {
+            if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
+            {
+                return [NSImage imageNamed:@"submenu"];
+            }
+            else
+            {
+                return nil;
+            }
+        }
+    }
+    else
+    {
+        if ([[aTableColumn identifier] isEqualToString:@"name"])
+        {
+            return [availableItems objectAtIndex:rowIndex];
+        }
+        else
+        {
+            if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]])
+            {
+                return [NSImage imageNamed:@"submenu"];
+            }
+            else
+            {
+                return nil;
+            }
+        }
+    }
+}
+
+- (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
+{
+    if (tableView == menuTableView)
+    {
+        [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
+        [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
+        return YES;
+    }
+    
+    if (tableView == allTableView)
+    {
+        [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
+        [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
+        return YES;
+    }
+    return NO;
+}
+
+- (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
+{
+    NSPasteboard *pb;
+    int dragRow;
+    NSString *dragData, *temp;
+    
+    pb = [info draggingPasteboard];
+    
+    if ([[pb types] containsObject:@"MenuTableViewPboardType"])
+    {
+        dragData = [pb stringForType:@"MenuTableViewPboardType"];
+        dragRow = [dragData intValue];
+        temp = [myItems objectAtIndex:dragRow];
+        [myItems removeObjectAtIndex:dragRow];
+        
+        if (tableView == menuTableView)
+        {
+            if (row > dragRow)
+            {
+                [myItems insertObject:temp atIndex:row - 1];
+            }
+            else
+            {
+                [myItems insertObject:temp atIndex:row];
+            }
+        }
+        else
+        {
+            if (![temp isEqualToString:@"<separator>"])
+            {
+                [availableItems addObject:temp];
+            }
+        }
+    }
+    else if ([[pb types] containsObject:@"AllTableViewPboardType"])
+    {
+        dragData = [pb stringForType:@"AllTableViewPboardType"];
+        dragRow = [dragData intValue];
+        temp = [availableItems objectAtIndex:dragRow];
+        
+        if (![temp isEqualToString:@"<separator>"])
+        {
+            [availableItems removeObjectAtIndex:dragRow];
+        }
+        [myItems insertObject:temp atIndex:row];
+    }
+    
+    [menuTableView reloadData];
+    [allTableView reloadData];
+    return YES;
+}
+
+- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
+{
+    if (tableView == allTableView)
+    {
+        if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"])
+        {
+            return NSDragOperationNone;
+        }
+        
+        if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"])
+        {
+            NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
+            if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"])
+            {
+                return NSDragOperationNone;
+            }
+        }
+        
+        [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
+        return NSDragOperationGeneric;
+    }
+    
+    if (operation == NSTableViewDropOn || row == -1)
+    {
+        return NSDragOperationNone;
+    }
+    
+    return NSDragOperationGeneric;
+}
+
+@end
diff --git a/StatusWindow.h b/StatusWindow.h
new file mode 100755 (executable)
index 0000000..c97566c
--- /dev/null
@@ -0,0 +1,8 @@
+/* StatusWindow */
+
+#import <Cocoa/Cocoa.h>
+
+@interface StatusWindow : NSWindow
+{
+}
+@end
diff --git a/StatusWindow.m b/StatusWindow.m
new file mode 100755 (executable)
index 0000000..946e544
--- /dev/null
@@ -0,0 +1,18 @@
+#import "StatusWindow.h"
+
+@implementation StatusWindow
+
+- (id)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)type defer:(BOOL)flag
+{
+    if ( (self = [super initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:type defer:flag]) )
+    {
+        [self setHasShadow:NO];
+        [self setOpaque:NO];
+        [self setLevel:NSStatusWindowLevel];
+        [self setIgnoresMouseEvents:YES];
+        [self setBackgroundColor:[NSColor colorWithCalibratedRed:0.5 green:0.5 blue:0.5 alpha:0.6]];
+    }
+    return self;
+}
+
+@end
diff --git a/StatusWindowController.h b/StatusWindowController.h
new file mode 100755 (executable)
index 0000000..ccf3d8a
--- /dev/null
@@ -0,0 +1,15 @@
+/* StatusWindowController */
+
+#import <Cocoa/Cocoa.h>
+
+@class StatusWindow;
+
+@interface StatusWindowController : NSObject
+{
+    IBOutlet NSTextField *statusField;
+    IBOutlet StatusWindow *statusWindow;
+}
+- (void)setUpcomingSongs:(NSString *)string numSongs:(int)songs;
+- (void)setTrackInfo:(NSString *)string lines:(int)lines;
+- (void)fadeWindowOut;
+@end
diff --git a/StatusWindowController.m b/StatusWindowController.m
new file mode 100755 (executable)
index 0000000..a9859c3
--- /dev/null
@@ -0,0 +1,50 @@
+#import "StatusWindowController.h"
+#import "StatusWindow.h"
+
+@implementation StatusWindowController
+
+- (id)init
+{
+    if ( (self = [super init]) )
+    {
+        [NSBundle loadNibNamed:@"StatusWindow" owner:self];
+        [statusWindow center];
+    }
+    return self;
+}
+
+- (void)setUpcomingSongs:(NSString *)string numSongs:(int)songs
+{
+    [statusField setStringValue:string];
+    [statusWindow setFrame:NSMakeRect(0, 0, 300, 40 + (songs * 17)) display:NO];
+    [statusWindow center];
+    [statusWindow makeKeyAndOrderFront:nil];
+}
+
+- (void)setTrackInfo:(NSString *)string lines:(int)lines
+{
+    [statusField setStringValue:string];
+    [statusWindow setFrame:NSMakeRect(0, 0, 316, 40 + (lines * 17)) display:NO];
+    [statusWindow center];
+    [statusWindow makeKeyAndOrderFront:nil];
+}
+
+- (void)fadeWindowOut
+{
+    [NSThread detachNewThreadSelector:@selector(fadeOutAux) toTarget:self withObject:nil];
+    
+}
+
+- (void)fadeOutAux
+{
+    NSAutoreleasePool *p00l = [[NSAutoreleasePool alloc] init];
+    float i;
+    for (i = 1.0; i > 0; i -= .003)
+    {
+        [statusWindow setAlphaValue:i];
+    }
+    [statusWindow close];
+    [p00l release];
+}
+
+@end
diff --git a/main.m b/main.m
new file mode 100755 (executable)
index 0000000..0eb6e57
--- /dev/null
+++ b/main.m
@@ -0,0 +1,14 @@
+//
+//  main.m
+//  MenuTunes
+//
+//  Created by Kent Sutherland on Sun Nov 17 2002.
+//  Copyright (c) 2002 Kent Sutherland. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char *argv[])
+{
+    return NSApplicationMain(argc, argv);
+}
diff --git a/submenu.tiff b/submenu.tiff
new file mode 100755 (executable)
index 0000000..b35f0f2
Binary files /dev/null and b/submenu.tiff differ