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 if ([df boolForKey:@"LaunchPlayerWithMT"])
80 [self applicationTerminated:nil];
84 [statusItem setImage:[NSImage imageNamed:@"menu"]];
85 [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]];
88 - (ITMTRemote *)loadRemote
90 NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
93 NSArray *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
94 NSEnumerator *enumerator = [bundlePathList objectEnumerator];
97 while ( (bundlePath = [enumerator nextObject]) ) {
98 NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
101 Class remoteClass = [remoteBundle principalClass];
103 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
104 [remoteClass isKindOfClass:[NSObject class]]) {
106 id remote = [remoteClass remote];
107 [remoteArray addObject:remote];
112 // if ( [remoteArray count] > 0 ) { // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
113 // if ( [remoteArray count] > 1 ) {
114 // [remoteArray sortUsingSelector:@selector(sortAlpha:)];
116 // [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
119 // NSLog(@"%@", [remoteArray objectAtIndex:0]); //DEBUG
120 return [remoteArray objectAtIndex:0];
123 /*************************************************************************/
125 #pragma mark INSTANCE METHODS
126 /*************************************************************************/
128 - (void)startTimerInNewThread
130 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
131 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
132 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
134 selector:@selector(timerUpdate)
136 repeats:YES] retain];
141 - (BOOL)songIsPlaying
143 return ( ! ([[currentRemote currentSongUniqueIdentifier] isEqualToString:@"0-0"]) );
146 - (BOOL)radioIsPlaying
148 return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
153 return ( ! [[currentRemote currentSongUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
156 - (NSString *)latestSongIdentifier
158 return _latestSongIdentifier;
161 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
163 [_latestSongIdentifier autorelease];
164 _latestSongIdentifier = [newIdentifier copy];
169 //This huge if statement is being nasty
170 /*if ( ( [self songChanged] ) ||
171 ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) ||
172 ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) )*/
174 if ([self songChanged]) {
175 [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]];
176 latestPlaylistClass = [currentRemote currentPlaylistClass];
177 [menuController rebuildSubmenus];
179 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
180 [self showCurrentTrackInfo];
187 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
188 [statusItem setMenu:[menuController menu]];
190 [statusItem setMenu:[menuController menuForNoPlayer]];
202 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
204 if (state == ITMTRemotePlayerPlaying) {
205 [currentRemote pause];
206 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
207 [currentRemote pause];
208 [currentRemote play];
210 [currentRemote play];
216 [currentRemote goToNextSong];
221 [currentRemote goToPreviousSong];
226 [currentRemote forward];
231 [currentRemote rewind];
234 - (void)selectPlaylistAtIndex:(int)index
236 [currentRemote switchToPlaylistAtIndex:index];
239 - (void)selectSongAtIndex:(int)index
241 [currentRemote switchToSongAtIndex:index];
244 - (void)selectSongRating:(int)rating
246 [currentRemote setCurrentSongRating:(float)rating / 100.0];
249 - (void)selectEQPresetAtIndex:(int)index
251 [currentRemote switchToEQAtIndex:index];
256 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
257 [currentRemote showPrimaryInterface];
259 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
260 NSLog(@"MenuTunes: Error Launching Player");
265 - (void)showPreferences
267 [[PreferencesController sharedPrefs] setController:self];
268 [[PreferencesController sharedPrefs] showPrefsWindow:self];
271 - (void)quitMenuTunes
273 [NSApp terminate:self];
279 - (void)closePreferences
281 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
286 - (ITMTRemote *)currentRemote
288 return currentRemote;
299 [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
300 [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
301 [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
302 [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
303 [[HotKeyCenter sharedCenter] removeHotKey:@"ShowPlayer"];
304 [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
305 [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleLoop"];
306 [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleShuffle"];
307 [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementVolume"];
308 [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementVolume"];
309 [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementRating"];
310 [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementRating"];
315 if ([df objectForKey:@"PlayPause"] != nil) {
316 [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
317 combo:[df keyComboForKey:@"PlayPause"]
318 target:self action:@selector(playPause)];
321 if ([df objectForKey:@"NextTrack"] != nil) {
322 [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
323 combo:[df keyComboForKey:@"NextTrack"]
324 target:self action:@selector(nextSong)];
327 if ([df objectForKey:@"PrevTrack"] != nil) {
328 [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
329 combo:[df keyComboForKey:@"PrevTrack"]
330 target:self action:@selector(prevSong)];
333 if ([df objectForKey:@"ShowPlayer"] != nil) {
334 [[HotKeyCenter sharedCenter] addHotKey:@"ShowPlayer"
335 combo:[df keyComboForKey:@"ShowPlayer"]
336 target:self action:@selector(showPlayer)];
339 if ([df objectForKey:@"TrackInfo"] != nil) {
340 [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
341 combo:[df keyComboForKey:@"TrackInfo"]
342 target:self action:@selector(showCurrentTrackInfo)];
345 if ([df objectForKey:@"UpcomingSongs"] != nil) {
346 [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
347 combo:[df keyComboForKey:@"UpcomingSongs"]
348 target:self action:@selector(showUpcomingSongs)];
351 if ([df objectForKey:@"ToggleLoop"] != nil) {
352 [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop"
353 combo:[df keyComboForKey:@"ToggleLoop"]
354 target:self action:@selector(toggleLoop)];
357 if ([df objectForKey:@"ToggleShuffle"] != nil) {
358 [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle"
359 combo:[df keyComboForKey:@"ToggleShuffle"]
360 target:self action:@selector(toggleShuffle)];
363 if ([df objectForKey:@"IncrementVolume"] != nil) {
364 [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume"
365 combo:[df keyComboForKey:@"IncrementVolume"]
366 target:self action:@selector(incrementVolume)];
369 if ([df objectForKey:@"DecrementVolume"] != nil) {
370 [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume"
371 combo:[df keyComboForKey:@"DecrementVolume"]
372 target:self action:@selector(decrementVolume)];
375 if ([df objectForKey:@"IncrementRating"] != nil) {
376 [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating"
377 combo:[df keyComboForKey:@"IncrementRating"]
378 target:self action:@selector(incrementRating)];
381 if ([df objectForKey:@"DecrementRating"] != nil) {
382 [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating"
383 combo:[df keyComboForKey:@"DecrementRating"]
384 target:self action:@selector(decrementRating)];
388 - (void)showCurrentTrackInfo
390 NSString *title = [currentRemote currentSongTitle];
393 NSString *album = nil;
394 NSString *artist = nil;
395 NSString *time = nil;
400 if ( [df boolForKey:@"showAlbum"] ) {
401 album = [currentRemote currentSongAlbum];
404 if ( [df boolForKey:@"showArtist"] ) {
405 artist = [currentRemote currentSongArtist];
408 if ( [df boolForKey:@"showTime"] ) {
409 time = [currentRemote currentSongLength];
412 if ( [df boolForKey:@"showNumber"] ) {
413 trackNumber = [currentRemote currentSongTrack];
414 trackTotal = [currentRemote currentAlbumTrackCount];
417 if ( [df boolForKey:@"showRating"] ) {
418 rating = ( [currentRemote currentSongRating] * 5 );
421 [statusWindowController showSongWindowWithTitle:title
425 trackNumber:trackNumber
426 trackTotal:trackTotal
429 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
430 [statusWindowController showSongWindowWithTitle:title
440 - (void)showUpcomingSongs
442 int curPlaylist = [currentRemote currentPlaylistIndex];
443 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
446 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
447 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
448 int curTrack = [currentRemote currentSongIndex];
451 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
453 [songList addObject:[currentRemote songTitleAtIndex:i]];
457 [statusWindowController showUpcomingSongsWithTitles:songList];
460 [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
464 - (void)incrementVolume
466 float volume = [currentRemote volume];
471 [currentRemote setVolume:volume];
473 //Show volume status window
474 [statusWindowController showVolumeWindowWithLevel:volume];
477 - (void)decrementVolume
479 float volume = [currentRemote volume];
484 [currentRemote setVolume:volume];
486 //Show volume status window
487 [statusWindowController showVolumeWindowWithLevel:volume];
490 - (void)incrementRating
492 float rating = [currentRemote currentSongRating];
497 [currentRemote setCurrentSongRating:rating];
499 //Show rating status window
500 [statusWindowController showRatingWindowWithLevel:rating];
503 - (void)decrementRating
505 float rating = [currentRemote currentSongRating];
510 [currentRemote setCurrentSongRating:rating];
512 //Show rating status window
513 [statusWindowController showRatingWindowWithLevel:rating];
518 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
520 switch (repeatMode) {
521 case ITMTRemotePlayerRepeatOff:
522 repeatMode = ITMTRemotePlayerRepeatAll;
524 case ITMTRemotePlayerRepeatAll:
525 repeatMode = ITMTRemotePlayerRepeatOne;
527 case ITMTRemotePlayerRepeatOne:
528 repeatMode = ITMTRemotePlayerRepeatOff;
531 [currentRemote setRepeatMode:repeatMode];
533 //Show loop status window
534 [statusWindowController showLoopWindowWithMode:repeatMode];
537 - (void)toggleShuffle
539 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
540 [currentRemote setShuffleEnabled:newShuffleEnabled];
541 //Show shuffle status window
542 [statusWindowController showLoopWindowWithMode:newShuffleEnabled];
545 /*************************************************************************/
547 #pragma mark WORKSPACE NOTIFICATION HANDLERS
548 /*************************************************************************/
550 - (void)applicationLaunched:(NSNotification *)note
552 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
553 [currentRemote begin];
554 [self setLatestSongIdentifier:@""];
556 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
558 selector:@selector(timerUpdate)
560 repeats:YES] retain];
561 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
563 playerRunningState = ITMTRemotePlayerRunning;
567 - (void)applicationTerminated:(NSNotification *)note
569 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
570 [currentRemote halt];
571 [refreshTimer invalidate];
572 [refreshTimer release];
575 playerRunningState = ITMTRemotePlayerNotRunning;
580 /*************************************************************************/
582 #pragma mark NSApplication DELEGATE METHODS
583 /*************************************************************************/
585 - (void)applicationWillTerminate:(NSNotification *)note
588 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
592 /*************************************************************************/
594 #pragma mark DEALLOCATION METHOD
595 /*************************************************************************/
599 [self applicationTerminated:nil];
600 [statusItem release];
601 [statusWindowController release];
602 [menuController release];