RAAAAAHHHHHHHHHHH NETWORK MENUTOOOONS!
[MenuTunes.git] / MainController.m
1 #import "MainController.h"
2 #import "MenuController.h"
3 #import "PreferencesController.h"
4 #import "NetworkController.h"
5 #import <ITKit/ITHotKeyCenter.h>
6 #import <ITKit/ITHotKey.h>
7 #import <ITKit/ITKeyCombo.h>
8 #import "StatusWindow.h"
9 #import "StatusWindowController.h"
10 #import "StatusItemHack.h"
11
12 @interface MainController(Private)
13 - (ITMTRemote *)loadRemote;
14 - (void)timerUpdate;
15 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
16 - (void)showCurrentTrackInfo;
17 - (void)applicationLaunched:(NSNotification *)note;
18 - (void)applicationTerminated:(NSNotification *)note;
19 @end
20
21 static MainController *sharedController;
22
23 @implementation MainController
24
25 + (MainController *)sharedController
26 {
27     return sharedController;
28 }
29
30 /*************************************************************************/
31 #pragma mark -
32 #pragma mark INITIALIZATION/DEALLOCATION METHODS
33 /*************************************************************************/
34
35 - (id)init
36 {
37     if ( ( self = [super init] ) ) {
38         sharedController = self;
39         
40         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
41         statusWindowController = [StatusWindowController sharedController];
42         menuController = [[MenuController alloc] init];
43         df = [[NSUserDefaults standardUserDefaults] retain];
44         timerUpdating = NO;
45         blinged = NO;
46     }
47     return self;
48 }
49
50 - (void)applicationDidFinishLaunching:(NSNotification *)note
51 {
52     //Turn on debug mode if needed
53     if ([df boolForKey:@"ITDebugMode"]) {
54         SetITDebugMode(YES);
55     }
56     
57     currentRemote = [self loadRemote];
58     [[self currentRemote] begin];
59     
60     //Turn on network stuff if needed
61     networkController = [[NetworkController alloc] init];
62     if ([df boolForKey:@"enableSharing"]) {
63         [self setServerStatus:YES];
64     } else if ([df boolForKey:@"useSharedPlayer"] && [df boolForKey:@"alwaysUseSharedPlayer"]) {
65         [self connectToServer];
66     }
67     
68     //Setup for notification of the remote player launching or quitting
69     [[[NSWorkspace sharedWorkspace] notificationCenter]
70             addObserver:self
71             selector:@selector(applicationTerminated:)
72             name:NSWorkspaceDidTerminateApplicationNotification
73             object:nil];
74     
75     [[[NSWorkspace sharedWorkspace] notificationCenter]
76             addObserver:self
77             selector:@selector(applicationLaunched:)
78             name:NSWorkspaceDidLaunchApplicationNotification
79             object:nil];
80     
81     if ( ! [df objectForKey:@"menu"] ) {  // If this is nil, defaults have never been registered.
82         [[PreferencesController sharedPrefs] registerDefaults];
83     }
84     
85     [StatusItemHack install];
86     statusItem = [[ITStatusItem alloc]
87             initWithStatusBar:[NSStatusBar systemStatusBar]
88             withLength:NSSquareStatusItemLength];
89     
90     bling = [[MTBlingController alloc] init];
91     [self blingTime];
92     registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0
93                              target:self
94                              selector:@selector(blingTime)
95                              userInfo:nil
96                              repeats:YES] retain];
97     
98     NS_DURING
99         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
100             [self applicationLaunched:nil];
101         } else {
102             if ([df boolForKey:@"LaunchPlayerWithMT"])
103                 [self showPlayer];
104             else
105                 [self applicationTerminated:nil];
106         }
107     NS_HANDLER
108         [self networkError:localException];
109     NS_ENDHANDLER
110     
111     [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
112     [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
113
114     [networkController startRemoteServerSearch];
115     [NSApp deactivate];
116 }
117
118 - (ITMTRemote *)loadRemote
119 {
120     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
121     ITDebugLog(@"Gathering remotes.");
122     if (folderPath) {
123         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
124         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
125         NSString     *bundlePath;
126
127         while ( (bundlePath = [enumerator nextObject]) ) {
128             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
129
130             if (remoteBundle) {
131                 Class remoteClass = [remoteBundle principalClass];
132
133                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
134                     [remoteClass isKindOfClass:[NSObject class]]) {
135                     id remote = [remoteClass remote];
136                     ITDebugLog(@"Adding remote at path %@", bundlePath);
137                     [remoteArray addObject:remote];
138                 }
139             }
140         }
141
142 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
143 //          if ( [remoteArray count] > 1 ) {
144 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
145 //          }
146 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
147 //      }
148     }
149 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
150     return [remoteArray objectAtIndex:0];
151 }
152
153 /*************************************************************************/
154 #pragma mark -
155 #pragma mark INSTANCE METHODS
156 /*************************************************************************/
157
158 /*- (void)startTimerInNewThread
159 {
160     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
161     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
162     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
163                              target:self
164                              selector:@selector(timerUpdate)
165                              userInfo:nil
166                              repeats:YES] retain];
167     [runLoop run];
168     ITDebugLog(@"Timer started.");
169     [pool release];
170 }*/
171
172 - (void)setBlingTime:(NSDate*)date
173 {
174     NSMutableDictionary *globalPrefs;
175     [df synchronize];
176     globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
177     if (date) {
178         [globalPrefs setObject:date forKey:@"ITMTTrialStart"];
179     } else {
180         [globalPrefs removeObjectForKey:@"ITMTTrialStart"];
181     }
182     [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
183     [df synchronize];
184     [globalPrefs release];
185 }
186
187 - (NSDate*)getBlingTime
188 {
189     [df synchronize];
190     return [[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialStart"];
191 }
192
193 - (void)blingTime
194 {
195     NSDate *now = [NSDate date];
196     if (![self blingBling]) {
197         if ( (! [self getBlingTime] ) || ([now timeIntervalSinceDate:[self getBlingTime]] < 0) ) {
198             [self setBlingTime:now];
199         }
200         if ( ([now timeIntervalSinceDate:[self getBlingTime]] >= 604800) && (blinged != YES) ) {
201             blinged = YES;
202             [statusItem setEnabled:NO];
203             [self clearHotKeys];
204             if ([refreshTimer isValid]) {
205                 [refreshTimer invalidate];
206             }
207             [statusWindowController showRegistrationQueryWindow];
208         }
209     } else {
210         if (blinged) {
211             [statusItem setEnabled:YES];
212             [self setupHotKeys];
213             if (![refreshTimer isValid]) {
214                 [refreshTimer release];
215                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
216                              target:self
217                              selector:@selector(timerUpdate)
218                              userInfo:nil
219                              repeats:YES] retain];
220             }
221             blinged = NO;
222         }
223         [self setBlingTime:nil];
224     }
225 }
226
227 - (void)blingNow
228 {
229     [bling showPanel];
230 }
231
232 - (BOOL)blingBling
233 {
234     if ( ! ([bling checkDone] == 2475) ) {
235         return NO;
236     } else {
237         return YES;
238     }
239 }
240
241 - (BOOL)songIsPlaying
242 {
243     NSString *identifier;
244     NS_DURING
245         identifier = [[self currentRemote] playerStateUniqueIdentifier];
246     NS_HANDLER
247         [self networkError:localException];
248     NS_ENDHANDLER
249     return ( ! ([identifier isEqualToString:@"0-0"]) );
250 }
251
252 - (BOOL)radioIsPlaying
253 {
254     ITMTRemotePlayerPlaylistClass class;
255     NS_DURING
256         class = [[self currentRemote] currentPlaylistClass];
257     NS_HANDLER
258         [self networkError:localException];
259     NS_ENDHANDLER
260     return (class  == ITMTRemotePlayerRadioPlaylist );
261 }
262
263 - (BOOL)songChanged
264 {
265     NSString *identifier;
266     NS_DURING
267         identifier = [[self currentRemote] playerStateUniqueIdentifier];
268     NS_HANDLER
269         [self networkError:localException];
270     NS_ENDHANDLER
271     return ( ! [identifier isEqualToString:_latestSongIdentifier] );
272 }
273
274 - (NSString *)latestSongIdentifier
275 {
276     return _latestSongIdentifier;
277 }
278
279 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
280 {
281     ITDebugLog(@"Setting latest song identifier to %@", newIdentifier);
282     [_latestSongIdentifier autorelease];
283     _latestSongIdentifier = [newIdentifier copy];
284 }
285
286 - (void)timerUpdate
287 {
288     if ([networkController isConnectedToServer]) {
289         [statusItem setMenu:[menuController menu]];
290     }
291     
292     if ( [self songChanged] && (timerUpdating != YES) ) {
293         ITDebugLog(@"The song changed.");
294         timerUpdating = YES;
295         
296         NS_DURING
297         latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
298         [menuController rebuildSubmenus];
299
300         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
301             [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
302         }
303         
304         [self setLatestSongIdentifier:[[self currentRemote] playerStateUniqueIdentifier]];
305         NS_HANDLER
306             [self networkError:localException];
307         NS_ENDHANDLER
308         
309         timerUpdating = NO;
310     }
311 }
312
313 - (void)menuClicked
314 {
315     ITDebugLog(@"Menu clicked.");
316     if ([networkController isConnectedToServer]) {
317         //Used the cached version
318         return;
319     }
320     
321     NS_DURING
322         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
323             [statusItem setMenu:[menuController menu]];
324         } else {
325             [statusItem setMenu:[menuController menuForNoPlayer]];
326         }
327     NS_HANDLER
328         [self networkError:localException];
329     NS_ENDHANDLER
330 }
331
332 //
333 //
334 // Menu Selectors
335 //
336 //
337
338 - (void)playPause
339 {
340     NS_DURING
341         ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState];
342         ITDebugLog(@"Play/Pause toggled");
343         if (state == ITMTRemotePlayerPlaying) {
344             [[self currentRemote] pause];
345         } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
346             [[self currentRemote] pause];
347             [[self currentRemote] play];
348         } else {
349             [[self currentRemote] play];
350         }
351     NS_HANDLER
352         [self networkError:localException];
353     NS_ENDHANDLER
354     
355     [self timerUpdate];
356 }
357
358 - (void)nextSong
359 {
360     ITDebugLog(@"Going to next song.");
361     NS_DURING
362         [[self currentRemote] goToNextSong];
363     NS_HANDLER
364         [self networkError:localException];
365     NS_ENDHANDLER
366     [self timerUpdate];
367 }
368
369 - (void)prevSong
370 {
371     ITDebugLog(@"Going to previous song.");
372     NS_DURING
373         [[self currentRemote] goToPreviousSong];
374     NS_HANDLER
375         [self networkError:localException];
376     NS_ENDHANDLER
377     [self timerUpdate];
378 }
379
380 - (void)fastForward
381 {
382     ITDebugLog(@"Fast forwarding.");
383     NS_DURING
384         [[self currentRemote] forward];
385     NS_HANDLER
386         [self networkError:localException];
387     NS_ENDHANDLER
388     [self timerUpdate];
389 }
390
391 - (void)rewind
392 {
393     ITDebugLog(@"Rewinding.");
394     NS_DURING
395         [[self currentRemote] rewind];
396     NS_HANDLER
397         [self networkError:localException];
398     NS_ENDHANDLER
399     [self timerUpdate];
400 }
401
402 - (void)selectPlaylistAtIndex:(int)index
403 {
404     ITDebugLog(@"Selecting playlist %i", index);
405     NS_DURING
406         [[self currentRemote] switchToPlaylistAtIndex:index];
407     NS_HANDLER
408         [self networkError:localException];
409     NS_ENDHANDLER
410     [self timerUpdate];
411 }
412
413 - (void)selectSongAtIndex:(int)index
414 {
415     ITDebugLog(@"Selecting song %i", index);
416     NS_DURING
417         [[self currentRemote] switchToSongAtIndex:index];
418     NS_HANDLER
419         [self networkError:localException];
420     NS_ENDHANDLER
421     [self timerUpdate];
422 }
423
424 - (void)selectSongRating:(int)rating
425 {
426     ITDebugLog(@"Selecting song rating %i", rating);
427     NS_DURING
428         [[self currentRemote] setCurrentSongRating:(float)rating / 100.0];
429     NS_HANDLER
430         [self networkError:localException];
431     NS_ENDHANDLER
432     [self timerUpdate];
433 }
434
435 - (void)selectEQPresetAtIndex:(int)index
436 {
437     ITDebugLog(@"Selecting EQ preset %i", index);
438     NS_DURING
439         [[self currentRemote] switchToEQAtIndex:index];
440     NS_HANDLER
441         [self networkError:localException];
442     NS_ENDHANDLER
443     [self timerUpdate];
444 }
445
446 - (void)showPlayer
447 {
448     ITDebugLog(@"Beginning show player.");
449     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
450         ITDebugLog(@"Showing player interface.");
451         NS_DURING
452             [[self currentRemote] showPrimaryInterface];
453         NS_HANDLER
454             [self networkError:localException];
455         NS_ENDHANDLER
456     } else {
457         ITDebugLog(@"Launching player.");
458         NS_DURING
459             if (![[NSWorkspace sharedWorkspace] launchApplication:[[self currentRemote] playerFullName]]) {
460                 ITDebugLog(@"Error Launching Player");
461             }
462         NS_HANDLER
463             [self networkError:localException];
464         NS_ENDHANDLER
465     }
466     ITDebugLog(@"Finished show player.");
467 }
468
469 - (void)showPreferences
470 {
471     ITDebugLog(@"Show preferences.");
472     [[PreferencesController sharedPrefs] setController:self];
473     [[PreferencesController sharedPrefs] showPrefsWindow:self];
474 }
475
476 - (void)quitMenuTunes
477 {
478     ITDebugLog(@"Quitting MenuTunes.");
479     [NSApp terminate:self];
480 }
481
482 //
483 //
484
485 - (void)closePreferences
486 {
487     ITDebugLog(@"Preferences closed.");
488     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
489         [self setupHotKeys];
490     }
491 }
492
493 - (ITMTRemote *)currentRemote
494 {
495     return currentRemote;
496 }
497
498 //
499 //
500 // Hot key setup
501 //
502 //
503
504 - (void)clearHotKeys
505 {
506     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
507     ITHotKey *nextHotKey;
508     ITDebugLog(@"Clearing hot keys.");
509     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
510         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
511     }
512     ITDebugLog(@"Done clearing hot keys.");
513 }
514
515 - (void)setupHotKeys
516 {
517     ITHotKey *hotKey;
518     ITDebugLog(@"Setting up hot keys.");
519     
520     if (playerRunningState == ITMTRemotePlayerNotRunning) {
521         return;
522     }
523     
524     if ([df objectForKey:@"PlayPause"] != nil) {
525         ITDebugLog(@"Setting up play pause hot key.");
526         hotKey = [[ITHotKey alloc] init];
527         [hotKey setName:@"PlayPause"];
528         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
529         [hotKey setTarget:self];
530         [hotKey setAction:@selector(playPause)];
531         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
532     }
533     
534     if ([df objectForKey:@"NextTrack"] != nil) {
535         ITDebugLog(@"Setting up next track hot key.");
536         hotKey = [[ITHotKey alloc] init];
537         [hotKey setName:@"NextTrack"];
538         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
539         [hotKey setTarget:self];
540         [hotKey setAction:@selector(nextSong)];
541         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
542     }
543     
544     if ([df objectForKey:@"PrevTrack"] != nil) {
545         ITDebugLog(@"Setting up previous track hot key.");
546         hotKey = [[ITHotKey alloc] init];
547         [hotKey setName:@"PrevTrack"];
548         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
549         [hotKey setTarget:self];
550         [hotKey setAction:@selector(prevSong)];
551         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
552     }
553     
554     if ([df objectForKey:@"ShowPlayer"] != nil) {
555         ITDebugLog(@"Setting up show player hot key.");
556         hotKey = [[ITHotKey alloc] init];
557         [hotKey setName:@"ShowPlayer"];
558         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
559         [hotKey setTarget:self];
560         [hotKey setAction:@selector(showPlayer)];
561         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
562     }
563     
564     if ([df objectForKey:@"TrackInfo"] != nil) {
565         ITDebugLog(@"Setting up track info hot key.");
566         hotKey = [[ITHotKey alloc] init];
567         [hotKey setName:@"TrackInfo"];
568         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
569         [hotKey setTarget:self];
570         [hotKey setAction:@selector(showCurrentTrackInfo)];
571         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
572     }
573     
574     if ([df objectForKey:@"UpcomingSongs"] != nil) {
575         ITDebugLog(@"Setting up upcoming songs hot key.");
576         hotKey = [[ITHotKey alloc] init];
577         [hotKey setName:@"UpcomingSongs"];
578         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
579         [hotKey setTarget:self];
580         [hotKey setAction:@selector(showUpcomingSongs)];
581         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
582     }
583     
584     if ([df objectForKey:@"ToggleLoop"] != nil) {
585         ITDebugLog(@"Setting up toggle loop hot key.");
586         hotKey = [[ITHotKey alloc] init];
587         [hotKey setName:@"ToggleLoop"];
588         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
589         [hotKey setTarget:self];
590         [hotKey setAction:@selector(toggleLoop)];
591         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
592     }
593     
594     if ([df objectForKey:@"ToggleShuffle"] != nil) {
595         ITDebugLog(@"Setting up toggle shuffle hot key.");
596         hotKey = [[ITHotKey alloc] init];
597         [hotKey setName:@"ToggleShuffle"];
598         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
599         [hotKey setTarget:self];
600         [hotKey setAction:@selector(toggleShuffle)];
601         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
602     }
603     
604     if ([df objectForKey:@"IncrementVolume"] != nil) {
605         ITDebugLog(@"Setting up increment volume hot key.");
606         hotKey = [[ITHotKey alloc] init];
607         [hotKey setName:@"IncrementVolume"];
608         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
609         [hotKey setTarget:self];
610         [hotKey setAction:@selector(incrementVolume)];
611         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
612     }
613     
614     if ([df objectForKey:@"DecrementVolume"] != nil) {
615         ITDebugLog(@"Setting up decrement volume hot key.");
616         hotKey = [[ITHotKey alloc] init];
617         [hotKey setName:@"DecrementVolume"];
618         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
619         [hotKey setTarget:self];
620         [hotKey setAction:@selector(decrementVolume)];
621         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
622     }
623     
624     if ([df objectForKey:@"IncrementRating"] != nil) {
625         ITDebugLog(@"Setting up increment rating hot key.");
626         hotKey = [[ITHotKey alloc] init];
627         [hotKey setName:@"IncrementRating"];
628         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
629         [hotKey setTarget:self];
630         [hotKey setAction:@selector(incrementRating)];
631         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
632     }
633     
634     if ([df objectForKey:@"DecrementRating"] != nil) {
635         ITDebugLog(@"Setting up decrement rating hot key.");
636         hotKey = [[ITHotKey alloc] init];
637         [hotKey setName:@"DecrementRating"];
638         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
639         [hotKey setTarget:self];
640         [hotKey setAction:@selector(decrementRating)];
641         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
642     }
643     ITDebugLog(@"Finished setting up hot keys.");
644 }
645
646 - (void)showCurrentTrackInfo
647 {
648     ITMTRemotePlayerSource  source;
649     NSString               *title;
650     NSString               *album       = nil;
651     NSString               *artist      = nil;
652     NSString               *time        = nil;
653     NSString               *track       = nil;
654     int                     rating      = -1;
655     
656     NS_DURING
657         source      = [[self currentRemote] currentSource];
658         title       = [[self currentRemote] currentSongTitle];
659     NS_HANDLER
660         [self networkError:localException];
661     NS_ENDHANDLER
662     
663     ITDebugLog(@"Showing track info status window.");
664     
665     if ( title ) {
666
667         if ( [df boolForKey:@"showAlbum"] ) {
668             NS_DURING
669                 album = [[self currentRemote] currentSongAlbum];
670             NS_HANDLER
671                 [self networkError:localException];
672             NS_ENDHANDLER
673         }
674
675         if ( [df boolForKey:@"showArtist"] ) {
676             NS_DURING
677                 artist = [[self currentRemote] currentSongArtist];
678             NS_HANDLER
679                 [self networkError:localException];
680             NS_ENDHANDLER
681         }
682
683         if ( [df boolForKey:@"showTime"] ) {
684             NS_DURING
685                 time = [NSString stringWithFormat:@"%@: %@ / %@",
686                 @"Time",
687                 [[self currentRemote] currentSongElapsed],
688                 [[self currentRemote] currentSongLength]];
689             NS_HANDLER
690                 [self networkError:localException];
691             NS_ENDHANDLER
692         }
693
694         if ( [df boolForKey:@"showTrackNumber"] ) {
695             int trackNo;
696             int trackCount;
697             
698             NS_DURING
699                 trackNo    = [[self currentRemote] currentSongTrack];
700                 trackCount = [[self currentRemote] currentAlbumTrackCount];
701             NS_HANDLER
702                 [self networkError:localException];
703             NS_ENDHANDLER
704             
705             if ( (trackNo > 0) || (trackCount > 0) ) {
706                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
707                     @"Track", trackNo, @"of", trackCount];
708             }
709         }
710
711         if ( [df boolForKey:@"showTrackRating"] ) {
712             float currentRating;
713             
714             NS_DURING
715                 currentRating = [[self currentRemote] currentSongRating];
716             NS_HANDLER
717                 [self networkError:localException];
718             NS_ENDHANDLER
719             
720             if (currentRating >= 0.0) {
721                 rating = ( currentRating * 5 );
722             }
723         }
724         
725     } else {
726         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
727     }
728
729     [statusWindowController showSongInfoWindowWithSource:source
730                                                    title:title
731                                                    album:album
732                                                   artist:artist
733                                                     time:time
734                                                    track:track
735                                                   rating:rating];
736 }
737
738 - (void)showUpcomingSongs
739 {
740     int numSongs;
741     
742     NS_DURING
743         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
744     NS_HANDLER
745         [self networkError:localException];
746     NS_ENDHANDLER
747     
748     ITDebugLog(@"Showing upcoming songs status window.");
749     NS_DURING
750         if (numSongs > 0) {
751             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
752             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
753             int curTrack = [[self currentRemote] currentSongIndex];
754             int i;
755     
756             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
757                 if (i <= numSongs) {
758                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
759                 }
760             }
761             
762             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
763         } else {
764             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
765         }
766     NS_HANDLER
767         [self networkError:localException];
768     NS_ENDHANDLER
769 }
770
771 - (void)incrementVolume
772 {
773     NS_DURING
774         float volume  = [[self currentRemote] volume];
775         float dispVol = volume;
776         ITDebugLog(@"Incrementing volume.");
777         volume  += 0.110;
778         dispVol += 0.100;
779         
780         if (volume > 1.0) {
781             volume  = 1.0;
782             dispVol = 1.0;
783         }
784     
785         ITDebugLog(@"Setting volume to %f", volume);
786         [[self currentRemote] setVolume:volume];
787     
788         // Show volume status window
789         [statusWindowController showVolumeWindowWithLevel:dispVol];
790     NS_HANDLER
791         [self networkError:localException];
792     NS_ENDHANDLER
793 }
794
795 - (void)decrementVolume
796 {
797     NS_DURING
798         float volume  = [[self currentRemote] volume];
799         float dispVol = volume;
800         ITDebugLog(@"Decrementing volume.");
801         volume  -= 0.090;
802         dispVol -= 0.100;
803     
804         if (volume < 0.0) {
805             volume  = 0.0;
806             dispVol = 0.0;
807         }
808         
809         ITDebugLog(@"Setting volume to %f", volume);
810         [[self currentRemote] setVolume:volume];
811         
812         //Show volume status window
813         [statusWindowController showVolumeWindowWithLevel:dispVol];
814     NS_HANDLER
815         [self networkError:localException];
816     NS_ENDHANDLER
817 }
818
819 - (void)incrementRating
820 {
821     NS_DURING
822         float rating = [[self currentRemote] currentSongRating];
823         ITDebugLog(@"Incrementing rating.");
824         
825         if ([[self currentRemote] currentPlaylistIndex] == 0) {
826             ITDebugLog(@"No song playing, rating change aborted.");
827             return;
828         }
829         
830         rating += 0.2;
831         if (rating > 1.0) {
832             rating = 1.0;
833         }
834         ITDebugLog(@"Setting rating to %f", rating);
835         [[self currentRemote] setCurrentSongRating:rating];
836         
837         //Show rating status window
838         [statusWindowController showRatingWindowWithRating:rating];
839     NS_HANDLER
840         [self networkError:localException];
841     NS_ENDHANDLER
842 }
843
844 - (void)decrementRating
845 {
846     NS_DURING
847         float rating = [[self currentRemote] currentSongRating];
848         ITDebugLog(@"Decrementing rating.");
849         
850         if ([[self currentRemote] currentPlaylistIndex] == 0) {
851             ITDebugLog(@"No song playing, rating change aborted.");
852             return;
853         }
854         
855         rating -= 0.2;
856         if (rating < 0.0) {
857             rating = 0.0;
858         }
859         ITDebugLog(@"Setting rating to %f", rating);
860         [[self currentRemote] setCurrentSongRating:rating];
861         
862         //Show rating status window
863         [statusWindowController showRatingWindowWithRating:rating];
864     NS_HANDLER
865         [self networkError:localException];
866     NS_ENDHANDLER
867 }
868
869 - (void)toggleLoop
870 {
871     NS_DURING
872         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
873         ITDebugLog(@"Toggling repeat mode.");
874         switch (repeatMode) {
875             case ITMTRemotePlayerRepeatOff:
876                 repeatMode = ITMTRemotePlayerRepeatAll;
877             break;
878             case ITMTRemotePlayerRepeatAll:
879                 repeatMode = ITMTRemotePlayerRepeatOne;
880             break;
881             case ITMTRemotePlayerRepeatOne:
882                 repeatMode = ITMTRemotePlayerRepeatOff;
883             break;
884         }
885         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
886         [[self currentRemote] setRepeatMode:repeatMode];
887         
888         //Show loop status window
889         [statusWindowController showRepeatWindowWithMode:repeatMode];
890     NS_HANDLER
891         [self networkError:localException];
892     NS_ENDHANDLER
893 }
894
895 - (void)toggleShuffle
896 {
897     NS_DURING
898         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
899         ITDebugLog(@"Toggling shuffle mode.");
900         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
901         //Show shuffle status window
902         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
903         [statusWindowController showShuffleWindow:newShuffleEnabled];
904     NS_HANDLER
905         [self networkError:localException];
906     NS_ENDHANDLER
907 }
908
909 - (void)registerNowOK
910 {
911     [[StatusWindow sharedWindow] setLocked:NO];
912     [[StatusWindow sharedWindow] vanish:self];
913     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
914
915     [self blingNow];
916 }
917
918 - (void)registerNowCancel
919 {
920     [[StatusWindow sharedWindow] setLocked:NO];
921     [[StatusWindow sharedWindow] vanish:self];
922     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
923
924     [NSApp terminate:self];
925 }
926
927 /*************************************************************************/
928 #pragma mark -
929 #pragma mark NETWORK HANDLERS
930 /*************************************************************************/
931
932 - (void)setServerStatus:(BOOL)newStatus
933 {
934     if (newStatus) {
935         //Turn on
936         [networkController setServerStatus:YES];
937     } else {
938         //Tear down
939         [networkController setServerStatus:NO];
940     }
941 }
942
943 - (BOOL)connectToServer
944 {
945     //Connect
946     if ([networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]]) {
947         currentRemote = [networkController sharedRemote];
948         [refreshTimer invalidate];
949         return YES;
950     } else {
951         currentRemote = [remoteArray objectAtIndex:0];
952         return NO;
953     }
954 }
955
956 - (BOOL)disconnectFromServer
957 {
958     //Disconnect
959     currentRemote = [remoteArray objectAtIndex:0];
960     [networkController disconnect];
961     [self timerUpdate];
962     return YES;
963 }
964
965 - (void)networkError:(NSException *)exception
966 {
967     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
968     NSRunAlertPanel(@"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);
969     if ([networkController isConnectedToServer] && [self disconnectFromServer]) {
970     } else {
971         ITDebugLog(@"CRITICAL ERROR DISCONNECTING!");
972     }
973 }
974
975 /*************************************************************************/
976 #pragma mark -
977 #pragma mark WORKSPACE NOTIFICATION HANDLERS
978 /*************************************************************************/
979
980 - (void)applicationLaunched:(NSNotification *)note
981 {
982     NS_DURING
983         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]]) {
984             ITDebugLog(@"Remote application launched.");
985             playerRunningState = ITMTRemotePlayerRunning;
986             [[self currentRemote] begin];
987             [self setLatestSongIdentifier:@""];
988             [self timerUpdate];
989             refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
990                                 target:self
991                                 selector:@selector(timerUpdate)
992                                 userInfo:nil
993                                 repeats:YES] retain];
994             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
995             [self setupHotKeys];
996         }
997     NS_HANDLER
998         [self networkError:localException];
999     NS_ENDHANDLER
1000 }
1001
1002  - (void)applicationTerminated:(NSNotification *)note
1003  {
1004     NS_DURING
1005         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]]) {
1006             ITDebugLog(@"Remote application terminated.");
1007             [[self currentRemote] halt];
1008             [refreshTimer invalidate];
1009             [refreshTimer release];
1010             refreshTimer = nil;
1011             [self clearHotKeys];
1012             playerRunningState = ITMTRemotePlayerNotRunning;
1013         }
1014     NS_HANDLER
1015         [self networkError:localException];
1016     NS_ENDHANDLER
1017  }
1018
1019
1020 /*************************************************************************/
1021 #pragma mark -
1022 #pragma mark NSApplication DELEGATE METHODS
1023 /*************************************************************************/
1024
1025 - (void)applicationWillTerminate:(NSNotification *)note
1026 {
1027     [self clearHotKeys];
1028     [networkController stopRemoteServerSearch];
1029     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1030 }
1031
1032
1033 /*************************************************************************/
1034 #pragma mark -
1035 #pragma mark DEALLOCATION METHOD
1036 /*************************************************************************/
1037
1038 - (void)dealloc
1039 {
1040     [self applicationTerminated:nil];
1041     [bling release];
1042     [statusItem release];
1043     [statusWindowController release];
1044     [menuController release];
1045     [networkController release];
1046     [super dealloc];
1047 }
1048
1049
1050 @end