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