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"])
87 [self applicationTerminated:nil];
91 [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
92 [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
95 - (ITMTRemote *)loadRemote
97 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
100 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
101 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
102 NSString *bundlePath;
104 while ( (bundlePath = [enumerator nextObject]) ) {
105 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
108 Class remoteClass = [remoteBundle principalClass];
110 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
111 [remoteClass isKindOfClass:[NSObject class]]) {
113 id remote = [remoteClass remote];
114 [remoteArray addObject:remote];
119 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
120 // if ( [remoteArray count] > 1 ) {
121 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
123 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
126 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
127 return [remoteArray objectAtIndex:0];
130 /*************************************************************************/
132 #pragma mark INSTANCE METHODS
133 /*************************************************************************/
135 - (void)startTimerInNewThread
137 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
138 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
139 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
141 selector:@selector(timerUpdate)
143 repeats:YES] retain];
148 - (BOOL)songIsPlaying
150 return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
153 - (BOOL)radioIsPlaying
155 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
160 return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
163 - (NSString *)latestSongIdentifier
165 return _latestSongIdentifier;
168 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
170 [_latestSongIdentifier autorelease];
171 _latestSongIdentifier = [newIdentifier copy];
176 //This huge if statement is being nasty
177 /*if ( ( [self songChanged] ) ||
178 ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) ||
179 ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) )*/
181 if ( [self songChanged] ) {
182 [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
183 latestPlaylistClass = [currentRemote currentPlaylistClass];
184 [menuController rebuildSubmenus];
186 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
187 // [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
194 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
195 [statusItem setMenu:[menuController menu]];
197 [statusItem setMenu:[menuController menuForNoPlayer]];
209 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
211 if (state == ITMTRemotePlayerPlaying) {
212 [currentRemote pause];
213 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
214 [currentRemote pause];
215 [currentRemote play];
217 [currentRemote play];
225 [currentRemote goToNextSong];
232 [currentRemote goToPreviousSong];
239 [currentRemote forward];
246 [currentRemote rewind];
251 - (void)selectPlaylistAtIndex:(int)index
253 [currentRemote switchToPlaylistAtIndex:index];
258 - (void)selectSongAtIndex:(int)index
260 [currentRemote switchToSongAtIndex:index];
265 - (void)selectSongRating:(int)rating
267 [currentRemote setCurrentSongRating:(float)rating / 100.0];
272 - (void)selectEQPresetAtIndex:(int)index
274 [currentRemote switchToEQAtIndex:index];
281 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
282 [currentRemote showPrimaryInterface];
284 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
285 NSLog(@"MenuTunes: Error Launching Player");
290 - (void)showPreferences
292 [[PreferencesController sharedPrefs] setController:self];
293 [[PreferencesController sharedPrefs] showPrefsWindow:self];
296 - (void)quitMenuTunes
298 [NSApp terminate:self];
304 - (void)closePreferences
306 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
311 - (ITMTRemote *)currentRemote
313 return currentRemote;
324 NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
325 ITHotKey *nextHotKey;
327 while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
328 [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
336 if ([df objectForKey:@"PlayPause"] != nil) {
337 hotKey = [[ITHotKey alloc] init];
338 [hotKey setName:@"PlayPause"];
339 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
340 [hotKey setTarget:self];
341 [hotKey setAction:@selector(playPause)];
342 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
345 if ([df objectForKey:@"NextTrack"] != nil) {
346 hotKey = [[ITHotKey alloc] init];
347 [hotKey setName:@"NextTrack"];
348 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
349 [hotKey setTarget:self];
350 [hotKey setAction:@selector(nextSong)];
351 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
354 if ([df objectForKey:@"PrevTrack"] != nil) {
355 hotKey = [[ITHotKey alloc] init];
356 [hotKey setName:@"PrevTrack"];
357 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
358 [hotKey setTarget:self];
359 [hotKey setAction:@selector(prevSong)];
360 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
363 if ([df objectForKey:@"ShowPlayer"] != nil) {
364 hotKey = [[ITHotKey alloc] init];
365 [hotKey setName:@"ShowPlayer"];
366 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
367 [hotKey setTarget:self];
368 [hotKey setAction:@selector(showPlayer)];
369 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
372 if ([df objectForKey:@"TrackInfo"] != nil) {
373 hotKey = [[ITHotKey alloc] init];
374 [hotKey setName:@"TrackInfo"];
375 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
376 [hotKey setTarget:self];
377 [hotKey setAction:@selector(showCurrentTrackInfo)];
378 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
381 if ([df objectForKey:@"UpcomingSongs"] != nil) {
382 hotKey = [[ITHotKey alloc] init];
383 [hotKey setName:@"UpcomingSongs"];
384 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
385 [hotKey setTarget:self];
386 [hotKey setAction:@selector(showUpcomingSongs)];
387 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
390 if ([df objectForKey:@"ToggleLoop"] != nil) {
391 hotKey = [[ITHotKey alloc] init];
392 [hotKey setName:@"ToggleLoop"];
393 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
394 [hotKey setTarget:self];
395 [hotKey setAction:@selector(toggleLoop)];
396 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
399 if ([df objectForKey:@"ToggleShuffle"] != nil) {
400 hotKey = [[ITHotKey alloc] init];
401 [hotKey setName:@"ToggleShuffle"];
402 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
403 [hotKey setTarget:self];
404 [hotKey setAction:@selector(toggleShuffle)];
405 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
408 if ([df objectForKey:@"IncrementVolume"] != nil) {
409 hotKey = [[ITHotKey alloc] init];
410 [hotKey setName:@"IncrementVolume"];
411 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
412 [hotKey setTarget:self];
413 [hotKey setAction:@selector(incrementVolume)];
414 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
417 if ([df objectForKey:@"DecrementVolume"] != nil) {
418 hotKey = [[ITHotKey alloc] init];
419 [hotKey setName:@"DecrementVolume"];
420 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
421 [hotKey setTarget:self];
422 [hotKey setAction:@selector(decrementVolume)];
423 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
426 if ([df objectForKey:@"IncrementRating"] != nil) {
427 hotKey = [[ITHotKey alloc] init];
428 [hotKey setName:@"IncrementRating"];
429 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
430 [hotKey setTarget:self];
431 [hotKey setAction:@selector(incrementRating)];
432 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
435 if ([df objectForKey:@"DecrementRating"] != nil) {
436 hotKey = [[ITHotKey alloc] init];
437 [hotKey setName:@"DecrementRating"];
438 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
439 [hotKey setTarget:self];
440 [hotKey setAction:@selector(decrementRating)];
441 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
445 - (void)showCurrentTrackInfo
447 ITMTRemotePlayerSource source = [currentRemote currentSource];
448 NSString *title = [currentRemote currentSongTitle];
449 NSString *album = nil;
450 NSString *artist = nil;
451 NSString *time = nil;
458 if ( [df boolForKey:@"showAlbum"] ) {
459 album = [currentRemote currentSongAlbum];
462 if ( [df boolForKey:@"showArtist"] ) {
463 artist = [currentRemote currentSongArtist];
466 if ( [df boolForKey:@"showTime"] ) {
467 time = [currentRemote currentSongLength];
470 if ( [df boolForKey:@"showNumber"] ) {
471 trackNumber = [currentRemote currentSongTrack];
472 trackTotal = [currentRemote currentAlbumTrackCount];
475 if ( [df boolForKey:@"showRating"] ) {
476 rating = ( [currentRemote currentSongRating] * 5 );
480 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
483 [statusWindowController showSongInfoWindowWithSource:source
488 trackNumber:trackNumber
489 trackTotal:trackTotal
493 - (void)showUpcomingSongs
495 int curPlaylist = [currentRemote currentPlaylistIndex];
496 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
499 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
500 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
501 int curTrack = [currentRemote currentSongIndex];
504 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
506 [songList addObject:[currentRemote songTitleAtIndex:i]];
510 [statusWindowController showUpcomingSongsWindowWithTitles:songList];
513 [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
517 - (void)incrementVolume
519 float volume = [currentRemote volume];
520 float dispVol = volume;
530 [currentRemote setVolume:volume];
532 // Show volume status window
533 [statusWindowController showVolumeWindowWithLevel:dispVol];
536 - (void)decrementVolume
538 float volume = [currentRemote volume];
539 float dispVol = volume;
549 [currentRemote setVolume:volume];
551 //Show volume status window
552 [statusWindowController showVolumeWindowWithLevel:dispVol];
555 - (void)incrementRating
557 float rating = [currentRemote currentSongRating];
562 [currentRemote setCurrentSongRating:rating];
564 //Show rating status window
565 [statusWindowController showRatingWindowWithRating:rating];
568 - (void)decrementRating
570 float rating = [currentRemote currentSongRating];
575 [currentRemote setCurrentSongRating:rating];
577 //Show rating status window
578 [statusWindowController showRatingWindowWithRating:rating];
583 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
584 switch (repeatMode) {
585 case ITMTRemotePlayerRepeatOff:
586 repeatMode = ITMTRemotePlayerRepeatAll;
588 case ITMTRemotePlayerRepeatAll:
589 repeatMode = ITMTRemotePlayerRepeatOne;
591 case ITMTRemotePlayerRepeatOne:
592 repeatMode = ITMTRemotePlayerRepeatOff;
595 [currentRemote setRepeatMode:repeatMode];
597 //Show loop status window
598 [statusWindowController showRepeatWindowWithMode:repeatMode];
601 - (void)toggleShuffle
603 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
604 [currentRemote setShuffleEnabled:newShuffleEnabled];
605 //Show shuffle status window
606 [statusWindowController showRepeatWindowWithMode:newShuffleEnabled];
609 /*************************************************************************/
611 #pragma mark WORKSPACE NOTIFICATION HANDLERS
612 /*************************************************************************/
614 - (void)applicationLaunched:(NSNotification *)note
616 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
617 [currentRemote begin];
618 [self setLatestSongIdentifier:@""];
620 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
622 selector:@selector(timerUpdate)
624 repeats:YES] retain];
625 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
627 playerRunningState = ITMTRemotePlayerRunning;
631 - (void)applicationTerminated:(NSNotification *)note
633 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
634 [currentRemote halt];
635 [refreshTimer invalidate];
636 [refreshTimer release];
639 playerRunningState = ITMTRemotePlayerNotRunning;
644 /*************************************************************************/
646 #pragma mark NSApplication DELEGATE METHODS
647 /*************************************************************************/
649 - (void)applicationWillTerminate:(NSNotification *)note
652 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
656 /*************************************************************************/
658 #pragma mark DEALLOCATION METHOD
659 /*************************************************************************/
663 [self applicationTerminated:nil];
664 [statusItem release];
665 [statusWindowController release];
666 [menuController release];