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:@"menu"]];
87 [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]];
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 currentSongUniqueIdentifier] isEqualToString:@"0-0"]) );
148 - (BOOL)radioIsPlaying
150 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
155 return ( ! [[currentRemote currentSongUniqueIdentifier] 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 currentSongUniqueIdentifier]];
178 latestPlaylistClass = [currentRemote currentPlaylistClass];
179 [menuController rebuildSubmenus];
181 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
182 [self showCurrentTrackInfo];
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];
218 [currentRemote goToNextSong];
223 [currentRemote goToPreviousSong];
228 [currentRemote forward];
233 [currentRemote rewind];
236 - (void)selectPlaylistAtIndex:(int)index
238 [currentRemote switchToPlaylistAtIndex:index];
241 - (void)selectSongAtIndex:(int)index
243 [currentRemote switchToSongAtIndex:index];
246 - (void)selectSongRating:(int)rating
248 [currentRemote setCurrentSongRating:(float)rating / 100.0];
251 - (void)selectEQPresetAtIndex:(int)index
253 [currentRemote switchToEQAtIndex:index];
258 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
259 [currentRemote showPrimaryInterface];
261 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
262 NSLog(@"MenuTunes: Error Launching Player");
267 - (void)showPreferences
269 [[PreferencesController sharedPrefs] setController:self];
270 [[PreferencesController sharedPrefs] showPrefsWindow:self];
273 - (void)quitMenuTunes
275 [NSApp terminate:self];
281 - (void)closePreferences
283 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
288 - (ITMTRemote *)currentRemote
290 return currentRemote;
301 NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
302 ITHotKey *nextHotKey;
304 while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
305 [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
313 if ([df objectForKey:@"PlayPause"] != nil) {
314 hotKey = [[ITHotKey alloc] init];
315 [hotKey setName:@"PlayPause"];
316 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
317 [hotKey setTarget:self];
318 [hotKey setAction:@selector(playPause)];
319 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
322 if ([df objectForKey:@"NextTrack"] != nil) {
323 hotKey = [[ITHotKey alloc] init];
324 [hotKey setName:@"NextTrack"];
325 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
326 [hotKey setTarget:self];
327 [hotKey setAction:@selector(nextSong)];
328 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
331 if ([df objectForKey:@"PrevTrack"] != nil) {
332 hotKey = [[ITHotKey alloc] init];
333 [hotKey setName:@"PrevTrack"];
334 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
335 [hotKey setTarget:self];
336 [hotKey setAction:@selector(prevSong)];
337 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
340 if ([df objectForKey:@"ShowPlayer"] != nil) {
341 hotKey = [[ITHotKey alloc] init];
342 [hotKey setName:@"ShowPlayer"];
343 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
344 [hotKey setTarget:self];
345 [hotKey setAction:@selector(showPlayer)];
346 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
349 if ([df objectForKey:@"TrackInfo"] != nil) {
350 hotKey = [[ITHotKey alloc] init];
351 [hotKey setName:@"TrackInfo"];
352 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
353 [hotKey setTarget:self];
354 [hotKey setAction:@selector(showCurrentTrackInfo)];
355 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
358 if ([df objectForKey:@"UpcomingSongs"] != nil) {
359 hotKey = [[ITHotKey alloc] init];
360 [hotKey setName:@"UpcomingSongs"];
361 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
362 [hotKey setTarget:self];
363 [hotKey setAction:@selector(showUpcomingSongs)];
364 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
367 if ([df objectForKey:@"ToggleLoop"] != nil) {
368 hotKey = [[ITHotKey alloc] init];
369 [hotKey setName:@"ToggleLoop"];
370 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
371 [hotKey setTarget:self];
372 [hotKey setAction:@selector(toggleLoop)];
373 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
376 if ([df objectForKey:@"ToggleShuffle"] != nil) {
377 hotKey = [[ITHotKey alloc] init];
378 [hotKey setName:@"ToggleShuffle"];
379 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
380 [hotKey setTarget:self];
381 [hotKey setAction:@selector(toggleShuffle)];
382 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
385 if ([df objectForKey:@"IncrementVolume"] != nil) {
386 hotKey = [[ITHotKey alloc] init];
387 [hotKey setName:@"IncrementVolume"];
388 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
389 [hotKey setTarget:self];
390 [hotKey setAction:@selector(incrementVolume)];
391 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
394 if ([df objectForKey:@"DecrementVolume"] != nil) {
395 hotKey = [[ITHotKey alloc] init];
396 [hotKey setName:@"DecrementVolume"];
397 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
398 [hotKey setTarget:self];
399 [hotKey setAction:@selector(decrementVolume)];
400 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
403 if ([df objectForKey:@"IncrementRating"] != nil) {
404 hotKey = [[ITHotKey alloc] init];
405 [hotKey setName:@"IncrementRating"];
406 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
407 [hotKey setTarget:self];
408 [hotKey setAction:@selector(incrementRating)];
409 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
412 if ([df objectForKey:@"DecrementRating"] != nil) {
413 hotKey = [[ITHotKey alloc] init];
414 [hotKey setName:@"DecrementRating"];
415 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
416 [hotKey setTarget:self];
417 [hotKey setAction:@selector(decrementRating)];
418 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
422 - (void)showCurrentTrackInfo
424 NSString *title = [currentRemote currentSongTitle];
427 NSString *album = nil;
428 NSString *artist = nil;
429 NSString *time = nil;
434 if ( [df boolForKey:@"showAlbum"] ) {
435 album = [currentRemote currentSongAlbum];
438 if ( [df boolForKey:@"showArtist"] ) {
439 artist = [currentRemote currentSongArtist];
442 if ( [df boolForKey:@"showTime"] ) {
443 time = [currentRemote currentSongLength];
446 if ( [df boolForKey:@"showNumber"] ) {
447 trackNumber = [currentRemote currentSongTrack];
448 trackTotal = [currentRemote currentAlbumTrackCount];
451 if ( [df boolForKey:@"showRating"] ) {
452 rating = ( [currentRemote currentSongRating] * 5 );
455 [statusWindowController showSongWindowWithTitle:title
459 trackNumber:trackNumber
460 trackTotal:trackTotal
463 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
464 [statusWindowController showSongWindowWithTitle:title
474 - (void)showUpcomingSongs
476 int curPlaylist = [currentRemote currentPlaylistIndex];
477 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
480 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
481 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
482 int curTrack = [currentRemote currentSongIndex];
485 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
487 [songList addObject:[currentRemote songTitleAtIndex:i]];
491 [statusWindowController showUpcomingSongsWithTitles:songList];
494 [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
498 - (void)incrementVolume
500 float volume = [currentRemote volume];
505 [currentRemote setVolume:volume];
507 //Show volume status window
508 [statusWindowController showVolumeWindowWithLevel:volume];
511 - (void)decrementVolume
513 float volume = [currentRemote volume];
518 [currentRemote setVolume:volume];
520 //Show volume status window
521 [statusWindowController showVolumeWindowWithLevel:volume];
524 - (void)incrementRating
526 float rating = [currentRemote currentSongRating];
531 [currentRemote setCurrentSongRating:rating];
533 //Show rating status window
534 [statusWindowController showRatingWindowWithLevel:rating];
537 - (void)decrementRating
539 float rating = [currentRemote currentSongRating];
544 [currentRemote setCurrentSongRating:rating];
546 //Show rating status window
547 [statusWindowController showRatingWindowWithLevel:rating];
552 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
554 switch (repeatMode) {
555 case ITMTRemotePlayerRepeatOff:
556 repeatMode = ITMTRemotePlayerRepeatAll;
558 case ITMTRemotePlayerRepeatAll:
559 repeatMode = ITMTRemotePlayerRepeatOne;
561 case ITMTRemotePlayerRepeatOne:
562 repeatMode = ITMTRemotePlayerRepeatOff;
565 [currentRemote setRepeatMode:repeatMode];
567 //Show loop status window
568 [statusWindowController showLoopWindowWithMode:repeatMode];
571 - (void)toggleShuffle
573 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
574 [currentRemote setShuffleEnabled:newShuffleEnabled];
575 //Show shuffle status window
576 [statusWindowController showLoopWindowWithMode:newShuffleEnabled];
579 /*************************************************************************/
581 #pragma mark WORKSPACE NOTIFICATION HANDLERS
582 /*************************************************************************/
584 - (void)applicationLaunched:(NSNotification *)note
586 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
587 [currentRemote begin];
588 [self setLatestSongIdentifier:@""];
590 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
592 selector:@selector(timerUpdate)
594 repeats:YES] retain];
595 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
597 playerRunningState = ITMTRemotePlayerRunning;
601 - (void)applicationTerminated:(NSNotification *)note
603 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
604 [currentRemote halt];
605 [refreshTimer invalidate];
606 [refreshTimer release];
609 playerRunningState = ITMTRemotePlayerNotRunning;
614 /*************************************************************************/
616 #pragma mark NSApplication DELEGATE METHODS
617 /*************************************************************************/
619 - (void)applicationWillTerminate:(NSNotification *)note
622 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
626 /*************************************************************************/
628 #pragma mark DEALLOCATION METHOD
629 /*************************************************************************/
633 [self applicationTerminated:nil];
634 [statusItem release];
635 [statusWindowController release];
636 [menuController release];