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 currentRemote = [self loadRemote];
49 [currentRemote begin];
51 //Setup for notification of the remote player launching or quitting
52 [[[NSWorkspace sharedWorkspace] notificationCenter]
54 selector:@selector(applicationTerminated:)
55 name:NSWorkspaceDidTerminateApplicationNotification
58 [[[NSWorkspace sharedWorkspace] notificationCenter]
60 selector:@selector(applicationLaunched:)
61 name:NSWorkspaceDidLaunchApplicationNotification
64 if ( ! [df objectForKey:@"menu"] ) { // If this is nil, defaults have never been registered.
65 [[PreferencesController sharedPrefs] registerDefaults];
68 [StatusItemHack install];
69 statusItem = [[ITStatusItem alloc]
70 initWithStatusBar:[NSStatusBar systemStatusBar]
71 withLength:NSSquareStatusItemLength];
73 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
74 [self applicationLaunched:nil];
76 if ([df boolForKey:@"LaunchPlayerWithMT"])
82 [self applicationTerminated:nil];
86 [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
87 [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
90 - (ITMTRemote *)loadRemote
92 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
95 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
96 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
99 while ( (bundlePath = [enumerator nextObject]) ) {
100 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
103 Class remoteClass = [remoteBundle principalClass];
105 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
106 [remoteClass isKindOfClass:[NSObject class]]) {
108 id remote = [remoteClass remote];
109 [remoteArray addObject:remote];
114 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
115 // if ( [remoteArray count] > 1 ) {
116 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
118 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
121 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
122 return [remoteArray objectAtIndex:0];
125 /*************************************************************************/
127 #pragma mark INSTANCE METHODS
128 /*************************************************************************/
130 - (void)startTimerInNewThread
132 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
133 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
134 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
136 selector:@selector(timerUpdate)
138 repeats:YES] retain];
143 - (BOOL)songIsPlaying
145 return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) );
148 - (BOOL)radioIsPlaying
150 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
155 return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
158 - (NSString *)latestSongIdentifier
160 return _latestSongIdentifier;
163 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
165 [_latestSongIdentifier autorelease];
166 _latestSongIdentifier = [newIdentifier copy];
171 //This huge if statement is being nasty
172 /*if ( ( [self songChanged] ) ||
173 ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) ||
174 ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) )*/
176 if ( [self songChanged] ) {
177 [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]];
178 latestPlaylistClass = [currentRemote currentPlaylistClass];
179 [menuController rebuildSubmenus];
181 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
182 // [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
189 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
190 [statusItem setMenu:[menuController menu]];
192 [statusItem setMenu:[menuController menuForNoPlayer]];
204 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
206 if (state == ITMTRemotePlayerPlaying) {
207 [currentRemote pause];
208 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
209 [currentRemote pause];
210 [currentRemote play];
212 [currentRemote play];
220 [currentRemote goToNextSong];
227 [currentRemote goToPreviousSong];
234 [currentRemote forward];
241 [currentRemote rewind];
246 - (void)selectPlaylistAtIndex:(int)index
248 [currentRemote switchToPlaylistAtIndex:index];
253 - (void)selectSongAtIndex:(int)index
255 [currentRemote switchToSongAtIndex:index];
260 - (void)selectSongRating:(int)rating
262 [currentRemote setCurrentSongRating:(float)rating / 100.0];
267 - (void)selectEQPresetAtIndex:(int)index
269 [currentRemote switchToEQAtIndex:index];
276 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
277 [currentRemote showPrimaryInterface];
279 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
280 NSLog(@"MenuTunes: Error Launching Player");
285 - (void)showPreferences
287 [[PreferencesController sharedPrefs] setController:self];
288 [[PreferencesController sharedPrefs] showPrefsWindow:self];
291 - (void)quitMenuTunes
293 [NSApp terminate:self];
299 - (void)closePreferences
301 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
306 - (ITMTRemote *)currentRemote
308 return currentRemote;
319 NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
320 ITHotKey *nextHotKey;
322 while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
323 [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
331 if ([df objectForKey:@"PlayPause"] != nil) {
332 hotKey = [[ITHotKey alloc] init];
333 [hotKey setName:@"PlayPause"];
334 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
335 [hotKey setTarget:self];
336 [hotKey setAction:@selector(playPause)];
337 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
340 if ([df objectForKey:@"NextTrack"] != nil) {
341 hotKey = [[ITHotKey alloc] init];
342 [hotKey setName:@"NextTrack"];
343 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
344 [hotKey setTarget:self];
345 [hotKey setAction:@selector(nextSong)];
346 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
349 if ([df objectForKey:@"PrevTrack"] != nil) {
350 hotKey = [[ITHotKey alloc] init];
351 [hotKey setName:@"PrevTrack"];
352 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
353 [hotKey setTarget:self];
354 [hotKey setAction:@selector(prevSong)];
355 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
358 if ([df objectForKey:@"ShowPlayer"] != nil) {
359 hotKey = [[ITHotKey alloc] init];
360 [hotKey setName:@"ShowPlayer"];
361 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
362 [hotKey setTarget:self];
363 [hotKey setAction:@selector(showPlayer)];
364 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
367 if ([df objectForKey:@"TrackInfo"] != nil) {
368 hotKey = [[ITHotKey alloc] init];
369 [hotKey setName:@"TrackInfo"];
370 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
371 [hotKey setTarget:self];
372 [hotKey setAction:@selector(showCurrentTrackInfo)];
373 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
376 if ([df objectForKey:@"UpcomingSongs"] != nil) {
377 hotKey = [[ITHotKey alloc] init];
378 [hotKey setName:@"UpcomingSongs"];
379 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
380 [hotKey setTarget:self];
381 [hotKey setAction:@selector(showUpcomingSongs)];
382 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
385 if ([df objectForKey:@"ToggleLoop"] != nil) {
386 hotKey = [[ITHotKey alloc] init];
387 [hotKey setName:@"ToggleLoop"];
388 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
389 [hotKey setTarget:self];
390 [hotKey setAction:@selector(toggleLoop)];
391 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
394 if ([df objectForKey:@"ToggleShuffle"] != nil) {
395 hotKey = [[ITHotKey alloc] init];
396 [hotKey setName:@"ToggleShuffle"];
397 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
398 [hotKey setTarget:self];
399 [hotKey setAction:@selector(toggleShuffle)];
400 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
403 if ([df objectForKey:@"IncrementVolume"] != nil) {
404 hotKey = [[ITHotKey alloc] init];
405 [hotKey setName:@"IncrementVolume"];
406 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
407 [hotKey setTarget:self];
408 [hotKey setAction:@selector(incrementVolume)];
409 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
412 if ([df objectForKey:@"DecrementVolume"] != nil) {
413 hotKey = [[ITHotKey alloc] init];
414 [hotKey setName:@"DecrementVolume"];
415 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
416 [hotKey setTarget:self];
417 [hotKey setAction:@selector(decrementVolume)];
418 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
421 if ([df objectForKey:@"IncrementRating"] != nil) {
422 hotKey = [[ITHotKey alloc] init];
423 [hotKey setName:@"IncrementRating"];
424 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
425 [hotKey setTarget:self];
426 [hotKey setAction:@selector(incrementRating)];
427 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
430 if ([df objectForKey:@"DecrementRating"] != nil) {
431 hotKey = [[ITHotKey alloc] init];
432 [hotKey setName:@"DecrementRating"];
433 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
434 [hotKey setTarget:self];
435 [hotKey setAction:@selector(decrementRating)];
436 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
440 - (void)showCurrentTrackInfo
442 ITMTRemotePlayerSource source = [currentRemote currentSource];
443 NSString *title = [currentRemote currentSongTitle];
444 NSString *album = nil;
445 NSString *artist = nil;
446 NSString *time = nil;
453 if ( [df boolForKey:@"showAlbum"] ) {
454 album = [currentRemote currentSongAlbum];
457 if ( [df boolForKey:@"showArtist"] ) {
458 artist = [currentRemote currentSongArtist];
461 if ( [df boolForKey:@"showTime"] ) {
462 time = [currentRemote currentSongLength];
465 if ( [df boolForKey:@"showNumber"] ) {
466 trackNumber = [currentRemote currentSongTrack];
467 trackTotal = [currentRemote currentAlbumTrackCount];
470 if ( [df boolForKey:@"showRating"] ) {
471 rating = ( [currentRemote currentSongRating] * 5 );
475 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
478 [statusWindowController showSongInfoWindowWithSource:source
483 trackNumber:trackNumber
484 trackTotal:trackTotal
488 - (void)showUpcomingSongs
490 int curPlaylist = [currentRemote currentPlaylistIndex];
491 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
494 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
495 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
496 int curTrack = [currentRemote currentSongIndex];
499 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
501 [songList addObject:[currentRemote songTitleAtIndex:i]];
505 [statusWindowController showUpcomingSongsWindowWithTitles:songList];
508 [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
512 - (void)incrementVolume
514 float volume = [currentRemote volume];
515 float dispVol = volume;
525 [currentRemote setVolume:volume];
527 // Show volume status window
528 [statusWindowController showVolumeWindowWithLevel:dispVol];
531 - (void)decrementVolume
533 float volume = [currentRemote volume];
534 float dispVol = volume;
544 [currentRemote setVolume:volume];
546 //Show volume status window
547 [statusWindowController showVolumeWindowWithLevel:dispVol];
550 - (void)incrementRating
552 float rating = [currentRemote currentSongRating];
557 [currentRemote setCurrentSongRating:rating];
559 //Show rating status window
560 [statusWindowController showRatingWindowWithRating:rating];
563 - (void)decrementRating
565 float rating = [currentRemote currentSongRating];
570 [currentRemote setCurrentSongRating:rating];
572 //Show rating status window
573 [statusWindowController showRatingWindowWithRating:rating];
578 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
579 switch (repeatMode) {
580 case ITMTRemotePlayerRepeatOff:
581 repeatMode = ITMTRemotePlayerRepeatAll;
583 case ITMTRemotePlayerRepeatAll:
584 repeatMode = ITMTRemotePlayerRepeatOne;
586 case ITMTRemotePlayerRepeatOne:
587 repeatMode = ITMTRemotePlayerRepeatOff;
590 [currentRemote setRepeatMode:repeatMode];
592 //Show loop status window
593 [statusWindowController showRepeatWindowWithMode:repeatMode];
596 - (void)toggleShuffle
598 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
599 [currentRemote setShuffleEnabled:newShuffleEnabled];
600 //Show shuffle status window
601 [statusWindowController showRepeatWindowWithMode:newShuffleEnabled];
604 /*************************************************************************/
606 #pragma mark WORKSPACE NOTIFICATION HANDLERS
607 /*************************************************************************/
609 - (void)applicationLaunched:(NSNotification *)note
611 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
612 [currentRemote begin];
613 [self setLatestSongIdentifier:@""];
615 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
617 selector:@selector(timerUpdate)
619 repeats:YES] retain];
620 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
622 playerRunningState = ITMTRemotePlayerRunning;
626 - (void)applicationTerminated:(NSNotification *)note
628 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
629 [currentRemote halt];
630 [refreshTimer invalidate];
631 [refreshTimer release];
634 playerRunningState = ITMTRemotePlayerNotRunning;
639 /*************************************************************************/
641 #pragma mark NSApplication DELEGATE METHODS
642 /*************************************************************************/
644 - (void)applicationWillTerminate:(NSNotification *)note
647 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
651 /*************************************************************************/
653 #pragma mark DEALLOCATION METHOD
654 /*************************************************************************/
658 [self applicationTerminated:nil];
659 [statusItem release];
660 [statusWindowController release];
661 [menuController release];