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];
47 - (void)applicationDidFinishLaunching:(NSNotification *)note
49 //Turn on debug mode if needed
50 if ([df boolForKey:@"ITDebugMode"]) {
54 bling = [[MTBlingController alloc] init];
57 currentRemote = [self loadRemote];
58 [currentRemote begin];
60 //Setup for notification of the remote player launching or quitting
61 [[[NSWorkspace sharedWorkspace] notificationCenter]
63 selector:@selector(applicationTerminated:)
64 name:NSWorkspaceDidTerminateApplicationNotification
67 [[[NSWorkspace sharedWorkspace] notificationCenter]
69 selector:@selector(applicationLaunched:)
70 name:NSWorkspaceDidLaunchApplicationNotification
73 if ( ! [df objectForKey:@"menu"] ) { // If this is nil, defaults have never been registered.
74 [[PreferencesController sharedPrefs] registerDefaults];
77 [StatusItemHack install];
78 statusItem = [[ITStatusItem alloc]
79 initWithStatusBar:[NSStatusBar systemStatusBar]
80 withLength:NSSquareStatusItemLength];
82 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
83 [self applicationLaunched:nil];
85 if ([df boolForKey:@"LaunchPlayerWithMT"])
88 [self applicationTerminated:nil];
91 [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
92 [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
97 - (ITMTRemote *)loadRemote
99 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
100 ITDebugLog(@"Gathering remotes.");
102 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
103 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
104 NSString *bundlePath;
106 while ( (bundlePath = [enumerator nextObject]) ) {
107 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
110 Class remoteClass = [remoteBundle principalClass];
112 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
113 [remoteClass isKindOfClass:[NSObject class]]) {
114 id remote = [remoteClass remote];
115 ITDebugLog(@"Adding remote at path %@", bundlePath);
116 [remoteArray addObject:remote];
121 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
122 // if ( [remoteArray count] > 1 ) {
123 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
125 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
128 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
129 return [remoteArray objectAtIndex:0];
132 /*************************************************************************/
134 #pragma mark INSTANCE METHODS
135 /*************************************************************************/
137 /*- (void)startTimerInNewThread
139 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
140 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
141 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
143 selector:@selector(timerUpdate)
145 repeats:YES] retain];
147 ITDebugLog(@"Timer started.");
153 NSDate *now = [NSDate date];
154 if ( (! blingDate) || ([now timeIntervalSinceDate:blingDate] >= 86400) ) {
155 [bling showPanelIfNeeded];
156 [blingDate autorelease];
157 blingDate = [now retain];
164 [blingDate autorelease];
165 blingDate = [[NSDate date] retain];
170 if ( ! ([bling checkDone] == 2475) ) {
177 - (BOOL)songIsPlaying
179 return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
182 - (BOOL)radioIsPlaying
184 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
189 return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
192 - (NSString *)latestSongIdentifier
194 return _latestSongIdentifier;
197 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
199 ITDebugLog(@"Setting latest song identifier to %@", newIdentifier);
200 [_latestSongIdentifier autorelease];
201 _latestSongIdentifier = [newIdentifier copy];
206 if ( [self songChanged] && (timerUpdating != YES) ) {
207 ITDebugLog(@"The song changed.");
209 latestPlaylistClass = [currentRemote currentPlaylistClass];
210 [menuController rebuildSubmenus];
212 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
213 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
216 [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
224 ITDebugLog(@"Menu clicked.");
225 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
226 [statusItem setMenu:[menuController menu]];
228 [statusItem setMenu:[menuController menuForNoPlayer]];
240 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
241 ITDebugLog(@"Play/Pause toggled");
242 if (state == ITMTRemotePlayerPlaying) {
243 [currentRemote pause];
244 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
245 [currentRemote pause];
246 [currentRemote play];
248 [currentRemote play];
255 ITDebugLog(@"Going to next song.");
256 [currentRemote goToNextSong];
262 ITDebugLog(@"Going to previous song.");
263 [currentRemote goToPreviousSong];
269 ITDebugLog(@"Fast forwarding.");
270 [currentRemote forward];
276 ITDebugLog(@"Rewinding.");
277 [currentRemote rewind];
281 - (void)selectPlaylistAtIndex:(int)index
283 ITDebugLog(@"Selecting playlist %i", index);
284 [currentRemote switchToPlaylistAtIndex:index];
288 - (void)selectSongAtIndex:(int)index
290 ITDebugLog(@"Selecting song %i", index);
291 [currentRemote switchToSongAtIndex:index];
295 - (void)selectSongRating:(int)rating
297 ITDebugLog(@"Selecting song rating %i", rating);
298 [currentRemote setCurrentSongRating:(float)rating / 100.0];
302 - (void)selectEQPresetAtIndex:(int)index
304 ITDebugLog(@"Selecting EQ preset %i", index);
305 [currentRemote switchToEQAtIndex:index];
311 ITDebugLog(@"Beginning show player.");
312 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
313 ITDebugLog(@"Showing player interface.");
314 [currentRemote showPrimaryInterface];
316 ITDebugLog(@"Launching player.");
317 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
318 ITDebugLog(@"Error Launching Player");
321 ITDebugLog(@"Finished show player.");
324 - (void)showPreferences
326 ITDebugLog(@"Show preferences.");
327 [[PreferencesController sharedPrefs] setController:self];
328 [[PreferencesController sharedPrefs] showPrefsWindow:self];
331 - (void)quitMenuTunes
333 ITDebugLog(@"Quitting MenuTunes.");
334 [NSApp terminate:self];
340 - (void)closePreferences
342 ITDebugLog(@"Preferences closed.");
343 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
348 - (ITMTRemote *)currentRemote
350 return currentRemote;
361 NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
362 ITHotKey *nextHotKey;
363 ITDebugLog(@"Clearing hot keys.");
364 while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
365 [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
367 ITDebugLog(@"Done clearing hot keys.");
373 ITDebugLog(@"Setting up hot keys.");
374 if ([df objectForKey:@"PlayPause"] != nil) {
375 ITDebugLog(@"Setting up play pause hot key.");
376 hotKey = [[ITHotKey alloc] init];
377 [hotKey setName:@"PlayPause"];
378 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
379 [hotKey setTarget:self];
380 [hotKey setAction:@selector(playPause)];
381 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
384 if ([df objectForKey:@"NextTrack"] != nil) {
385 ITDebugLog(@"Setting up next track hot key.");
386 hotKey = [[ITHotKey alloc] init];
387 [hotKey setName:@"NextTrack"];
388 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
389 [hotKey setTarget:self];
390 [hotKey setAction:@selector(nextSong)];
391 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
394 if ([df objectForKey:@"PrevTrack"] != nil) {
395 ITDebugLog(@"Setting up previous track hot key.");
396 hotKey = [[ITHotKey alloc] init];
397 [hotKey setName:@"PrevTrack"];
398 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
399 [hotKey setTarget:self];
400 [hotKey setAction:@selector(prevSong)];
401 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
404 if ([df objectForKey:@"ShowPlayer"] != nil) {
405 ITDebugLog(@"Setting up show player hot key.");
406 hotKey = [[ITHotKey alloc] init];
407 [hotKey setName:@"ShowPlayer"];
408 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
409 [hotKey setTarget:self];
410 [hotKey setAction:@selector(showPlayer)];
411 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
414 if ([df objectForKey:@"TrackInfo"] != nil) {
415 ITDebugLog(@"Setting up track info hot key.");
416 hotKey = [[ITHotKey alloc] init];
417 [hotKey setName:@"TrackInfo"];
418 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
419 [hotKey setTarget:self];
420 [hotKey setAction:@selector(showCurrentTrackInfo)];
421 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
424 if ([df objectForKey:@"UpcomingSongs"] != nil) {
425 ITDebugLog(@"Setting up upcoming songs hot key.");
426 hotKey = [[ITHotKey alloc] init];
427 [hotKey setName:@"UpcomingSongs"];
428 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
429 [hotKey setTarget:self];
430 [hotKey setAction:@selector(showUpcomingSongs)];
431 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
434 if ([df objectForKey:@"ToggleLoop"] != nil) {
435 ITDebugLog(@"Setting up toggle loop hot key.");
436 hotKey = [[ITHotKey alloc] init];
437 [hotKey setName:@"ToggleLoop"];
438 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
439 [hotKey setTarget:self];
440 [hotKey setAction:@selector(toggleLoop)];
441 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
444 if ([df objectForKey:@"ToggleShuffle"] != nil) {
445 ITDebugLog(@"Setting up toggle shuffle hot key.");
446 hotKey = [[ITHotKey alloc] init];
447 [hotKey setName:@"ToggleShuffle"];
448 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
449 [hotKey setTarget:self];
450 [hotKey setAction:@selector(toggleShuffle)];
451 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
454 if ([df objectForKey:@"IncrementVolume"] != nil) {
455 ITDebugLog(@"Setting up increment volume hot key.");
456 hotKey = [[ITHotKey alloc] init];
457 [hotKey setName:@"IncrementVolume"];
458 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
459 [hotKey setTarget:self];
460 [hotKey setAction:@selector(incrementVolume)];
461 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
464 if ([df objectForKey:@"DecrementVolume"] != nil) {
465 ITDebugLog(@"Setting up decrement volume hot key.");
466 hotKey = [[ITHotKey alloc] init];
467 [hotKey setName:@"DecrementVolume"];
468 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
469 [hotKey setTarget:self];
470 [hotKey setAction:@selector(decrementVolume)];
471 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
474 if ([df objectForKey:@"IncrementRating"] != nil) {
475 ITDebugLog(@"Setting up increment rating hot key.");
476 hotKey = [[ITHotKey alloc] init];
477 [hotKey setName:@"IncrementRating"];
478 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
479 [hotKey setTarget:self];
480 [hotKey setAction:@selector(incrementRating)];
481 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
484 if ([df objectForKey:@"DecrementRating"] != nil) {
485 ITDebugLog(@"Setting up decrement rating hot key.");
486 hotKey = [[ITHotKey alloc] init];
487 [hotKey setName:@"DecrementRating"];
488 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
489 [hotKey setTarget:self];
490 [hotKey setAction:@selector(decrementRating)];
491 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
493 ITDebugLog(@"Finished setting up hot keys.");
496 - (void)showCurrentTrackInfo
498 ITMTRemotePlayerSource source = [currentRemote currentSource];
499 NSString *title = [currentRemote currentSongTitle];
500 NSString *album = nil;
501 NSString *artist = nil;
502 NSString *time = nil;
503 NSString *track = nil;
506 ITDebugLog(@"Showing track info status window.");
510 if ( [df boolForKey:@"showAlbum"] ) {
511 album = [currentRemote currentSongAlbum];
514 if ( [df boolForKey:@"showArtist"] ) {
515 artist = [currentRemote currentSongArtist];
518 if ( [df boolForKey:@"showTime"] ) {
519 time = [NSString stringWithFormat:@"%@: %@ / %@",
521 [currentRemote currentSongElapsed],
522 [currentRemote currentSongLength]];
525 if ( [df boolForKey:@"showTrackNumber"] ) {
526 int trackNo = [currentRemote currentSongTrack];
527 int trackCount = [currentRemote currentAlbumTrackCount];
529 if ( (trackNo > 0) || (trackCount > 0) ) {
530 track = [NSString stringWithFormat:@"%@: %i %@ %i",
531 @"Track", trackNo, @"of", trackCount];
535 if ( [df boolForKey:@"showTrackRating"] ) {
536 rating = ( [currentRemote currentSongRating] * 5 );
540 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
543 [statusWindowController showSongInfoWindowWithSource:source
552 - (void)showUpcomingSongs
554 int curPlaylist = [currentRemote currentPlaylistIndex];
555 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
556 ITDebugLog(@"Showing upcoming songs status window.");
558 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
559 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
560 int curTrack = [currentRemote currentSongIndex];
563 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
565 [songList addObject:[currentRemote songTitleAtIndex:i]];
569 [statusWindowController showUpcomingSongsWindowWithTitles:songList];
572 [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
576 - (void)incrementVolume
578 float volume = [currentRemote volume];
579 float dispVol = volume;
580 ITDebugLog(@"Incrementing volume.");
589 ITDebugLog(@"Setting volume to %f", volume);
590 [currentRemote setVolume:volume];
592 // Show volume status window
593 [statusWindowController showVolumeWindowWithLevel:dispVol];
596 - (void)decrementVolume
598 float volume = [currentRemote volume];
599 float dispVol = volume;
600 ITDebugLog(@"Decrementing volume.");
609 ITDebugLog(@"Setting volume to %f", volume);
610 [currentRemote setVolume:volume];
612 //Show volume status window
613 [statusWindowController showVolumeWindowWithLevel:dispVol];
616 - (void)incrementRating
618 float rating = [currentRemote currentSongRating];
619 ITDebugLog(@"Incrementing rating.");
624 ITDebugLog(@"Setting rating to %f", rating);
625 [currentRemote setCurrentSongRating:rating];
627 //Show rating status window
628 [statusWindowController showRatingWindowWithRating:rating];
631 - (void)decrementRating
633 float rating = [currentRemote currentSongRating];
634 ITDebugLog(@"Decrementing rating.");
639 ITDebugLog(@"Setting rating to %f", rating);
640 [currentRemote setCurrentSongRating:rating];
642 //Show rating status window
643 [statusWindowController showRatingWindowWithRating:rating];
648 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
649 ITDebugLog(@"Toggling repeat mode.");
650 switch (repeatMode) {
651 case ITMTRemotePlayerRepeatOff:
652 repeatMode = ITMTRemotePlayerRepeatAll;
654 case ITMTRemotePlayerRepeatAll:
655 repeatMode = ITMTRemotePlayerRepeatOne;
657 case ITMTRemotePlayerRepeatOne:
658 repeatMode = ITMTRemotePlayerRepeatOff;
661 ITDebugLog(@"Setting repeat mode to %i", repeatMode);
662 [currentRemote setRepeatMode:repeatMode];
664 //Show loop status window
665 [statusWindowController showRepeatWindowWithMode:repeatMode];
668 - (void)toggleShuffle
670 BOOL newShuffleEnabled = ( ! [currentRemote shuffleEnabled] );
671 ITDebugLog(@"Toggling shuffle mode.");
672 [currentRemote setShuffleEnabled:newShuffleEnabled];
673 //Show shuffle status window
674 ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
675 [statusWindowController showShuffleWindow:newShuffleEnabled];
678 /*************************************************************************/
680 #pragma mark WORKSPACE NOTIFICATION HANDLERS
681 /*************************************************************************/
683 - (void)applicationLaunched:(NSNotification *)note
685 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
686 ITDebugLog(@"Remote application launched.");
687 [currentRemote begin];
688 [self setLatestSongIdentifier:@""];
690 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
692 selector:@selector(timerUpdate)
694 repeats:YES] retain];
695 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
697 playerRunningState = ITMTRemotePlayerRunning;
701 - (void)applicationTerminated:(NSNotification *)note
703 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
704 ITDebugLog(@"Remote application terminated.");
705 [currentRemote halt];
706 [refreshTimer invalidate];
707 [refreshTimer release];
710 playerRunningState = ITMTRemotePlayerNotRunning;
715 /*************************************************************************/
717 #pragma mark NSApplication DELEGATE METHODS
718 /*************************************************************************/
720 - (void)applicationWillTerminate:(NSNotification *)note
723 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
727 /*************************************************************************/
729 #pragma mark DEALLOCATION METHOD
730 /*************************************************************************/
734 [self applicationTerminated:nil];
736 [statusItem release];
737 [statusWindowController release];
738 [menuController release];