Adding loads of neato registration stuff!
[MenuTunes.git] / MainController.m
1 #import "MainController.h"
2 #import "MenuController.h"
3 #import "PreferencesController.h"
4 #import <ITKit/ITHotKeyCenter.h>
5 #import <ITKit/ITHotKey.h>
6 #import <ITKit/ITKeyCombo.h>
7 #import "StatusWindowController.h"
8 #import "StatusItemHack.h"
9
10 @interface MainController(Private)
11 - (ITMTRemote *)loadRemote;
12 - (void)timerUpdate;
13 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
14 - (void)showCurrentTrackInfo;
15 - (void)applicationLaunched:(NSNotification *)note;
16 - (void)applicationTerminated:(NSNotification *)note;
17 @end
18
19 static MainController *sharedController;
20
21 @implementation MainController
22
23 + (MainController *)sharedController
24 {
25     return sharedController;
26 }
27
28 /*************************************************************************/
29 #pragma mark -
30 #pragma mark INITIALIZATION/DEALLOCATION METHODS
31 /*************************************************************************/
32
33 - (id)init
34 {
35     if ( ( self = [super init] ) ) {
36         sharedController = self;
37         
38         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
39         statusWindowController = [[StatusWindowController alloc] init];
40         menuController = [[MenuController alloc] init];
41         df = [[NSUserDefaults standardUserDefaults] retain];
42     }
43     return self;
44 }
45
46 - (void)applicationDidFinishLaunching:(NSNotification *)note
47 {
48     //Turn on debug mode if needed
49     if ([df boolForKey:@"ITDebugMode"]) {
50         SetITDebugMode(YES);
51     }
52     
53     bling = [[MTBlingController alloc] init];
54     blingDate = nil;
55     
56     currentRemote = [self loadRemote];
57     [currentRemote begin];
58     
59     //Setup for notification of the remote player launching or quitting
60     [[[NSWorkspace sharedWorkspace] notificationCenter]
61             addObserver:self
62             selector:@selector(applicationTerminated:)
63             name:NSWorkspaceDidTerminateApplicationNotification
64             object:nil];
65     
66     [[[NSWorkspace sharedWorkspace] notificationCenter]
67             addObserver:self
68             selector:@selector(applicationLaunched:)
69             name:NSWorkspaceDidLaunchApplicationNotification
70             object:nil];
71     
72     if ( ! [df objectForKey:@"menu"] ) {  // If this is nil, defaults have never been registered.
73         [[PreferencesController sharedPrefs] registerDefaults];
74     }
75     
76     [StatusItemHack install];
77     statusItem = [[ITStatusItem alloc]
78             initWithStatusBar:[NSStatusBar systemStatusBar]
79             withLength:NSSquareStatusItemLength];
80     
81     if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
82         [self applicationLaunched:nil];
83     } else {
84         if ([df boolForKey:@"LaunchPlayerWithMT"])
85             [self showPlayer];
86         else
87             [self applicationTerminated:nil];
88     }
89     
90     [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
91     [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
92 }
93
94 - (ITMTRemote *)loadRemote
95 {
96     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
97     ITDebugLog(@"Gathering remotes.");
98     if (folderPath) {
99         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
100         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
101         NSString     *bundlePath;
102
103         while ( (bundlePath = [enumerator nextObject]) ) {
104             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
105
106             if (remoteBundle) {
107                 Class remoteClass = [remoteBundle principalClass];
108
109                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
110                     [remoteClass isKindOfClass:[NSObject class]]) {
111                     id remote = [remoteClass remote];
112                     ITDebugLog(@"Adding remote at path %@", bundlePath);
113                     [remoteArray addObject:remote];
114                 }
115             }
116         }
117
118 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
119 //          if ( [remoteArray count] > 1 ) {
120 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
121 //          }
122 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
123 //      }
124     }
125 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
126     return [remoteArray objectAtIndex:0];
127 }
128
129 /*************************************************************************/
130 #pragma mark -
131 #pragma mark INSTANCE METHODS
132 /*************************************************************************/
133
134 /*- (void)startTimerInNewThread
135 {
136     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
137     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
138     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
139                              target:self
140                              selector:@selector(timerUpdate)
141                              userInfo:nil
142                              repeats:YES] retain];
143     [runLoop run];
144     ITDebugLog(@"Timer started.");
145     [pool release];
146 }*/
147
148 - (void)blingTime
149 {
150     NSDate *now = [NSDate date];
151     if ( (! blingDate) || ([now timeIntervalSinceDate:blingDate] >= 86400) ) {
152         [bling showPanelIfNeeded];
153         [blingDate autorelease];
154         blingDate = [now retain];
155     }
156 }
157
158 - (void)blingNow
159 {
160     [bling showPanel];
161     [blingDate autorelease];
162     blingDate = [[NSDate date] retain];
163 }
164
165 - (BOOL)blingBling
166 {
167     if ( ! ([bling checkDone] == 2475) ) {
168         return NO;
169     } else {
170         return YES;
171     }
172 }
173
174 - (BOOL)songIsPlaying
175 {
176     return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
177 }
178
179 - (BOOL)radioIsPlaying
180 {
181     return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
182 }
183
184 - (BOOL)songChanged
185 {
186     return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
187 }
188
189 - (NSString *)latestSongIdentifier
190 {
191     return _latestSongIdentifier;
192 }
193
194 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
195 {
196     ITDebugLog(@"Setting latest song identifier to %@", newIdentifier);
197     [_latestSongIdentifier autorelease];
198     _latestSongIdentifier = [newIdentifier copy];
199 }
200
201 - (void)timerUpdate
202 {
203     if ( [self songChanged] ) {
204         ITDebugLog(@"The song changed.");
205         [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
206         latestPlaylistClass = [currentRemote currentPlaylistClass];
207         [menuController rebuildSubmenus];
208
209         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
210 //            [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
211         }
212     }
213 }
214
215 - (void)menuClicked
216 {
217     ITDebugLog(@"Menu clicked.");
218     if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
219         [statusItem setMenu:[menuController menu]];
220     } else {
221         [statusItem setMenu:[menuController menuForNoPlayer]];
222     }
223 }
224
225 //
226 //
227 // Menu Selectors
228 //
229 //
230
231 - (void)playPause
232 {
233     ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
234     ITDebugLog(@"Play/Pause toggled");
235     if (state == ITMTRemotePlayerPlaying) {
236         [currentRemote pause];
237     } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
238         [currentRemote pause];
239         [currentRemote play];
240     } else {
241         [currentRemote play];
242     }
243     [self timerUpdate];
244 }
245
246 - (void)nextSong
247 {
248     ITDebugLog(@"Going to next song.");
249     [currentRemote goToNextSong];
250     [self timerUpdate];
251 }
252
253 - (void)prevSong
254 {
255     ITDebugLog(@"Going to previous song.");
256     [currentRemote goToPreviousSong];
257     [self timerUpdate];
258 }
259
260 - (void)fastForward
261 {
262     ITDebugLog(@"Fast forwarding.");
263     [currentRemote forward];
264     [self timerUpdate];
265 }
266
267 - (void)rewind
268 {
269     ITDebugLog(@"Rewinding.");
270     [currentRemote rewind];
271     [self timerUpdate];
272 }
273
274 - (void)selectPlaylistAtIndex:(int)index
275 {
276     ITDebugLog(@"Selecting playlist %i", index);
277     [currentRemote switchToPlaylistAtIndex:index];
278     [self timerUpdate];
279 }
280
281 - (void)selectSongAtIndex:(int)index
282 {
283     ITDebugLog(@"Selecting song %i", index);
284     [currentRemote switchToSongAtIndex:index];
285     [self timerUpdate];
286 }
287
288 - (void)selectSongRating:(int)rating
289 {
290     ITDebugLog(@"Selecting song rating %i", rating);
291     [currentRemote setCurrentSongRating:(float)rating / 100.0];
292     [self timerUpdate];
293 }
294
295 - (void)selectEQPresetAtIndex:(int)index
296 {
297     ITDebugLog(@"Selecting EQ preset %i", index);
298     [currentRemote switchToEQAtIndex:index];
299     [self timerUpdate];
300 }
301
302 - (void)showPlayer
303 {
304     ITDebugLog(@"Beginning show player.");
305     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
306         ITDebugLog(@"Showing player interface.");
307         [currentRemote showPrimaryInterface];
308     } else {
309         ITDebugLog(@"Launching player.");
310         if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
311             ITDebugLog(@"Error Launching Player");
312         }
313     }
314     ITDebugLog(@"Finished show player.");
315 }
316
317 - (void)showPreferences
318 {
319     ITDebugLog(@"Show preferences.");
320     [[PreferencesController sharedPrefs] setController:self];
321     [[PreferencesController sharedPrefs] showPrefsWindow:self];
322 }
323
324 - (void)quitMenuTunes
325 {
326     ITDebugLog(@"Quitting MenuTunes.");
327     [NSApp terminate:self];
328 }
329
330 //
331 //
332
333 - (void)closePreferences
334 {
335     ITDebugLog(@"Preferences closed.");
336     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
337         [self setupHotKeys];
338     }
339 }
340
341 - (ITMTRemote *)currentRemote
342 {
343     return currentRemote;
344 }
345
346 //
347 //
348 // Hot key setup
349 //
350 //
351
352 - (void)clearHotKeys
353 {
354     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
355     ITHotKey *nextHotKey;
356     ITDebugLog(@"Clearing hot keys.");
357     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
358         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
359     }
360     ITDebugLog(@"Done clearing hot keys.");
361 }
362
363 - (void)setupHotKeys
364 {
365     ITHotKey *hotKey;
366     ITDebugLog(@"Setting up hot keys.");
367     if ([df objectForKey:@"PlayPause"] != nil) {
368         ITDebugLog(@"Setting up play pause hot key.");
369         hotKey = [[ITHotKey alloc] init];
370         [hotKey setName:@"PlayPause"];
371         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
372         [hotKey setTarget:self];
373         [hotKey setAction:@selector(playPause)];
374         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
375     }
376     
377     if ([df objectForKey:@"NextTrack"] != nil) {
378         ITDebugLog(@"Setting up next track hot key.");
379         hotKey = [[ITHotKey alloc] init];
380         [hotKey setName:@"NextTrack"];
381         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
382         [hotKey setTarget:self];
383         [hotKey setAction:@selector(nextSong)];
384         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
385     }
386     
387     if ([df objectForKey:@"PrevTrack"] != nil) {
388         ITDebugLog(@"Setting up previous track hot key.");
389         hotKey = [[ITHotKey alloc] init];
390         [hotKey setName:@"PrevTrack"];
391         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
392         [hotKey setTarget:self];
393         [hotKey setAction:@selector(prevSong)];
394         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
395     }
396     
397     if ([df objectForKey:@"ShowPlayer"] != nil) {
398         ITDebugLog(@"Setting up show player hot key.");
399         hotKey = [[ITHotKey alloc] init];
400         [hotKey setName:@"ShowPlayer"];
401         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
402         [hotKey setTarget:self];
403         [hotKey setAction:@selector(showPlayer)];
404         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
405     }
406     
407     if ([df objectForKey:@"TrackInfo"] != nil) {
408         ITDebugLog(@"Setting up track info hot key.");
409         hotKey = [[ITHotKey alloc] init];
410         [hotKey setName:@"TrackInfo"];
411         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
412         [hotKey setTarget:self];
413         [hotKey setAction:@selector(showCurrentTrackInfo)];
414         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
415     }
416     
417     if ([df objectForKey:@"UpcomingSongs"] != nil) {
418         ITDebugLog(@"Setting up upcoming songs hot key.");
419         hotKey = [[ITHotKey alloc] init];
420         [hotKey setName:@"UpcomingSongs"];
421         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
422         [hotKey setTarget:self];
423         [hotKey setAction:@selector(showUpcomingSongs)];
424         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
425     }
426     
427     if ([df objectForKey:@"ToggleLoop"] != nil) {
428         ITDebugLog(@"Setting up toggle loop hot key.");
429         hotKey = [[ITHotKey alloc] init];
430         [hotKey setName:@"ToggleLoop"];
431         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
432         [hotKey setTarget:self];
433         [hotKey setAction:@selector(toggleLoop)];
434         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
435     }
436     
437     if ([df objectForKey:@"ToggleShuffle"] != nil) {
438         ITDebugLog(@"Setting up toggle shuffle hot key.");
439         hotKey = [[ITHotKey alloc] init];
440         [hotKey setName:@"ToggleShuffle"];
441         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
442         [hotKey setTarget:self];
443         [hotKey setAction:@selector(toggleShuffle)];
444         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
445     }
446     
447     if ([df objectForKey:@"IncrementVolume"] != nil) {
448         ITDebugLog(@"Setting up increment volume hot key.");
449         hotKey = [[ITHotKey alloc] init];
450         [hotKey setName:@"IncrementVolume"];
451         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
452         [hotKey setTarget:self];
453         [hotKey setAction:@selector(incrementVolume)];
454         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
455     }
456     
457     if ([df objectForKey:@"DecrementVolume"] != nil) {
458         ITDebugLog(@"Setting up decrement volume hot key.");
459         hotKey = [[ITHotKey alloc] init];
460         [hotKey setName:@"DecrementVolume"];
461         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
462         [hotKey setTarget:self];
463         [hotKey setAction:@selector(decrementVolume)];
464         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
465     }
466     
467     if ([df objectForKey:@"IncrementRating"] != nil) {
468         ITDebugLog(@"Setting up increment rating hot key.");
469         hotKey = [[ITHotKey alloc] init];
470         [hotKey setName:@"IncrementRating"];
471         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
472         [hotKey setTarget:self];
473         [hotKey setAction:@selector(incrementRating)];
474         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
475     }
476     
477     if ([df objectForKey:@"DecrementRating"] != nil) {
478         ITDebugLog(@"Setting up decrement rating hot key.");
479         hotKey = [[ITHotKey alloc] init];
480         [hotKey setName:@"DecrementRating"];
481         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
482         [hotKey setTarget:self];
483         [hotKey setAction:@selector(decrementRating)];
484         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
485     }
486     ITDebugLog(@"Finished setting up hot keys.");
487 }
488
489 - (void)showCurrentTrackInfo
490 {
491     ITMTRemotePlayerSource  source      = [currentRemote currentSource];
492     NSString               *title       = [currentRemote currentSongTitle];
493     NSString               *album       = nil;
494     NSString               *artist      = nil;
495     NSString               *time        = nil;
496     int                     trackNumber = 0;
497     int                     trackTotal  = 0;
498     int                     rating      = -1;
499     ITDebugLog(@"Showing track info status window.");
500     if ( title ) {
501
502         if ( [df boolForKey:@"showAlbum"] ) {
503             album = [currentRemote currentSongAlbum];
504         }
505
506         if ( [df boolForKey:@"showArtist"] ) {
507             artist = [currentRemote currentSongArtist];
508         }
509
510         if ( [df boolForKey:@"showTime"] ) {
511             time = [currentRemote currentSongLength];
512         }
513
514         if ( [df boolForKey:@"showNumber"] ) {
515             trackNumber = [currentRemote currentSongTrack];
516             trackTotal  = [currentRemote currentAlbumTrackCount];
517         }
518
519         if ( [df boolForKey:@"showRating"] ) {
520             rating = ( [currentRemote currentSongRating] * 5 );
521         }
522         
523     } else {
524         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
525     }
526
527     [statusWindowController showSongInfoWindowWithSource:source
528                                                    title:title
529                                                    album:album
530                                                   artist:artist
531                                                     time:time
532                                              trackNumber:trackNumber
533                                               trackTotal:trackTotal
534                                                   rating:rating];
535 }
536
537 - (void)showUpcomingSongs
538 {
539     int curPlaylist = [currentRemote currentPlaylistIndex];
540     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
541     ITDebugLog(@"Showing upcoming songs status window.");
542     if (numSongs > 0) {
543         NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
544         int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
545         int curTrack = [currentRemote currentSongIndex];
546         int i;
547
548         for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
549             if (i <= numSongs) {
550                 [songList addObject:[currentRemote songTitleAtIndex:i]];
551             }
552         }
553         
554         [statusWindowController showUpcomingSongsWindowWithTitles:songList];
555         
556     } else {
557         [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
558     }
559 }
560
561 - (void)incrementVolume
562 {
563     float volume  = [currentRemote volume];
564     float dispVol = volume;
565     ITDebugLog(@"Incrementing volume.");
566     volume  += 0.110;
567     dispVol += 0.100;
568     
569     if (volume > 1.0) {
570         volume  = 1.0;
571         dispVol = 1.0;
572     }
573
574     ITDebugLog(@"Setting volume to %f", volume);
575     [currentRemote setVolume:volume];
576
577     // Show volume status window
578     [statusWindowController showVolumeWindowWithLevel:dispVol];
579 }
580
581 - (void)decrementVolume
582 {
583     float volume  = [currentRemote volume];
584     float dispVol = volume;
585     ITDebugLog(@"Decrementing volume.");
586     volume  -= 0.090;
587     dispVol -= 0.100;
588
589     if (volume < 0.0) {
590         volume  = 0.0;
591         dispVol = 0.0;
592     }
593     
594     ITDebugLog(@"Setting volume to %f", volume);
595     [currentRemote setVolume:volume];
596     
597     //Show volume status window
598     [statusWindowController showVolumeWindowWithLevel:dispVol];
599 }
600
601 - (void)incrementRating
602 {
603     float rating = [currentRemote currentSongRating];
604     ITDebugLog(@"Incrementing rating.");
605     rating += 0.2;
606     if (rating > 1.0) {
607         rating = 1.0;
608     }
609     ITDebugLog(@"Setting rating to %f", rating);
610     [currentRemote setCurrentSongRating:rating];
611     
612     //Show rating status window
613     [statusWindowController showRatingWindowWithRating:rating];
614 }
615
616 - (void)decrementRating
617 {
618     float rating = [currentRemote currentSongRating];
619     ITDebugLog(@"Decrementing rating.");
620     rating -= 0.2;
621     if (rating < 0.0) {
622         rating = 0.0;
623     }
624     ITDebugLog(@"Setting rating to %f", rating);
625     [currentRemote setCurrentSongRating:rating];
626     
627     //Show rating status window
628     [statusWindowController showRatingWindowWithRating:rating];
629 }
630
631 - (void)toggleLoop
632 {
633     ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
634     ITDebugLog(@"Toggling repeat mode.");
635     switch (repeatMode) {
636         case ITMTRemotePlayerRepeatOff:
637             repeatMode = ITMTRemotePlayerRepeatAll;
638         break;
639         case ITMTRemotePlayerRepeatAll:
640             repeatMode = ITMTRemotePlayerRepeatOne;
641         break;
642         case ITMTRemotePlayerRepeatOne:
643             repeatMode = ITMTRemotePlayerRepeatOff;
644         break;
645     }
646     ITDebugLog(@"Setting repeat mode to %i", repeatMode);
647     [currentRemote setRepeatMode:repeatMode];
648     
649     //Show loop status window
650     [statusWindowController showRepeatWindowWithMode:repeatMode];
651 }
652
653 - (void)toggleShuffle
654 {
655     bool newShuffleEnabled = ![currentRemote shuffleEnabled];
656     ITDebugLog(@"Toggling shuffle mode.");
657     [currentRemote setShuffleEnabled:newShuffleEnabled];
658     //Show shuffle status window
659     ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
660     [statusWindowController showRepeatWindowWithMode:newShuffleEnabled];
661 }
662
663 /*************************************************************************/
664 #pragma mark -
665 #pragma mark WORKSPACE NOTIFICATION HANDLERS
666 /*************************************************************************/
667
668 - (void)applicationLaunched:(NSNotification *)note
669 {
670     if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
671         ITDebugLog(@"Remote application launched.");
672         [currentRemote begin];
673         [self setLatestSongIdentifier:@""];
674         [self timerUpdate];
675         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
676                              target:self
677                              selector:@selector(timerUpdate)
678                              userInfo:nil
679                              repeats:YES] retain];
680         //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
681         [self setupHotKeys];
682         playerRunningState = ITMTRemotePlayerRunning;
683     }
684 }
685
686  - (void)applicationTerminated:(NSNotification *)note
687  {
688      if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
689         ITDebugLog(@"Remote application terminated.");
690         [currentRemote halt];
691         [refreshTimer invalidate];
692         [refreshTimer release];
693         refreshTimer = nil;
694         [self clearHotKeys];
695         playerRunningState = ITMTRemotePlayerNotRunning;
696      }
697  }
698
699
700 /*************************************************************************/
701 #pragma mark -
702 #pragma mark NSApplication DELEGATE METHODS
703 /*************************************************************************/
704
705 - (void)applicationWillTerminate:(NSNotification *)note
706 {
707     [self clearHotKeys];
708     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
709 }
710
711
712 /*************************************************************************/
713 #pragma mark -
714 #pragma mark DEALLOCATION METHOD
715 /*************************************************************************/
716
717 - (void)dealloc
718 {
719     [self applicationTerminated:nil];
720     [bling release];
721     [statusItem release];
722     [statusWindowController release];
723     [menuController release];
724     [super dealloc];
725 }
726
727
728 @end