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 currentRemote = [self loadRemote];
54 [currentRemote begin];
56 //Setup for notification of the remote player launching or quitting
57 [[[NSWorkspace sharedWorkspace] notificationCenter]
59 selector:@selector(applicationTerminated:)
60 name:NSWorkspaceDidTerminateApplicationNotification
63 [[[NSWorkspace sharedWorkspace] notificationCenter]
65 selector:@selector(applicationLaunched:)
66 name:NSWorkspaceDidLaunchApplicationNotification
69 if ( ! [df objectForKey:@"menu"] ) { // If this is nil, defaults have never been registered.
70 [[PreferencesController sharedPrefs] registerDefaults];
73 [StatusItemHack install];
74 statusItem = [[ITStatusItem alloc]
75 initWithStatusBar:[NSStatusBar systemStatusBar]
76 withLength:NSSquareStatusItemLength];
78 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
79 [self applicationLaunched:nil];
81 if ([df boolForKey:@"LaunchPlayerWithMT"])
84 [self applicationTerminated:nil];
87 [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
88 [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
91 - (ITMTRemote *)loadRemote
93 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
94 ITDebugLog(@"Gathering remotes.");
96 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
97 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
100 while ( (bundlePath = [enumerator nextObject]) ) {
101 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
104 Class remoteClass = [remoteBundle principalClass];
106 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
107 [remoteClass isKindOfClass:[NSObject class]]) {
108 id remote = [remoteClass remote];
109 ITDebugLog(@"Adding remote at path %@", bundlePath);
110 [remoteArray addObject:remote];
115 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
116 // if ( [remoteArray count] > 1 ) {
117 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
119 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
122 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
123 return [remoteArray objectAtIndex:0];
126 /*************************************************************************/
128 #pragma mark INSTANCE METHODS
129 /*************************************************************************/
131 /*- (void)startTimerInNewThread
133 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
134 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
135 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
137 selector:@selector(timerUpdate)
139 repeats:YES] retain];
141 ITDebugLog(@"Timer started.");
145 - (BOOL)songIsPlaying
147 return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
150 - (BOOL)radioIsPlaying
152 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
157 return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
160 - (NSString *)latestSongIdentifier
162 return _latestSongIdentifier;
165 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
167 ITDebugLog(@"Setting latest song identifier to %@", newIdentifier);
168 [_latestSongIdentifier autorelease];
169 _latestSongIdentifier = [newIdentifier copy];
174 if ( [self songChanged] ) {
175 ITDebugLog(@"The song changed.");
176 [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
177 latestPlaylistClass = [currentRemote currentPlaylistClass];
178 [menuController rebuildSubmenus];
180 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
181 // [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
188 ITDebugLog(@"Menu clicked.");
189 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
190 [statusItem setMenu:[menuController menu]];
192 [statusItem setMenu:[menuController menuForNoPlayer]];
204 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
205 ITDebugLog(@"Play/Pause toggled");
206 if (state == ITMTRemotePlayerPlaying) {
207 [currentRemote pause];
208 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
209 [currentRemote pause];
210 [currentRemote play];
212 [currentRemote play];
219 ITDebugLog(@"Going to next song.");
220 [currentRemote goToNextSong];
226 ITDebugLog(@"Going to previous song.");
227 [currentRemote goToPreviousSong];
233 ITDebugLog(@"Fast forwarding.");
234 [currentRemote forward];
240 ITDebugLog(@"Rewinding.");
241 [currentRemote rewind];
245 - (void)selectPlaylistAtIndex:(int)index
247 ITDebugLog(@"Selecting playlist %i", index);
248 [currentRemote switchToPlaylistAtIndex:index];
252 - (void)selectSongAtIndex:(int)index
254 ITDebugLog(@"Selecting song %i", index);
255 [currentRemote switchToSongAtIndex:index];
259 - (void)selectSongRating:(int)rating
261 ITDebugLog(@"Selecting song rating %i", rating);
262 [currentRemote setCurrentSongRating:(float)rating / 100.0];
266 - (void)selectEQPresetAtIndex:(int)index
268 ITDebugLog(@"Selecting EQ preset %i", index);
269 [currentRemote switchToEQAtIndex:index];
275 ITDebugLog(@"Beginning show player.");
276 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
277 ITDebugLog(@"Showing player interface.");
278 [currentRemote showPrimaryInterface];
280 ITDebugLog(@"Launching player.");
281 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
282 ITDebugLog(@"Error Launching Player");
285 ITDebugLog(@"Finished show player.");
288 - (void)showPreferences
290 ITDebugLog(@"Show preferences.");
291 [[PreferencesController sharedPrefs] setController:self];
292 [[PreferencesController sharedPrefs] showPrefsWindow:self];
295 - (void)quitMenuTunes
297 ITDebugLog(@"Quitting MenuTunes.");
298 [NSApp terminate:self];
304 - (void)closePreferences
306 ITDebugLog(@"Preferences closed.");
307 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
312 - (ITMTRemote *)currentRemote
314 return currentRemote;
325 NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
326 ITHotKey *nextHotKey;
327 ITDebugLog(@"Clearing hot keys.");
328 while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
329 [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
331 ITDebugLog(@"Done clearing hot keys.");
337 ITDebugLog(@"Setting up hot keys.");
338 if ([df objectForKey:@"PlayPause"] != nil) {
339 ITDebugLog(@"Setting up play pause hot key.");
340 hotKey = [[ITHotKey alloc] init];
341 [hotKey setName:@"PlayPause"];
342 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
343 [hotKey setTarget:self];
344 [hotKey setAction:@selector(playPause)];
345 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
348 if ([df objectForKey:@"NextTrack"] != nil) {
349 ITDebugLog(@"Setting up next track hot key.");
350 hotKey = [[ITHotKey alloc] init];
351 [hotKey setName:@"NextTrack"];
352 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
353 [hotKey setTarget:self];
354 [hotKey setAction:@selector(nextSong)];
355 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
358 if ([df objectForKey:@"PrevTrack"] != nil) {
359 ITDebugLog(@"Setting up previous track hot key.");
360 hotKey = [[ITHotKey alloc] init];
361 [hotKey setName:@"PrevTrack"];
362 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
363 [hotKey setTarget:self];
364 [hotKey setAction:@selector(prevSong)];
365 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
368 if ([df objectForKey:@"ShowPlayer"] != nil) {
369 ITDebugLog(@"Setting up show player hot key.");
370 hotKey = [[ITHotKey alloc] init];
371 [hotKey setName:@"ShowPlayer"];
372 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
373 [hotKey setTarget:self];
374 [hotKey setAction:@selector(showPlayer)];
375 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
378 if ([df objectForKey:@"TrackInfo"] != nil) {
379 ITDebugLog(@"Setting up track info hot key.");
380 hotKey = [[ITHotKey alloc] init];
381 [hotKey setName:@"TrackInfo"];
382 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
383 [hotKey setTarget:self];
384 [hotKey setAction:@selector(showCurrentTrackInfo)];
385 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
388 if ([df objectForKey:@"UpcomingSongs"] != nil) {
389 ITDebugLog(@"Setting up upcoming songs hot key.");
390 hotKey = [[ITHotKey alloc] init];
391 [hotKey setName:@"UpcomingSongs"];
392 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
393 [hotKey setTarget:self];
394 [hotKey setAction:@selector(showUpcomingSongs)];
395 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
398 if ([df objectForKey:@"ToggleLoop"] != nil) {
399 ITDebugLog(@"Setting up toggle loop hot key.");
400 hotKey = [[ITHotKey alloc] init];
401 [hotKey setName:@"ToggleLoop"];
402 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
403 [hotKey setTarget:self];
404 [hotKey setAction:@selector(toggleLoop)];
405 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
408 if ([df objectForKey:@"ToggleShuffle"] != nil) {
409 ITDebugLog(@"Setting up toggle shuffle hot key.");
410 hotKey = [[ITHotKey alloc] init];
411 [hotKey setName:@"ToggleShuffle"];
412 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
413 [hotKey setTarget:self];
414 [hotKey setAction:@selector(toggleShuffle)];
415 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
418 if ([df objectForKey:@"IncrementVolume"] != nil) {
419 ITDebugLog(@"Setting up increment volume hot key.");
420 hotKey = [[ITHotKey alloc] init];
421 [hotKey setName:@"IncrementVolume"];
422 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
423 [hotKey setTarget:self];
424 [hotKey setAction:@selector(incrementVolume)];
425 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
428 if ([df objectForKey:@"DecrementVolume"] != nil) {
429 ITDebugLog(@"Setting up decrement volume hot key.");
430 hotKey = [[ITHotKey alloc] init];
431 [hotKey setName:@"DecrementVolume"];
432 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
433 [hotKey setTarget:self];
434 [hotKey setAction:@selector(decrementVolume)];
435 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
438 if ([df objectForKey:@"IncrementRating"] != nil) {
439 ITDebugLog(@"Setting up increment rating hot key.");
440 hotKey = [[ITHotKey alloc] init];
441 [hotKey setName:@"IncrementRating"];
442 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
443 [hotKey setTarget:self];
444 [hotKey setAction:@selector(incrementRating)];
445 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
448 if ([df objectForKey:@"DecrementRating"] != nil) {
449 ITDebugLog(@"Setting up decrement rating hot key.");
450 hotKey = [[ITHotKey alloc] init];
451 [hotKey setName:@"DecrementRating"];
452 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
453 [hotKey setTarget:self];
454 [hotKey setAction:@selector(decrementRating)];
455 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
457 ITDebugLog(@"Finished setting up hot keys.");
460 - (void)showCurrentTrackInfo
462 ITMTRemotePlayerSource source = [currentRemote currentSource];
463 NSString *title = [currentRemote currentSongTitle];
464 NSString *album = nil;
465 NSString *artist = nil;
466 NSString *time = nil;
470 ITDebugLog(@"Showing track info status window.");
473 if ( [df boolForKey:@"showAlbum"] ) {
474 album = [currentRemote currentSongAlbum];
477 if ( [df boolForKey:@"showArtist"] ) {
478 artist = [currentRemote currentSongArtist];
481 if ( [df boolForKey:@"showTime"] ) {
482 time = [currentRemote currentSongLength];
485 if ( [df boolForKey:@"showNumber"] ) {
486 trackNumber = [currentRemote currentSongTrack];
487 trackTotal = [currentRemote currentAlbumTrackCount];
490 if ( [df boolForKey:@"showRating"] ) {
491 rating = ( [currentRemote currentSongRating] * 5 );
495 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
498 [statusWindowController showSongInfoWindowWithSource:source
503 trackNumber:trackNumber
504 trackTotal:trackTotal
508 - (void)showUpcomingSongs
510 int curPlaylist = [currentRemote currentPlaylistIndex];
511 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
512 ITDebugLog(@"Showing upcoming songs status window.");
514 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
515 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
516 int curTrack = [currentRemote currentSongIndex];
519 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
521 [songList addObject:[currentRemote songTitleAtIndex:i]];
525 [statusWindowController showUpcomingSongsWindowWithTitles:songList];
528 [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
532 - (void)incrementVolume
534 float volume = [currentRemote volume];
535 float dispVol = volume;
536 ITDebugLog(@"Incrementing volume.");
545 ITDebugLog(@"Setting volume to %f", volume);
546 [currentRemote setVolume:volume];
548 // Show volume status window
549 [statusWindowController showVolumeWindowWithLevel:dispVol];
552 - (void)decrementVolume
554 float volume = [currentRemote volume];
555 float dispVol = volume;
556 ITDebugLog(@"Decrementing volume.");
565 ITDebugLog(@"Setting volume to %f", volume);
566 [currentRemote setVolume:volume];
568 //Show volume status window
569 [statusWindowController showVolumeWindowWithLevel:dispVol];
572 - (void)incrementRating
574 float rating = [currentRemote currentSongRating];
575 ITDebugLog(@"Incrementing rating.");
580 ITDebugLog(@"Setting rating to %f", rating);
581 [currentRemote setCurrentSongRating:rating];
583 //Show rating status window
584 [statusWindowController showRatingWindowWithRating:rating];
587 - (void)decrementRating
589 float rating = [currentRemote currentSongRating];
590 ITDebugLog(@"Decrementing rating.");
595 ITDebugLog(@"Setting rating to %f", rating);
596 [currentRemote setCurrentSongRating:rating];
598 //Show rating status window
599 [statusWindowController showRatingWindowWithRating:rating];
604 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
605 ITDebugLog(@"Toggling repeat mode.");
606 switch (repeatMode) {
607 case ITMTRemotePlayerRepeatOff:
608 repeatMode = ITMTRemotePlayerRepeatAll;
610 case ITMTRemotePlayerRepeatAll:
611 repeatMode = ITMTRemotePlayerRepeatOne;
613 case ITMTRemotePlayerRepeatOne:
614 repeatMode = ITMTRemotePlayerRepeatOff;
617 ITDebugLog(@"Setting repeat mode to %i", repeatMode);
618 [currentRemote setRepeatMode:repeatMode];
620 //Show loop status window
621 [statusWindowController showRepeatWindowWithMode:repeatMode];
624 - (void)toggleShuffle
626 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
627 ITDebugLog(@"Toggling shuffle mode.");
628 [currentRemote setShuffleEnabled:newShuffleEnabled];
629 //Show shuffle status window
630 ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
631 [statusWindowController showRepeatWindowWithMode:newShuffleEnabled];
634 /*************************************************************************/
636 #pragma mark WORKSPACE NOTIFICATION HANDLERS
637 /*************************************************************************/
639 - (void)applicationLaunched:(NSNotification *)note
641 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
642 ITDebugLog(@"Remote application launched.");
643 [currentRemote begin];
644 [self setLatestSongIdentifier:@""];
646 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
648 selector:@selector(timerUpdate)
650 repeats:YES] retain];
651 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
653 playerRunningState = ITMTRemotePlayerRunning;
657 - (void)applicationTerminated:(NSNotification *)note
659 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
660 ITDebugLog(@"Remote application terminated.");
661 [currentRemote halt];
662 [refreshTimer invalidate];
663 [refreshTimer release];
666 playerRunningState = ITMTRemotePlayerNotRunning;
671 /*************************************************************************/
673 #pragma mark NSApplication DELEGATE METHODS
674 /*************************************************************************/
676 - (void)applicationWillTerminate:(NSNotification *)note
679 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
683 /*************************************************************************/
685 #pragma mark DEALLOCATION METHOD
686 /*************************************************************************/
690 [self applicationTerminated:nil];
691 [statusItem release];
692 [statusWindowController release];
693 [menuController release];