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