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