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