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