--- /dev/null
+{
+ 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
--- /dev/null
+{
+ 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
--- /dev/null
+<?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>
--- /dev/null
+{
+ 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
--- /dev/null
+<?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>
--- /dev/null
+{
+ 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
--- /dev/null
+<?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>
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
+
--- /dev/null
+/* KeyBroadcaster */
+
+#import <Cocoa/Cocoa.h>
+
+@interface KeyBroadcaster : NSButton
+{
+}
++ (long)cocoaToCarbonModifiers:(long)modifiers;
+@end
--- /dev/null
+#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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
+
+
+
+
+
--- /dev/null
+/* MTApplication */
+
+#import <Cocoa/Cocoa.h>
+
+@interface MTApplication : NSApplication
+{
+}
+@end
--- /dev/null
+#import "MTApplication.h"
+#import "HotKeyCenter.h"
+
+@implementation MTApplication
+
+- (void)sendEvent:(NSEvent *)event
+{
+ [[HotKeyCenter sharedCenter] sendEvent:event];
+ [super sendEvent:event];
+}
+
+@end
--- /dev/null
+/* 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+/* 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
--- /dev/null
+#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
--- /dev/null
+/* StatusWindow */
+
+#import <Cocoa/Cocoa.h>
+
+@interface StatusWindow : NSWindow
+{
+}
+@end
--- /dev/null
+#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
--- /dev/null
+/* 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
--- /dev/null
+#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
--- /dev/null
+//
+// 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);
+}