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