X-Git-Url: http://git.ithinksw.org/MenuTunes.git/blobdiff_plain/e350cc5032334a724daffdf6d2016e3653efc825..259ae5fac769954c7db5974b86e791a929fe6700:/MainController.m?ds=inline diff --git a/MainController.m b/MainController.m index 1b670eb..7a8c874 100755 --- a/MainController.m +++ b/MainController.m @@ -1,18 +1,83 @@ #import "MainController.h" +#import "MenuController.h" #import "PreferencesController.h" -#import "HotKeyCenter.h" +#import "NetworkController.h" +#import "NetworkObject.h" +#import +#import +#import +#import +#import "StatusWindow.h" #import "StatusWindowController.h" +#import "AudioscrobblerController.h" +#import "StatusItemHack.h" + +@interface NSMenu (MenuImpl) +- (id)_menuImpl; +@end + +@interface NSCarbonMenuImpl:NSObject +{ + NSMenu *_menu; +} + ++ (void)initialize; ++ (void)setupForNoMenuBar; +- (void)dealloc; +- (void)setMenu:fp8; +- menu; +- (void)itemChanged:fp8; +- (void)itemAdded:fp8; +- (void)itemRemoved:fp8; +- (void)performActionWithHighlightingForItemAtIndex:(int)fp8; +- (void)performMenuAction:(SEL)fp8 withTarget:fp12; +- (void)setupCarbonMenuBar; +- (void)setAsMainCarbonMenuBar; +- (void)clearAsMainCarbonMenuBar; +- (void)popUpMenu:fp8 atLocation:(NSPoint)fp12 width:(float)fp20 forView:fp24 withSelectedItem:(int)fp28 withFont:fp32; +- (void)_popUpContextMenu:fp8 withEvent:fp12 forView:fp16 withFont:fp20; +- (void)_popUpContextMenu:fp8 withEvent:fp12 forView:fp16; +- window; +@end + +@implementation NSImage (SmoothAdditions) + +- (NSImage *)imageScaledSmoothlyToSize:(NSSize)scaledSize +{ + NSImage *newImage; + NSImageRep *rep = [self bestRepresentationForDevice:nil]; + + newImage = [[NSImage alloc] initWithSize:scaledSize]; + [newImage lockFocus]; + { + [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; + [rep drawInRect:NSMakeRect(3, 3, scaledSize.width - 6, scaledSize.height - 6)]; + } + [newImage unlockFocus]; + return [newImage autorelease]; +} + +@end @interface MainController(Private) - (ITMTRemote *)loadRemote; -- (void)setupHotKeys; -- (void)timerUpdate; -- (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers - onItem:(NSMenuItem *)item; +- (void)setLatestSongIdentifier:(NSString *)newIdentifier; +- (void)applicationLaunched:(NSNotification *)note; +- (void)applicationTerminated:(NSNotification *)note; + +- (void)invalidateStatusWindowUpdateTimer; @end +static MainController *sharedController; + @implementation MainController ++ (MainController *)sharedController +{ + return sharedController; +} + /*************************************************************************/ #pragma mark - #pragma mark INITIALIZATION/DEALLOCATION METHODS @@ -21,18 +86,67 @@ - (id)init { if ( ( self = [super init] ) ) { + sharedController = self; + + _statusWindowUpdateTimer = nil; + _audioscrobblerTimer = nil; + remoteArray = [[NSMutableArray alloc] initWithCapacity:1]; - statusWindowController = [[StatusWindowController alloc] init]; + [[PreferencesController sharedPrefs] setController:self]; + statusWindowController = [StatusWindowController sharedController]; + menuController = [[MenuController alloc] init]; df = [[NSUserDefaults standardUserDefaults] retain]; - [self setLatestSongIdentifier:@"0-0"]; + timerUpdating = NO; + blinged = NO; } return self; } - (void)applicationDidFinishLaunching:(NSNotification *)note { + NSString *iTunesPath = [df stringForKey:@"CustomPlayerPath"]; + NSDictionary *iTunesInfoPlist; + float iTunesVersion; + + //Turn on debug mode if needed + /*if ((GetCurrentKeyModifiers() & (controlKey | rightControlKey)) != 0) + if ((GetCurrentKeyModifiers() & (optionKey | rightOptionKey)) != 0) + if ((GetCurrentKeyModifiers() & (shiftKey | rightShiftKey)) != 0)*/ + if ([df boolForKey:@"ITDebugMode"] || ((GetCurrentKeyModifiers() & (controlKey | rightControlKey)) != 0)) { + SetITDebugMode(YES); + [[StatusWindowController sharedController] showDebugModeEnabledWindow]; + } + + //Check if iTunes 4.7 or later is installed + if (!iTunesPath) { + iTunesPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes.app"]; + } + iTunesInfoPlist = [[NSBundle bundleWithPath:iTunesPath] infoDictionary]; + iTunesVersion = [[iTunesInfoPlist objectForKey:@"CFBundleVersion"] floatValue]; + ITDebugLog(@"iTunes version found: %f.", iTunesVersion); + if (iTunesVersion >= 4.7) { + _needsPolling = NO; + } else { + _needsPolling = YES; + } + + if (([df integerForKey:@"appVersion"] < 1200) && ([df integerForKey:@"SongsInAdvance"] > 0)) { + [df removePersistentDomainForName:@"com.ithinksw.menutunes"]; + [df synchronize]; + [[PreferencesController sharedPrefs] registerDefaults]; + [[StatusWindowController sharedController] showPreferencesUpdateWindow]; + } + currentRemote = [self loadRemote]; - [currentRemote begin]; + [[self currentRemote] begin]; + + //Turn on network stuff if needed + networkController = [[NetworkController alloc] init]; + if ([df boolForKey:@"enableSharing"]) { + [self setServerStatus:YES]; + } else if ([df boolForKey:@"useSharedPlayer"]) { + [self checkForRemoteServerAndConnectImmediately:YES]; + } //Setup for notification of the remote player launching or quitting [[[NSWorkspace sharedWorkspace] notificationCenter] @@ -46,23 +160,70 @@ selector:@selector(applicationLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil]; - - if ( ! [df objectForKey:@"menu"] ) { // If this is nil, defaults have never been registered. + + if (![df objectForKey:@"menu"]) { // If this is nil, defaults have never been registered. [[PreferencesController sharedPrefs] registerDefaults]; } - statusItem = [[ITStatusItem alloc] - initWithStatusBar:[NSStatusBar systemStatusBar] - withLength:NSSquareStatusItemLength]; + if ([df boolForKey:@"ITMTNoStatusItem"]) { + statusItem = nil; + } else { + [StatusItemHack install]; + statusItem = [[ITStatusItem alloc] + initWithStatusBar:[NSStatusBar systemStatusBar] + withLength:NSSquareStatusItemLength]; + } + + /*bling = [[MTBlingController alloc] init]; + [self blingTime]; + registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0 + target:self + selector:@selector(blingTime) + userInfo:nil + repeats:YES] retain];*/ + + NS_DURING + if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) { + [self applicationLaunched:nil]; + } else { + if ([df boolForKey:@"LaunchPlayerWithMT"]) + [self showPlayer]; + else + [self applicationTerminated:nil]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER - [statusItem setImage:[NSImage imageNamed:@"menu"]]; - [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]]; + [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]]; + [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]]; + + if ([df boolForKey:@"audioscrobblerEnabled"]) { + [[AudioscrobblerController sharedController] attemptHandshake:NO]; + } + + [networkController startRemoteServerSearch]; + [NSApp deactivate]; + [self performSelector:@selector(rawr) withObject:nil afterDelay:1.0]; + + bling = [[MTBlingController alloc] init]; + [self blingTime]; + registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0 + target:self + selector:@selector(blingTime) + userInfo:nil + repeats:YES] retain]; +} + +- (void)rawr +{ + _open = YES; } - (ITMTRemote *)loadRemote { NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath]; - + ITDebugLog(@"Gathering remotes."); if (folderPath) { NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath]; NSEnumerator *enumerator = [bundlePathList objectEnumerator]; @@ -75,9 +236,9 @@ Class remoteClass = [remoteBundle principalClass]; if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] && - [remoteClass isKindOfClass:[NSObject class]]) { - + [(NSObject *)remoteClass isKindOfClass:[NSObject class]]) { id remote = [remoteClass remote]; + ITDebugLog(@"Adding remote at path %@", bundlePath); [remoteArray addObject:remote]; } } @@ -99,7 +260,7 @@ #pragma mark INSTANCE METHODS /*************************************************************************/ -- (void)startTimerInNewThread +/*- (void)startTimerInNewThread { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; @@ -109,175 +270,127 @@ userInfo:nil repeats:YES] retain]; [runLoop run]; + ITDebugLog(@"Timer started."); [pool release]; -} - +}*/ -/*************************************************************************/ -#pragma mark - -#pragma mark MENU BUILDING METHODS -/*************************************************************************/ - -- (NSMenu *)menu +- (void)setBlingTime:(NSDate*)date { - NSMenu *theMenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - [theMenu addItem:[self playlistMenuItem]]; - [theMenu addItem:[self upcomingSongsMenuItem]]; - [theMenu addItem:[self ratingMenuItem]]; - - return theMenu; + NSMutableDictionary *globalPrefs; + [df synchronize]; + globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy]; + if (date) { + [globalPrefs setObject:date forKey:@"ITMTTrialStart"]; + [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"]; + } else { + [globalPrefs removeObjectForKey:@"ITMTTrialStart"]; + [globalPrefs removeObjectForKey:@"ITMTTrialVers"]; + } + [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"]; + [df synchronize]; + [globalPrefs release]; } -- (NSMenu *)menuForNoPlayer +- (NSDate*)getBlingTime { - return nil; + [df synchronize]; + return [[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialStart"]; } -- (NSMenuItem *)playlistMenuItem +- (void)blingTime { - NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Playlists" - action:nil - keyEquivalent:@""] autorelease]; - NSMenu *submenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - int currentPlaylist = [currentRemote currentPlaylistIndex]; - NSArray *playlists = [currentRemote playlists]; - NSEnumerator *playlistEnum = [playlists objectEnumerator]; - int playlistTag = 1; - id aPlaylist; - - [item setSubmenu:submenu]; - [submenu setAutoenablesItems:NO]; - - while ( (aPlaylist = [playlistEnum nextObject]) ) { - NSMenuItem *playlistItem = [[[NSMenuItem alloc] initWithTitle:aPlaylist - action:@selector(selectPlaylist:) - keyEquivalent:@""] autorelease]; - [playlistItem setTag:playlistTag]; - [playlistItem setTarget:self]; - playlistTag++; - [submenu addItem:playlistItem]; - } - - if ( (! [self radioIsPlaying]) && currentPlaylist) { - [[submenu itemAtIndex:(currentPlaylist - 1)] setState:NSOnState]; - } - - return item; -} - -- (NSMenuItem *)upcomingSongsMenuItem -{ - NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Upcoming Songs" - action:nil - keyEquivalent:@""] autorelease]; - NSMenu *submenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - int curIndex = [currentRemote currentPlaylistIndex]; - int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curIndex]; - int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"]; - - [item setSubmenu:submenu]; - - if ( [self radioIsPlaying] ) { - [submenu addItemWithTitle:@"No Upcoming Songs..." action:nil keyEquivalent:@""]; - [submenu addItemWithTitle:@"Playing Radio Stream" action:nil keyEquivalent:@""]; + NSDate *now = [NSDate date]; + if (![self blingBling]) { + if ( (! [self getBlingTime] ) || ([now timeIntervalSinceDate:[self getBlingTime]] < 0) ) { + [self setBlingTime:now]; + } else if ([[[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialVers"] intValue] < MT_CURRENT_VERSION) { + if ([now timeIntervalSinceDate:[self getBlingTime]] >= 345600) { + [self setBlingTime:[now addTimeInterval:-259200]]; + } else { + NSMutableDictionary *globalPrefs; + [df synchronize]; + globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy]; + [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"]; + [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"]; + [df synchronize]; + [globalPrefs release]; + } + } + + if ( ([now timeIntervalSinceDate:[self getBlingTime]] >= 604800) && (blinged != YES) ) { + blinged = YES; + [statusItem setEnabled:NO]; + [[ITHotKeyCenter sharedCenter] setEnabled:NO]; + if ([refreshTimer isValid]) { + [refreshTimer invalidate]; + [refreshTimer release]; + refreshTimer = nil; + } + [statusWindowController showRegistrationQueryWindow]; + } } else { - if ( ! (numSongs > 0) ) { - [submenu addItemWithTitle:@"No Songs in Playlist" action:nil keyEquivalent:@""]; - } else { - int curTrack = [currentRemote currentSongIndex]; - int i; - - for (i = curTrack + 1; ( (i <= curTrack + numSongsInAdvance) && (i <= numSongs) ); i++) { - - NSString *curSong = [currentRemote songTitleAtIndex:i]; - NSMenuItem *songItem = [[[NSMenuItem alloc] initWithTitle:curSong - action:@selector(selectSong:) - keyEquivalent:@""] autorelease]; - [songItem setRepresentedObject:[NSNumber numberWithInt:i]]; - [submenu addItem:songItem]; + if (blinged) { + [statusItem setEnabled:YES]; + [[ITHotKeyCenter sharedCenter] setEnabled:YES]; + if (_needsPolling && ![refreshTimer isValid]) { + [refreshTimer release]; + refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5) + target:self + selector:@selector(timerUpdate) + userInfo:nil + repeats:YES] retain]; } + blinged = NO; } + [self setBlingTime:nil]; } - - return item; } -- (NSMenuItem *)ratingMenuItem +- (void)blingNow { - NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Rating" - action:nil - keyEquivalent:@""] autorelease]; - NSMenu *submenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - [item setSubmenu:submenu]; - - [submenu addItemWithTitle:[NSString stringWithUTF8String:"☆☆☆☆☆"] action:nil keyEquivalent:@""]; - [submenu addItemWithTitle:[NSString stringWithUTF8String:"★☆☆☆☆"] action:nil keyEquivalent:@""]; - [submenu addItemWithTitle:[NSString stringWithUTF8String:"★★☆☆☆"] action:nil keyEquivalent:@""]; - [submenu addItemWithTitle:[NSString stringWithUTF8String:"★★★☆☆"] action:nil keyEquivalent:@""]; - [submenu addItemWithTitle:[NSString stringWithUTF8String:"★★★★☆"] action:nil keyEquivalent:@""]; - [submenu addItemWithTitle:[NSString stringWithUTF8String:"★★★★★"] action:nil keyEquivalent:@""]; - - if ( ! ( [self radioIsPlaying] || [self songIsPlaying] ) ) { - - NSEnumerator *itemEnum; - id anItem; - int itemTag = 0; - SEL itemSelector = @selector(selectSongRating:); - - itemEnum = [[submenu itemArray] objectEnumerator]; - while ( (anItem = [itemEnum nextObject]) ) { - [anItem setAction:itemSelector]; - [anItem setTag:itemTag]; - itemTag += 20; - } - } - - return item; + [bling showPanel]; } -- (NSMenuItem *)eqMenuItem +- (BOOL)blingBling { - NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Equalizer" - action:nil - keyEquivalent:@""] autorelease]; - NSMenu *submenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; - - NSArray *eqPresets = [currentRemote eqPresets]; - NSEnumerator *eqEnum = [eqPresets objectEnumerator]; - int eqTag = 0; - id anEq; - - [item setSubmenu:submenu]; - - while ( ( anEq = [eqEnum nextObject]) ) { - NSMenuItem *eqItem = [[[NSMenuItem alloc] initWithTitle:anEq - action:@selector(selectEQPreset:) - keyEquivalent:@""] autorelease]; - [eqItem setTag:eqTag]; - eqTag++; - [submenu addItem:eqItem]; + if ( ! ([bling checkDone] == 2475) ) { + return NO; + } else { + return YES; } - - [[submenu itemAtIndex:([currentRemote currentEQPresetIndex] - 1)] setState:NSOnState]; } - (BOOL)songIsPlaying { - return ( ! ([[currentRemote currentSongUniqueIdentifier] isEqualToString:@"0-0"]) ); + NSString *identifier = nil; + NS_DURING + identifier = [[self currentRemote] playerStateUniqueIdentifier]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + return ( ! ([identifier isEqualToString:@"0-0"]) ); } - (BOOL)radioIsPlaying { - return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ); + ITMTRemotePlayerPlaylistClass class = nil; + NS_DURING + class = [[self currentRemote] currentPlaylistClass]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + return (class == ITMTRemotePlayerRadioPlaylist ); } - (BOOL)songChanged { - return ( ! [[currentRemote currentSongUniqueIdentifier] isEqualToString:_latestSongIdentifier] ); + NSString *identifier = nil; + NS_DURING + identifier = [[self currentRemote] playerStateUniqueIdentifier]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + return ( ! [identifier isEqualToString:_latestSongIdentifier] ); } - (NSString *)latestSongIdentifier @@ -287,380 +400,463 @@ - (void)setLatestSongIdentifier:(NSString *)newIdentifier { + ITDebugLog(@"Setting latest song identifier:"); + ITDebugLog(@" - Identifier: %@", newIdentifier); [_latestSongIdentifier autorelease]; - _latestSongIdentifier = [newIdentifier copy]; + _latestSongIdentifier = [newIdentifier retain]; } -/* -//Recreate the status item menu -- (void)rebuildMenu +- (void)timerUpdate { - NSArray *myMenu = [df arrayForKey:@"menu"]; - int playlist = [currentRemote currentPlaylistIndex]; - int i; - - if ([currentRemote playerRunningState] == ITMTRemotePlayerNotRunning) { - return; - } - - trackInfoIndex = -1; - lastPlaylistIndex = -1; - - [menu release]; - menu = [[NSMenu alloc] initWithTitle:@""]; - - playPauseItem = nil; - - upcomingSongsItem = nil; - [upcomingSongsMenu release]; - upcomingSongsMenu = nil; - - if (ratingItem) { - [ratingItem setSubmenu:nil]; - } - - playlistItem = nil; - [playlistMenu release]; - playlistMenu = nil; - - eqItem = nil; - [eqMenu release]; - eqMenu = nil; - - //Build the custom menu - for (i = 0; i < [myMenu count]; i++) { - NSString *item = [myMenu objectAtIndex:i]; - if ([item isEqualToString:@"Play/Pause"]) { - KeyCombo *tempCombo = [df keyComboForKey:@"PlayPause"]; - playPauseItem = [menu addItemWithTitle:@"Play" - action:@selector(playPause:) - keyEquivalent:@""]; - - if (tempCombo) { - [self setKeyEquivalentForCode:[tempCombo keyCode] - andModifiers:[tempCombo modifiers] onItem:playPauseItem]; - [tempCombo release]; - } - } else if ([item isEqualToString:@"Next Track"]) { - KeyCombo *tempCombo = [df keyComboForKey:@"NextTrack"]; - NSMenuItem *nextTrack = [menu addItemWithTitle:@"Next Track" - action:@selector(nextSong:) - keyEquivalent:@""]; - - if (tempCombo) { - [self setKeyEquivalentForCode:[tempCombo keyCode] - andModifiers:[tempCombo modifiers] onItem:nextTrack]; - [tempCombo release]; - } - } else if ([item isEqualToString:@"Previous Track"]) { - KeyCombo *tempCombo = [df keyComboForKey:@"PrevTrack"]; - NSMenuItem *prevTrack = [menu addItemWithTitle:@"Previous Track" - action:@selector(prevSong:) - keyEquivalent:@""]; - - if (tempCombo) { - [self setKeyEquivalentForCode:[tempCombo keyCode] - andModifiers:[tempCombo modifiers] onItem:prevTrack]; - [tempCombo release]; + NSString *identifier = [[self currentRemote] playerStateUniqueIdentifier]; + if (refreshTimer && identifier == nil) { + if ([statusItem isEnabled]) { + [statusItem setToolTip:@"iTunes not responding."]; + } + [statusItem setEnabled:NO]; + return; + } else if (![statusItem isEnabled]) { + [statusItem setEnabled:YES]; + [statusItem setToolTip:_toolTip]; + return; + } + + if ( [self songChanged] && (timerUpdating != YES) && (playerRunningState == ITMTRemotePlayerRunning) ) { + ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier); + if ([df boolForKey:@"runScripts"]) { + NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]]; + NSEnumerator *scriptsEnum = [scripts objectEnumerator]; + NSString *nextScript; + ITDebugLog(@"Running AppleScripts for song change."); + while ( (nextScript = [scriptsEnum nextObject]) ) { + NSDictionary *error; + NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error]; + ITDebugLog(@"Running script: %@", nextScript); + if (!currentScript || ![currentScript executeAndReturnError:nil]) { + ITDebugLog(@"Error running script %@.", nextScript); + } + [currentScript release]; } - } else if ([item isEqualToString:@"Fast Forward"]) { - [menu addItemWithTitle:@"Fast Forward" - action:@selector(fastForward:) - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Rewind"]) { - [menu addItemWithTitle:@"Rewind" - action:@selector(rewind:) - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Show Player"]) { - [menu addItemWithTitle:[NSString stringWithFormat:@"Show %@", [currentRemote playerSimpleName]] - action:@selector(showPlayer:) - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Upcoming Songs"]) { - upcomingSongsItem = [menu addItemWithTitle:@"Upcoming Songs" - action:nil - keyEquivalent:@""]; - upcomingSongsMenu = [[NSMenu alloc] initWithTitle:@""]; - [upcomingSongsItem setSubmenu:upcomingSongsMenu]; - [upcomingSongsItem setEnabled:NO]; - } else if ([item isEqualToString:@"Playlists"]) { - playlistItem = [menu addItemWithTitle:@"Playlists" - action:nil - keyEquivalent:@""]; - } else if ([item isEqualToString:@"EQ Presets"]) { - eqItem = [menu addItemWithTitle:@"EQ Presets" - action:nil - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Preferences…"]) { - [menu addItemWithTitle:@"Preferences…" - action:@selector(showPreferences:) - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Quit"]) { - [menu addItemWithTitle:@"Quit" - action:@selector(quitMenuTunes:) - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Current Track Info"]) { - trackInfoIndex = [menu numberOfItems]; - [menu addItemWithTitle:@"No Song" - action:nil - keyEquivalent:@""]; - } else if ([item isEqualToString:@"Song Rating"]) { - ratingItem = [menu addItemWithTitle:@"Song Rating" - action:nil - keyEquivalent:@""]; - [ratingItem setSubmenu:ratingMenu]; - } else if ([item isEqualToString:@""]) { - [menu addItem:[NSMenuItem separatorItem]]; } + + timerUpdating = YES; + [statusItem setEnabled:NO]; + + NS_DURING + latestPlaylistClass = [[self currentRemote] currentPlaylistClass]; + + if ([menuController rebuildSubmenus]) { + if ( [df boolForKey:@"showSongInfoOnChange"] ) { + [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0]; + } + [self setLatestSongIdentifier:identifier]; + //Create the tooltip for the status item + if ( [df boolForKey:@"showToolTip"] ) { + NSString *artist = [[self currentRemote] currentSongArtist]; + NSString *title = [[self currentRemote] currentSongTitle]; + ITDebugLog(@"Creating status item tooltip."); + if (artist) { + _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title]; + } else if (title) { + _toolTip = title; + } else { + _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing."); + } + [statusItem setToolTip:_toolTip]; + } else { + [statusItem setToolTip:nil]; + } + } + + if ([df boolForKey:@"audioscrobblerEnabled"]) { + int length = [[self currentRemote] currentSongDuration]; + if (_audioscrobblerTimer) { + [_audioscrobblerTimer invalidate]; + } + if (length > 30) { + _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:((length < 240) ? length / 2 : 120) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:YES]; + } + } else { + _audioscrobblerTimer = nil; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + timerUpdating = NO; + [statusItem setEnabled:YES]; } - - if (playlistItem) { - [self rebuildPlaylistMenu]; - } - - if (eqItem) { - [self rebuildEQPresetsMenu]; + + if ([networkController isConnectedToServer]) { + [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]]; } - - isPlayingRadio = ([currentRemote classOfPlaylistAtIndex:playlist] == ITMTRemotePlayerRadioPlaylist); - - if (upcomingSongsItem) { - [self rebuildUpcomingSongsMenu]; +} + +- (void)menuClicked +{ + ITDebugLog(@"Menu clicked."); + + if (([[self currentRemote] playerStateUniqueIdentifier] == nil) && playerRunningState == ITMTRemotePlayerRunning) { + if (refreshTimer) { + if ([statusItem isEnabled]) { + [statusItem setToolTip:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.")]; + } + [statusItem setEnabled:NO]; + } else { + NSMenu *menu = [[NSMenu alloc] init]; + [menu addItemWithTitle:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.") action:nil keyEquivalent:@""]; + [statusItem setMenu:[menu autorelease]]; + } + return; + } else if (![statusItem isEnabled]) { + [statusItem setEnabled:YES]; + [statusItem setToolTip:_toolTip]; + return; + } + + if ([networkController isConnectedToServer]) { + //Used the cached version + return; } - if (ratingItem) { - if (isPlayingRadio || !playlist) { - [ratingItem setEnabled:NO]; + NS_DURING + if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) { + [statusItem setMenu:[menuController menu]]; } else { - int currentSongRating = ([currentRemote currentSongRating] * 5); - [[ratingMenu itemAtIndex:lastSongRating] setState:NSOffState]; - lastSongRating = currentSongRating; - [[ratingMenu itemAtIndex:lastSongRating] setState:NSOnState]; - [ratingItem setEnabled:YES]; - } - } - - //Set the new unique song identifier - lastSongIdentifier = [[currentRemote currentSongUniqueIdentifier] retain]; - - //If we're in a playlist or radio mode - if ( ![lastSongIdentifier isEqualToString:@"0-0"] && (trackInfoIndex > -1) ) { - NSString *title; - - if ( (i = [menu indexOfItemWithTitle:@"No Song"]) ) { - if ( (i > -1) ) { - [menu removeItemAtIndex:i]; - [menu insertItemWithTitle:@"Now Playing" action:NULL keyEquivalent:@"" atIndex:i]; - } + [statusItem setMenu:[menuController menuForNoPlayer]]; } - - title = [currentRemote currentSongTitle]; - - if (!isPlayingRadio) { - if ([df boolForKey:@"showTime"]) { - NSString *length = [currentRemote currentSongLength]; - char character = [length characterAtIndex:0]; - if ( (character > '0') && (character < '9') ) { - [menu insertItemWithTitle:[NSString stringWithFormat:@" %@", [currentRemote currentSongLength]] action:nil keyEquivalent:@"" atIndex:trackInfoIndex + 1]; - } - } - - if ([df boolForKey:@"showRating"]) { - if (title) { //Check to see if there's a song playing - [menu insertItemWithTitle:[NSString stringWithFormat:@" %@", [[ratingMenu itemAtIndex:[currentRemote currentSongRating] * 5] title]] action:nil keyEquivalent:@"" atIndex:trackInfoIndex + 1]; - } - } - - if ([df boolForKey:@"showArtist"]) { - NSString *artist = [currentRemote currentSongArtist]; - if ([artist length] > 0) { - [menu insertItemWithTitle:[NSString stringWithFormat:@" %@", artist] action:nil keyEquivalent:@"" atIndex:trackInfoIndex + 1]; - } - } - - if ([df boolForKey:@"showNumber"]) { - int track = [currentRemote currentSongTrack]; - int total = [currentRemote currentAlbumTrackCount]; - if (total > 0) { - [menu insertItemWithTitle:[NSString stringWithFormat:@" Track %i of %i", track, total] action:nil keyEquivalent:@"" atIndex:trackInfoIndex + 1]; - } - } - - if ([df boolForKey:@"showAlbum"]) { - NSString *album = [currentRemote currentSongAlbum]; - if ([album length] > 0) { - [menu insertItemWithTitle:[NSString stringWithFormat:@" %@", album] action:nil keyEquivalent:@"" atIndex:trackInfoIndex + 1]; - } - } - } - - if ([title length] > 0) { - [menu insertItemWithTitle:[NSString stringWithFormat:@" %@", title] action:nil keyEquivalent:@"" atIndex:trackInfoIndex + 1]; - } - } - - [statusItem setMenu:menu]; - - [self clearHotKeys]; - [self setupHotKeys]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - -//Build a menu with the list of all available EQ presets -- (void)rebuildEQPresetsMenu +- (void)trackChanged:(NSNotification *)note { - NSArray *eqPresets = [currentRemote eqPresets]; - int i; - - [eqMenu autorelease]; - eqMenu = [[NSMenu alloc] initWithTitle:@""]; - - for (i = 0; i < [eqPresets count]; i++) { - NSString *name; - NSMenuItem *tempItem; - if ( ( name = [eqPresets objectAtIndex:i] ) ) { - tempItem = [[NSMenuItem alloc] initWithTitle:name action:@selector(selectEQPreset:) keyEquivalent:@""]; - [tempItem setTag:i]; - [eqMenu addItem:tempItem]; - [tempItem autorelease]; + //If we're running the timer, shut it off since we don't need it! + /*if (refreshTimer && [refreshTimer isValid]) { + ITDebugLog(@"Invalidating refresh timer."); + [refreshTimer invalidate]; + [refreshTimer release]; + refreshTimer = nil; + }*/ + + if (![self songChanged]) { + return; } + NSString *identifier = [[self currentRemote] playerStateUniqueIdentifier]; + if ( [df boolForKey:@"showSongInfoOnChange"] ) { + [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0]; + } + [_lastTrackInfo release]; + _lastTrackInfo = [[note userInfo] retain]; + + [self setLatestSongIdentifier:identifier]; + ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier); + if ([df boolForKey:@"runScripts"]) { + NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]]; + NSEnumerator *scriptsEnum = [scripts objectEnumerator]; + NSString *nextScript; + ITDebugLog(@"Running AppleScripts for song change."); + while ( (nextScript = [scriptsEnum nextObject]) ) { + NSDictionary *error; + NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error]; + ITDebugLog(@"Running script: %@", nextScript); + if (!currentScript || ![currentScript executeAndReturnError:nil]) { + ITDebugLog(@"Error running script %@.", nextScript); + } + [currentScript release]; + } + } + + [statusItem setEnabled:NO]; + + NS_DURING + latestPlaylistClass = [[self currentRemote] currentPlaylistClass]; + + if ([menuController rebuildSubmenus]) { + /*if ( [df boolForKey:@"showSongInfoOnChange"] ) { + [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0]; + }*/ + [self setLatestSongIdentifier:identifier]; + //Create the tooltip for the status item + if ( [df boolForKey:@"showToolTip"] ) { + ITDebugLog(@"Creating status item tooltip."); + NSString *artist = [_lastTrackInfo objectForKey:@"Artist"], *title = [_lastTrackInfo objectForKey:@"Name"]; + if (artist) { + _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title]; + } else if (title) { + _toolTip = title; + } else { + _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");; + } + [statusItem setToolTip:_toolTip]; + } else { + [statusItem setToolTip:nil]; + } + } + + if ([df boolForKey:@"audioscrobblerEnabled"]) { + int length = [[self currentRemote] currentSongDuration]; + if (_audioscrobblerTimer) { + [_audioscrobblerTimer invalidate]; + } + if (length > 30) { + _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:((length < 240) ? length / 2 : 120) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:YES]; + } + } else { + _audioscrobblerTimer = nil; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + timerUpdating = NO; + [statusItem setEnabled:YES]; + + if ([networkController isConnectedToServer]) { + [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]]; } - - [eqItem setSubmenu:eqMenu]; - [eqItem setEnabled:YES]; - [[eqMenu itemAtIndex:([currentRemote currentEQPresetIndex] - 1)] setState:NSOnState]; } -*/ - -- (void)timerUpdate +- (void)submitAudioscrobblerTrack:(NSTimer *)timer { - if ( ( [self songChanged] ) || - ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) || - ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) ) { - [statusItem setMenu:[self menu]]; - [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]]; - latestPlaylistClass = [currentRemote currentPlaylistClass]; - - if ( [df boolForKey:@"showSongInfoOnChange"] ) { - [self showCurrentTrackInfo]; - } - } -/* - //Update Play/Pause menu item - if (playPauseItem){ - if ([currentRemote playerPlayingState] == ITMTRemotePlayerPlaying) { - [playPauseItem setTitle:@"Pause"]; - } else { - [playPauseItem setTitle:@"Play"]; - } - } -*/ + int interval = [timer timeInterval]; + [timer invalidate]; + _audioscrobblerTimer = nil; + ITDebugLog(@"Audioscrobbler: Attempting to submit current track"); + if ([df boolForKey:@"audioscrobblerEnabled"]) { + NS_DURING + int elapsed = [[self currentRemote] currentSongPlayed]; + if ((abs(elapsed - interval) < 5) && ([[self currentRemote] playerPlayingState] == ITMTRemotePlayerPlaying)) { + NSString *title = [[self currentRemote] currentSongTitle], *artist = [[self currentRemote] currentSongArtist]; + if (title && artist) { + ITDebugLog(@"Audioscrobbler: Submitting current track"); + [[AudioscrobblerController sharedController] submitTrack:title + artist:artist + album:[[self currentRemote] currentSongAlbum] + length:[[self currentRemote] currentSongDuration]]; + } + } else if (interval - elapsed > 0) { + ITDebugLog(@"Audioscrobbler: Creating a new timer that will run in %i seconds", interval - elapsed); + _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:(interval - elapsed) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:YES]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + } } -/* // // // Menu Selectors // // -- (void)selectSong:(id)sender +- (void)playPause { - [currentRemote switchToSongAtIndex:[[sender representedObject] intValue]]; + NS_DURING + ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState]; + ITDebugLog(@"Play/Pause toggled"); + if (state == ITMTRemotePlayerPlaying) { + [[self currentRemote] pause]; + } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) { + [[self currentRemote] pause]; + [[self currentRemote] play]; + } else { + [[self currentRemote] play]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + + if (refreshTimer) { + [self timerUpdate]; + } } -*/ -- (void)selectPlaylist:(id)sender + +- (void)nextSong { - int playlist = [sender tag]; - [currentRemote switchToPlaylistAtIndex:playlist]; + ITDebugLog(@"Going to next song."); + NS_DURING + [[self currentRemote] goToNextSong]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -/* -- (void)selectEQPreset:(id)sender + +- (void)prevSong { - int curSet = [currentRemote currentEQPresetIndex]; - int item = [sender tag]; - - [currentRemote switchToEQAtIndex:item]; - [[eqMenu itemAtIndex:curSet - 1] setState:NSOffState]; - [[eqMenu itemAtIndex:item] setState:NSOnState]; + ITDebugLog(@"Going to previous song."); + NS_DURING + [[self currentRemote] goToPreviousSong]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -*/ -/* -- (void)selectSongRating:(id)sender + +- (void)fastForward { - int newRating = [sender tag]; -// [[ratingMenu itemAtIndex:lastSongRating] setState:NSOffState]; - [sender setState:NSOnState]; - [currentRemote setCurrentSongRating:(float)newRating / 100.0]; - lastSongRating = newRating / 20; + ITDebugLog(@"Fast forwarding."); + NS_DURING + [[self currentRemote] forward]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -*/ -/* -- (void)playPause:(id)sender + +- (void)rewind { - ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState]; - - if (state == ITMTRemotePlayerPlaying) { - [currentRemote pause]; - [playPauseItem setTitle:@"Play"]; - } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) { - [currentRemote pause]; - [currentRemote play]; - } else { - [currentRemote play]; - [playPauseItem setTitle:@"Pause"]; - } + ITDebugLog(@"Rewinding."); + NS_DURING + [[self currentRemote] rewind]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -- (void)nextSong:(id)sender +- (void)selectPlaylistAtIndex:(int)index { - [currentRemote goToNextSong]; + ITDebugLog(@"Selecting playlist %i", index); + NS_DURING + [[self currentRemote] switchToPlaylistAtIndex:(index % 1000) ofSourceAtIndex:(index / 1000)]; + //[[self currentRemote] switchToPlaylistAtIndex:index]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -- (void)prevSong:(id)sender +- (void)selectSongAtIndex:(int)index { - [currentRemote goToPreviousSong]; + ITDebugLog(@"Selecting song %i", index); + NS_DURING + [[self currentRemote] switchToSongAtIndex:index]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -- (void)fastForward:(id)sender +- (void)selectSongRating:(int)rating { - [currentRemote forward]; - [playPauseItem setTitle:@"Play"]; + ITDebugLog(@"Selecting song rating %i", rating); + NS_DURING + [[self currentRemote] setCurrentSongRating:(float)rating / 100.0]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -- (void)rewind:(id)sender +- (void)selectEQPresetAtIndex:(int)index { - [currentRemote rewind]; - [playPauseItem setTitle:@"Play"]; + ITDebugLog(@"Selecting EQ preset %i", index); + NS_DURING + if (index == -1) { + [[self currentRemote] setEqualizerEnabled:![[self currentRemote] equalizerEnabled]]; + } else { + [[self currentRemote] switchToEQAtIndex:index]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (refreshTimer) { + [self timerUpdate]; + } } -*/ -// -// -- (void)quitMenuTunes:(id)sender +- (void)makePlaylistWithTerm:(NSString *)term ofType:(int)type { - [NSApp terminate:self]; + ITDebugLog(@"Making playlist with term %@, type %i", term, type); + NS_DURING + [[self currentRemote] makePlaylistWithTerm:term ofType:type]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + ITDebugLog(@"Done making playlist"); } -- (void)showPlayer:(id)sender +- (void)showPlayer { - if ( ( playerRunningState == ITMTRemotePlayerRunning) ) { - [currentRemote showPrimaryInterface]; - } else { - if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) { - NSLog(@"Error Launching Player"); - } - } + ITDebugLog(@"Beginning show player."); + //if ( ( playerRunningState == ITMTRemotePlayerRunning) ) { + ITDebugLog(@"Showing player interface."); + NS_DURING + [[self currentRemote] showPrimaryInterface]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + /*} else { + ITDebugLog(@"Launching player."); + NS_DURING + NSString *path; + if ( (path = [df stringForKey:@"CustomPlayerPath"]) ) { + } else { + pathITDebugLog(@"Showing player interface."); = [[self currentRemote] playerFullName]; + } + if (![[NSWorkspace sharedWorkspace] launchApplication:path]) { + ITDebugLog(@"Error Launching Player"); + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + }*/ + ITDebugLog(@"Finished show player."); } -- (void)showPreferences:(id)sender +- (void)showPreferences { - [[PreferencesController sharedPrefs] setController:self]; + ITDebugLog(@"Show preferences."); [[PreferencesController sharedPrefs] showPrefsWindow:self]; } +- (void)showPreferencesAndClose +{ + ITDebugLog(@"Show preferences."); + [[PreferencesController sharedPrefs] showPrefsWindow:self]; + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; + [[StatusWindow sharedWindow] vanish:self]; + [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; +} + +- (void)showTestWindow +{ + [self showCurrentTrackInfo]; +} + +- (void)quitMenuTunes +{ + ITDebugLog(@"Quitting MenuTunes."); + [NSApp terminate:self]; +} + +// +// + +- (MenuController *)menuController +{ + return menuController; +} + - (void)closePreferences { + ITDebugLog(@"Preferences closed."); if ( ( playerRunningState == ITMTRemotePlayerRunning) ) { [self setupHotKeys]; } @@ -668,6 +864,10 @@ - (ITMTRemote *)currentRemote { + if ([networkController isConnectedToServer] && ![[networkController networkObject] isValid]) { + [self networkError:nil]; + return nil; + } return currentRemote; } @@ -679,345 +879,773 @@ - (void)clearHotKeys { - [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleLoop"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleShuffle"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementVolume"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementVolume"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementRating"]; - [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementRating"]; + NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator]; + ITHotKey *nextHotKey; + ITDebugLog(@"Clearing hot keys."); + while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) { + [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey]; + } + ITDebugLog(@"Done clearing hot keys."); } - (void)setupHotKeys { + ITHotKey *hotKey; + ITDebugLog(@"Setting up hot keys."); + + if (playerRunningState == ITMTRemotePlayerNotRunning && ![[NetworkController sharedController] isConnectedToServer]) { + return; + } + if ([df objectForKey:@"PlayPause"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause" - combo:[df keyComboForKey:@"PlayPause"] - target:self action:@selector(playPause:)]; + ITDebugLog(@"Setting up play pause hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"PlayPause"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(playPause)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"NextTrack"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack" - combo:[df keyComboForKey:@"NextTrack"] - target:self action:@selector(nextSong:)]; + ITDebugLog(@"Setting up next track hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"NextTrack"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(nextSong)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"PrevTrack"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack" - combo:[df keyComboForKey:@"PrevTrack"] - target:self action:@selector(prevSong:)]; + ITDebugLog(@"Setting up previous track hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"PrevTrack"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(prevSong)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + + if ([df objectForKey:@"FastForward"] != nil) { + ITDebugLog(@"Setting up fast forward hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"FastForward"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"FastForward"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(fastForward)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + + if ([df objectForKey:@"Rewind"] != nil) { + ITDebugLog(@"Setting up rewind hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"Rewind"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"Rewind"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(rewind)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + + if ([df objectForKey:@"ShowPlayer"] != nil) { + ITDebugLog(@"Setting up show player hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"ShowPlayer"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(showPlayer)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"TrackInfo"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo" - combo:[df keyComboForKey:@"TrackInfo"] - target:self action:@selector(showCurrentTrackInfo)]; + ITDebugLog(@"Setting up track info hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"TrackInfo"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(showCurrentTrackInfoHotKey)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"UpcomingSongs"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs" - combo:[df keyComboForKey:@"UpcomingSongs"] - target:self action:@selector(showUpcomingSongs)]; + ITDebugLog(@"Setting up upcoming songs hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"UpcomingSongs"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(showUpcomingSongs)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"ToggleLoop"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop" - combo:[df keyComboForKey:@"ToggleLoop"] - target:self action:NULL/*Set this to something*/]; + ITDebugLog(@"Setting up toggle loop hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"ToggleLoop"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(toggleLoop)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"ToggleShuffle"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle" - combo:[df keyComboForKey:@"ToggleShuffle"] - target:self action:NULL/*Set this to something*/]; + ITDebugLog(@"Setting up toggle shuffle hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"ToggleShuffle"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(toggleShuffle)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"IncrementVolume"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume" - combo:[df keyComboForKey:@"IncrementVolume"] - target:self action:NULL/*Set this to something*/]; + ITDebugLog(@"Setting up increment volume hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"IncrementVolume"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(incrementVolume)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"DecrementVolume"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume" - combo:[df keyComboForKey:@"DecrementVolume"] - target:self action:NULL/*Set this to something*/]; + ITDebugLog(@"Setting up decrement volume hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"DecrementVolume"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(decrementVolume)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"IncrementRating"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating" - combo:[df keyComboForKey:@"IncrementRating"] - target:self action:NULL/*Set this to something*/]; + ITDebugLog(@"Setting up increment rating hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"IncrementRating"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(incrementRating)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } if ([df objectForKey:@"DecrementRating"] != nil) { - [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating" - combo:[df keyComboForKey:@"DecrementRating"] - target:self action:NULL/*Set this to something*/]; + ITDebugLog(@"Setting up decrement rating hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"DecrementRating"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(decrementRating)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + + if ([df objectForKey:@"ToggleShufflability"] != nil) { + ITDebugLog(@"Setting up toggle song shufflability hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"ToggleShufflability"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShufflability"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(toggleSongShufflable)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + + if ([df objectForKey:@"PopupMenu"] != nil) { + ITDebugLog(@"Setting up popup menu hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"PopupMenu"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PopupMenu"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(popupMenu)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; } + + int i; + for (i = 0; i <= 5; i++) { + NSString *curName = [NSString stringWithFormat:@"SetRating%i", i]; + if ([df objectForKey:curName] != nil) { + ITDebugLog(@"Setting up set rating %i hot key.", i); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:curName]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:curName]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(setRating:)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + } + ITDebugLog(@"Finished setting up hot keys."); } -- (void)showCurrentTrackInfo +- (void)showCurrentTrackInfoHotKey { - NSString *title = [currentRemote currentSongTitle]; + //If we're already visible and the setting says so, vanish instead of displaying again. + if ([df boolForKey:@"ToggleTrackInfoWithHotKey"] && [statusWindowController currentStatusWindowType] == StatusWindowTrackInfoType && [[StatusWindow sharedWindow] visibilityState] == ITWindowVisibleState) { + ITDebugLog(@"Track window is already visible, hiding track window."); + [self invalidateStatusWindowUpdateTimer]; + [[StatusWindow sharedWindow] vanish:nil]; + return; + } + [self showCurrentTrackInfo]; +} +- (void)showCurrentTrackInfo +{ + ITMTRemotePlayerSource source = 0; + NSString *title = nil; + NSString *album = nil; + NSString *artist = nil; + NSString *composer = nil; + NSString *time = nil; + NSString *track = nil; + NSImage *art = nil; + int rating = -1; + int playCount = -1; + + ITDebugLog(@"Showing track info status window."); + + NS_DURING + source = [[self currentRemote] currentSource]; + title = [[self currentRemote] currentSongTitle]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if ( title ) { - NSString *album = nil; - NSString *artist = nil; - NSString *time = nil; - int trackNumber = 0; - int trackTotal = 0; - int rating = 0; - + if ( [df boolForKey:@"showAlbumArtwork"] ) { + NSSize oldSize, newSize; + NS_DURING + art = [[self currentRemote] currentSongAlbumArt]; + oldSize = [art size]; + if (oldSize.width > oldSize.height) { + newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width)); + } + else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110); + art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + } + if ( [df boolForKey:@"showAlbum"] ) { - album = [currentRemote currentSongAlbum]; + NS_DURING + album = [[self currentRemote] currentSongAlbum]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } if ( [df boolForKey:@"showArtist"] ) { - artist = [currentRemote currentSongArtist]; + NS_DURING + artist = [[self currentRemote] currentSongArtist]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - if ( [df boolForKey:@"showTime"] ) { - time = [currentRemote currentSongLength]; + if ( [df boolForKey:@"showComposer"] ) { + NS_DURING + composer = [[self currentRemote] currentSongComposer]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - if ( [df boolForKey:@"showNumber"] ) { - trackNumber = [currentRemote currentSongTrack]; - trackTotal = [currentRemote currentAlbumTrackCount]; + if ( [df boolForKey:@"showTime"] ) { + NS_DURING + time = [NSString stringWithFormat:@"%@: %@ / %@", + NSLocalizedString(@"time", @"Time"), + [[self currentRemote] currentSongElapsed], + [[self currentRemote] currentSongLength]]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + _timeUpdateCount = 0; + [self invalidateStatusWindowUpdateTimer]; + _statusWindowUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES]; } - if ( [df boolForKey:@"showRating"] ) { - rating = ( [currentRemote currentSongRating] * 5 ); + if ( [df boolForKey:@"showTrackNumber"] ) { + int trackNo = 0; + int trackCount = 0; + + NS_DURING + trackNo = [[self currentRemote] currentSongTrack]; + trackCount = [[self currentRemote] currentAlbumTrackCount]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + + if ( (trackNo > 0) || (trackCount > 0) ) { + track = [NSString stringWithFormat:@"%@: %i %@ %i", + @"Track", trackNo, @"of", trackCount]; + } } - [statusWindowController showSongWindowWithTitle:title - album:album - artist:artist - time:time - trackNumber:trackNumber - trackTotal:trackTotal - rating:rating]; + if ( [df boolForKey:@"showTrackRating"] ) { + float currentRating = 0; + + NS_DURING + currentRating = [[self currentRemote] currentSongRating]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + + if (currentRating >= 0.0) { + rating = ( currentRating * 5 ); + } + } + + if ( [df boolForKey:@"showPlayCount"] && ![self radioIsPlaying] && [[self currentRemote] currentSource] == ITMTRemoteLibrarySource ) { + NS_DURING + playCount = [[self currentRemote] currentSongPlayCount]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + } } else { - title = @"No song is playing."; - [statusWindowController showSongWindowWithTitle:title - album:nil - artist:nil - time:nil - trackNumber:0 - trackTotal:0 - rating:0]; + title = NSLocalizedString(@"noSongPlaying", @"No song is playing."); } + ITDebugLog(@"Showing current track info status window."); + [statusWindowController showSongInfoWindowWithSource:source + title:title + album:album + artist:artist + composer:composer + time:time + track:track + rating:rating + playCount:playCount + image:art]; } -- (void)showUpcomingSongs +- (void)updateTime:(NSTimer *)timer { - int curPlaylist = [currentRemote currentPlaylistIndex]; - int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist]; + StatusWindow *sw = [StatusWindow sharedWindow]; + _timeUpdateCount++; + if (_timeUpdateCount < (int)[sw exitDelay] + (int)[[sw exitEffect] effectTime] + (int)[[sw entryEffect] effectTime]) { + NSString *time = nil, *length; + NS_DURING + length = [[self currentRemote] currentSongLength]; + if (length) { + time = [NSString stringWithFormat:@"%@: %@ / %@", + NSLocalizedString(@"time", @"Time"), + [[self currentRemote] currentSongElapsed], + length]; + [[StatusWindowController sharedController] updateTime:time]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + } else { + [self invalidateStatusWindowUpdateTimer]; + } +} - if (numSongs > 0) { - NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5]; - int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"]; - int curTrack = [currentRemote currentSongIndex]; - int i; +- (void)invalidateStatusWindowUpdateTimer +{ + if (_statusWindowUpdateTimer) { + [_statusWindowUpdateTimer invalidate]; + _statusWindowUpdateTimer = nil; + } +} - for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) { - if (i <= numSongs) { - [songList addObject:[currentRemote songTitleAtIndex:i]]; +- (void)showUpcomingSongs +{ + int numSongs = 0; + NS_DURING + numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + + [self invalidateStatusWindowUpdateTimer]; + + ITDebugLog(@"Showing upcoming songs status window."); + NS_DURING + if (numSongs > 0) { + int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"]; + NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance]; + int curTrack = [[self currentRemote] currentSongIndex]; + int i; + + for (i = curTrack + 1; i <= curTrack + numSongsInAdvance && i <= numSongs; i++) { + if ([[self currentRemote] songEnabledAtIndex:i]) { + [songList addObject:[[self currentRemote] songTitleAtIndex:i]]; + } else { + numSongsInAdvance++; + } + } + + if ([songList count] == 0) { + [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]; } + + [statusWindowController showUpcomingSongsWindowWithTitles:songList]; + } else { + [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]]; } - - [statusWindowController showUpcomingSongsWithTitles:songList]; - - } else { - [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:@"No upcoming songs."]]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)popupMenu +{ + if (!_popped) { + _popped = YES; + [self menuClicked]; + NSMenu *menu = [statusItem menu]; + [(NSCarbonMenuImpl *)[menu _menuImpl] popUpMenu:menu atLocation:[NSEvent mouseLocation] width:1 forView:nil withSelectedItem:-30 withFont:[NSFont menuFontOfSize:32]]; + _popped = NO; } } -- (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers - onItem:(NSMenuItem *)item +- (void)incrementVolume { - unichar charcode = 'a'; - int i; - long cocoaModifiers = 0; - static long carbonToCocoa[6][2] = - { - { cmdKey, NSCommandKeyMask }, - { optionKey, NSAlternateKeyMask }, - { controlKey, NSControlKeyMask }, - { shiftKey, NSShiftKeyMask }, - }; - - for (i = 0; i < 6; i++) { - if (modifiers & carbonToCocoa[i][0]) { - cocoaModifiers += carbonToCocoa[i][1]; + NS_DURING + float volume = [[self currentRemote] volume]; + float dispVol = volume; + ITDebugLog(@"Incrementing volume."); + volume += 0.110; + dispVol += 0.100; + + if (volume > 1.0) { + volume = 1.0; + dispVol = 1.0; } - } - [item setKeyEquivalentModifierMask:cocoaModifiers]; - //Missing key combos for some keys. Must find them later. - switch (code) - { - case 36: - charcode = '\r'; - break; - - case 48: - charcode = '\t'; - break; - - //Space -- ARGH! - case 49: - { - // Haven't tested this, though it should work. - unichar buffer; - [[NSString stringWithString:@"Space"] getCharacters:&buffer]; - charcode = buffer; - /*MenuRef menuRef = _NSGetCarbonMenu([item menu]); - NSLog(@"%@", menuRef); - SetMenuItemCommandKey(menuRef, 0, NO, 49); - SetMenuItemModifiers(menuRef, 0, kMenuNoCommandModifier); - SetMenuItemKeyGlyph(menuRef, 0, kMenuBlankGlyph); - charcode = 'b';*/ - + ITDebugLog(@"Setting volume to %f", volume); + [[self currentRemote] setVolume:volume]; + + // Show volume status window + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showVolumeWindowWithLevel:dispVol]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)decrementVolume +{ + NS_DURING + float volume = [[self currentRemote] volume]; + float dispVol = volume; + ITDebugLog(@"Decrementing volume."); + volume -= 0.090; + dispVol -= 0.100; + + if (volume < 0.0) { + volume = 0.0; + dispVol = 0.0; } - break; - - case 51: - charcode = NSDeleteFunctionKey; - break; - - case 53: - charcode = '\e'; - break; - - case 71: - charcode = '\e'; - break; - - case 76: - charcode = '\r'; - break; - - case 96: - charcode = NSF5FunctionKey; - break; - - case 97: - charcode = NSF6FunctionKey; - break; - - case 98: - charcode = NSF7FunctionKey; - break; - - case 99: - charcode = NSF3FunctionKey; - break; - - case 100: - charcode = NSF8FunctionKey; - break; - - case 101: - charcode = NSF9FunctionKey; - break; - - case 103: - charcode = NSF11FunctionKey; - break; - - case 105: - charcode = NSF3FunctionKey; - break; - - case 107: - charcode = NSF14FunctionKey; - break; - - case 109: - charcode = NSF10FunctionKey; - break; - case 111: - charcode = NSF12FunctionKey; - break; + ITDebugLog(@"Setting volume to %f", volume); + [[self currentRemote] setVolume:volume]; - case 113: - charcode = NSF13FunctionKey; - break; - - case 114: - charcode = NSInsertFunctionKey; - break; - - case 115: - charcode = NSHomeFunctionKey; - break; - - case 116: - charcode = NSPageUpFunctionKey; - break; - - case 117: - charcode = NSDeleteFunctionKey; - break; - - case 118: - charcode = NSF4FunctionKey; - break; + //Show volume status window + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showVolumeWindowWithLevel:dispVol]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)incrementRating +{ + NS_DURING + float rating = [[self currentRemote] currentSongRating]; + ITDebugLog(@"Incrementing rating."); - case 119: - charcode = NSEndFunctionKey; - break; + if ([[self currentRemote] currentPlaylistIndex] == 0) { + ITDebugLog(@"No song playing, rating change aborted."); + return; + } - case 120: - charcode = NSF2FunctionKey; - break; + rating += 0.2; + if (rating > 1.0) { + rating = 1.0; + } + ITDebugLog(@"Setting rating to %f", rating); + [[self currentRemote] setCurrentSongRating:rating]; - case 121: - charcode = NSPageDownFunctionKey; - break; + //Show rating status window + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showRatingWindowWithRating:rating]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)decrementRating +{ + NS_DURING + float rating = [[self currentRemote] currentSongRating]; + ITDebugLog(@"Decrementing rating."); - case 122: - charcode = NSF1FunctionKey; - break; + if ([[self currentRemote] currentPlaylistIndex] == 0) { + ITDebugLog(@"No song playing, rating change aborted."); + return; + } - case 123: - charcode = NSLeftArrowFunctionKey; - break; + rating -= 0.2; + if (rating < 0.0) { + rating = 0.0; + } + ITDebugLog(@"Setting rating to %f", rating); + [[self currentRemote] setCurrentSongRating:rating]; - case 124: - charcode = NSRightArrowFunctionKey; - break; + //Show rating status window + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showRatingWindowWithRating:rating]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)setRating:(ITHotKey *)sender +{ + if ([self songIsPlaying]) { + int stars = [[sender name] characterAtIndex:9] - 48; + [self selectSongRating:stars * 20]; + [statusWindowController showRatingWindowWithRating:(float)stars / 5.0]; + } +} + +- (void)toggleLoop +{ + NS_DURING + ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode]; + ITDebugLog(@"Toggling repeat mode."); + switch (repeatMode) { + case ITMTRemotePlayerRepeatOff: + repeatMode = ITMTRemotePlayerRepeatAll; + break; + case ITMTRemotePlayerRepeatAll: + repeatMode = ITMTRemotePlayerRepeatOne; + break; + case ITMTRemotePlayerRepeatOne: + repeatMode = ITMTRemotePlayerRepeatOff; + break; + } + ITDebugLog(@"Setting repeat mode to %i", repeatMode); + [[self currentRemote] setRepeatMode:repeatMode]; - case 125: - charcode = NSDownArrowFunctionKey; - break; + //Show loop status window + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showRepeatWindowWithMode:repeatMode]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)toggleShuffle +{ + NS_DURING + BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] ); + ITDebugLog(@"Toggling shuffle mode."); + [[self currentRemote] setShuffleEnabled:newShuffleEnabled]; + //Show shuffle status window + ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled); + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showShuffleWindow:newShuffleEnabled]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER +} + +- (void)toggleSongShufflable +{ + if ([self songIsPlaying]) { + NS_DURING + BOOL flag = ![[self currentRemote] currentSongShufflable]; + ITDebugLog(@"Toggling shufflability."); + [[self currentRemote] setCurrentSongShufflable:flag]; + //Show song shufflability status window + [self invalidateStatusWindowUpdateTimer]; + [statusWindowController showSongShufflabilityWindow:flag]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + } +} + +- (void)registerNowOK +{ + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; + [[StatusWindow sharedWindow] vanish:self]; + [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; + + [self blingNow]; +} + +- (void)registerNowCancel +{ + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; + [[StatusWindow sharedWindow] vanish:self]; + [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; + + [NSApp terminate:self]; +} + +/*************************************************************************/ +#pragma mark - +#pragma mark NETWORK HANDLERS +/*************************************************************************/ + +- (void)setServerStatus:(BOOL)newStatus +{ + if (newStatus) { + //Turn on + [networkController setServerStatus:YES]; + } else { + //Tear down + [networkController setServerStatus:NO]; + } +} + +- (int)connectToServer +{ + int result; + ITDebugLog(@"Attempting to connect to shared remote."); + result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]]; + //Connect + if (result == 1) { + [[PreferencesController sharedPrefs] resetRemotePlayerTextFields]; + currentRemote = [[[networkController networkObject] remote] retain]; - case 126: - charcode = NSUpArrowFunctionKey; - break; + [self setupHotKeys]; + //playerRunningState = ITMTRemotePlayerRunning; + playerRunningState = [[self currentRemote] playerRunningState]; + if (_needsPolling) { + if (refreshTimer) { + [refreshTimer invalidate]; + } + refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5) + target:self + selector:@selector(timerUpdate) + userInfo:nil + repeats:YES] retain]; + } + [self timerUpdate]; + ITDebugLog(@"Connection successful."); + return 1; + } else if (result == 0) { + ITDebugLog(@"Connection failed."); + currentRemote = [remoteArray objectAtIndex:0]; + return 0; + } else { + //Do something about the password being invalid + ITDebugLog(@"Connection failed."); + currentRemote = [remoteArray objectAtIndex:0]; + return -1; } +} + +- (BOOL)disconnectFromServer +{ + ITDebugLog(@"Disconnecting from shared remote."); + //Disconnect + [currentRemote release]; + currentRemote = [remoteArray objectAtIndex:0]; + [networkController disconnect]; - if (charcode == 'a') { - unsigned long state; - long keyTrans; - char charCode; - Ptr kchr; - state = 0; - kchr = (Ptr) GetScriptVariable(smCurrentScript, smKCHRCache); - keyTrans = KeyTranslate(kchr, code, &state); - charCode = keyTrans; - [item setKeyEquivalent:[NSString stringWithCString:&charCode length:1]]; - } else if (charcode != 'b') { - [item setKeyEquivalent:[NSString stringWithCharacters:&charcode length:1]]; + if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) { + [self applicationLaunched:nil]; + } else { + [self applicationTerminated:nil]; + } + if (refreshTimer) { + [self timerUpdate]; + }; + return YES; +} + +- (void)checkForRemoteServer +{ + [self checkForRemoteServerAndConnectImmediately:NO]; +} + +- (void)checkForRemoteServerAndConnectImmediately:(BOOL)connectImmediately +{ + ITDebugLog(@"Checking for remote server."); + if (!_checkingForServer) { + if (!_serverCheckLock) { + _serverCheckLock = [[NSLock alloc] init]; + } + [_serverCheckLock lock]; + _checkingForServer = YES; + [_serverCheckLock unlock]; + [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:[NSNumber numberWithBool:connectImmediately]]; } } +- (void)runRemoteServerCheck:(id)sender +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + ITDebugLog(@"Remote server check running."); + if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) { + ITDebugLog(@"Remote server found."); + if ([sender boolValue]) { + [self performSelectorOnMainThread:@selector(connectToServer) withObject:nil waitUntilDone:NO]; + } else { + [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO]; + } + } else { + ITDebugLog(@"Remote server not found."); + [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO]; + } + [_serverCheckLock lock]; + _checkingForServer = NO; + [_serverCheckLock unlock]; + [pool release]; +} + +- (void)remoteServerFound:(id)sender +{ + if (![networkController isServerOn] && ![networkController isConnectedToServer]) { + [self invalidateStatusWindowUpdateTimer]; + [[StatusWindowController sharedController] showReconnectQueryWindow]; + } +} + +- (void)remoteServerNotFound:(id)sender +{ + if (![[NetworkController sharedController] isConnectedToServer]) { + [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO]; + } +} + +- (void)networkError:(NSException *)exception +{ + ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]); + if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) { + //NSRunCriticalAlertPanel(@"Remote MenuTunes Disconnected", @"The MenuTunes server you were connected to stopped responding or quit. MenuTunes will revert back to the local player.", @"OK", nil, nil); + [self invalidateStatusWindowUpdateTimer]; + [[StatusWindowController sharedController] showNetworkErrorQueryWindow]; + if ([self disconnectFromServer]) { + [[PreferencesController sharedPrefs] resetRemotePlayerTextFields]; + [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO]; + } else { + ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!"); + } + } +} + +- (void)reconnect +{ + /*if ([self connectToServer] == 0) { + [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO]; + }*/ + [self checkForRemoteServerAndConnectImmediately:YES]; + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; + [[StatusWindow sharedWindow] vanish:self]; + [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; +} + +- (void)cancelReconnect +{ + [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO]; + [[StatusWindow sharedWindow] vanish:self]; + [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES]; +} + /*************************************************************************/ #pragma mark - #pragma mark WORKSPACE NOTIFICATION HANDLERS @@ -1025,31 +1653,61 @@ - (void)applicationLaunched:(NSNotification *)note { - if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) { - [NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil]; - [self setupHotKeys]; - playerRunningState = ITMTRemotePlayerRunning; - } + NS_DURING + if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) { + ITDebugLog(@"Remote application launched."); + playerRunningState = ITMTRemotePlayerRunning; + [[self currentRemote] begin]; + [self setLatestSongIdentifier:@""]; + [self timerUpdate]; + if (_needsPolling) { + refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5) + target:self + selector:@selector(timerUpdate) + userInfo:nil + repeats:YES] retain]; + } + //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil]; + if (![df boolForKey:@"UsePollingOnly"]) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackChanged:) name:@"ITMTTrackChanged" object:nil]; + } + [self setupHotKeys]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)applicationTerminated:(NSNotification *)note { - if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) { -/* - NSMenu *notRunningMenu = [[NSMenu alloc] initWithTitle:@""]; - [notRunningMenu addItemWithTitle:[NSString stringWithFormat:@"Open %@", [currentRemote playerSimpleName]] action:@selector(showPlayer:) keyEquivalent:@""]; - [notRunningMenu addItem:[NSMenuItem separatorItem]]; - [notRunningMenu addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""]; - [notRunningMenu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""]; -*/ - [refreshTimer invalidate]; - [refreshTimer release]; - refreshTimer = nil; - [self clearHotKeys]; - playerRunningState = ITMTRemotePlayerNotRunning; - - [statusItem setMenu:[self menuForNoPlayer]]; - } + NS_DURING + if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) { + ITDebugLog(@"Remote application terminated."); + playerRunningState = ITMTRemotePlayerNotRunning; + [[self currentRemote] halt]; + [refreshTimer invalidate]; + [refreshTimer release]; + refreshTimer = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ITMTTrackChanged" object:nil]; + [statusItem setEnabled:YES]; + [statusItem setToolTip:@"iTunes not running."]; + [self clearHotKeys]; + + + if ([df objectForKey:@"ShowPlayer"] != nil) { + ITHotKey *hotKey; + ITDebugLog(@"Setting up show player hot key."); + hotKey = [[ITHotKey alloc] init]; + [hotKey setName:@"ShowPlayer"]; + [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]]; + [hotKey setTarget:self]; + [hotKey setAction:@selector(showPlayer)]; + [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]]; + } + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } @@ -1060,10 +1718,19 @@ - (void)applicationWillTerminate:(NSNotification *)note { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; + [networkController stopRemoteServerSearch]; [self clearHotKeys]; [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; } +- (void)applicationDidBecomeActive:(NSNotification *)note +{ + //This appears to not work in 10.4 + if (_open && !blinged && ![[ITAboutWindowController sharedController] isVisible] && ![NSApp mainWindow] && ([[StatusWindow sharedWindow] exitMode] == ITTransientStatusWindowExitAfterDelay)) { + [[MainController sharedController] showPreferences]; + } +} /*************************************************************************/ #pragma mark - @@ -1072,17 +1739,14 @@ - (void)dealloc { - if (refreshTimer) { - [refreshTimer invalidate]; - [refreshTimer release]; - refreshTimer = nil; - } - - [currentRemote halt]; + [self applicationTerminated:nil]; + [bling release]; [statusItem release]; [statusWindowController release]; + [menuController release]; + [networkController release]; + [_serverCheckLock release]; [super dealloc]; } - @end