The hotkey for showing track info now calls a different method that will hide the...
[MenuTunes.git] / MainController.m
1 #import "MainController.h"
2 #import "MenuController.h"
3 #import "PreferencesController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import <ITKit/ITHotKeyCenter.h>
7 #import <ITKit/ITHotKey.h>
8 #import <ITKit/ITKeyCombo.h>
9 #import <ITKit/ITCategory-NSMenu.h>
10 #import "StatusWindow.h"
11 #import "StatusWindowController.h"
12 #import "AudioscrobblerController.h"
13 #import "StatusItemHack.h"
14
15 @interface NSMenu (MenuImpl)
16 - (id)_menuImpl;
17 @end
18
19 @interface NSCarbonMenuImpl:NSObject
20 {
21     NSMenu *_menu;
22 }
23
24 + (void)initialize;
25 + (void)setupForNoMenuBar;
26 - (void)dealloc;
27 - (void)setMenu:fp8;
28 - menu;
29 - (void)itemChanged:fp8;
30 - (void)itemAdded:fp8;
31 - (void)itemRemoved:fp8;
32 - (void)performActionWithHighlightingForItemAtIndex:(int)fp8;
33 - (void)performMenuAction:(SEL)fp8 withTarget:fp12;
34 - (void)setupCarbonMenuBar;
35 - (void)setAsMainCarbonMenuBar;
36 - (void)clearAsMainCarbonMenuBar;
37 - (void)popUpMenu:fp8 atLocation:(NSPoint)fp12 width:(float)fp20 forView:fp24 withSelectedItem:(int)fp28 withFont:fp32;
38 - (void)_popUpContextMenu:fp8 withEvent:fp12 forView:fp16 withFont:fp20;
39 - (void)_popUpContextMenu:fp8 withEvent:fp12 forView:fp16;
40 - window;
41 @end
42
43 @implementation NSImage (SmoothAdditions)
44
45 - (NSImage *)imageScaledSmoothlyToSize:(NSSize)scaledSize
46 {
47     NSImage *newImage;
48     NSImageRep *rep = [self bestRepresentationForDevice:nil];
49     
50     newImage = [[NSImage alloc] initWithSize:scaledSize];
51     [newImage lockFocus];
52     {
53         [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
54         [[NSGraphicsContext currentContext] setShouldAntialias:YES];
55         [rep drawInRect:NSMakeRect(3, 3, scaledSize.width - 6, scaledSize.height - 6)];
56     }
57     [newImage unlockFocus];
58     return [newImage autorelease];
59 }
60
61 @end
62
63 @interface MainController(Private)
64 - (ITMTRemote *)loadRemote;
65 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
66 - (void)applicationLaunched:(NSNotification *)note;
67 - (void)applicationTerminated:(NSNotification *)note;
68
69 - (void)invalidateStatusWindowUpdateTimer;
70 @end
71
72 static MainController *sharedController;
73
74 @implementation MainController
75
76 + (MainController *)sharedController
77 {
78     return sharedController;
79 }
80
81 /*************************************************************************/
82 #pragma mark -
83 #pragma mark INITIALIZATION/DEALLOCATION METHODS
84 /*************************************************************************/
85
86 - (id)init
87 {
88     if ( ( self = [super init] ) ) {
89         sharedController = self;
90         
91                 _statusWindowUpdateTimer = nil;
92                 _audioscrobblerTimer = nil;
93                 
94         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
95         [[PreferencesController sharedPrefs] setController:self];
96         statusWindowController = [StatusWindowController sharedController];
97         menuController = [[MenuController alloc] init];
98         df = [[NSUserDefaults standardUserDefaults] retain];
99         timerUpdating = NO;
100         blinged = NO;
101     }
102     return self;
103 }
104
105 - (void)applicationDidFinishLaunching:(NSNotification *)note
106 {
107         NSString *iTunesPath = [df stringForKey:@"CustomPlayerPath"];
108         NSDictionary *iTunesInfoPlist;
109         float iTunesVersion;
110         
111     //Turn on debug mode if needed
112         /*if ((GetCurrentKeyModifiers() & (controlKey | rightControlKey)) != 0)
113     if ((GetCurrentKeyModifiers() & (optionKey | rightOptionKey)) != 0)
114     if ((GetCurrentKeyModifiers() & (shiftKey | rightShiftKey)) != 0)*/
115     if ([df boolForKey:@"ITDebugMode"] || ((GetCurrentKeyModifiers() & (controlKey | rightControlKey)) != 0)) {
116         SetITDebugMode(YES);
117                 [[StatusWindowController sharedController] showDebugModeEnabledWindow];
118     }
119
120         //Check if iTunes 4.7 or later is installed     
121         if (!iTunesPath) {
122                 iTunesPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes.app"];
123         }
124         iTunesInfoPlist = [[NSBundle bundleWithPath:iTunesPath] infoDictionary];
125         iTunesVersion = [[iTunesInfoPlist objectForKey:@"CFBundleVersion"] floatValue];
126         ITDebugLog(@"iTunes version found: %f.", iTunesVersion);
127         if (iTunesVersion >= 4.7) {
128                 _needsPolling = NO;
129         } else {
130                 _needsPolling = YES;
131         }
132         
133     if (([df integerForKey:@"appVersion"] < 1200) && ([df integerForKey:@"SongsInAdvance"] > 0)) {
134         [df removePersistentDomainForName:@"com.ithinksw.menutunes"];
135         [df synchronize];
136         [[PreferencesController sharedPrefs] registerDefaults];
137         [[StatusWindowController sharedController] showPreferencesUpdateWindow];
138     }
139     
140     currentRemote = [self loadRemote];
141     [[self currentRemote] begin];
142     
143     //Turn on network stuff if needed
144     networkController = [[NetworkController alloc] init];
145     if ([df boolForKey:@"enableSharing"]) {
146         [self setServerStatus:YES];
147     } else if ([df boolForKey:@"useSharedPlayer"]) {
148         [self checkForRemoteServerAndConnectImmediately:YES];
149     }
150     
151     //Setup for notification of the remote player launching or quitting
152     [[[NSWorkspace sharedWorkspace] notificationCenter]
153             addObserver:self
154             selector:@selector(applicationTerminated:)
155             name:NSWorkspaceDidTerminateApplicationNotification
156             object:nil];
157     
158     [[[NSWorkspace sharedWorkspace] notificationCenter]
159             addObserver:self
160             selector:@selector(applicationLaunched:)
161             name:NSWorkspaceDidLaunchApplicationNotification
162             object:nil];
163     
164     if (![df objectForKey:@"menu"]) {  // If this is nil, defaults have never been registered.
165         [[PreferencesController sharedPrefs] registerDefaults];
166     }
167     
168     if ([df boolForKey:@"ITMTNoStatusItem"]) {
169         statusItem = nil;
170     } else {
171         [StatusItemHack install];
172         statusItem = [[ITStatusItem alloc]
173                 initWithStatusBar:[NSStatusBar systemStatusBar]
174                 withLength:NSSquareStatusItemLength];
175     }
176     
177     /*bling = [[MTBlingController alloc] init];
178     [self blingTime];
179     registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0
180                              target:self
181                              selector:@selector(blingTime)
182                              userInfo:nil
183                              repeats:YES] retain];*/
184     
185     NS_DURING
186         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
187             [self applicationLaunched:nil];
188         } else {
189             if ([df boolForKey:@"LaunchPlayerWithMT"])
190                 [self showPlayer];
191             else
192                 [self applicationTerminated:nil];
193         }
194     NS_HANDLER
195         [self networkError:localException];
196     NS_ENDHANDLER
197     
198     [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
199     [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
200
201     [networkController startRemoteServerSearch];
202     [NSApp deactivate];
203         [self performSelector:@selector(rawr) withObject:nil afterDelay:1.0];
204         
205         bling = [[MTBlingController alloc] init];
206     [self blingTime];
207     registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0
208                              target:self
209                              selector:@selector(blingTime)
210                              userInfo:nil
211                              repeats:YES] retain];
212 }
213
214 - (void)rawr
215 {
216         _open = YES;
217 }
218
219 - (ITMTRemote *)loadRemote
220 {
221     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
222     ITDebugLog(@"Gathering remotes.");
223     if (folderPath) {
224         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
225         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
226         NSString     *bundlePath;
227
228         while ( (bundlePath = [enumerator nextObject]) ) {
229             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
230
231             if (remoteBundle) {
232                 Class remoteClass = [remoteBundle principalClass];
233
234                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
235                     [(NSObject *)remoteClass isKindOfClass:[NSObject class]]) {
236                     id remote = [remoteClass remote];
237                     ITDebugLog(@"Adding remote at path %@", bundlePath);
238                     [remoteArray addObject:remote];
239                 }
240             }
241         }
242
243 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
244 //          if ( [remoteArray count] > 1 ) {
245 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
246 //          }
247 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
248 //      }
249     }
250 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
251     return [remoteArray objectAtIndex:0];
252 }
253
254 /*************************************************************************/
255 #pragma mark -
256 #pragma mark INSTANCE METHODS
257 /*************************************************************************/
258
259 /*- (void)startTimerInNewThread
260 {
261     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
262     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
263     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
264                              target:self
265                              selector:@selector(timerUpdate)
266                              userInfo:nil
267                              repeats:YES] retain];
268     [runLoop run];
269     ITDebugLog(@"Timer started.");
270     [pool release];
271 }*/
272
273 - (void)setBlingTime:(NSDate*)date
274 {
275     NSMutableDictionary *globalPrefs;
276     [df synchronize];
277     globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
278     if (date) {
279         [globalPrefs setObject:date forKey:@"ITMTTrialStart"];
280         [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"];
281     } else {
282         [globalPrefs removeObjectForKey:@"ITMTTrialStart"];
283         [globalPrefs removeObjectForKey:@"ITMTTrialVers"];
284     }
285     [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
286     [df synchronize];
287     [globalPrefs release];
288 }
289
290 - (NSDate*)getBlingTime
291 {
292     [df synchronize];
293     return [[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialStart"];
294 }
295
296 - (void)blingTime
297 {
298     NSDate *now = [NSDate date];
299     if (![self blingBling]) {
300         if ( (! [self getBlingTime] ) || ([now timeIntervalSinceDate:[self getBlingTime]] < 0) ) {
301             [self setBlingTime:now];
302         } else if ([[[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialVers"] intValue] < MT_CURRENT_VERSION) {
303             if ([now timeIntervalSinceDate:[self getBlingTime]] >= 345600) {
304                 [self setBlingTime:[now addTimeInterval:-259200]];
305             } else {
306                 NSMutableDictionary *globalPrefs;
307                 [df synchronize];
308                 globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
309                 [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"];
310                 [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
311                 [df synchronize];
312                 [globalPrefs release];
313             }
314         }
315         
316         if ( ([now timeIntervalSinceDate:[self getBlingTime]] >= 604800) && (blinged != YES) ) {
317             blinged = YES;
318             [statusItem setEnabled:NO];
319                         [[ITHotKeyCenter sharedCenter] setEnabled:NO];
320             if ([refreshTimer isValid]) {
321                 [refreshTimer invalidate];
322                                 [refreshTimer release];
323                                 refreshTimer = nil;
324             }
325             [statusWindowController showRegistrationQueryWindow];
326         }
327     } else {
328         if (blinged) {
329             [statusItem setEnabled:YES];
330             [[ITHotKeyCenter sharedCenter] setEnabled:YES];
331             if (_needsPolling && ![refreshTimer isValid]) {
332                 [refreshTimer release];
333                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
334                              target:self
335                              selector:@selector(timerUpdate)
336                              userInfo:nil
337                              repeats:YES] retain];
338             }
339             blinged = NO;
340         }
341         [self setBlingTime:nil];
342     }
343 }
344
345 - (void)blingNow
346 {
347     [bling showPanel];
348 }
349
350 - (BOOL)blingBling
351 {
352     if ( ! ([bling checkDone] == 2475) ) {
353         return NO;
354     } else {
355         return YES;
356     }
357 }
358
359 - (BOOL)songIsPlaying
360 {
361     NSString *identifier = nil;
362     NS_DURING
363         identifier = [[self currentRemote] playerStateUniqueIdentifier];
364     NS_HANDLER
365         [self networkError:localException];
366     NS_ENDHANDLER
367     return ( ! ([identifier isEqualToString:@"0-0"]) );
368 }
369
370 - (BOOL)radioIsPlaying
371 {
372     ITMTRemotePlayerPlaylistClass class = nil;
373     NS_DURING
374         class = [[self currentRemote] currentPlaylistClass];
375     NS_HANDLER
376         [self networkError:localException];
377     NS_ENDHANDLER
378     return (class  == ITMTRemotePlayerRadioPlaylist );
379 }
380
381 - (BOOL)songChanged
382 {
383     NSString *identifier = nil;
384     NS_DURING
385         identifier = [[self currentRemote] playerStateUniqueIdentifier];
386     NS_HANDLER
387         [self networkError:localException];
388     NS_ENDHANDLER
389     return ( ! [identifier isEqualToString:_latestSongIdentifier] );
390 }
391
392 - (NSString *)latestSongIdentifier
393 {
394     return _latestSongIdentifier;
395 }
396
397 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
398 {
399     ITDebugLog(@"Setting latest song identifier:");
400     ITDebugLog(@"   - Identifier: %@", newIdentifier);
401     [_latestSongIdentifier autorelease];
402     _latestSongIdentifier = [newIdentifier retain];
403 }
404
405 - (void)timerUpdate
406 {
407         NSString *identifier = [[self currentRemote] playerStateUniqueIdentifier];
408         if (refreshTimer && identifier == nil) {
409                 if ([statusItem isEnabled]) {
410                         [statusItem setToolTip:@"iTunes not responding."];
411                 }
412                 [statusItem setEnabled:NO];
413                 return;
414         } else if (![statusItem isEnabled]) {
415                 [statusItem setEnabled:YES];
416                 [statusItem setToolTip:_toolTip];
417                 return;
418         }
419         
420         if ( [self songChanged] && (timerUpdating != YES) && (playerRunningState == ITMTRemotePlayerRunning) ) {
421         ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier);
422         if ([df boolForKey:@"runScripts"]) {
423             NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
424             NSEnumerator *scriptsEnum = [scripts objectEnumerator];
425             NSString *nextScript;
426             ITDebugLog(@"Running AppleScripts for song change.");
427             while ( (nextScript = [scriptsEnum nextObject]) ) {
428                 NSDictionary *error;
429                 NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
430                 ITDebugLog(@"Running script: %@", nextScript);
431                 if (!currentScript || ![currentScript executeAndReturnError:nil]) {
432                     ITDebugLog(@"Error running script %@.", nextScript);
433                 }
434                 [currentScript release];
435             }
436         }
437         
438         timerUpdating = YES;
439         [statusItem setEnabled:NO];
440                 
441         NS_DURING
442             latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
443                         
444                         if ([menuController rebuildSubmenus]) {
445                                 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
446                                         [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
447                                 }
448                                 [self setLatestSongIdentifier:identifier];
449                                 //Create the tooltip for the status item
450                                 if ( [df boolForKey:@"showToolTip"] ) {
451                                         NSString *artist = [[self currentRemote] currentSongArtist];
452                                         NSString *title = [[self currentRemote] currentSongTitle];
453                                         ITDebugLog(@"Creating status item tooltip.");
454                                         if (artist) {
455                                                 _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
456                                         } else if (title) {
457                                                 _toolTip = title;
458                                         } else {
459                                                 _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
460                                         }
461                                         [statusItem setToolTip:_toolTip];
462                                 } else {
463                                         [statusItem setToolTip:nil];
464                                 }
465                         }
466                         
467                         if ([df boolForKey:@"audioscrobblerEnabled"]) {
468                                 int length = [[self currentRemote] currentSongDuration];
469                                 if (_audioscrobblerTimer) {
470                                         [_audioscrobblerTimer invalidate];
471                                 }
472                                 if (length > 0) {
473                                         _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:((length < 240) ? length / 2 : 120) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:NO];
474                                 }
475                         } else {
476                                 _audioscrobblerTimer = nil;
477                         }
478         NS_HANDLER
479             [self networkError:localException];
480         NS_ENDHANDLER
481         timerUpdating = NO;
482         [statusItem setEnabled:YES];
483     }
484         
485     if ([networkController isConnectedToServer]) {
486         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
487     }
488 }
489
490 - (void)menuClicked
491 {
492     ITDebugLog(@"Menu clicked.");
493         
494         if (([[self currentRemote] playerStateUniqueIdentifier] == nil) && playerRunningState == ITMTRemotePlayerRunning) {
495                 if (refreshTimer) {
496                         if ([statusItem isEnabled]) {
497                                 [statusItem setToolTip:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.")];
498                         }
499                         [statusItem setEnabled:NO];
500                 } else {
501                         NSMenu *menu = [[NSMenu alloc] init];
502                         [menu addItemWithTitle:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.") action:nil keyEquivalent:@""];
503                         [statusItem setMenu:[menu autorelease]];
504                 }
505                 return;
506         } else if (![statusItem isEnabled]) {
507                 [statusItem setEnabled:YES];
508                 [statusItem setToolTip:_toolTip];
509                 return;
510         }
511         
512     if ([networkController isConnectedToServer]) {
513         //Used the cached version
514         return;
515     }
516     
517     NS_DURING
518         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
519             [statusItem setMenu:[menuController menu]];
520         } else {
521             [statusItem setMenu:[menuController menuForNoPlayer]];
522         }
523     NS_HANDLER
524         [self networkError:localException];
525     NS_ENDHANDLER
526 }
527
528 - (void)trackChanged:(NSNotification *)note
529 {
530         //If we're running the timer, shut it off since we don't need it!
531         /*if (refreshTimer && [refreshTimer isValid]) {
532                 ITDebugLog(@"Invalidating refresh timer.");
533                 [refreshTimer invalidate];
534                 [refreshTimer release];
535                 refreshTimer = nil;
536         }*/
537         
538         if (![self songChanged]) {
539                 return;
540         }
541         NSString *identifier = [[self currentRemote] playerStateUniqueIdentifier];
542         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
543                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
544         }
545         [_lastTrackInfo release];
546         _lastTrackInfo = [[note userInfo] retain];
547         
548         [self setLatestSongIdentifier:identifier];
549         ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier);
550         if ([df boolForKey:@"runScripts"]) {
551                 NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
552                 NSEnumerator *scriptsEnum = [scripts objectEnumerator];
553                 NSString *nextScript;
554                 ITDebugLog(@"Running AppleScripts for song change.");
555                 while ( (nextScript = [scriptsEnum nextObject]) ) {
556                         NSDictionary *error;
557                         NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
558                         ITDebugLog(@"Running script: %@", nextScript);
559                         if (!currentScript || ![currentScript executeAndReturnError:nil]) {
560                                 ITDebugLog(@"Error running script %@.", nextScript);
561                         }
562                         [currentScript release];
563                 }
564         }
565         
566         [statusItem setEnabled:NO];
567         
568         NS_DURING
569                 latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
570                 
571                 if ([menuController rebuildSubmenus]) {
572                         /*if ( [df boolForKey:@"showSongInfoOnChange"] ) {
573                                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
574                         }*/
575                         [self setLatestSongIdentifier:identifier];
576                         //Create the tooltip for the status item
577                         if ( [df boolForKey:@"showToolTip"] ) {
578                                 ITDebugLog(@"Creating status item tooltip.");
579                                 NSString *artist = [_lastTrackInfo objectForKey:@"Artist"], *title = [_lastTrackInfo objectForKey:@"Name"];
580                                 if (artist) {
581                                         _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
582                                 } else if (title) {
583                                         _toolTip = title;
584                                 } else {
585                                         _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");;
586                                 }
587                                 [statusItem setToolTip:_toolTip];
588                         } else {
589                                 [statusItem setToolTip:nil];
590                         }
591                 }
592                 
593                 if ([df boolForKey:@"audioscrobblerEnabled"]) {
594                         int length = [[self currentRemote] currentSongDuration];
595                         if (_audioscrobblerTimer) {
596                                 [_audioscrobblerTimer invalidate];
597                         }
598                         if (length > 0) {
599                                 _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:((length < 240) ? length / 2 : 120) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:NO];
600                         }
601                 } else {
602                         _audioscrobblerTimer = nil;
603                 }
604         NS_HANDLER
605                 [self networkError:localException];
606         NS_ENDHANDLER
607         timerUpdating = NO;
608         [statusItem setEnabled:YES];
609         
610         if ([networkController isConnectedToServer]) {
611         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
612     }
613 }
614
615 - (void)submitAudioscrobblerTrack:(NSTimer *)timer
616 {
617         if ([df boolForKey:@"audioscrobblerEnabled"]) {
618                 NS_DURING
619                         NSString *title = [[self currentRemote] currentSongTitle], *artist = [[self currentRemote] currentSongArtist];
620                         if (title && artist) {
621                                 [[AudioscrobblerController sharedController] submitTrack:title
622                                                                                                                                 artist:artist
623                                                                                                                                 album:[[self currentRemote] currentSongAlbum]
624                                                                                                                                 length:[[self currentRemote] currentSongDuration]];
625                         }
626                 NS_HANDLER
627                         [self networkError:localException];
628                 NS_ENDHANDLER
629                 [timer invalidate];
630                 _audioscrobblerTimer = nil;
631         }
632 }
633
634 //
635 //
636 // Menu Selectors
637 //
638 //
639
640 - (void)playPause
641 {
642     NS_DURING
643         ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState];
644         ITDebugLog(@"Play/Pause toggled");
645         if (state == ITMTRemotePlayerPlaying) {
646             [[self currentRemote] pause];
647         } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
648             [[self currentRemote] pause];
649             [[self currentRemote] play];
650         } else {
651             [[self currentRemote] play];
652         }
653     NS_HANDLER
654         [self networkError:localException];
655     NS_ENDHANDLER
656     
657         if (refreshTimer) {
658                 [self timerUpdate];
659         }
660 }
661
662 - (void)nextSong
663 {
664     ITDebugLog(@"Going to next song.");
665     NS_DURING
666         [[self currentRemote] goToNextSong];
667     NS_HANDLER
668         [self networkError:localException];
669     NS_ENDHANDLER
670     if (refreshTimer) {
671                 [self timerUpdate];
672         }
673 }
674
675 - (void)prevSong
676 {
677     ITDebugLog(@"Going to previous song.");
678     NS_DURING
679         [[self currentRemote] goToPreviousSong];
680     NS_HANDLER
681         [self networkError:localException];
682     NS_ENDHANDLER
683     if (refreshTimer) {
684                 [self timerUpdate];
685         }
686 }
687
688 - (void)fastForward
689 {
690     ITDebugLog(@"Fast forwarding.");
691     NS_DURING
692         [[self currentRemote] forward];
693     NS_HANDLER
694         [self networkError:localException];
695     NS_ENDHANDLER
696     if (refreshTimer) {
697                 [self timerUpdate];
698         }
699 }
700
701 - (void)rewind
702 {
703     ITDebugLog(@"Rewinding.");
704     NS_DURING
705         [[self currentRemote] rewind];
706     NS_HANDLER
707         [self networkError:localException];
708     NS_ENDHANDLER
709     if (refreshTimer) {
710                 [self timerUpdate];
711         }
712 }
713
714 - (void)selectPlaylistAtIndex:(int)index
715 {
716     ITDebugLog(@"Selecting playlist %i", index);
717     NS_DURING
718         [[self currentRemote] switchToPlaylistAtIndex:(index % 1000) ofSourceAtIndex:(index / 1000)];
719         //[[self currentRemote] switchToPlaylistAtIndex:index];
720     NS_HANDLER
721         [self networkError:localException];
722     NS_ENDHANDLER
723     if (refreshTimer) {
724                 [self timerUpdate];
725         }
726 }
727
728 - (void)selectSongAtIndex:(int)index
729 {
730     ITDebugLog(@"Selecting song %i", index);
731     NS_DURING
732         [[self currentRemote] switchToSongAtIndex:index];
733     NS_HANDLER
734         [self networkError:localException];
735     NS_ENDHANDLER
736     if (refreshTimer) {
737                 [self timerUpdate];
738         }
739 }
740
741 - (void)selectSongRating:(int)rating
742 {
743     ITDebugLog(@"Selecting song rating %i", rating);
744     NS_DURING
745         [[self currentRemote] setCurrentSongRating:(float)rating / 100.0];
746     NS_HANDLER
747         [self networkError:localException];
748     NS_ENDHANDLER
749     if (refreshTimer) {
750                 [self timerUpdate];
751         }
752 }
753
754 - (void)selectEQPresetAtIndex:(int)index
755 {
756     ITDebugLog(@"Selecting EQ preset %i", index);
757     NS_DURING
758         if (index == -1) {
759             [[self currentRemote] setEqualizerEnabled:![[self currentRemote] equalizerEnabled]];
760         } else {
761             [[self currentRemote] switchToEQAtIndex:index];
762         }
763     NS_HANDLER
764         [self networkError:localException];
765     NS_ENDHANDLER
766     if (refreshTimer) {
767                 [self timerUpdate];
768         }
769 }
770
771 - (void)makePlaylistWithTerm:(NSString *)term ofType:(int)type
772 {
773     ITDebugLog(@"Making playlist with term %@, type %i", term, type);
774     NS_DURING
775         [[self currentRemote] makePlaylistWithTerm:term ofType:type];
776     NS_HANDLER
777         [self networkError:localException];
778     NS_ENDHANDLER
779     ITDebugLog(@"Done making playlist");
780 }
781
782 - (void)showPlayer
783 {
784     ITDebugLog(@"Beginning show player.");
785     //if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
786         ITDebugLog(@"Showing player interface.");
787         NS_DURING
788             [[self currentRemote] showPrimaryInterface];
789         NS_HANDLER
790             [self networkError:localException];
791         NS_ENDHANDLER
792     /*} else {
793         ITDebugLog(@"Launching player.");
794         NS_DURING
795             NSString *path;
796             if ( (path = [df stringForKey:@"CustomPlayerPath"]) ) {
797             } else {
798                 pathITDebugLog(@"Showing player interface."); = [[self currentRemote] playerFullName];
799             }
800             if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
801                 ITDebugLog(@"Error Launching Player");
802             }
803         NS_HANDLER
804             [self networkError:localException];
805         NS_ENDHANDLER
806     }*/
807     ITDebugLog(@"Finished show player.");
808 }
809
810 - (void)showPreferences
811 {
812     ITDebugLog(@"Show preferences.");
813     [[PreferencesController sharedPrefs] showPrefsWindow:self];
814 }
815
816 - (void)showPreferencesAndClose
817 {
818     ITDebugLog(@"Show preferences.");
819     [[PreferencesController sharedPrefs] showPrefsWindow:self];
820     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
821     [[StatusWindow sharedWindow] vanish:self];
822     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
823 }
824
825 - (void)showTestWindow
826 {
827     [self showCurrentTrackInfo];
828 }
829
830 - (void)quitMenuTunes
831 {
832     ITDebugLog(@"Quitting MenuTunes.");
833     [NSApp terminate:self];
834 }
835
836 //
837 //
838
839 - (MenuController *)menuController
840 {
841     return menuController;
842 }
843
844 - (void)closePreferences
845 {
846     ITDebugLog(@"Preferences closed.");
847     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
848         [self setupHotKeys];
849     }
850 }
851
852 - (ITMTRemote *)currentRemote
853 {
854     if ([networkController isConnectedToServer] && ![[networkController networkObject] isValid]) {
855         [self networkError:nil];
856         return nil;
857     }
858     return currentRemote;
859 }
860
861 //
862 //
863 // Hot key setup
864 //
865 //
866
867 - (void)clearHotKeys
868 {
869     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
870     ITHotKey *nextHotKey;
871     ITDebugLog(@"Clearing hot keys.");
872     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
873         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
874     }
875     ITDebugLog(@"Done clearing hot keys.");
876 }
877
878 - (void)setupHotKeys
879 {
880     ITHotKey *hotKey;
881     ITDebugLog(@"Setting up hot keys.");
882     
883     if (playerRunningState == ITMTRemotePlayerNotRunning && ![[NetworkController sharedController] isConnectedToServer]) {
884         return;
885     }
886     
887     if ([df objectForKey:@"PlayPause"] != nil) {
888         ITDebugLog(@"Setting up play pause hot key.");
889         hotKey = [[ITHotKey alloc] init];
890         [hotKey setName:@"PlayPause"];
891         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
892         [hotKey setTarget:self];
893         [hotKey setAction:@selector(playPause)];
894         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
895     }
896     
897     if ([df objectForKey:@"NextTrack"] != nil) {
898         ITDebugLog(@"Setting up next track hot key.");
899         hotKey = [[ITHotKey alloc] init];
900         [hotKey setName:@"NextTrack"];
901         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
902         [hotKey setTarget:self];
903         [hotKey setAction:@selector(nextSong)];
904         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
905     }
906     
907     if ([df objectForKey:@"PrevTrack"] != nil) {
908         ITDebugLog(@"Setting up previous track hot key.");
909         hotKey = [[ITHotKey alloc] init];
910         [hotKey setName:@"PrevTrack"];
911         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
912         [hotKey setTarget:self];
913         [hotKey setAction:@selector(prevSong)];
914         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
915     }
916     
917     if ([df objectForKey:@"FastForward"] != nil) {
918         ITDebugLog(@"Setting up fast forward hot key.");
919         hotKey = [[ITHotKey alloc] init];
920         [hotKey setName:@"FastForward"];
921         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"FastForward"]]];
922         [hotKey setTarget:self];
923         [hotKey setAction:@selector(fastForward)];
924         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
925     }
926     
927     if ([df objectForKey:@"Rewind"] != nil) {
928         ITDebugLog(@"Setting up rewind hot key.");
929         hotKey = [[ITHotKey alloc] init];
930         [hotKey setName:@"Rewind"];
931         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"Rewind"]]];
932         [hotKey setTarget:self];
933         [hotKey setAction:@selector(rewind)];
934         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
935     }
936     
937     if ([df objectForKey:@"ShowPlayer"] != nil) {
938         ITDebugLog(@"Setting up show player hot key.");
939         hotKey = [[ITHotKey alloc] init];
940         [hotKey setName:@"ShowPlayer"];
941         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
942         [hotKey setTarget:self];
943         [hotKey setAction:@selector(showPlayer)];
944         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
945     }
946     
947     if ([df objectForKey:@"TrackInfo"] != nil) {
948         ITDebugLog(@"Setting up track info hot key.");
949         hotKey = [[ITHotKey alloc] init];
950         [hotKey setName:@"TrackInfo"];
951         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
952         [hotKey setTarget:self];
953         [hotKey setAction:@selector(showCurrentTrackInfoHotKey)];
954         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
955     }
956     
957     if ([df objectForKey:@"UpcomingSongs"] != nil) {
958         ITDebugLog(@"Setting up upcoming songs hot key.");
959         hotKey = [[ITHotKey alloc] init];
960         [hotKey setName:@"UpcomingSongs"];
961         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
962         [hotKey setTarget:self];
963         [hotKey setAction:@selector(showUpcomingSongs)];
964         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
965     }
966     
967     if ([df objectForKey:@"ToggleLoop"] != nil) {
968         ITDebugLog(@"Setting up toggle loop hot key.");
969         hotKey = [[ITHotKey alloc] init];
970         [hotKey setName:@"ToggleLoop"];
971         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
972         [hotKey setTarget:self];
973         [hotKey setAction:@selector(toggleLoop)];
974         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
975     }
976     
977     if ([df objectForKey:@"ToggleShuffle"] != nil) {
978         ITDebugLog(@"Setting up toggle shuffle hot key.");
979         hotKey = [[ITHotKey alloc] init];
980         [hotKey setName:@"ToggleShuffle"];
981         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
982         [hotKey setTarget:self];
983         [hotKey setAction:@selector(toggleShuffle)];
984         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
985     }
986     
987     if ([df objectForKey:@"IncrementVolume"] != nil) {
988         ITDebugLog(@"Setting up increment volume hot key.");
989         hotKey = [[ITHotKey alloc] init];
990         [hotKey setName:@"IncrementVolume"];
991         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
992         [hotKey setTarget:self];
993         [hotKey setAction:@selector(incrementVolume)];
994         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
995     }
996     
997     if ([df objectForKey:@"DecrementVolume"] != nil) {
998         ITDebugLog(@"Setting up decrement volume hot key.");
999         hotKey = [[ITHotKey alloc] init];
1000         [hotKey setName:@"DecrementVolume"];
1001         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
1002         [hotKey setTarget:self];
1003         [hotKey setAction:@selector(decrementVolume)];
1004         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1005     }
1006     
1007     if ([df objectForKey:@"IncrementRating"] != nil) {
1008         ITDebugLog(@"Setting up increment rating hot key.");
1009         hotKey = [[ITHotKey alloc] init];
1010         [hotKey setName:@"IncrementRating"];
1011         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
1012         [hotKey setTarget:self];
1013         [hotKey setAction:@selector(incrementRating)];
1014         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1015     }
1016     
1017     if ([df objectForKey:@"DecrementRating"] != nil) {
1018         ITDebugLog(@"Setting up decrement rating hot key.");
1019         hotKey = [[ITHotKey alloc] init];
1020         [hotKey setName:@"DecrementRating"];
1021         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
1022         [hotKey setTarget:self];
1023         [hotKey setAction:@selector(decrementRating)];
1024         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1025     }
1026     
1027         if ([df objectForKey:@"ToggleShufflability"] != nil) {
1028         ITDebugLog(@"Setting up toggle song shufflability hot key.");
1029         hotKey = [[ITHotKey alloc] init];
1030         [hotKey setName:@"ToggleShufflability"];
1031         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShufflability"]]];
1032         [hotKey setTarget:self];
1033         [hotKey setAction:@selector(toggleSongShufflable)];
1034         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1035     }
1036         
1037     if ([df objectForKey:@"PopupMenu"] != nil) {
1038         ITDebugLog(@"Setting up popup menu hot key.");
1039         hotKey = [[ITHotKey alloc] init];
1040         [hotKey setName:@"PopupMenu"];
1041         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PopupMenu"]]];
1042         [hotKey setTarget:self];
1043         [hotKey setAction:@selector(popupMenu)];
1044         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1045     }
1046     
1047     int i;
1048     for (i = 0; i <= 5; i++) {
1049         NSString *curName = [NSString stringWithFormat:@"SetRating%i", i];
1050         if ([df objectForKey:curName] != nil) {
1051             ITDebugLog(@"Setting up set rating %i hot key.", i);
1052             hotKey = [[ITHotKey alloc] init];
1053             [hotKey setName:curName];
1054             [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:curName]]];
1055             [hotKey setTarget:self];
1056             [hotKey setAction:@selector(setRating:)];
1057             [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1058         }
1059     }
1060     ITDebugLog(@"Finished setting up hot keys.");
1061 }
1062
1063 - (void)showCurrentTrackInfoHotKey
1064 {
1065         //If we're already visible and the setting says so, vanish instead of displaying again.
1066         if ([df boolForKey:@"ToggleTrackInfoWithHotKey"] && [statusWindowController currentStatusWindowType] == StatusWindowTrackInfoType && [[StatusWindow sharedWindow] visibilityState] == ITWindowVisibleState) {
1067                 ITDebugLog(@"Track window is already visible, hiding track window.");
1068                 [self invalidateStatusWindowUpdateTimer];
1069                 [[StatusWindow sharedWindow] vanish:nil];
1070                 return;
1071         }
1072         [self showCurrentTrackInfo];
1073 }
1074
1075 - (void)showCurrentTrackInfo
1076 {
1077     ITMTRemotePlayerSource  source      = 0;
1078     NSString               *title       = nil;
1079     NSString               *album       = nil;
1080     NSString               *artist      = nil;
1081     NSString               *composer    = nil;
1082     NSString               *time        = nil;
1083     NSString               *track       = nil;
1084     NSImage                *art         = nil;
1085     int                     rating      = -1;
1086     int                     playCount   = -1;
1087         
1088     ITDebugLog(@"Showing track info status window.");
1089     
1090     NS_DURING
1091         source      = [[self currentRemote] currentSource];
1092         title       = [[self currentRemote] currentSongTitle];
1093     NS_HANDLER
1094         [self networkError:localException];
1095     NS_ENDHANDLER
1096     
1097     if ( title ) {
1098         if ( [df boolForKey:@"showAlbumArtwork"] ) {
1099                         NSSize oldSize, newSize;
1100                         NS_DURING
1101                                 art = [[self currentRemote] currentSongAlbumArt];
1102                                 oldSize = [art size];
1103                                 if (oldSize.width > oldSize.height) {
1104                                         newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width));
1105                                 }
1106                                 else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110);
1107                                 art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize];
1108                         NS_HANDLER
1109                                 [self networkError:localException];
1110                         NS_ENDHANDLER
1111         }
1112         
1113         if ( [df boolForKey:@"showAlbum"] ) {
1114             NS_DURING
1115                 album = [[self currentRemote] currentSongAlbum];
1116             NS_HANDLER
1117                 [self networkError:localException];
1118             NS_ENDHANDLER
1119         }
1120
1121         if ( [df boolForKey:@"showArtist"] ) {
1122             NS_DURING
1123                 artist = [[self currentRemote] currentSongArtist];
1124             NS_HANDLER
1125                 [self networkError:localException];
1126             NS_ENDHANDLER
1127         }
1128
1129         if ( [df boolForKey:@"showComposer"] ) {
1130             NS_DURING
1131                 composer = [[self currentRemote] currentSongComposer];
1132             NS_HANDLER
1133                 [self networkError:localException];
1134             NS_ENDHANDLER
1135         }
1136
1137         if ( [df boolForKey:@"showTime"] ) {
1138             NS_DURING
1139                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1140                 NSLocalizedString(@"time", @"Time"),
1141                 [[self currentRemote] currentSongElapsed],
1142                 [[self currentRemote] currentSongLength]];
1143             NS_HANDLER
1144                 [self networkError:localException];
1145             NS_ENDHANDLER
1146                         _timeUpdateCount = 0;
1147                         [self invalidateStatusWindowUpdateTimer];
1148                         _statusWindowUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES];
1149         }
1150
1151         if ( [df boolForKey:@"showTrackNumber"] ) {
1152             int trackNo    = 0;
1153             int trackCount = 0;
1154             
1155             NS_DURING
1156                 trackNo    = [[self currentRemote] currentSongTrack];
1157                 trackCount = [[self currentRemote] currentAlbumTrackCount];
1158             NS_HANDLER
1159                 [self networkError:localException];
1160             NS_ENDHANDLER
1161             
1162             if ( (trackNo > 0) || (trackCount > 0) ) {
1163                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
1164                     @"Track", trackNo, @"of", trackCount];
1165             }
1166         }
1167
1168         if ( [df boolForKey:@"showTrackRating"] ) {
1169             float currentRating = 0;
1170             
1171             NS_DURING
1172                 currentRating = [[self currentRemote] currentSongRating];
1173             NS_HANDLER
1174                 [self networkError:localException];
1175             NS_ENDHANDLER
1176             
1177             if (currentRating >= 0.0) {
1178                 rating = ( currentRating * 5 );
1179             }
1180         }
1181         
1182         if ( [df boolForKey:@"showPlayCount"] && ![self radioIsPlaying] && [[self currentRemote] currentSource] == ITMTRemoteLibrarySource ) {
1183             NS_DURING
1184                 playCount = [[self currentRemote] currentSongPlayCount];
1185             NS_HANDLER
1186                 [self networkError:localException];
1187             NS_ENDHANDLER
1188         }
1189     } else {
1190         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
1191     }
1192     ITDebugLog(@"Showing current track info status window.");
1193     [statusWindowController showSongInfoWindowWithSource:source
1194                                                    title:title
1195                                                    album:album
1196                                                   artist:artist
1197                                                 composer:composer
1198                                                     time:time
1199                                                    track:track
1200                                                   rating:rating
1201                                                playCount:playCount
1202                                                    image:art];
1203 }
1204
1205 - (void)updateTime:(NSTimer *)timer
1206 {
1207         StatusWindow *sw = [StatusWindow sharedWindow];
1208         _timeUpdateCount++;
1209         if (_timeUpdateCount < (int)[sw exitDelay] + (int)[[sw exitEffect] effectTime] + (int)[[sw entryEffect] effectTime]) {
1210                 NSString *time = nil, *length;
1211                 NS_DURING
1212                         length = [[self currentRemote] currentSongLength];
1213                         if (length) {
1214                                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1215                                                         NSLocalizedString(@"time", @"Time"),
1216                                                         [[self currentRemote] currentSongElapsed],
1217                                                         length];
1218                                 [[StatusWindowController sharedController] updateTime:time];
1219                         }
1220                 NS_HANDLER
1221                         [self networkError:localException];
1222                 NS_ENDHANDLER
1223         } else {
1224                 [self invalidateStatusWindowUpdateTimer];
1225         }
1226 }
1227
1228 - (void)invalidateStatusWindowUpdateTimer
1229 {
1230         if (_statusWindowUpdateTimer) {
1231                 [_statusWindowUpdateTimer invalidate];
1232                 _statusWindowUpdateTimer = nil;
1233         }
1234 }
1235
1236 - (void)showUpcomingSongs
1237 {
1238     int numSongs = 0;
1239     NS_DURING
1240         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
1241     NS_HANDLER
1242         [self networkError:localException];
1243     NS_ENDHANDLER
1244     
1245         [self invalidateStatusWindowUpdateTimer];
1246         
1247     ITDebugLog(@"Showing upcoming songs status window.");
1248     NS_DURING
1249         if (numSongs > 0) {
1250             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
1251             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance];
1252             int curTrack = [[self currentRemote] currentSongIndex];
1253             int i;
1254     
1255             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance && i <= numSongs; i++) {
1256                 if ([[self currentRemote] songEnabledAtIndex:i]) {
1257                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
1258                 } else {
1259                                         numSongsInAdvance++;
1260                                 }
1261             }
1262             
1263             if ([songList count] == 0) {
1264                 [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")];
1265             }
1266             
1267             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
1268         } else {
1269             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
1270         }
1271     NS_HANDLER
1272         [self networkError:localException];
1273     NS_ENDHANDLER
1274 }
1275
1276 - (void)popupMenu
1277 {
1278     if (!_popped) {
1279         _popped = YES;
1280         [self menuClicked];
1281         NSMenu *menu = [statusItem menu];
1282         [(NSCarbonMenuImpl *)[menu _menuImpl] popUpMenu:menu atLocation:[NSEvent mouseLocation] width:1 forView:nil withSelectedItem:-30 withFont:[NSFont menuFontOfSize:32]];
1283         _popped = NO;
1284     }
1285 }
1286
1287 - (void)incrementVolume
1288 {
1289     NS_DURING
1290         float volume  = [[self currentRemote] volume];
1291         float dispVol = volume;
1292         ITDebugLog(@"Incrementing volume.");
1293         volume  += 0.110;
1294         dispVol += 0.100;
1295         
1296         if (volume > 1.0) {
1297             volume  = 1.0;
1298             dispVol = 1.0;
1299         }
1300     
1301         ITDebugLog(@"Setting volume to %f", volume);
1302         [[self currentRemote] setVolume:volume];
1303     
1304         // Show volume status window
1305                 [self invalidateStatusWindowUpdateTimer];
1306         [statusWindowController showVolumeWindowWithLevel:dispVol];
1307     NS_HANDLER
1308         [self networkError:localException];
1309     NS_ENDHANDLER
1310 }
1311
1312 - (void)decrementVolume
1313 {
1314     NS_DURING
1315         float volume  = [[self currentRemote] volume];
1316         float dispVol = volume;
1317         ITDebugLog(@"Decrementing volume.");
1318         volume  -= 0.090;
1319         dispVol -= 0.100;
1320     
1321         if (volume < 0.0) {
1322             volume  = 0.0;
1323             dispVol = 0.0;
1324         }
1325         
1326         ITDebugLog(@"Setting volume to %f", volume);
1327         [[self currentRemote] setVolume:volume];
1328         
1329         //Show volume status window
1330                 [self invalidateStatusWindowUpdateTimer];
1331         [statusWindowController showVolumeWindowWithLevel:dispVol];
1332     NS_HANDLER
1333         [self networkError:localException];
1334     NS_ENDHANDLER
1335 }
1336
1337 - (void)incrementRating
1338 {
1339     NS_DURING
1340         float rating = [[self currentRemote] currentSongRating];
1341         ITDebugLog(@"Incrementing rating.");
1342         
1343         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1344             ITDebugLog(@"No song playing, rating change aborted.");
1345             return;
1346         }
1347         
1348         rating += 0.2;
1349         if (rating > 1.0) {
1350             rating = 1.0;
1351         }
1352         ITDebugLog(@"Setting rating to %f", rating);
1353         [[self currentRemote] setCurrentSongRating:rating];
1354         
1355         //Show rating status window
1356                 [self invalidateStatusWindowUpdateTimer];
1357         [statusWindowController showRatingWindowWithRating:rating];
1358     NS_HANDLER
1359         [self networkError:localException];
1360     NS_ENDHANDLER
1361 }
1362
1363 - (void)decrementRating
1364 {
1365     NS_DURING
1366         float rating = [[self currentRemote] currentSongRating];
1367         ITDebugLog(@"Decrementing rating.");
1368         
1369         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1370             ITDebugLog(@"No song playing, rating change aborted.");
1371             return;
1372         }
1373         
1374         rating -= 0.2;
1375         if (rating < 0.0) {
1376             rating = 0.0;
1377         }
1378         ITDebugLog(@"Setting rating to %f", rating);
1379         [[self currentRemote] setCurrentSongRating:rating];
1380         
1381         //Show rating status window
1382                 [self invalidateStatusWindowUpdateTimer];
1383         [statusWindowController showRatingWindowWithRating:rating];
1384     NS_HANDLER
1385         [self networkError:localException];
1386     NS_ENDHANDLER
1387 }
1388
1389 - (void)setRating:(ITHotKey *)sender
1390 {
1391         if ([self songIsPlaying]) {
1392                 int stars = [[sender name] characterAtIndex:9] - 48;
1393                 [self selectSongRating:stars * 20];
1394                 [statusWindowController showRatingWindowWithRating:(float)stars / 5.0];
1395         }
1396 }
1397
1398 - (void)toggleLoop
1399 {
1400     NS_DURING
1401         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
1402         ITDebugLog(@"Toggling repeat mode.");
1403         switch (repeatMode) {
1404             case ITMTRemotePlayerRepeatOff:
1405                 repeatMode = ITMTRemotePlayerRepeatAll;
1406             break;
1407             case ITMTRemotePlayerRepeatAll:
1408                 repeatMode = ITMTRemotePlayerRepeatOne;
1409             break;
1410             case ITMTRemotePlayerRepeatOne:
1411                 repeatMode = ITMTRemotePlayerRepeatOff;
1412             break;
1413         }
1414         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
1415         [[self currentRemote] setRepeatMode:repeatMode];
1416         
1417         //Show loop status window
1418                 [self invalidateStatusWindowUpdateTimer];
1419         [statusWindowController showRepeatWindowWithMode:repeatMode];
1420     NS_HANDLER
1421         [self networkError:localException];
1422     NS_ENDHANDLER
1423 }
1424
1425 - (void)toggleShuffle
1426 {
1427     NS_DURING
1428         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
1429         ITDebugLog(@"Toggling shuffle mode.");
1430         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
1431         //Show shuffle status window
1432         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
1433                 [self invalidateStatusWindowUpdateTimer];
1434         [statusWindowController showShuffleWindow:newShuffleEnabled];
1435     NS_HANDLER
1436         [self networkError:localException];
1437     NS_ENDHANDLER
1438 }
1439
1440 - (void)toggleSongShufflable
1441 {
1442         if ([self songIsPlaying]) {
1443                 NS_DURING
1444                         BOOL flag = ![[self currentRemote] currentSongShufflable];
1445                         ITDebugLog(@"Toggling shufflability.");
1446                         [[self currentRemote] setCurrentSongShufflable:flag];
1447                         //Show song shufflability status window
1448                         [self invalidateStatusWindowUpdateTimer];
1449                         [statusWindowController showSongShufflabilityWindow:flag];
1450                 NS_HANDLER
1451                         [self networkError:localException];
1452                 NS_ENDHANDLER
1453         }
1454 }
1455
1456 - (void)registerNowOK
1457 {
1458     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1459     [[StatusWindow sharedWindow] vanish:self];
1460     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1461
1462     [self blingNow];
1463 }
1464
1465 - (void)registerNowCancel
1466 {
1467     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1468     [[StatusWindow sharedWindow] vanish:self];
1469     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1470
1471     [NSApp terminate:self];
1472 }
1473
1474 /*************************************************************************/
1475 #pragma mark -
1476 #pragma mark NETWORK HANDLERS
1477 /*************************************************************************/
1478
1479 - (void)setServerStatus:(BOOL)newStatus
1480 {
1481     if (newStatus) {
1482         //Turn on
1483         [networkController setServerStatus:YES];
1484     } else {
1485         //Tear down
1486         [networkController setServerStatus:NO];
1487     }
1488 }
1489
1490 - (int)connectToServer
1491 {
1492     int result;
1493     ITDebugLog(@"Attempting to connect to shared remote.");
1494     result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]];
1495     //Connect
1496     if (result == 1) {
1497         [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1498         currentRemote = [[[networkController networkObject] remote] retain];
1499         
1500         [self setupHotKeys];
1501         //playerRunningState = ITMTRemotePlayerRunning;
1502         playerRunningState = [[self currentRemote] playerRunningState];
1503                 if (_needsPolling) {
1504                         if (refreshTimer) {
1505                                 [refreshTimer invalidate];
1506                         }
1507                         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1508                                                                          target:self
1509                                                                          selector:@selector(timerUpdate)
1510                                                                          userInfo:nil
1511                                                                          repeats:YES] retain];
1512                 }
1513         [self timerUpdate];
1514         ITDebugLog(@"Connection successful.");
1515         return 1;
1516     } else if (result == 0) {
1517         ITDebugLog(@"Connection failed.");
1518         currentRemote = [remoteArray objectAtIndex:0];
1519         return 0;
1520     } else {
1521         //Do something about the password being invalid
1522         ITDebugLog(@"Connection failed.");
1523         currentRemote = [remoteArray objectAtIndex:0];
1524         return -1;
1525     }
1526 }
1527
1528 - (BOOL)disconnectFromServer
1529 {
1530     ITDebugLog(@"Disconnecting from shared remote.");
1531     //Disconnect
1532     [currentRemote release];
1533     currentRemote = [remoteArray objectAtIndex:0];
1534     [networkController disconnect];
1535     
1536     if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
1537         [self applicationLaunched:nil];
1538     } else {
1539         [self applicationTerminated:nil];
1540     }
1541     if (refreshTimer) {
1542                 [self timerUpdate];
1543         };
1544     return YES;
1545 }
1546
1547 - (void)checkForRemoteServer
1548 {
1549     [self checkForRemoteServerAndConnectImmediately:NO];
1550 }
1551
1552 - (void)checkForRemoteServerAndConnectImmediately:(BOOL)connectImmediately
1553 {
1554     ITDebugLog(@"Checking for remote server.");
1555     if (!_checkingForServer) {
1556         if (!_serverCheckLock) {
1557             _serverCheckLock = [[NSLock alloc] init];
1558         }
1559         [_serverCheckLock lock];
1560         _checkingForServer = YES;
1561         [_serverCheckLock unlock];
1562         [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:[NSNumber numberWithBool:connectImmediately]];
1563     }
1564 }
1565
1566 - (void)runRemoteServerCheck:(id)sender
1567 {
1568     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1569     ITDebugLog(@"Remote server check running.");
1570     if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) {
1571         ITDebugLog(@"Remote server found.");
1572         if ([sender boolValue]) {
1573             [self performSelectorOnMainThread:@selector(connectToServer) withObject:nil waitUntilDone:NO];
1574         } else {
1575             [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO];
1576         }
1577     } else {
1578         ITDebugLog(@"Remote server not found.");
1579         [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO];
1580     }
1581     [_serverCheckLock lock];
1582     _checkingForServer = NO;
1583     [_serverCheckLock unlock];
1584     [pool release];
1585 }
1586
1587 - (void)remoteServerFound:(id)sender
1588 {
1589     if (![networkController isServerOn] && ![networkController isConnectedToServer]) {
1590                 [self invalidateStatusWindowUpdateTimer];
1591         [[StatusWindowController sharedController] showReconnectQueryWindow];
1592     }
1593 }
1594
1595 - (void)remoteServerNotFound:(id)sender
1596 {
1597     if (![[NetworkController sharedController] isConnectedToServer]) {
1598         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1599     }
1600 }
1601
1602 - (void)networkError:(NSException *)exception
1603 {
1604     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
1605     if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) {
1606         //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);
1607                 [self invalidateStatusWindowUpdateTimer];
1608         [[StatusWindowController sharedController] showNetworkErrorQueryWindow];
1609         if ([self disconnectFromServer]) {
1610             [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1611             [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1612         } else {
1613             ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!");
1614         }
1615     }
1616 }
1617
1618 - (void)reconnect
1619 {
1620     /*if ([self connectToServer] == 0) {
1621         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1622     }*/
1623     [self checkForRemoteServerAndConnectImmediately:YES];
1624     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1625     [[StatusWindow sharedWindow] vanish:self];
1626     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1627 }
1628
1629 - (void)cancelReconnect
1630 {
1631     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1632     [[StatusWindow sharedWindow] vanish:self];
1633     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1634 }
1635
1636 /*************************************************************************/
1637 #pragma mark -
1638 #pragma mark WORKSPACE NOTIFICATION HANDLERS
1639 /*************************************************************************/
1640
1641 - (void)applicationLaunched:(NSNotification *)note
1642 {
1643     NS_DURING
1644         if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) {
1645             ITDebugLog(@"Remote application launched.");
1646             playerRunningState = ITMTRemotePlayerRunning;
1647             [[self currentRemote] begin];
1648             [self setLatestSongIdentifier:@""];
1649             [self timerUpdate];
1650                         if (_needsPolling) {
1651                                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1652                                                                         target:self
1653                                                                         selector:@selector(timerUpdate)
1654                                                                         userInfo:nil
1655                                                                         repeats:YES] retain];
1656                         }
1657             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
1658                         if (![df boolForKey:@"UsePollingOnly"]) {
1659                                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackChanged:) name:@"ITMTTrackChanged" object:nil];
1660                         }
1661             [self setupHotKeys];
1662         }
1663     NS_HANDLER
1664         [self networkError:localException];
1665     NS_ENDHANDLER
1666 }
1667
1668  - (void)applicationTerminated:(NSNotification *)note
1669  {
1670     NS_DURING
1671         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) {
1672             ITDebugLog(@"Remote application terminated.");
1673             playerRunningState = ITMTRemotePlayerNotRunning;
1674             [[self currentRemote] halt];
1675             [refreshTimer invalidate];
1676             [refreshTimer release];
1677             refreshTimer = nil;
1678                         [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ITMTTrackChanged" object:nil];
1679                         [statusItem setEnabled:YES];
1680                         [statusItem setToolTip:@"iTunes not running."];
1681             [self clearHotKeys];
1682
1683                         
1684             if ([df objectForKey:@"ShowPlayer"] != nil) {
1685                 ITHotKey *hotKey;
1686                 ITDebugLog(@"Setting up show player hot key.");
1687                 hotKey = [[ITHotKey alloc] init];
1688                 [hotKey setName:@"ShowPlayer"];
1689                 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
1690                 [hotKey setTarget:self];
1691                 [hotKey setAction:@selector(showPlayer)];
1692                 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1693             }
1694         }
1695     NS_HANDLER
1696         [self networkError:localException];
1697     NS_ENDHANDLER
1698  }
1699
1700
1701 /*************************************************************************/
1702 #pragma mark -
1703 #pragma mark NSApplication DELEGATE METHODS
1704 /*************************************************************************/
1705
1706 - (void)applicationWillTerminate:(NSNotification *)note
1707 {
1708         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
1709     [networkController stopRemoteServerSearch];
1710     [self clearHotKeys];
1711     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1712 }
1713
1714 - (void)applicationDidBecomeActive:(NSNotification *)note
1715 {
1716         //This appears to not work in 10.4
1717         if (_open && !blinged && ![[ITAboutWindowController sharedController] isVisible] && ![NSApp mainWindow] && ([[StatusWindow sharedWindow] exitMode] == ITTransientStatusWindowExitAfterDelay)) {
1718                 [[MainController sharedController] showPreferences];
1719         }
1720 }
1721
1722 /*************************************************************************/
1723 #pragma mark -
1724 #pragma mark DEALLOCATION METHOD
1725 /*************************************************************************/
1726
1727 - (void)dealloc
1728 {
1729     [self applicationTerminated:nil];
1730     [bling release];
1731     [statusItem release];
1732     [statusWindowController release];
1733     [menuController release];
1734     [networkController release];
1735     [_serverCheckLock release];
1736     [super dealloc];
1737 }
1738
1739 @end