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