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