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