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