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"
10 @interface MainController(Private)
11 - (ITMTRemote *)loadRemote;
13 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
14 - (void)showCurrentTrackInfo;
15 - (void)applicationLaunched:(NSNotification *)note;
16 - (void)applicationTerminated:(NSNotification *)note;
19 static MainController *sharedController;
21 @implementation MainController
23 + (MainController *)sharedController
25 return sharedController;
28 /*************************************************************************/
30 #pragma mark INITIALIZATION/DEALLOCATION METHODS
31 /*************************************************************************/
35 if ( ( self = [super init] ) ) {
36 sharedController = self;
38 remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
39 statusWindowController = [StatusWindowController sharedController];
40 menuController = [[MenuController alloc] init];
41 df = [[NSUserDefaults standardUserDefaults] retain];
46 - (void)applicationDidFinishLaunching:(NSNotification *)note
48 //Turn on debug mode if needed
49 if ([df boolForKey:@"ITDebugMode"]) {
53 bling = [[MTBlingController alloc] init];
56 currentRemote = [self loadRemote];
57 [currentRemote begin];
59 //Setup for notification of the remote player launching or quitting
60 [[[NSWorkspace sharedWorkspace] notificationCenter]
62 selector:@selector(applicationTerminated:)
63 name:NSWorkspaceDidTerminateApplicationNotification
66 [[[NSWorkspace sharedWorkspace] notificationCenter]
68 selector:@selector(applicationLaunched:)
69 name:NSWorkspaceDidLaunchApplicationNotification
72 if ( ! [df objectForKey:@"menu"] ) { // If this is nil, defaults have never been registered.
73 [[PreferencesController sharedPrefs] registerDefaults];
76 [StatusItemHack install];
77 statusItem = [[ITStatusItem alloc]
78 initWithStatusBar:[NSStatusBar systemStatusBar]
79 withLength:NSSquareStatusItemLength];
81 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
82 [self applicationLaunched:nil];
84 if ([df boolForKey:@"LaunchPlayerWithMT"])
87 [self applicationTerminated:nil];
90 [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
91 [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
96 - (ITMTRemote *)loadRemote
98 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
99 ITDebugLog(@"Gathering remotes.");
101 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
102 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
103 NSString *bundlePath;
105 while ( (bundlePath = [enumerator nextObject]) ) {
106 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
109 Class remoteClass = [remoteBundle principalClass];
111 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
112 [remoteClass isKindOfClass:[NSObject class]]) {
113 id remote = [remoteClass remote];
114 ITDebugLog(@"Adding remote at path %@", bundlePath);
115 [remoteArray addObject:remote];
120 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
121 // if ( [remoteArray count] > 1 ) {
122 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
124 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
127 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
128 return [remoteArray objectAtIndex:0];
131 /*************************************************************************/
133 #pragma mark INSTANCE METHODS
134 /*************************************************************************/
136 /*- (void)startTimerInNewThread
138 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
139 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
140 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
142 selector:@selector(timerUpdate)
144 repeats:YES] retain];
146 ITDebugLog(@"Timer started.");
152 NSDate *now = [NSDate date];
153 if ( (! blingDate) || ([now timeIntervalSinceDate:blingDate] >= 86400) ) {
154 [bling showPanelIfNeeded];
155 [blingDate autorelease];
156 blingDate = [now retain];
163 [blingDate autorelease];
164 blingDate = [[NSDate date] retain];
169 if ( ! ([bling checkDone] == 2475) ) {
176 - (BOOL)songIsPlaying
178 return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
181 - (BOOL)radioIsPlaying
183 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
188 return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
191 - (NSString *)latestSongIdentifier
193 return _latestSongIdentifier;
196 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
198 ITDebugLog(@"Setting latest song identifier to %@", newIdentifier);
199 [_latestSongIdentifier autorelease];
200 _latestSongIdentifier = [newIdentifier copy];
205 if ( [self songChanged] ) {
206 ITDebugLog(@"The song changed.");
207 [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
208 latestPlaylistClass = [currentRemote currentPlaylistClass];
209 [menuController rebuildSubmenus];
211 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
212 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
219 ITDebugLog(@"Menu clicked.");
220 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
221 [statusItem setMenu:[menuController menu]];
223 [statusItem setMenu:[menuController menuForNoPlayer]];
235 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
236 ITDebugLog(@"Play/Pause toggled");
237 if (state == ITMTRemotePlayerPlaying) {
238 [currentRemote pause];
239 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
240 [currentRemote pause];
241 [currentRemote play];
243 [currentRemote play];
250 ITDebugLog(@"Going to next song.");
251 [currentRemote goToNextSong];
257 ITDebugLog(@"Going to previous song.");
258 [currentRemote goToPreviousSong];
264 ITDebugLog(@"Fast forwarding.");
265 [currentRemote forward];
271 ITDebugLog(@"Rewinding.");
272 [currentRemote rewind];
276 - (void)selectPlaylistAtIndex:(int)index
278 ITDebugLog(@"Selecting playlist %i", index);
279 [currentRemote switchToPlaylistAtIndex:index];
283 - (void)selectSongAtIndex:(int)index
285 ITDebugLog(@"Selecting song %i", index);
286 [currentRemote switchToSongAtIndex:index];
290 - (void)selectSongRating:(int)rating
292 ITDebugLog(@"Selecting song rating %i", rating);
293 [currentRemote setCurrentSongRating:(float)rating / 100.0];
297 - (void)selectEQPresetAtIndex:(int)index
299 ITDebugLog(@"Selecting EQ preset %i", index);
300 [currentRemote switchToEQAtIndex:index];
306 ITDebugLog(@"Beginning show player.");
307 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
308 ITDebugLog(@"Showing player interface.");
309 [currentRemote showPrimaryInterface];
311 ITDebugLog(@"Launching player.");
312 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
313 ITDebugLog(@"Error Launching Player");
316 ITDebugLog(@"Finished show player.");
319 - (void)showPreferences
321 ITDebugLog(@"Show preferences.");
322 [[PreferencesController sharedPrefs] setController:self];
323 [[PreferencesController sharedPrefs] showPrefsWindow:self];
326 - (void)quitMenuTunes
328 ITDebugLog(@"Quitting MenuTunes.");
329 [NSApp terminate:self];
335 - (void)closePreferences
337 ITDebugLog(@"Preferences closed.");
338 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
343 - (ITMTRemote *)currentRemote
345 return currentRemote;
356 NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
357 ITHotKey *nextHotKey;
358 ITDebugLog(@"Clearing hot keys.");
359 while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
360 [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
362 ITDebugLog(@"Done clearing hot keys.");
368 ITDebugLog(@"Setting up hot keys.");
369 if ([df objectForKey:@"PlayPause"] != nil) {
370 ITDebugLog(@"Setting up play pause hot key.");
371 hotKey = [[ITHotKey alloc] init];
372 [hotKey setName:@"PlayPause"];
373 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
374 [hotKey setTarget:self];
375 [hotKey setAction:@selector(playPause)];
376 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
379 if ([df objectForKey:@"NextTrack"] != nil) {
380 ITDebugLog(@"Setting up next track hot key.");
381 hotKey = [[ITHotKey alloc] init];
382 [hotKey setName:@"NextTrack"];
383 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
384 [hotKey setTarget:self];
385 [hotKey setAction:@selector(nextSong)];
386 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
389 if ([df objectForKey:@"PrevTrack"] != nil) {
390 ITDebugLog(@"Setting up previous track hot key.");
391 hotKey = [[ITHotKey alloc] init];
392 [hotKey setName:@"PrevTrack"];
393 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
394 [hotKey setTarget:self];
395 [hotKey setAction:@selector(prevSong)];
396 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
399 if ([df objectForKey:@"ShowPlayer"] != nil) {
400 ITDebugLog(@"Setting up show player hot key.");
401 hotKey = [[ITHotKey alloc] init];
402 [hotKey setName:@"ShowPlayer"];
403 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
404 [hotKey setTarget:self];
405 [hotKey setAction:@selector(showPlayer)];
406 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
409 if ([df objectForKey:@"TrackInfo"] != nil) {
410 ITDebugLog(@"Setting up track info hot key.");
411 hotKey = [[ITHotKey alloc] init];
412 [hotKey setName:@"TrackInfo"];
413 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
414 [hotKey setTarget:self];
415 [hotKey setAction:@selector(showCurrentTrackInfo)];
416 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
419 if ([df objectForKey:@"UpcomingSongs"] != nil) {
420 ITDebugLog(@"Setting up upcoming songs hot key.");
421 hotKey = [[ITHotKey alloc] init];
422 [hotKey setName:@"UpcomingSongs"];
423 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
424 [hotKey setTarget:self];
425 [hotKey setAction:@selector(showUpcomingSongs)];
426 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
429 if ([df objectForKey:@"ToggleLoop"] != nil) {
430 ITDebugLog(@"Setting up toggle loop hot key.");
431 hotKey = [[ITHotKey alloc] init];
432 [hotKey setName:@"ToggleLoop"];
433 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
434 [hotKey setTarget:self];
435 [hotKey setAction:@selector(toggleLoop)];
436 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
439 if ([df objectForKey:@"ToggleShuffle"] != nil) {
440 ITDebugLog(@"Setting up toggle shuffle hot key.");
441 hotKey = [[ITHotKey alloc] init];
442 [hotKey setName:@"ToggleShuffle"];
443 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
444 [hotKey setTarget:self];
445 [hotKey setAction:@selector(toggleShuffle)];
446 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
449 if ([df objectForKey:@"IncrementVolume"] != nil) {
450 ITDebugLog(@"Setting up increment volume hot key.");
451 hotKey = [[ITHotKey alloc] init];
452 [hotKey setName:@"IncrementVolume"];
453 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
454 [hotKey setTarget:self];
455 [hotKey setAction:@selector(incrementVolume)];
456 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
459 if ([df objectForKey:@"DecrementVolume"] != nil) {
460 ITDebugLog(@"Setting up decrement volume hot key.");
461 hotKey = [[ITHotKey alloc] init];
462 [hotKey setName:@"DecrementVolume"];
463 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
464 [hotKey setTarget:self];
465 [hotKey setAction:@selector(decrementVolume)];
466 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
469 if ([df objectForKey:@"IncrementRating"] != nil) {
470 ITDebugLog(@"Setting up increment rating hot key.");
471 hotKey = [[ITHotKey alloc] init];
472 [hotKey setName:@"IncrementRating"];
473 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
474 [hotKey setTarget:self];
475 [hotKey setAction:@selector(incrementRating)];
476 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
479 if ([df objectForKey:@"DecrementRating"] != nil) {
480 ITDebugLog(@"Setting up decrement rating hot key.");
481 hotKey = [[ITHotKey alloc] init];
482 [hotKey setName:@"DecrementRating"];
483 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
484 [hotKey setTarget:self];
485 [hotKey setAction:@selector(decrementRating)];
486 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
488 ITDebugLog(@"Finished setting up hot keys.");
491 - (void)showCurrentTrackInfo
493 ITMTRemotePlayerSource source = [currentRemote currentSource];
494 NSString *title = [currentRemote currentSongTitle];
495 NSString *album = nil;
496 NSString *artist = nil;
497 NSString *time = nil;
498 NSString *track = nil;
501 ITDebugLog(@"Showing track info status window.");
505 if ( [df boolForKey:@"showAlbum"] ) {
506 album = [currentRemote currentSongAlbum];
509 if ( [df boolForKey:@"showArtist"] ) {
510 artist = [currentRemote currentSongArtist];
513 if ( [df boolForKey:@"showTime"] ) {
514 time = [NSString stringWithFormat:@"%@: %@ / %@",
516 [currentRemote currentSongElapsed],
517 [currentRemote currentSongLength]];
520 if ( [df boolForKey:@"showTrackNumber"] ) {
521 int trackNo = [currentRemote currentSongTrack];
522 int trackCount = [currentRemote currentAlbumTrackCount];
524 if ( (trackNo > 0) || (trackCount > 0) ) {
525 track = [NSString stringWithFormat:@"%@: %i %@ %i",
526 @"Track", trackNo, @"of", trackCount];
530 if ( [df boolForKey:@"showTrackRating"] ) {
531 rating = ( [currentRemote currentSongRating] * 5 );
535 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
538 [statusWindowController showSongInfoWindowWithSource:source
547 - (void)showUpcomingSongs
549 int curPlaylist = [currentRemote currentPlaylistIndex];
550 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
551 ITDebugLog(@"Showing upcoming songs status window.");
553 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
554 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
555 int curTrack = [currentRemote currentSongIndex];
558 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
560 [songList addObject:[currentRemote songTitleAtIndex:i]];
564 [statusWindowController showUpcomingSongsWindowWithTitles:songList];
567 [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
571 - (void)incrementVolume
573 float volume = [currentRemote volume];
574 float dispVol = volume;
575 ITDebugLog(@"Incrementing volume.");
584 ITDebugLog(@"Setting volume to %f", volume);
585 [currentRemote setVolume:volume];
587 // Show volume status window
588 [statusWindowController showVolumeWindowWithLevel:dispVol];
591 - (void)decrementVolume
593 float volume = [currentRemote volume];
594 float dispVol = volume;
595 ITDebugLog(@"Decrementing volume.");
604 ITDebugLog(@"Setting volume to %f", volume);
605 [currentRemote setVolume:volume];
607 //Show volume status window
608 [statusWindowController showVolumeWindowWithLevel:dispVol];
611 - (void)incrementRating
613 float rating = [currentRemote currentSongRating];
614 ITDebugLog(@"Incrementing rating.");
619 ITDebugLog(@"Setting rating to %f", rating);
620 [currentRemote setCurrentSongRating:rating];
622 //Show rating status window
623 [statusWindowController showRatingWindowWithRating:rating];
626 - (void)decrementRating
628 float rating = [currentRemote currentSongRating];
629 ITDebugLog(@"Decrementing rating.");
634 ITDebugLog(@"Setting rating to %f", rating);
635 [currentRemote setCurrentSongRating:rating];
637 //Show rating status window
638 [statusWindowController showRatingWindowWithRating:rating];
643 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
644 ITDebugLog(@"Toggling repeat mode.");
645 switch (repeatMode) {
646 case ITMTRemotePlayerRepeatOff:
647 repeatMode = ITMTRemotePlayerRepeatAll;
649 case ITMTRemotePlayerRepeatAll:
650 repeatMode = ITMTRemotePlayerRepeatOne;
652 case ITMTRemotePlayerRepeatOne:
653 repeatMode = ITMTRemotePlayerRepeatOff;
656 ITDebugLog(@"Setting repeat mode to %i", repeatMode);
657 [currentRemote setRepeatMode:repeatMode];
659 //Show loop status window
660 [statusWindowController showRepeatWindowWithMode:repeatMode];
663 - (void)toggleShuffle
665 BOOL newShuffleEnabled = ( ! [currentRemote shuffleEnabled] );
666 ITDebugLog(@"Toggling shuffle mode.");
667 [currentRemote setShuffleEnabled:newShuffleEnabled];
668 //Show shuffle status window
669 ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
670 [statusWindowController showShuffleWindow:newShuffleEnabled];
673 /*************************************************************************/
675 #pragma mark WORKSPACE NOTIFICATION HANDLERS
676 /*************************************************************************/
678 - (void)applicationLaunched:(NSNotification *)note
680 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
681 ITDebugLog(@"Remote application launched.");
682 [currentRemote begin];
683 [self setLatestSongIdentifier:@""];
685 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
687 selector:@selector(timerUpdate)
689 repeats:YES] retain];
690 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
692 playerRunningState = ITMTRemotePlayerRunning;
696 - (void)applicationTerminated:(NSNotification *)note
698 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
699 ITDebugLog(@"Remote application terminated.");
700 [currentRemote halt];
701 [refreshTimer invalidate];
702 [refreshTimer release];
705 playerRunningState = ITMTRemotePlayerNotRunning;
710 /*************************************************************************/
712 #pragma mark NSApplication DELEGATE METHODS
713 /*************************************************************************/
715 - (void)applicationWillTerminate:(NSNotification *)note
718 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
722 /*************************************************************************/
724 #pragma mark DEALLOCATION METHOD
725 /*************************************************************************/
729 [self applicationTerminated:nil];
731 [statusItem release];
732 [statusWindowController release];
733 [menuController release];