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