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