From: Alexander Strange Date: Sun, 1 Dec 2002 06:58:09 +0000 (+0000) Subject: Initial revision X-Git-Tag: v1.0~279 X-Git-Url: http://git.ithinksw.org/MenuTunes.git/commitdiff_plain/93f1f93114865d18b5870f04763c921ff33b8e59 Initial revision --- 93f1f93114865d18b5870f04763c921ff33b8e59 diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings new file mode 100755 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 index 0000000..7856ab6 --- /dev/null +++ b/English.lproj/KeyCodes.plist @@ -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 index 0000000..a5a8984 --- /dev/null +++ b/English.lproj/MainMenu.nib/classes.nib @@ -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 index 0000000..9b3242d --- /dev/null +++ b/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,10 @@ + + + + + IBFramework Version + 286.0 + IBSystem Version + 6F21 + + diff --git a/English.lproj/MainMenu.nib/objects.nib b/English.lproj/MainMenu.nib/objects.nib new file mode 100755 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 index 0000000..3c22618 --- /dev/null +++ b/English.lproj/Preferences.nib/classes.nib @@ -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 index 0000000..3f55daf --- /dev/null +++ b/English.lproj/Preferences.nib/info.nib @@ -0,0 +1,26 @@ + + + + + IBDocumentLocation + 61 9 356 240 0 0 1152 746 + IBFramework Version + 286.0 + IBGroupedObjects + + 0 + + 123 + 124 + + + IBLastGroupID + 1 + IBOpenObjects + + 6 + + IBSystem Version + 6F21 + + diff --git a/English.lproj/Preferences.nib/objects.nib b/English.lproj/Preferences.nib/objects.nib new file mode 100755 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 index 0000000..7d510ed --- /dev/null +++ b/English.lproj/StatusWindow.nib/classes.nib @@ -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 index 0000000..93c48ef --- /dev/null +++ b/English.lproj/StatusWindow.nib/info.nib @@ -0,0 +1,14 @@ + + + + + IBFramework Version + 286.0 + IBOpenObjects + + 5 + + IBSystem Version + 6F21 + + diff --git a/English.lproj/StatusWindow.nib/objects.nib b/English.lproj/StatusWindow.nib/objects.nib new file mode 100755 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 index 0000000..10ab9f8 --- /dev/null +++ b/HotKeyCenter.h @@ -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 + +#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 index 0000000..9698fdb --- /dev/null +++ b/HotKeyCenter.m @@ -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 + +#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 index 0000000..19df649 --- /dev/null +++ b/KeyBroadcaster.h @@ -0,0 +1,9 @@ +/* KeyBroadcaster */ + +#import + +@interface KeyBroadcaster : NSButton +{ +} ++ (long)cocoaToCarbonModifiers:(long)modifiers; +@end diff --git a/KeyBroadcaster.m b/KeyBroadcaster.m new file mode 100755 index 0000000..7b47d83 --- /dev/null +++ b/KeyBroadcaster.m @@ -0,0 +1,58 @@ +#import "KeyBroadcaster.h" +#import + +@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 index 0000000..743c77b --- /dev/null +++ b/KeyCombo.h @@ -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 + + +@interface KeyCombo : NSObject +{ + 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 index 0000000..716d4ae --- /dev/null +++ b/KeyCombo.m @@ -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 +#import + +@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 index 0000000..5692b39 --- /dev/null +++ b/MTApplication.h @@ -0,0 +1,8 @@ +/* MTApplication */ + +#import + +@interface MTApplication : NSApplication +{ +} +@end diff --git a/MTApplication.m b/MTApplication.m new file mode 100755 index 0000000..24ff6a4 --- /dev/null +++ b/MTApplication.m @@ -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 index 0000000..37ed1b6 Binary files /dev/null and b/MenuInverted.tiff differ diff --git a/MenuNormal.tiff b/MenuNormal.tiff new file mode 100755 index 0000000..8e94b1d Binary files /dev/null and b/MenuNormal.tiff differ diff --git a/MenuTunes.h b/MenuTunes.h new file mode 100755 index 0000000..e726be6 --- /dev/null +++ b/MenuTunes.h @@ -0,0 +1,54 @@ +/* MenuTunes */ + +#import +#import + +@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 index 0000000..9879f8a --- /dev/null +++ b/MenuTunes.m @@ -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", @"", @"Upcoming Songs", @"Playlists", @"", @"PreferencesÉ", @"Quit", @"", @"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:@""]) + { + [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 index 0000000..1eeb4f4 --- /dev/null +++ b/MenuTunesView.h @@ -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 + +@interface MenuTunesView : NSView +{ + NSImage *image, *altImage, *curImage; +} + +@end diff --git a/MenuTunesView.m b/MenuTunesView.m new file mode 100755 index 0000000..824683b --- /dev/null +++ b/MenuTunesView.m @@ -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 index 0000000..f1e3be7 --- /dev/null +++ b/PreferencesController.h @@ -0,0 +1,52 @@ +/* PreferencesController */ + +#import + +@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 index 0000000..9568696 --- /dev/null +++ b/PreferencesController.m @@ -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", @"", 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", @"", @"Upcoming Songs", @"Playlists", @"", @"PreferencesÉ", @"Quit", @"", @"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:@""]) + { + [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 )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:@""]) + { + [availableItems addObject:temp]; + } + } + } + else if ([[pb types] containsObject:@"AllTableViewPboardType"]) + { + dragData = [pb stringForType:@"AllTableViewPboardType"]; + dragRow = [dragData intValue]; + temp = [availableItems objectAtIndex:dragRow]; + + if (![temp isEqualToString:@""]) + { + [availableItems removeObjectAtIndex:dragRow]; + } + [myItems insertObject:temp atIndex:row]; + } + + [menuTableView reloadData]; + [allTableView reloadData]; + return YES; +} + +- (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id )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 index 0000000..c97566c --- /dev/null +++ b/StatusWindow.h @@ -0,0 +1,8 @@ +/* StatusWindow */ + +#import + +@interface StatusWindow : NSWindow +{ +} +@end diff --git a/StatusWindow.m b/StatusWindow.m new file mode 100755 index 0000000..946e544 --- /dev/null +++ b/StatusWindow.m @@ -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 index 0000000..ccf3d8a --- /dev/null +++ b/StatusWindowController.h @@ -0,0 +1,15 @@ +/* StatusWindowController */ + +#import + +@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 index 0000000..a9859c3 --- /dev/null +++ b/StatusWindowController.m @@ -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 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 + +int main(int argc, const char *argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/submenu.tiff b/submenu.tiff new file mode 100755 index 0000000..b35f0f2 Binary files /dev/null and b/submenu.tiff differ