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 alloc] init];
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"]];
94 - (ITMTRemote *)loadRemote
96 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
97 ITDebugLog(@"Gathering remotes.");
99 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
100 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
101 NSString *bundlePath;
103 while ( (bundlePath = [enumerator nextObject]) ) {
104 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
107 Class remoteClass = [remoteBundle principalClass];
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];
118 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
119 // if ( [remoteArray count] > 1 ) {
120 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
122 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
125 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
126 return [remoteArray objectAtIndex:0];
129 /*************************************************************************/
131 #pragma mark INSTANCE METHODS
132 /*************************************************************************/
134 /*- (void)startTimerInNewThread
136 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
137 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
138 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
140 selector:@selector(timerUpdate)
142 repeats:YES] retain];
144 ITDebugLog(@"Timer started.");
150 NSDate *now = [NSDate date];
151 if ( (! blingDate) || ([now timeIntervalSinceDate:blingDate] >= 86400) ) {
152 [bling showPanelIfNeeded];
153 [blingDate autorelease];
154 blingDate = [now retain];
161 [blingDate autorelease];
162 blingDate = [[NSDate date] retain];
167 if ( ! ([bling checkDone] == 2475) ) {
174 - (BOOL)songIsPlaying
176 return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
179 - (BOOL)radioIsPlaying
181 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
186 return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
189 - (NSString *)latestSongIdentifier
191 return _latestSongIdentifier;
194 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
196 ITDebugLog(@"Setting latest song identifier to %@", newIdentifier);
197 [_latestSongIdentifier autorelease];
198 _latestSongIdentifier = [newIdentifier copy];
203 if ( [self songChanged] ) {
204 ITDebugLog(@"The song changed.");
205 [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
206 latestPlaylistClass = [currentRemote currentPlaylistClass];
207 [menuController rebuildSubmenus];
209 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
210 // [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
217 ITDebugLog(@"Menu clicked.");
218 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
219 [statusItem setMenu:[menuController menu]];
221 [statusItem setMenu:[menuController menuForNoPlayer]];
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];
241 [currentRemote play];
248 ITDebugLog(@"Going to next song.");
249 [currentRemote goToNextSong];
255 ITDebugLog(@"Going to previous song.");
256 [currentRemote goToPreviousSong];
262 ITDebugLog(@"Fast forwarding.");
263 [currentRemote forward];
269 ITDebugLog(@"Rewinding.");
270 [currentRemote rewind];
274 - (void)selectPlaylistAtIndex:(int)index
276 ITDebugLog(@"Selecting playlist %i", index);
277 [currentRemote switchToPlaylistAtIndex:index];
281 - (void)selectSongAtIndex:(int)index
283 ITDebugLog(@"Selecting song %i", index);
284 [currentRemote switchToSongAtIndex:index];
288 - (void)selectSongRating:(int)rating
290 ITDebugLog(@"Selecting song rating %i", rating);
291 [currentRemote setCurrentSongRating:(float)rating / 100.0];
295 - (void)selectEQPresetAtIndex:(int)index
297 ITDebugLog(@"Selecting EQ preset %i", index);
298 [currentRemote switchToEQAtIndex:index];
304 ITDebugLog(@"Beginning show player.");
305 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
306 ITDebugLog(@"Showing player interface.");
307 [currentRemote showPrimaryInterface];
309 ITDebugLog(@"Launching player.");
310 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
311 ITDebugLog(@"Error Launching Player");
314 ITDebugLog(@"Finished show player.");
317 - (void)showPreferences
319 ITDebugLog(@"Show preferences.");
320 [[PreferencesController sharedPrefs] setController:self];
321 [[PreferencesController sharedPrefs] showPrefsWindow:self];
324 - (void)quitMenuTunes
326 ITDebugLog(@"Quitting MenuTunes.");
327 [NSApp terminate:self];
333 - (void)closePreferences
335 ITDebugLog(@"Preferences closed.");
336 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
341 - (ITMTRemote *)currentRemote
343 return currentRemote;
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];
360 ITDebugLog(@"Done clearing hot keys.");
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]];
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]];
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]];
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]];
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]];
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]];
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]];
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]];
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]];
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]];
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]];
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]];
486 ITDebugLog(@"Finished setting up hot keys.");
489 - (void)showCurrentTrackInfo
491 ITMTRemotePlayerSource source = [currentRemote currentSource];
492 NSString *title = [currentRemote currentSongTitle];
493 NSString *album = nil;
494 NSString *artist = nil;
495 NSString *time = nil;
499 ITDebugLog(@"Showing track info status window.");
502 if ( [df boolForKey:@"showAlbum"] ) {
503 album = [currentRemote currentSongAlbum];
506 if ( [df boolForKey:@"showArtist"] ) {
507 artist = [currentRemote currentSongArtist];
510 if ( [df boolForKey:@"showTime"] ) {
511 time = [currentRemote currentSongLength];
514 if ( [df boolForKey:@"showNumber"] ) {
515 trackNumber = [currentRemote currentSongTrack];
516 trackTotal = [currentRemote currentAlbumTrackCount];
519 if ( [df boolForKey:@"showRating"] ) {
520 rating = ( [currentRemote currentSongRating] * 5 );
524 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
527 [statusWindowController showSongInfoWindowWithSource:source
532 trackNumber:trackNumber
533 trackTotal:trackTotal
537 - (void)showUpcomingSongs
539 int curPlaylist = [currentRemote currentPlaylistIndex];
540 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
541 ITDebugLog(@"Showing upcoming songs status window.");
543 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
544 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
545 int curTrack = [currentRemote currentSongIndex];
548 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
550 [songList addObject:[currentRemote songTitleAtIndex:i]];
554 [statusWindowController showUpcomingSongsWindowWithTitles:songList];
557 [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
561 - (void)incrementVolume
563 float volume = [currentRemote volume];
564 float dispVol = volume;
565 ITDebugLog(@"Incrementing volume.");
574 ITDebugLog(@"Setting volume to %f", volume);
575 [currentRemote setVolume:volume];
577 // Show volume status window
578 [statusWindowController showVolumeWindowWithLevel:dispVol];
581 - (void)decrementVolume
583 float volume = [currentRemote volume];
584 float dispVol = volume;
585 ITDebugLog(@"Decrementing volume.");
594 ITDebugLog(@"Setting volume to %f", volume);
595 [currentRemote setVolume:volume];
597 //Show volume status window
598 [statusWindowController showVolumeWindowWithLevel:dispVol];
601 - (void)incrementRating
603 float rating = [currentRemote currentSongRating];
604 ITDebugLog(@"Incrementing rating.");
609 ITDebugLog(@"Setting rating to %f", rating);
610 [currentRemote setCurrentSongRating:rating];
612 //Show rating status window
613 [statusWindowController showRatingWindowWithRating:rating];
616 - (void)decrementRating
618 float rating = [currentRemote currentSongRating];
619 ITDebugLog(@"Decrementing rating.");
624 ITDebugLog(@"Setting rating to %f", rating);
625 [currentRemote setCurrentSongRating:rating];
627 //Show rating status window
628 [statusWindowController showRatingWindowWithRating:rating];
633 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
634 ITDebugLog(@"Toggling repeat mode.");
635 switch (repeatMode) {
636 case ITMTRemotePlayerRepeatOff:
637 repeatMode = ITMTRemotePlayerRepeatAll;
639 case ITMTRemotePlayerRepeatAll:
640 repeatMode = ITMTRemotePlayerRepeatOne;
642 case ITMTRemotePlayerRepeatOne:
643 repeatMode = ITMTRemotePlayerRepeatOff;
646 ITDebugLog(@"Setting repeat mode to %i", repeatMode);
647 [currentRemote setRepeatMode:repeatMode];
649 //Show loop status window
650 [statusWindowController showRepeatWindowWithMode:repeatMode];
653 - (void)toggleShuffle
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];
663 /*************************************************************************/
665 #pragma mark WORKSPACE NOTIFICATION HANDLERS
666 /*************************************************************************/
668 - (void)applicationLaunched:(NSNotification *)note
670 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
671 ITDebugLog(@"Remote application launched.");
672 [currentRemote begin];
673 [self setLatestSongIdentifier:@""];
675 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
677 selector:@selector(timerUpdate)
679 repeats:YES] retain];
680 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
682 playerRunningState = ITMTRemotePlayerRunning;
686 - (void)applicationTerminated:(NSNotification *)note
688 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
689 ITDebugLog(@"Remote application terminated.");
690 [currentRemote halt];
691 [refreshTimer invalidate];
692 [refreshTimer release];
695 playerRunningState = ITMTRemotePlayerNotRunning;
700 /*************************************************************************/
702 #pragma mark NSApplication DELEGATE METHODS
703 /*************************************************************************/
705 - (void)applicationWillTerminate:(NSNotification *)note
708 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
712 /*************************************************************************/
714 #pragma mark DEALLOCATION METHOD
715 /*************************************************************************/
719 [self applicationTerminated:nil];
721 [statusItem release];
722 [statusWindowController release];
723 [menuController release];