cfc95185a1620ac17439a132486848db370f73fd
[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     ITDebugLog(@"Showing track info status window.");
1032     
1033     NS_DURING
1034         source      = [[self currentRemote] currentSource];
1035         title       = [[self currentRemote] currentSongTitle];
1036     NS_HANDLER
1037         [self networkError:localException];
1038     NS_ENDHANDLER
1039     
1040     if ( title ) {
1041         if ( [df boolForKey:@"showAlbumArtwork"] ) {
1042             NSSize oldSize, newSize;
1043              NS_DURING
1044                  art = [[self currentRemote] currentSongAlbumArt];
1045                  oldSize = [art size];
1046                  if (oldSize.width > oldSize.height) newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width));
1047                  else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110);
1048                 art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize];
1049             NS_HANDLER
1050                 [self networkError:localException];
1051             NS_ENDHANDLER
1052         }
1053         
1054         if ( [df boolForKey:@"showAlbum"] ) {
1055             NS_DURING
1056                 album = [[self currentRemote] currentSongAlbum];
1057             NS_HANDLER
1058                 [self networkError:localException];
1059             NS_ENDHANDLER
1060         }
1061
1062         if ( [df boolForKey:@"showArtist"] ) {
1063             NS_DURING
1064                 artist = [[self currentRemote] currentSongArtist];
1065             NS_HANDLER
1066                 [self networkError:localException];
1067             NS_ENDHANDLER
1068         }
1069
1070         if ( [df boolForKey:@"showComposer"] ) {
1071             NS_DURING
1072                 composer = [[self currentRemote] currentSongComposer];
1073             NS_HANDLER
1074                 [self networkError:localException];
1075             NS_ENDHANDLER
1076         }
1077
1078         if ( [df boolForKey:@"showTime"] ) {
1079             NS_DURING
1080                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1081                 NSLocalizedString(@"time", @"Time"),
1082                 [[self currentRemote] currentSongElapsed],
1083                 [[self currentRemote] currentSongLength]];
1084             NS_HANDLER
1085                 [self networkError:localException];
1086             NS_ENDHANDLER
1087                         _timeUpdateCount = 0;
1088                         [self invalidateStatusWindowUpdateTimer];
1089                         _statusWindowUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES];
1090         }
1091
1092         if ( [df boolForKey:@"showTrackNumber"] ) {
1093             int trackNo    = 0;
1094             int trackCount = 0;
1095             
1096             NS_DURING
1097                 trackNo    = [[self currentRemote] currentSongTrack];
1098                 trackCount = [[self currentRemote] currentAlbumTrackCount];
1099             NS_HANDLER
1100                 [self networkError:localException];
1101             NS_ENDHANDLER
1102             
1103             if ( (trackNo > 0) || (trackCount > 0) ) {
1104                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
1105                     @"Track", trackNo, @"of", trackCount];
1106             }
1107         }
1108
1109         if ( [df boolForKey:@"showTrackRating"] ) {
1110             float currentRating = 0;
1111             
1112             NS_DURING
1113                 currentRating = [[self currentRemote] currentSongRating];
1114             NS_HANDLER
1115                 [self networkError:localException];
1116             NS_ENDHANDLER
1117             
1118             if (currentRating >= 0.0) {
1119                 rating = ( currentRating * 5 );
1120             }
1121         }
1122         
1123         if ( [df boolForKey:@"showPlayCount"] && ![self radioIsPlaying] && [[self currentRemote] currentSource] == ITMTRemoteLibrarySource ) {
1124             NS_DURING
1125                 playCount = [[self currentRemote] currentSongPlayCount];
1126             NS_HANDLER
1127                 [self networkError:localException];
1128             NS_ENDHANDLER
1129         }
1130     } else {
1131         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
1132     }
1133     ITDebugLog(@"Showing current track info status window.");
1134     [statusWindowController showSongInfoWindowWithSource:source
1135                                                    title:title
1136                                                    album:album
1137                                                   artist:artist
1138                                                 composer:composer
1139                                                     time:time
1140                                                    track:track
1141                                                   rating:rating
1142                                                playCount:playCount
1143                                                    image:art];
1144 }
1145
1146 - (void)updateTime:(NSTimer *)timer
1147 {
1148         StatusWindow *sw = [StatusWindow sharedWindow];
1149         _timeUpdateCount++;
1150         if (_timeUpdateCount < (int)[sw exitDelay] + (int)[[sw exitEffect] effectTime] + (int)[[sw entryEffect] effectTime]) {
1151                 NSString *time = nil, *length;
1152                 NS_DURING
1153                         length = [[self currentRemote] currentSongLength];
1154                         if (length) {
1155                                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1156                                                         NSLocalizedString(@"time", @"Time"),
1157                                                         [[self currentRemote] currentSongElapsed],
1158                                                         length];
1159                                 [[StatusWindowController sharedController] updateTime:time];
1160                         }
1161                 NS_HANDLER
1162                         [self networkError:localException];
1163                 NS_ENDHANDLER
1164         } else {
1165                 [self invalidateStatusWindowUpdateTimer];
1166         }
1167 }
1168
1169 - (void)invalidateStatusWindowUpdateTimer
1170 {
1171         if (_statusWindowUpdateTimer) {
1172                 [_statusWindowUpdateTimer invalidate];
1173                 _statusWindowUpdateTimer = nil;
1174         }
1175 }
1176
1177 - (void)showUpcomingSongs
1178 {
1179     int numSongs = 0;
1180     NS_DURING
1181         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
1182     NS_HANDLER
1183         [self networkError:localException];
1184     NS_ENDHANDLER
1185     
1186         [self invalidateStatusWindowUpdateTimer];
1187         
1188     ITDebugLog(@"Showing upcoming songs status window.");
1189     NS_DURING
1190         if (numSongs > 0) {
1191             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
1192             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance];
1193             int curTrack = [[self currentRemote] currentSongIndex];
1194             int i;
1195     
1196             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance && i <= numSongs; i++) {
1197                 if ([[self currentRemote] songEnabledAtIndex:i]) {
1198                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
1199                 } else {
1200                                         numSongsInAdvance++;
1201                                 }
1202             }
1203             
1204             if ([songList count] == 0) {
1205                 [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")];
1206             }
1207             
1208             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
1209         } else {
1210             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
1211         }
1212     NS_HANDLER
1213         [self networkError:localException];
1214     NS_ENDHANDLER
1215 }
1216
1217 - (void)popupMenu
1218 {
1219     if (!_popped) {
1220         _popped = YES;
1221         [self menuClicked];
1222         NSMenu *menu = [statusItem menu];
1223         [(NSCarbonMenuImpl *)[menu _menuImpl] popUpMenu:menu atLocation:[NSEvent mouseLocation] width:1 forView:nil withSelectedItem:-30 withFont:[NSFont menuFontOfSize:32]];
1224         _popped = NO;
1225     }
1226 }
1227
1228 - (void)incrementVolume
1229 {
1230     NS_DURING
1231         float volume  = [[self currentRemote] volume];
1232         float dispVol = volume;
1233         ITDebugLog(@"Incrementing volume.");
1234         volume  += 0.110;
1235         dispVol += 0.100;
1236         
1237         if (volume > 1.0) {
1238             volume  = 1.0;
1239             dispVol = 1.0;
1240         }
1241     
1242         ITDebugLog(@"Setting volume to %f", volume);
1243         [[self currentRemote] setVolume:volume];
1244     
1245         // Show volume status window
1246                 [self invalidateStatusWindowUpdateTimer];
1247         [statusWindowController showVolumeWindowWithLevel:dispVol];
1248     NS_HANDLER
1249         [self networkError:localException];
1250     NS_ENDHANDLER
1251 }
1252
1253 - (void)decrementVolume
1254 {
1255     NS_DURING
1256         float volume  = [[self currentRemote] volume];
1257         float dispVol = volume;
1258         ITDebugLog(@"Decrementing volume.");
1259         volume  -= 0.090;
1260         dispVol -= 0.100;
1261     
1262         if (volume < 0.0) {
1263             volume  = 0.0;
1264             dispVol = 0.0;
1265         }
1266         
1267         ITDebugLog(@"Setting volume to %f", volume);
1268         [[self currentRemote] setVolume:volume];
1269         
1270         //Show volume status window
1271                 [self invalidateStatusWindowUpdateTimer];
1272         [statusWindowController showVolumeWindowWithLevel:dispVol];
1273     NS_HANDLER
1274         [self networkError:localException];
1275     NS_ENDHANDLER
1276 }
1277
1278 - (void)incrementRating
1279 {
1280     NS_DURING
1281         float rating = [[self currentRemote] currentSongRating];
1282         ITDebugLog(@"Incrementing rating.");
1283         
1284         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1285             ITDebugLog(@"No song playing, rating change aborted.");
1286             return;
1287         }
1288         
1289         rating += 0.2;
1290         if (rating > 1.0) {
1291             rating = 1.0;
1292         }
1293         ITDebugLog(@"Setting rating to %f", rating);
1294         [[self currentRemote] setCurrentSongRating:rating];
1295         
1296         //Show rating status window
1297                 [self invalidateStatusWindowUpdateTimer];
1298         [statusWindowController showRatingWindowWithRating:rating];
1299     NS_HANDLER
1300         [self networkError:localException];
1301     NS_ENDHANDLER
1302 }
1303
1304 - (void)decrementRating
1305 {
1306     NS_DURING
1307         float rating = [[self currentRemote] currentSongRating];
1308         ITDebugLog(@"Decrementing rating.");
1309         
1310         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1311             ITDebugLog(@"No song playing, rating change aborted.");
1312             return;
1313         }
1314         
1315         rating -= 0.2;
1316         if (rating < 0.0) {
1317             rating = 0.0;
1318         }
1319         ITDebugLog(@"Setting rating to %f", rating);
1320         [[self currentRemote] setCurrentSongRating:rating];
1321         
1322         //Show rating status window
1323                 [self invalidateStatusWindowUpdateTimer];
1324         [statusWindowController showRatingWindowWithRating:rating];
1325     NS_HANDLER
1326         [self networkError:localException];
1327     NS_ENDHANDLER
1328 }
1329
1330 - (void)setRating:(ITHotKey *)sender
1331 {
1332     int stars = [[sender name] characterAtIndex:9] - 48;
1333     [self selectSongRating:stars * 20];
1334     [statusWindowController showRatingWindowWithRating:(float)stars / 5.0];
1335 }
1336
1337 - (void)toggleLoop
1338 {
1339     NS_DURING
1340         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
1341         ITDebugLog(@"Toggling repeat mode.");
1342         switch (repeatMode) {
1343             case ITMTRemotePlayerRepeatOff:
1344                 repeatMode = ITMTRemotePlayerRepeatAll;
1345             break;
1346             case ITMTRemotePlayerRepeatAll:
1347                 repeatMode = ITMTRemotePlayerRepeatOne;
1348             break;
1349             case ITMTRemotePlayerRepeatOne:
1350                 repeatMode = ITMTRemotePlayerRepeatOff;
1351             break;
1352         }
1353         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
1354         [[self currentRemote] setRepeatMode:repeatMode];
1355         
1356         //Show loop status window
1357                 [self invalidateStatusWindowUpdateTimer];
1358         [statusWindowController showRepeatWindowWithMode:repeatMode];
1359     NS_HANDLER
1360         [self networkError:localException];
1361     NS_ENDHANDLER
1362 }
1363
1364 - (void)toggleShuffle
1365 {
1366     NS_DURING
1367         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
1368         ITDebugLog(@"Toggling shuffle mode.");
1369         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
1370         //Show shuffle status window
1371         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
1372                 [self invalidateStatusWindowUpdateTimer];
1373         [statusWindowController showShuffleWindow:newShuffleEnabled];
1374     NS_HANDLER
1375         [self networkError:localException];
1376     NS_ENDHANDLER
1377 }
1378
1379 - (void)toggleSongShufflable
1380 {
1381         if ([self songIsPlaying]) {
1382                 NS_DURING
1383                         BOOL flag = ![[self currentRemote] currentSongShufflable];
1384                         ITDebugLog(@"Toggling shufflability.");
1385                         [[self currentRemote] setCurrentSongShufflable:flag];
1386                         //Show song shufflability status window
1387                         [self invalidateStatusWindowUpdateTimer];
1388                         [statusWindowController showSongShufflabilityWindow:flag];
1389                 NS_HANDLER
1390                         [self networkError:localException];
1391                 NS_ENDHANDLER
1392         }
1393 }
1394
1395 - (void)registerNowOK
1396 {
1397     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1398     [[StatusWindow sharedWindow] vanish:self];
1399     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1400
1401     [self blingNow];
1402 }
1403
1404 - (void)registerNowCancel
1405 {
1406     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1407     [[StatusWindow sharedWindow] vanish:self];
1408     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1409
1410     [NSApp terminate:self];
1411 }
1412
1413 /*************************************************************************/
1414 #pragma mark -
1415 #pragma mark NETWORK HANDLERS
1416 /*************************************************************************/
1417
1418 - (void)setServerStatus:(BOOL)newStatus
1419 {
1420     if (newStatus) {
1421         //Turn on
1422         [networkController setServerStatus:YES];
1423     } else {
1424         //Tear down
1425         [networkController setServerStatus:NO];
1426     }
1427 }
1428
1429 - (int)connectToServer
1430 {
1431     int result;
1432     ITDebugLog(@"Attempting to connect to shared remote.");
1433     result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]];
1434     //Connect
1435     if (result == 1) {
1436         [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1437         currentRemote = [[[networkController networkObject] remote] retain];
1438         
1439         [self setupHotKeys];
1440         //playerRunningState = ITMTRemotePlayerRunning;
1441         playerRunningState = [[self currentRemote] playerRunningState];
1442                 if (_needsPolling) {
1443                         if (refreshTimer) {
1444                                 [refreshTimer invalidate];
1445                         }
1446                         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1447                                                                          target:self
1448                                                                          selector:@selector(timerUpdate)
1449                                                                          userInfo:nil
1450                                                                          repeats:YES] retain];
1451                 }
1452         [self timerUpdate];
1453         ITDebugLog(@"Connection successful.");
1454         return 1;
1455     } else if (result == 0) {
1456         ITDebugLog(@"Connection failed.");
1457         currentRemote = [remoteArray objectAtIndex:0];
1458         return 0;
1459     } else {
1460         //Do something about the password being invalid
1461         ITDebugLog(@"Connection failed.");
1462         currentRemote = [remoteArray objectAtIndex:0];
1463         return -1;
1464     }
1465 }
1466
1467 - (BOOL)disconnectFromServer
1468 {
1469     ITDebugLog(@"Disconnecting from shared remote.");
1470     //Disconnect
1471     [currentRemote release];
1472     currentRemote = [remoteArray objectAtIndex:0];
1473     [networkController disconnect];
1474     
1475     if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
1476         [self applicationLaunched:nil];
1477     } else {
1478         [self applicationTerminated:nil];
1479     }
1480     if (refreshTimer) {
1481                 [self timerUpdate];
1482         };
1483     return YES;
1484 }
1485
1486 - (void)checkForRemoteServer
1487 {
1488     [self checkForRemoteServerAndConnectImmediately:NO];
1489 }
1490
1491 - (void)checkForRemoteServerAndConnectImmediately:(BOOL)connectImmediately
1492 {
1493     ITDebugLog(@"Checking for remote server.");
1494     if (!_checkingForServer) {
1495         if (!_serverCheckLock) {
1496             _serverCheckLock = [[NSLock alloc] init];
1497         }
1498         [_serverCheckLock lock];
1499         _checkingForServer = YES;
1500         [_serverCheckLock unlock];
1501         [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:[NSNumber numberWithBool:connectImmediately]];
1502     }
1503 }
1504
1505 - (void)runRemoteServerCheck:(id)sender
1506 {
1507     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1508     ITDebugLog(@"Remote server check running.");
1509     if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) {
1510         ITDebugLog(@"Remote server found.");
1511         if ([sender boolValue]) {
1512             [self performSelectorOnMainThread:@selector(connectToServer) withObject:nil waitUntilDone:NO];
1513         } else {
1514             [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO];
1515         }
1516     } else {
1517         ITDebugLog(@"Remote server not found.");
1518         [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO];
1519     }
1520     [_serverCheckLock lock];
1521     _checkingForServer = NO;
1522     [_serverCheckLock unlock];
1523     [pool release];
1524 }
1525
1526 - (void)remoteServerFound:(id)sender
1527 {
1528     if (![networkController isServerOn] && ![networkController isConnectedToServer]) {
1529                 [self invalidateStatusWindowUpdateTimer];
1530         [[StatusWindowController sharedController] showReconnectQueryWindow];
1531     }
1532 }
1533
1534 - (void)remoteServerNotFound:(id)sender
1535 {
1536     if (![[NetworkController sharedController] isConnectedToServer]) {
1537         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1538     }
1539 }
1540
1541 - (void)networkError:(NSException *)exception
1542 {
1543     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
1544     if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) {
1545         //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);
1546                 [self invalidateStatusWindowUpdateTimer];
1547         [[StatusWindowController sharedController] showNetworkErrorQueryWindow];
1548         if ([self disconnectFromServer]) {
1549             [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1550             [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1551         } else {
1552             ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!");
1553         }
1554     }
1555 }
1556
1557 - (void)reconnect
1558 {
1559     /*if ([self connectToServer] == 0) {
1560         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1561     }*/
1562     [self checkForRemoteServerAndConnectImmediately:YES];
1563     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1564     [[StatusWindow sharedWindow] vanish:self];
1565     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1566 }
1567
1568 - (void)cancelReconnect
1569 {
1570     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1571     [[StatusWindow sharedWindow] vanish:self];
1572     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1573 }
1574
1575 /*************************************************************************/
1576 #pragma mark -
1577 #pragma mark WORKSPACE NOTIFICATION HANDLERS
1578 /*************************************************************************/
1579
1580 - (void)applicationLaunched:(NSNotification *)note
1581 {
1582     NS_DURING
1583         if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) {
1584             ITDebugLog(@"Remote application launched.");
1585             playerRunningState = ITMTRemotePlayerRunning;
1586             [[self currentRemote] begin];
1587             [self setLatestSongIdentifier:@""];
1588             [self timerUpdate];
1589                         if (_needsPolling) {
1590                                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1591                                                                         target:self
1592                                                                         selector:@selector(timerUpdate)
1593                                                                         userInfo:nil
1594                                                                         repeats:YES] retain];
1595                         }
1596             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
1597                         if (![df boolForKey:@"UsePollingOnly"]) {
1598                                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackChanged:) name:@"ITMTTrackChanged" object:nil];
1599                         }
1600             [self setupHotKeys];
1601         }
1602     NS_HANDLER
1603         [self networkError:localException];
1604     NS_ENDHANDLER
1605 }
1606
1607  - (void)applicationTerminated:(NSNotification *)note
1608  {
1609     NS_DURING
1610         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) {
1611             ITDebugLog(@"Remote application terminated.");
1612             playerRunningState = ITMTRemotePlayerNotRunning;
1613             [[self currentRemote] halt];
1614             [refreshTimer invalidate];
1615             [refreshTimer release];
1616             refreshTimer = nil;
1617                         [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ITMTTrackChanged" object:nil];
1618                         [statusItem setEnabled:YES];
1619                         [statusItem setToolTip:@"iTunes not running."];
1620             [self clearHotKeys];
1621
1622                         
1623             if ([df objectForKey:@"ShowPlayer"] != nil) {
1624                 ITHotKey *hotKey;
1625                 ITDebugLog(@"Setting up show player hot key.");
1626                 hotKey = [[ITHotKey alloc] init];
1627                 [hotKey setName:@"ShowPlayer"];
1628                 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
1629                 [hotKey setTarget:self];
1630                 [hotKey setAction:@selector(showPlayer)];
1631                 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1632             }
1633         }
1634     NS_HANDLER
1635         [self networkError:localException];
1636     NS_ENDHANDLER
1637  }
1638
1639
1640 /*************************************************************************/
1641 #pragma mark -
1642 #pragma mark NSApplication DELEGATE METHODS
1643 /*************************************************************************/
1644
1645 - (void)applicationWillTerminate:(NSNotification *)note
1646 {
1647         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
1648     [networkController stopRemoteServerSearch];
1649     [self clearHotKeys];
1650     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1651 }
1652
1653 - (void)applicationDidBecomeActive:(NSNotification *)note
1654 {
1655         //This appears to not work in 10.4
1656         if (_open && !blinged && ![[ITAboutWindowController sharedController] isVisible] && ![NSApp mainWindow] && ([[StatusWindow sharedWindow] exitMode] == ITTransientStatusWindowExitAfterDelay)) {
1657                 [[MainController sharedController] showPreferences];
1658         }
1659 }
1660
1661 /*************************************************************************/
1662 #pragma mark -
1663 #pragma mark DEALLOCATION METHOD
1664 /*************************************************************************/
1665
1666 - (void)dealloc
1667 {
1668     [self applicationTerminated:nil];
1669     [bling release];
1670     [statusItem release];
1671     [statusWindowController release];
1672     [menuController release];
1673     [networkController release];
1674     [_serverCheckLock release];
1675     [super dealloc];
1676 }
1677
1678 @end