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