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