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