1 #import "MainController.h"
2 #import "MenuController.h"
3 #import "PreferencesController.h"
4 #import "HotKeyCenter.h"
5 #import "StatusWindowController.h"
6 #import "StatusItemHack.h"
8 @interface MainController(Private)
9 - (ITMTRemote *)loadRemote;
11 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
12 - (void)showCurrentTrackInfo;
13 - (void)applicationLaunched:(NSNotification *)note;
14 - (void)applicationTerminated:(NSNotification *)note;
17 static MainController *sharedController;
19 @implementation MainController
21 + (MainController *)sharedController
23 return sharedController;
26 /*************************************************************************/
28 #pragma mark INITIALIZATION/DEALLOCATION METHODS
29 /*************************************************************************/
33 if ( ( self = [super init] ) ) {
34 sharedController = self;
36 remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
37 statusWindowController = [[StatusWindowController alloc] init];
38 menuController = [[MenuController alloc] init];
39 df = [[NSUserDefaults standardUserDefaults] retain];
44 - (void)applicationDidFinishLaunching:(NSNotification *)note
46 currentRemote = [self loadRemote];
47 [currentRemote begin];
49 //Setup for notification of the remote player launching or quitting
50 [[[NSWorkspace sharedWorkspace] notificationCenter]
52 selector:@selector(applicationTerminated:)
53 name:NSWorkspaceDidTerminateApplicationNotification
56 [[[NSWorkspace sharedWorkspace] notificationCenter]
58 selector:@selector(applicationLaunched:)
59 name:NSWorkspaceDidLaunchApplicationNotification
62 if ( ! [df objectForKey:@"menu"] ) { // If this is nil, defaults have never been registered.
63 [[PreferencesController sharedPrefs] registerDefaults];
66 [StatusItemHack install];
67 statusItem = [[ITStatusItem alloc]
68 initWithStatusBar:[NSStatusBar systemStatusBar]
69 withLength:NSSquareStatusItemLength];
71 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
72 [self applicationLaunched:nil];
74 [self applicationTerminated:nil];
77 [statusItem setImage:[NSImage imageNamed:@"menu"]];
78 [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]];
81 - (ITMTRemote *)loadRemote
83 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
86 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
87 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
90 while ( (bundlePath = [enumerator nextObject]) ) {
91 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
94 Class remoteClass = [remoteBundle principalClass];
96 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
97 [remoteClass isKindOfClass:[NSObject class]]) {
99 id remote = [remoteClass remote];
100 [remoteArray addObject:remote];
105 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
106 // if ( [remoteArray count] > 1 ) {
107 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
109 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
112 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
113 return [remoteArray objectAtIndex:0];
116 /*************************************************************************/
118 #pragma mark INSTANCE METHODS
119 /*************************************************************************/
121 - (void)startTimerInNewThread
123 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
124 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
125 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
127 selector:@selector(timerUpdate)
129 repeats:YES] retain];
134 - (BOOL)songIsPlaying
136 return ( ! ([[currentRemote currentSongUniqueIdentifier] isEqualToString:@"0-0"]) );
139 - (BOOL)radioIsPlaying
141 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
146 return ( ! [[currentRemote currentSongUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
149 - (NSString *)latestSongIdentifier
151 return _latestSongIdentifier;
154 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
156 [_latestSongIdentifier autorelease];
157 _latestSongIdentifier = [newIdentifier copy];
162 //This huge if statement is being nasty
163 /*if ( ( [self songChanged] ) ||
164 ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) ||
165 ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) )*/
167 if ([self songChanged]) {
168 [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]];
169 latestPlaylistClass = [currentRemote currentPlaylistClass];
170 [menuController rebuildSubmenus];
172 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
173 [self showCurrentTrackInfo];
180 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
181 [statusItem setMenu:[menuController menu]];
183 [statusItem setMenu:[menuController menuForNoPlayer]];
195 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
197 if (state == ITMTRemotePlayerPlaying) {
198 [currentRemote pause];
199 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
200 [currentRemote pause];
201 [currentRemote play];
203 [currentRemote play];
209 [currentRemote goToNextSong];
214 [currentRemote goToPreviousSong];
219 [currentRemote forward];
224 [currentRemote rewind];
227 - (void)selectPlaylistAtIndex:(int)index
229 [currentRemote switchToPlaylistAtIndex:index];
232 - (void)selectSongAtIndex:(int)index
234 [currentRemote switchToSongAtIndex:index];
237 - (void)selectSongRating:(int)rating
239 [currentRemote setCurrentSongRating:(float)rating / 100.0];
242 - (void)selectEQPresetAtIndex:(int)index
244 [currentRemote switchToEQAtIndex:index];
249 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
250 [currentRemote showPrimaryInterface];
252 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
253 NSLog(@"MenuTunes: Error Launching Player");
258 - (void)showPreferences
260 [[PreferencesController sharedPrefs] setController:self];
261 [[PreferencesController sharedPrefs] showPrefsWindow:self];
264 - (void)quitMenuTunes
266 [NSApp terminate:self];
272 - (void)closePreferences
274 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
279 - (ITMTRemote *)currentRemote
281 return currentRemote;
292 [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
293 [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
294 [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
295 [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
296 [[HotKeyCenter sharedCenter] removeHotKey:@"ShowPlayer"];
297 [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
298 [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleLoop"];
299 [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleShuffle"];
300 [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementVolume"];
301 [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementVolume"];
302 [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementRating"];
303 [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementRating"];
308 if ([df objectForKey:@"PlayPause"] != nil) {
309 [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
310 combo:[df keyComboForKey:@"PlayPause"]
311 target:self action:@selector(playPause)];
314 if ([df objectForKey:@"NextTrack"] != nil) {
315 [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
316 combo:[df keyComboForKey:@"NextTrack"]
317 target:self action:@selector(nextSong)];
320 if ([df objectForKey:@"PrevTrack"] != nil) {
321 [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
322 combo:[df keyComboForKey:@"PrevTrack"]
323 target:self action:@selector(prevSong)];
326 if ([df objectForKey:@"ShowPlayer"] != nil) {
327 [[HotKeyCenter sharedCenter] addHotKey:@"ShowPlayer"
328 combo:[df keyComboForKey:@"ShowPlayer"]
329 target:self action:@selector(showPlayer)];
332 if ([df objectForKey:@"TrackInfo"] != nil) {
333 [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
334 combo:[df keyComboForKey:@"TrackInfo"]
335 target:self action:@selector(showCurrentTrackInfo)];
338 if ([df objectForKey:@"UpcomingSongs"] != nil) {
339 [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
340 combo:[df keyComboForKey:@"UpcomingSongs"]
341 target:self action:@selector(showUpcomingSongs)];
344 if ([df objectForKey:@"ToggleLoop"] != nil) {
345 [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop"
346 combo:[df keyComboForKey:@"ToggleLoop"]
347 target:self action:@selector(toggleLoop)];
350 if ([df objectForKey:@"ToggleShuffle"] != nil) {
351 [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle"
352 combo:[df keyComboForKey:@"ToggleShuffle"]
353 target:self action:@selector(toggleShuffle)];
356 if ([df objectForKey:@"IncrementVolume"] != nil) {
357 [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume"
358 combo:[df keyComboForKey:@"IncrementVolume"]
359 target:self action:@selector(incrementVolume)];
362 if ([df objectForKey:@"DecrementVolume"] != nil) {
363 [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume"
364 combo:[df keyComboForKey:@"DecrementVolume"]
365 target:self action:@selector(decrementVolume)];
368 if ([df objectForKey:@"IncrementRating"] != nil) {
369 [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating"
370 combo:[df keyComboForKey:@"IncrementRating"]
371 target:self action:@selector(incrementRating)];
374 if ([df objectForKey:@"DecrementRating"] != nil) {
375 [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating"
376 combo:[df keyComboForKey:@"DecrementRating"]
377 target:self action:@selector(decrementRating)];
381 - (void)showCurrentTrackInfo
383 NSString *title = [currentRemote currentSongTitle];
386 NSString *album = nil;
387 NSString *artist = nil;
388 NSString *time = nil;
393 if ( [df boolForKey:@"showAlbum"] ) {
394 album = [currentRemote currentSongAlbum];
397 if ( [df boolForKey:@"showArtist"] ) {
398 artist = [currentRemote currentSongArtist];
401 if ( [df boolForKey:@"showTime"] ) {
402 time = [currentRemote currentSongLength];
405 if ( [df boolForKey:@"showNumber"] ) {
406 trackNumber = [currentRemote currentSongTrack];
407 trackTotal = [currentRemote currentAlbumTrackCount];
410 if ( [df boolForKey:@"showRating"] ) {
411 rating = ( [currentRemote currentSongRating] * 5 );
414 [statusWindowController showSongWindowWithTitle:title
418 trackNumber:trackNumber
419 trackTotal:trackTotal
422 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
423 [statusWindowController showSongWindowWithTitle:title
433 - (void)showUpcomingSongs
435 int curPlaylist = [currentRemote currentPlaylistIndex];
436 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
439 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
440 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
441 int curTrack = [currentRemote currentSongIndex];
444 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
446 [songList addObject:[currentRemote songTitleAtIndex:i]];
450 [statusWindowController showUpcomingSongsWithTitles:songList];
453 [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
457 - (void)incrementVolume
459 float volume = [currentRemote volume];
464 [currentRemote setVolume:volume];
466 //Show volume status window
467 [statusWindowController showVolumeWindowWithLevel:volume];
470 - (void)decrementVolume
472 float volume = [currentRemote volume];
477 [currentRemote setVolume:volume];
479 //Show volume status window
480 [statusWindowController showVolumeWindowWithLevel:volume];
483 - (void)incrementRating
485 float rating = [currentRemote currentSongRating];
490 [currentRemote setCurrentSongRating:rating];
492 //Show rating status window
493 [statusWindowController showRatingWindowWithLevel:rating];
496 - (void)decrementRating
498 float rating = [currentRemote currentSongRating];
503 [currentRemote setCurrentSongRating:rating];
505 //Show rating status window
506 [statusWindowController showRatingWindowWithLevel:rating];
511 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
513 switch (repeatMode) {
514 case ITMTRemotePlayerRepeatOff:
515 repeatMode = ITMTRemotePlayerRepeatAll;
517 case ITMTRemotePlayerRepeatAll:
518 repeatMode = ITMTRemotePlayerRepeatOne;
520 case ITMTRemotePlayerRepeatOne:
521 repeatMode = ITMTRemotePlayerRepeatOff;
524 [currentRemote setRepeatMode:repeatMode];
526 //Show loop status window
527 [statusWindowController showLoopWindowWithMode:repeatMode];
530 - (void)toggleShuffle
532 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
533 [currentRemote setShuffleEnabled:newShuffleEnabled];
534 //Show shuffle status window
535 [statusWindowController showLoopWindowWithMode:newShuffleEnabled];
538 /*************************************************************************/
540 #pragma mark WORKSPACE NOTIFICATION HANDLERS
541 /*************************************************************************/
543 - (void)applicationLaunched:(NSNotification *)note
545 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
546 [currentRemote begin];
547 [self setLatestSongIdentifier:@""];
549 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
551 selector:@selector(timerUpdate)
553 repeats:YES] retain];
554 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
556 playerRunningState = ITMTRemotePlayerRunning;
560 - (void)applicationTerminated:(NSNotification *)note
562 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
563 [currentRemote halt];
564 [refreshTimer invalidate];
565 [refreshTimer release];
568 playerRunningState = ITMTRemotePlayerNotRunning;
573 /*************************************************************************/
575 #pragma mark NSApplication DELEGATE METHODS
576 /*************************************************************************/
578 - (void)applicationWillTerminate:(NSNotification *)note
581 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
585 /*************************************************************************/
587 #pragma mark DEALLOCATION METHOD
588 /*************************************************************************/
592 [self applicationTerminated:nil];
593 [statusItem release];
594 [statusWindowController release];
595 [menuController release];