Added a currentSongPlayed method that returns the integer seconds played in the curre...
[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 > 30) {
473                                         _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:((length < 240) ? length / 2 : 120) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:YES];
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 > 30) {
599                                 _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:((length < 240) ? length / 2 : 120) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:YES];
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         int interval = [timer timeInterval];
618         [timer invalidate];
619         _audioscrobblerTimer = nil;
620         ITDebugLog(@"Audioscrobbler: Attempting to submit current track");
621         if ([df boolForKey:@"audioscrobblerEnabled"]) {
622                 NS_DURING
623                         int elapsed = [[self currentRemote] currentSongPlayed];
624                         if ((abs(elapsed - interval) < 5) && ([[self currentRemote] playerPlayingState] == ITMTRemotePlayerPlaying)) {
625                                 NSString *title = [[self currentRemote] currentSongTitle], *artist = [[self currentRemote] currentSongArtist];
626                                 if (title && artist) {
627                                         ITDebugLog(@"Audioscrobbler: Submitting current track");
628                                         [[AudioscrobblerController sharedController] submitTrack:title
629                                                                                                                                         artist:artist
630                                                                                                                                         album:[[self currentRemote] currentSongAlbum]
631                                                                                                                                         length:[[self currentRemote] currentSongDuration]];
632                                 }
633                         } else if (interval - elapsed > 0) {
634                                 ITDebugLog(@"Audioscrobbler: Creating a new timer that will run in %i seconds", interval - elapsed);
635                                 _audioscrobblerTimer = [NSTimer scheduledTimerWithTimeInterval:(interval - elapsed) target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:YES];
636                         }
637                 NS_HANDLER
638                         [self networkError:localException];
639                 NS_ENDHANDLER
640         }
641 }
642
643 //
644 //
645 // Menu Selectors
646 //
647 //
648
649 - (void)playPause
650 {
651     NS_DURING
652         ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState];
653         ITDebugLog(@"Play/Pause toggled");
654         if (state == ITMTRemotePlayerPlaying) {
655             [[self currentRemote] pause];
656         } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
657             [[self currentRemote] pause];
658             [[self currentRemote] play];
659         } else {
660             [[self currentRemote] play];
661         }
662     NS_HANDLER
663         [self networkError:localException];
664     NS_ENDHANDLER
665     
666         if (refreshTimer) {
667                 [self timerUpdate];
668         }
669 }
670
671 - (void)nextSong
672 {
673     ITDebugLog(@"Going to next song.");
674     NS_DURING
675         [[self currentRemote] goToNextSong];
676     NS_HANDLER
677         [self networkError:localException];
678     NS_ENDHANDLER
679     if (refreshTimer) {
680                 [self timerUpdate];
681         }
682 }
683
684 - (void)prevSong
685 {
686     ITDebugLog(@"Going to previous song.");
687     NS_DURING
688         [[self currentRemote] goToPreviousSong];
689     NS_HANDLER
690         [self networkError:localException];
691     NS_ENDHANDLER
692     if (refreshTimer) {
693                 [self timerUpdate];
694         }
695 }
696
697 - (void)fastForward
698 {
699     ITDebugLog(@"Fast forwarding.");
700     NS_DURING
701         [[self currentRemote] forward];
702     NS_HANDLER
703         [self networkError:localException];
704     NS_ENDHANDLER
705     if (refreshTimer) {
706                 [self timerUpdate];
707         }
708 }
709
710 - (void)rewind
711 {
712     ITDebugLog(@"Rewinding.");
713     NS_DURING
714         [[self currentRemote] rewind];
715     NS_HANDLER
716         [self networkError:localException];
717     NS_ENDHANDLER
718     if (refreshTimer) {
719                 [self timerUpdate];
720         }
721 }
722
723 - (void)selectPlaylistAtIndex:(int)index
724 {
725     ITDebugLog(@"Selecting playlist %i", index);
726     NS_DURING
727         [[self currentRemote] switchToPlaylistAtIndex:(index % 1000) ofSourceAtIndex:(index / 1000)];
728         //[[self currentRemote] switchToPlaylistAtIndex:index];
729     NS_HANDLER
730         [self networkError:localException];
731     NS_ENDHANDLER
732     if (refreshTimer) {
733                 [self timerUpdate];
734         }
735 }
736
737 - (void)selectSongAtIndex:(int)index
738 {
739     ITDebugLog(@"Selecting song %i", index);
740     NS_DURING
741         [[self currentRemote] switchToSongAtIndex:index];
742     NS_HANDLER
743         [self networkError:localException];
744     NS_ENDHANDLER
745     if (refreshTimer) {
746                 [self timerUpdate];
747         }
748 }
749
750 - (void)selectSongRating:(int)rating
751 {
752     ITDebugLog(@"Selecting song rating %i", rating);
753     NS_DURING
754         [[self currentRemote] setCurrentSongRating:(float)rating / 100.0];
755     NS_HANDLER
756         [self networkError:localException];
757     NS_ENDHANDLER
758     if (refreshTimer) {
759                 [self timerUpdate];
760         }
761 }
762
763 - (void)selectEQPresetAtIndex:(int)index
764 {
765     ITDebugLog(@"Selecting EQ preset %i", index);
766     NS_DURING
767         if (index == -1) {
768             [[self currentRemote] setEqualizerEnabled:![[self currentRemote] equalizerEnabled]];
769         } else {
770             [[self currentRemote] switchToEQAtIndex:index];
771         }
772     NS_HANDLER
773         [self networkError:localException];
774     NS_ENDHANDLER
775     if (refreshTimer) {
776                 [self timerUpdate];
777         }
778 }
779
780 - (void)makePlaylistWithTerm:(NSString *)term ofType:(int)type
781 {
782     ITDebugLog(@"Making playlist with term %@, type %i", term, type);
783     NS_DURING
784         [[self currentRemote] makePlaylistWithTerm:term ofType:type];
785     NS_HANDLER
786         [self networkError:localException];
787     NS_ENDHANDLER
788     ITDebugLog(@"Done making playlist");
789 }
790
791 - (void)showPlayer
792 {
793     ITDebugLog(@"Beginning show player.");
794     //if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
795         ITDebugLog(@"Showing player interface.");
796         NS_DURING
797             [[self currentRemote] showPrimaryInterface];
798         NS_HANDLER
799             [self networkError:localException];
800         NS_ENDHANDLER
801     /*} else {
802         ITDebugLog(@"Launching player.");
803         NS_DURING
804             NSString *path;
805             if ( (path = [df stringForKey:@"CustomPlayerPath"]) ) {
806             } else {
807                 pathITDebugLog(@"Showing player interface."); = [[self currentRemote] playerFullName];
808             }
809             if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
810                 ITDebugLog(@"Error Launching Player");
811             }
812         NS_HANDLER
813             [self networkError:localException];
814         NS_ENDHANDLER
815     }*/
816     ITDebugLog(@"Finished show player.");
817 }
818
819 - (void)showPreferences
820 {
821     ITDebugLog(@"Show preferences.");
822     [[PreferencesController sharedPrefs] showPrefsWindow:self];
823 }
824
825 - (void)showPreferencesAndClose
826 {
827     ITDebugLog(@"Show preferences.");
828     [[PreferencesController sharedPrefs] showPrefsWindow:self];
829     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
830     [[StatusWindow sharedWindow] vanish:self];
831     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
832 }
833
834 - (void)showTestWindow
835 {
836     [self showCurrentTrackInfo];
837 }
838
839 - (void)quitMenuTunes
840 {
841     ITDebugLog(@"Quitting MenuTunes.");
842     [NSApp terminate:self];
843 }
844
845 //
846 //
847
848 - (MenuController *)menuController
849 {
850     return menuController;
851 }
852
853 - (void)closePreferences
854 {
855     ITDebugLog(@"Preferences closed.");
856     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
857         [self setupHotKeys];
858     }
859 }
860
861 - (ITMTRemote *)currentRemote
862 {
863     if ([networkController isConnectedToServer] && ![[networkController networkObject] isValid]) {
864         [self networkError:nil];
865         return nil;
866     }
867     return currentRemote;
868 }
869
870 //
871 //
872 // Hot key setup
873 //
874 //
875
876 - (void)clearHotKeys
877 {
878     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
879     ITHotKey *nextHotKey;
880     ITDebugLog(@"Clearing hot keys.");
881     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
882         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
883     }
884     ITDebugLog(@"Done clearing hot keys.");
885 }
886
887 - (void)setupHotKeys
888 {
889     ITHotKey *hotKey;
890     ITDebugLog(@"Setting up hot keys.");
891     
892     if (playerRunningState == ITMTRemotePlayerNotRunning && ![[NetworkController sharedController] isConnectedToServer]) {
893         return;
894     }
895     
896     if ([df objectForKey:@"PlayPause"] != nil) {
897         ITDebugLog(@"Setting up play pause hot key.");
898         hotKey = [[ITHotKey alloc] init];
899         [hotKey setName:@"PlayPause"];
900         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
901         [hotKey setTarget:self];
902         [hotKey setAction:@selector(playPause)];
903         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
904     }
905     
906     if ([df objectForKey:@"NextTrack"] != nil) {
907         ITDebugLog(@"Setting up next track hot key.");
908         hotKey = [[ITHotKey alloc] init];
909         [hotKey setName:@"NextTrack"];
910         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
911         [hotKey setTarget:self];
912         [hotKey setAction:@selector(nextSong)];
913         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
914     }
915     
916     if ([df objectForKey:@"PrevTrack"] != nil) {
917         ITDebugLog(@"Setting up previous track hot key.");
918         hotKey = [[ITHotKey alloc] init];
919         [hotKey setName:@"PrevTrack"];
920         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
921         [hotKey setTarget:self];
922         [hotKey setAction:@selector(prevSong)];
923         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
924     }
925     
926     if ([df objectForKey:@"FastForward"] != nil) {
927         ITDebugLog(@"Setting up fast forward hot key.");
928         hotKey = [[ITHotKey alloc] init];
929         [hotKey setName:@"FastForward"];
930         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"FastForward"]]];
931         [hotKey setTarget:self];
932         [hotKey setAction:@selector(fastForward)];
933         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
934     }
935     
936     if ([df objectForKey:@"Rewind"] != nil) {
937         ITDebugLog(@"Setting up rewind hot key.");
938         hotKey = [[ITHotKey alloc] init];
939         [hotKey setName:@"Rewind"];
940         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"Rewind"]]];
941         [hotKey setTarget:self];
942         [hotKey setAction:@selector(rewind)];
943         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
944     }
945     
946     if ([df objectForKey:@"ShowPlayer"] != nil) {
947         ITDebugLog(@"Setting up show player hot key.");
948         hotKey = [[ITHotKey alloc] init];
949         [hotKey setName:@"ShowPlayer"];
950         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
951         [hotKey setTarget:self];
952         [hotKey setAction:@selector(showPlayer)];
953         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
954     }
955     
956     if ([df objectForKey:@"TrackInfo"] != nil) {
957         ITDebugLog(@"Setting up track info hot key.");
958         hotKey = [[ITHotKey alloc] init];
959         [hotKey setName:@"TrackInfo"];
960         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
961         [hotKey setTarget:self];
962         [hotKey setAction:@selector(showCurrentTrackInfoHotKey)];
963         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
964     }
965     
966     if ([df objectForKey:@"UpcomingSongs"] != nil) {
967         ITDebugLog(@"Setting up upcoming songs hot key.");
968         hotKey = [[ITHotKey alloc] init];
969         [hotKey setName:@"UpcomingSongs"];
970         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
971         [hotKey setTarget:self];
972         [hotKey setAction:@selector(showUpcomingSongs)];
973         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
974     }
975     
976     if ([df objectForKey:@"ToggleLoop"] != nil) {
977         ITDebugLog(@"Setting up toggle loop hot key.");
978         hotKey = [[ITHotKey alloc] init];
979         [hotKey setName:@"ToggleLoop"];
980         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
981         [hotKey setTarget:self];
982         [hotKey setAction:@selector(toggleLoop)];
983         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
984     }
985     
986     if ([df objectForKey:@"ToggleShuffle"] != nil) {
987         ITDebugLog(@"Setting up toggle shuffle hot key.");
988         hotKey = [[ITHotKey alloc] init];
989         [hotKey setName:@"ToggleShuffle"];
990         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
991         [hotKey setTarget:self];
992         [hotKey setAction:@selector(toggleShuffle)];
993         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
994     }
995     
996     if ([df objectForKey:@"IncrementVolume"] != nil) {
997         ITDebugLog(@"Setting up increment volume hot key.");
998         hotKey = [[ITHotKey alloc] init];
999         [hotKey setName:@"IncrementVolume"];
1000         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
1001         [hotKey setTarget:self];
1002         [hotKey setAction:@selector(incrementVolume)];
1003         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1004     }
1005     
1006     if ([df objectForKey:@"DecrementVolume"] != nil) {
1007         ITDebugLog(@"Setting up decrement volume hot key.");
1008         hotKey = [[ITHotKey alloc] init];
1009         [hotKey setName:@"DecrementVolume"];
1010         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
1011         [hotKey setTarget:self];
1012         [hotKey setAction:@selector(decrementVolume)];
1013         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1014     }
1015     
1016     if ([df objectForKey:@"IncrementRating"] != nil) {
1017         ITDebugLog(@"Setting up increment rating hot key.");
1018         hotKey = [[ITHotKey alloc] init];
1019         [hotKey setName:@"IncrementRating"];
1020         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
1021         [hotKey setTarget:self];
1022         [hotKey setAction:@selector(incrementRating)];
1023         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1024     }
1025     
1026     if ([df objectForKey:@"DecrementRating"] != nil) {
1027         ITDebugLog(@"Setting up decrement rating hot key.");
1028         hotKey = [[ITHotKey alloc] init];
1029         [hotKey setName:@"DecrementRating"];
1030         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
1031         [hotKey setTarget:self];
1032         [hotKey setAction:@selector(decrementRating)];
1033         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1034     }
1035     
1036         if ([df objectForKey:@"ToggleShufflability"] != nil) {
1037         ITDebugLog(@"Setting up toggle song shufflability hot key.");
1038         hotKey = [[ITHotKey alloc] init];
1039         [hotKey setName:@"ToggleShufflability"];
1040         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShufflability"]]];
1041         [hotKey setTarget:self];
1042         [hotKey setAction:@selector(toggleSongShufflable)];
1043         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1044     }
1045         
1046     if ([df objectForKey:@"PopupMenu"] != nil) {
1047         ITDebugLog(@"Setting up popup menu hot key.");
1048         hotKey = [[ITHotKey alloc] init];
1049         [hotKey setName:@"PopupMenu"];
1050         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PopupMenu"]]];
1051         [hotKey setTarget:self];
1052         [hotKey setAction:@selector(popupMenu)];
1053         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1054     }
1055     
1056     int i;
1057     for (i = 0; i <= 5; i++) {
1058         NSString *curName = [NSString stringWithFormat:@"SetRating%i", i];
1059         if ([df objectForKey:curName] != nil) {
1060             ITDebugLog(@"Setting up set rating %i hot key.", i);
1061             hotKey = [[ITHotKey alloc] init];
1062             [hotKey setName:curName];
1063             [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:curName]]];
1064             [hotKey setTarget:self];
1065             [hotKey setAction:@selector(setRating:)];
1066             [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1067         }
1068     }
1069     ITDebugLog(@"Finished setting up hot keys.");
1070 }
1071
1072 - (void)showCurrentTrackInfoHotKey
1073 {
1074         //If we're already visible and the setting says so, vanish instead of displaying again.
1075         if ([df boolForKey:@"ToggleTrackInfoWithHotKey"] && [statusWindowController currentStatusWindowType] == StatusWindowTrackInfoType && [[StatusWindow sharedWindow] visibilityState] == ITWindowVisibleState) {
1076                 ITDebugLog(@"Track window is already visible, hiding track window.");
1077                 [self invalidateStatusWindowUpdateTimer];
1078                 [[StatusWindow sharedWindow] vanish:nil];
1079                 return;
1080         }
1081         [self showCurrentTrackInfo];
1082 }
1083
1084 - (void)showCurrentTrackInfo
1085 {
1086     ITMTRemotePlayerSource  source      = 0;
1087     NSString               *title       = nil;
1088     NSString               *album       = nil;
1089     NSString               *artist      = nil;
1090     NSString               *composer    = nil;
1091     NSString               *time        = nil;
1092     NSString               *track       = nil;
1093     NSImage                *art         = nil;
1094     int                     rating      = -1;
1095     int                     playCount   = -1;
1096         
1097     ITDebugLog(@"Showing track info status window.");
1098     
1099     NS_DURING
1100         source      = [[self currentRemote] currentSource];
1101         title       = [[self currentRemote] currentSongTitle];
1102     NS_HANDLER
1103         [self networkError:localException];
1104     NS_ENDHANDLER
1105     
1106     if ( title ) {
1107         if ( [df boolForKey:@"showAlbumArtwork"] ) {
1108                         NSSize oldSize, newSize;
1109                         NS_DURING
1110                                 art = [[self currentRemote] currentSongAlbumArt];
1111                                 oldSize = [art size];
1112                                 if (oldSize.width > oldSize.height) {
1113                                         newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width));
1114                                 }
1115                                 else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110);
1116                                 art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize];
1117                         NS_HANDLER
1118                                 [self networkError:localException];
1119                         NS_ENDHANDLER
1120         }
1121         
1122         if ( [df boolForKey:@"showAlbum"] ) {
1123             NS_DURING
1124                 album = [[self currentRemote] currentSongAlbum];
1125             NS_HANDLER
1126                 [self networkError:localException];
1127             NS_ENDHANDLER
1128         }
1129
1130         if ( [df boolForKey:@"showArtist"] ) {
1131             NS_DURING
1132                 artist = [[self currentRemote] currentSongArtist];
1133             NS_HANDLER
1134                 [self networkError:localException];
1135             NS_ENDHANDLER
1136         }
1137
1138         if ( [df boolForKey:@"showComposer"] ) {
1139             NS_DURING
1140                 composer = [[self currentRemote] currentSongComposer];
1141             NS_HANDLER
1142                 [self networkError:localException];
1143             NS_ENDHANDLER
1144         }
1145
1146         if ( [df boolForKey:@"showTime"] ) {
1147             NS_DURING
1148                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1149                 NSLocalizedString(@"time", @"Time"),
1150                 [[self currentRemote] currentSongElapsed],
1151                 [[self currentRemote] currentSongLength]];
1152             NS_HANDLER
1153                 [self networkError:localException];
1154             NS_ENDHANDLER
1155                         _timeUpdateCount = 0;
1156                         [self invalidateStatusWindowUpdateTimer];
1157                         _statusWindowUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES];
1158         }
1159
1160         if ( [df boolForKey:@"showTrackNumber"] ) {
1161             int trackNo    = 0;
1162             int trackCount = 0;
1163             
1164             NS_DURING
1165                 trackNo    = [[self currentRemote] currentSongTrack];
1166                 trackCount = [[self currentRemote] currentAlbumTrackCount];
1167             NS_HANDLER
1168                 [self networkError:localException];
1169             NS_ENDHANDLER
1170             
1171             if ( (trackNo > 0) || (trackCount > 0) ) {
1172                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
1173                     @"Track", trackNo, @"of", trackCount];
1174             }
1175         }
1176
1177         if ( [df boolForKey:@"showTrackRating"] ) {
1178             float currentRating = 0;
1179             
1180             NS_DURING
1181                 currentRating = [[self currentRemote] currentSongRating];
1182             NS_HANDLER
1183                 [self networkError:localException];
1184             NS_ENDHANDLER
1185             
1186             if (currentRating >= 0.0) {
1187                 rating = ( currentRating * 5 );
1188             }
1189         }
1190         
1191         if ( [df boolForKey:@"showPlayCount"] && ![self radioIsPlaying] && [[self currentRemote] currentSource] == ITMTRemoteLibrarySource ) {
1192             NS_DURING
1193                 playCount = [[self currentRemote] currentSongPlayCount];
1194             NS_HANDLER
1195                 [self networkError:localException];
1196             NS_ENDHANDLER
1197         }
1198     } else {
1199         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
1200     }
1201     ITDebugLog(@"Showing current track info status window.");
1202     [statusWindowController showSongInfoWindowWithSource:source
1203                                                    title:title
1204                                                    album:album
1205                                                   artist:artist
1206                                                 composer:composer
1207                                                     time:time
1208                                                    track:track
1209                                                   rating:rating
1210                                                playCount:playCount
1211                                                    image:art];
1212 }
1213
1214 - (void)updateTime:(NSTimer *)timer
1215 {
1216         StatusWindow *sw = [StatusWindow sharedWindow];
1217         _timeUpdateCount++;
1218         if (_timeUpdateCount < (int)[sw exitDelay] + (int)[[sw exitEffect] effectTime] + (int)[[sw entryEffect] effectTime]) {
1219                 NSString *time = nil, *length;
1220                 NS_DURING
1221                         length = [[self currentRemote] currentSongLength];
1222                         if (length) {
1223                                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1224                                                         NSLocalizedString(@"time", @"Time"),
1225                                                         [[self currentRemote] currentSongElapsed],
1226                                                         length];
1227                                 [[StatusWindowController sharedController] updateTime:time];
1228                         }
1229                 NS_HANDLER
1230                         [self networkError:localException];
1231                 NS_ENDHANDLER
1232         } else {
1233                 [self invalidateStatusWindowUpdateTimer];
1234         }
1235 }
1236
1237 - (void)invalidateStatusWindowUpdateTimer
1238 {
1239         if (_statusWindowUpdateTimer) {
1240                 [_statusWindowUpdateTimer invalidate];
1241                 _statusWindowUpdateTimer = nil;
1242         }
1243 }
1244
1245 - (void)showUpcomingSongs
1246 {
1247     int numSongs = 0;
1248     NS_DURING
1249         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
1250     NS_HANDLER
1251         [self networkError:localException];
1252     NS_ENDHANDLER
1253     
1254         [self invalidateStatusWindowUpdateTimer];
1255         
1256     ITDebugLog(@"Showing upcoming songs status window.");
1257     NS_DURING
1258         if (numSongs > 0) {
1259             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
1260             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance];
1261             int curTrack = [[self currentRemote] currentSongIndex];
1262             int i;
1263     
1264             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance && i <= numSongs; i++) {
1265                 if ([[self currentRemote] songEnabledAtIndex:i]) {
1266                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
1267                 } else {
1268                                         numSongsInAdvance++;
1269                                 }
1270             }
1271             
1272             if ([songList count] == 0) {
1273                 [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")];
1274             }
1275             
1276             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
1277         } else {
1278             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
1279         }
1280     NS_HANDLER
1281         [self networkError:localException];
1282     NS_ENDHANDLER
1283 }
1284
1285 - (void)popupMenu
1286 {
1287     if (!_popped) {
1288         _popped = YES;
1289         [self menuClicked];
1290         NSMenu *menu = [statusItem menu];
1291         [(NSCarbonMenuImpl *)[menu _menuImpl] popUpMenu:menu atLocation:[NSEvent mouseLocation] width:1 forView:nil withSelectedItem:-30 withFont:[NSFont menuFontOfSize:32]];
1292         _popped = NO;
1293     }
1294 }
1295
1296 - (void)incrementVolume
1297 {
1298     NS_DURING
1299         float volume  = [[self currentRemote] volume];
1300         float dispVol = volume;
1301         ITDebugLog(@"Incrementing volume.");
1302         volume  += 0.110;
1303         dispVol += 0.100;
1304         
1305         if (volume > 1.0) {
1306             volume  = 1.0;
1307             dispVol = 1.0;
1308         }
1309     
1310         ITDebugLog(@"Setting volume to %f", volume);
1311         [[self currentRemote] setVolume:volume];
1312     
1313         // Show volume status window
1314                 [self invalidateStatusWindowUpdateTimer];
1315         [statusWindowController showVolumeWindowWithLevel:dispVol];
1316     NS_HANDLER
1317         [self networkError:localException];
1318     NS_ENDHANDLER
1319 }
1320
1321 - (void)decrementVolume
1322 {
1323     NS_DURING
1324         float volume  = [[self currentRemote] volume];
1325         float dispVol = volume;
1326         ITDebugLog(@"Decrementing volume.");
1327         volume  -= 0.090;
1328         dispVol -= 0.100;
1329     
1330         if (volume < 0.0) {
1331             volume  = 0.0;
1332             dispVol = 0.0;
1333         }
1334         
1335         ITDebugLog(@"Setting volume to %f", volume);
1336         [[self currentRemote] setVolume:volume];
1337         
1338         //Show volume status window
1339                 [self invalidateStatusWindowUpdateTimer];
1340         [statusWindowController showVolumeWindowWithLevel:dispVol];
1341     NS_HANDLER
1342         [self networkError:localException];
1343     NS_ENDHANDLER
1344 }
1345
1346 - (void)incrementRating
1347 {
1348     NS_DURING
1349         float rating = [[self currentRemote] currentSongRating];
1350         ITDebugLog(@"Incrementing rating.");
1351         
1352         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1353             ITDebugLog(@"No song playing, rating change aborted.");
1354             return;
1355         }
1356         
1357         rating += 0.2;
1358         if (rating > 1.0) {
1359             rating = 1.0;
1360         }
1361         ITDebugLog(@"Setting rating to %f", rating);
1362         [[self currentRemote] setCurrentSongRating:rating];
1363         
1364         //Show rating status window
1365                 [self invalidateStatusWindowUpdateTimer];
1366         [statusWindowController showRatingWindowWithRating:rating];
1367     NS_HANDLER
1368         [self networkError:localException];
1369     NS_ENDHANDLER
1370 }
1371
1372 - (void)decrementRating
1373 {
1374     NS_DURING
1375         float rating = [[self currentRemote] currentSongRating];
1376         ITDebugLog(@"Decrementing rating.");
1377         
1378         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1379             ITDebugLog(@"No song playing, rating change aborted.");
1380             return;
1381         }
1382         
1383         rating -= 0.2;
1384         if (rating < 0.0) {
1385             rating = 0.0;
1386         }
1387         ITDebugLog(@"Setting rating to %f", rating);
1388         [[self currentRemote] setCurrentSongRating:rating];
1389         
1390         //Show rating status window
1391                 [self invalidateStatusWindowUpdateTimer];
1392         [statusWindowController showRatingWindowWithRating:rating];
1393     NS_HANDLER
1394         [self networkError:localException];
1395     NS_ENDHANDLER
1396 }
1397
1398 - (void)setRating:(ITHotKey *)sender
1399 {
1400         if ([self songIsPlaying]) {
1401                 int stars = [[sender name] characterAtIndex:9] - 48;
1402                 [self selectSongRating:stars * 20];
1403                 [statusWindowController showRatingWindowWithRating:(float)stars / 5.0];
1404         }
1405 }
1406
1407 - (void)toggleLoop
1408 {
1409     NS_DURING
1410         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
1411         ITDebugLog(@"Toggling repeat mode.");
1412         switch (repeatMode) {
1413             case ITMTRemotePlayerRepeatOff:
1414                 repeatMode = ITMTRemotePlayerRepeatAll;
1415             break;
1416             case ITMTRemotePlayerRepeatAll:
1417                 repeatMode = ITMTRemotePlayerRepeatOne;
1418             break;
1419             case ITMTRemotePlayerRepeatOne:
1420                 repeatMode = ITMTRemotePlayerRepeatOff;
1421             break;
1422         }
1423         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
1424         [[self currentRemote] setRepeatMode:repeatMode];
1425         
1426         //Show loop status window
1427                 [self invalidateStatusWindowUpdateTimer];
1428         [statusWindowController showRepeatWindowWithMode:repeatMode];
1429     NS_HANDLER
1430         [self networkError:localException];
1431     NS_ENDHANDLER
1432 }
1433
1434 - (void)toggleShuffle
1435 {
1436     NS_DURING
1437         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
1438         ITDebugLog(@"Toggling shuffle mode.");
1439         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
1440         //Show shuffle status window
1441         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
1442                 [self invalidateStatusWindowUpdateTimer];
1443         [statusWindowController showShuffleWindow:newShuffleEnabled];
1444     NS_HANDLER
1445         [self networkError:localException];
1446     NS_ENDHANDLER
1447 }
1448
1449 - (void)toggleSongShufflable
1450 {
1451         if ([self songIsPlaying]) {
1452                 NS_DURING
1453                         BOOL flag = ![[self currentRemote] currentSongShufflable];
1454                         ITDebugLog(@"Toggling shufflability.");
1455                         [[self currentRemote] setCurrentSongShufflable:flag];
1456                         //Show song shufflability status window
1457                         [self invalidateStatusWindowUpdateTimer];
1458                         [statusWindowController showSongShufflabilityWindow:flag];
1459                 NS_HANDLER
1460                         [self networkError:localException];
1461                 NS_ENDHANDLER
1462         }
1463 }
1464
1465 - (void)registerNowOK
1466 {
1467     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1468     [[StatusWindow sharedWindow] vanish:self];
1469     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1470
1471     [self blingNow];
1472 }
1473
1474 - (void)registerNowCancel
1475 {
1476     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1477     [[StatusWindow sharedWindow] vanish:self];
1478     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1479
1480     [NSApp terminate:self];
1481 }
1482
1483 /*************************************************************************/
1484 #pragma mark -
1485 #pragma mark NETWORK HANDLERS
1486 /*************************************************************************/
1487
1488 - (void)setServerStatus:(BOOL)newStatus
1489 {
1490     if (newStatus) {
1491         //Turn on
1492         [networkController setServerStatus:YES];
1493     } else {
1494         //Tear down
1495         [networkController setServerStatus:NO];
1496     }
1497 }
1498
1499 - (int)connectToServer
1500 {
1501     int result;
1502     ITDebugLog(@"Attempting to connect to shared remote.");
1503     result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]];
1504     //Connect
1505     if (result == 1) {
1506         [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1507         currentRemote = [[[networkController networkObject] remote] retain];
1508         
1509         [self setupHotKeys];
1510         //playerRunningState = ITMTRemotePlayerRunning;
1511         playerRunningState = [[self currentRemote] playerRunningState];
1512                 if (_needsPolling) {
1513                         if (refreshTimer) {
1514                                 [refreshTimer invalidate];
1515                         }
1516                         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1517                                                                          target:self
1518                                                                          selector:@selector(timerUpdate)
1519                                                                          userInfo:nil
1520                                                                          repeats:YES] retain];
1521                 }
1522         [self timerUpdate];
1523         ITDebugLog(@"Connection successful.");
1524         return 1;
1525     } else if (result == 0) {
1526         ITDebugLog(@"Connection failed.");
1527         currentRemote = [remoteArray objectAtIndex:0];
1528         return 0;
1529     } else {
1530         //Do something about the password being invalid
1531         ITDebugLog(@"Connection failed.");
1532         currentRemote = [remoteArray objectAtIndex:0];
1533         return -1;
1534     }
1535 }
1536
1537 - (BOOL)disconnectFromServer
1538 {
1539     ITDebugLog(@"Disconnecting from shared remote.");
1540     //Disconnect
1541     [currentRemote release];
1542     currentRemote = [remoteArray objectAtIndex:0];
1543     [networkController disconnect];
1544     
1545     if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
1546         [self applicationLaunched:nil];
1547     } else {
1548         [self applicationTerminated:nil];
1549     }
1550     if (refreshTimer) {
1551                 [self timerUpdate];
1552         };
1553     return YES;
1554 }
1555
1556 - (void)checkForRemoteServer
1557 {
1558     [self checkForRemoteServerAndConnectImmediately:NO];
1559 }
1560
1561 - (void)checkForRemoteServerAndConnectImmediately:(BOOL)connectImmediately
1562 {
1563     ITDebugLog(@"Checking for remote server.");
1564     if (!_checkingForServer) {
1565         if (!_serverCheckLock) {
1566             _serverCheckLock = [[NSLock alloc] init];
1567         }
1568         [_serverCheckLock lock];
1569         _checkingForServer = YES;
1570         [_serverCheckLock unlock];
1571         [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:[NSNumber numberWithBool:connectImmediately]];
1572     }
1573 }
1574
1575 - (void)runRemoteServerCheck:(id)sender
1576 {
1577     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1578     ITDebugLog(@"Remote server check running.");
1579     if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) {
1580         ITDebugLog(@"Remote server found.");
1581         if ([sender boolValue]) {
1582             [self performSelectorOnMainThread:@selector(connectToServer) withObject:nil waitUntilDone:NO];
1583         } else {
1584             [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO];
1585         }
1586     } else {
1587         ITDebugLog(@"Remote server not found.");
1588         [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO];
1589     }
1590     [_serverCheckLock lock];
1591     _checkingForServer = NO;
1592     [_serverCheckLock unlock];
1593     [pool release];
1594 }
1595
1596 - (void)remoteServerFound:(id)sender
1597 {
1598     if (![networkController isServerOn] && ![networkController isConnectedToServer]) {
1599                 [self invalidateStatusWindowUpdateTimer];
1600         [[StatusWindowController sharedController] showReconnectQueryWindow];
1601     }
1602 }
1603
1604 - (void)remoteServerNotFound:(id)sender
1605 {
1606     if (![[NetworkController sharedController] isConnectedToServer]) {
1607         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1608     }
1609 }
1610
1611 - (void)networkError:(NSException *)exception
1612 {
1613     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
1614     if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) {
1615         //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);
1616                 [self invalidateStatusWindowUpdateTimer];
1617         [[StatusWindowController sharedController] showNetworkErrorQueryWindow];
1618         if ([self disconnectFromServer]) {
1619             [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1620             [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1621         } else {
1622             ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!");
1623         }
1624     }
1625 }
1626
1627 - (void)reconnect
1628 {
1629     /*if ([self connectToServer] == 0) {
1630         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1631     }*/
1632     [self checkForRemoteServerAndConnectImmediately:YES];
1633     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1634     [[StatusWindow sharedWindow] vanish:self];
1635     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1636 }
1637
1638 - (void)cancelReconnect
1639 {
1640     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1641     [[StatusWindow sharedWindow] vanish:self];
1642     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1643 }
1644
1645 /*************************************************************************/
1646 #pragma mark -
1647 #pragma mark WORKSPACE NOTIFICATION HANDLERS
1648 /*************************************************************************/
1649
1650 - (void)applicationLaunched:(NSNotification *)note
1651 {
1652     NS_DURING
1653         if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) {
1654             ITDebugLog(@"Remote application launched.");
1655             playerRunningState = ITMTRemotePlayerRunning;
1656             [[self currentRemote] begin];
1657             [self setLatestSongIdentifier:@""];
1658             [self timerUpdate];
1659                         if (_needsPolling) {
1660                                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1661                                                                         target:self
1662                                                                         selector:@selector(timerUpdate)
1663                                                                         userInfo:nil
1664                                                                         repeats:YES] retain];
1665                         }
1666             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
1667                         if (![df boolForKey:@"UsePollingOnly"]) {
1668                                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackChanged:) name:@"ITMTTrackChanged" object:nil];
1669                         }
1670             [self setupHotKeys];
1671         }
1672     NS_HANDLER
1673         [self networkError:localException];
1674     NS_ENDHANDLER
1675 }
1676
1677  - (void)applicationTerminated:(NSNotification *)note
1678  {
1679     NS_DURING
1680         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) {
1681             ITDebugLog(@"Remote application terminated.");
1682             playerRunningState = ITMTRemotePlayerNotRunning;
1683             [[self currentRemote] halt];
1684             [refreshTimer invalidate];
1685             [refreshTimer release];
1686             refreshTimer = nil;
1687                         [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ITMTTrackChanged" object:nil];
1688                         [statusItem setEnabled:YES];
1689                         [statusItem setToolTip:@"iTunes not running."];
1690             [self clearHotKeys];
1691
1692                         
1693             if ([df objectForKey:@"ShowPlayer"] != nil) {
1694                 ITHotKey *hotKey;
1695                 ITDebugLog(@"Setting up show player hot key.");
1696                 hotKey = [[ITHotKey alloc] init];
1697                 [hotKey setName:@"ShowPlayer"];
1698                 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
1699                 [hotKey setTarget:self];
1700                 [hotKey setAction:@selector(showPlayer)];
1701                 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1702             }
1703         }
1704     NS_HANDLER
1705         [self networkError:localException];
1706     NS_ENDHANDLER
1707  }
1708
1709
1710 /*************************************************************************/
1711 #pragma mark -
1712 #pragma mark NSApplication DELEGATE METHODS
1713 /*************************************************************************/
1714
1715 - (void)applicationWillTerminate:(NSNotification *)note
1716 {
1717         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
1718     [networkController stopRemoteServerSearch];
1719     [self clearHotKeys];
1720     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1721 }
1722
1723 - (void)applicationDidBecomeActive:(NSNotification *)note
1724 {
1725         //This appears to not work in 10.4
1726         if (_open && !blinged && ![[ITAboutWindowController sharedController] isVisible] && ![NSApp mainWindow] && ([[StatusWindow sharedWindow] exitMode] == ITTransientStatusWindowExitAfterDelay)) {
1727                 [[MainController sharedController] showPreferences];
1728         }
1729 }
1730
1731 /*************************************************************************/
1732 #pragma mark -
1733 #pragma mark DEALLOCATION METHOD
1734 /*************************************************************************/
1735
1736 - (void)dealloc
1737 {
1738     [self applicationTerminated:nil];
1739     [bling release];
1740     [statusItem release];
1741     [statusWindowController release];
1742     [menuController release];
1743     [networkController release];
1744     [_serverCheckLock release];
1745     [super dealloc];
1746 }
1747
1748 @end