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