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