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 if ([self songChanged]) {
170 [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]];
171 [menuController rebuildSubmenus];
173 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
174 [self showCurrentTrackInfo];
181 if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
182 [statusItem setMenu:[menuController menu]];
184 [statusItem setMenu:[menuController menuForNoPlayer]];
196 ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
198 if (state == ITMTRemotePlayerPlaying) {
199 [currentRemote pause];
200 } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
201 [currentRemote pause];
202 [currentRemote play];
204 [currentRemote play];
210 [currentRemote goToNextSong];
215 [currentRemote goToPreviousSong];
220 [currentRemote forward];
225 [currentRemote rewind];
228 - (void)selectPlaylistAtIndex:(int)index
230 [currentRemote switchToPlaylistAtIndex:index];
233 - (void)selectSongAtIndex:(int)index
235 [currentRemote switchToSongAtIndex:index];
238 - (void)selectSongRating:(int)rating
240 [currentRemote setCurrentSongRating:(float)rating / 100.0];
243 - (void)selectEQPresetAtIndex:(int)index
245 [currentRemote switchToEQAtIndex:index];
250 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
251 [currentRemote showPrimaryInterface];
253 if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
254 NSLog(@"MenuTunes: Error Launching Player");
259 - (void)showPreferences
261 [[PreferencesController sharedPrefs] setController:self];
262 [[PreferencesController sharedPrefs] showPrefsWindow:self];
265 - (void)quitMenuTunes
267 [NSApp terminate:self];
273 - (void)closePreferences
275 if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
280 - (ITMTRemote *)currentRemote
282 return currentRemote;
293 [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
294 [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
295 [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
296 [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
297 [[HotKeyCenter sharedCenter] removeHotKey:@"ShowPlayer"];
298 [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
299 [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleLoop"];
300 [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleShuffle"];
301 [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementVolume"];
302 [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementVolume"];
303 [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementRating"];
304 [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementRating"];
309 if ([df objectForKey:@"PlayPause"] != nil) {
310 [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
311 combo:[df keyComboForKey:@"PlayPause"]
312 target:self action:@selector(playPause)];
315 if ([df objectForKey:@"NextTrack"] != nil) {
316 [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
317 combo:[df keyComboForKey:@"NextTrack"]
318 target:self action:@selector(nextSong)];
321 if ([df objectForKey:@"PrevTrack"] != nil) {
322 [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
323 combo:[df keyComboForKey:@"PrevTrack"]
324 target:self action:@selector(prevSong)];
327 if ([df objectForKey:@"ShowPlayer"] != nil) {
328 [[HotKeyCenter sharedCenter] addHotKey:@"ShowPlayer"
329 combo:[df keyComboForKey:@"ShowPlayer"]
330 target:self action:@selector(showPlayer)];
333 if ([df objectForKey:@"TrackInfo"] != nil) {
334 [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
335 combo:[df keyComboForKey:@"TrackInfo"]
336 target:self action:@selector(showCurrentTrackInfo)];
339 if ([df objectForKey:@"UpcomingSongs"] != nil) {
340 [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
341 combo:[df keyComboForKey:@"UpcomingSongs"]
342 target:self action:@selector(showUpcomingSongs)];
345 if ([df objectForKey:@"ToggleLoop"] != nil) {
346 [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop"
347 combo:[df keyComboForKey:@"ToggleLoop"]
348 target:self action:@selector(toggleLoop)];
351 if ([df objectForKey:@"ToggleShuffle"] != nil) {
352 [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle"
353 combo:[df keyComboForKey:@"ToggleShuffle"]
354 target:self action:@selector(toggleShuffle)];
357 if ([df objectForKey:@"IncrementVolume"] != nil) {
358 [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume"
359 combo:[df keyComboForKey:@"IncrementVolume"]
360 target:self action:@selector(incrementVolume)];
363 if ([df objectForKey:@"DecrementVolume"] != nil) {
364 [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume"
365 combo:[df keyComboForKey:@"DecrementVolume"]
366 target:self action:@selector(decrementVolume)];
369 if ([df objectForKey:@"IncrementRating"] != nil) {
370 [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating"
371 combo:[df keyComboForKey:@"IncrementRating"]
372 target:self action:@selector(incrementRating)];
375 if ([df objectForKey:@"DecrementRating"] != nil) {
376 [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating"
377 combo:[df keyComboForKey:@"DecrementRating"]
378 target:self action:@selector(decrementRating)];
382 - (void)showCurrentTrackInfo
384 NSString *title = [currentRemote currentSongTitle];
387 NSString *album = nil;
388 NSString *artist = nil;
389 NSString *time = nil;
394 if ( [df boolForKey:@"showAlbum"] ) {
395 album = [currentRemote currentSongAlbum];
398 if ( [df boolForKey:@"showArtist"] ) {
399 artist = [currentRemote currentSongArtist];
402 if ( [df boolForKey:@"showTime"] ) {
403 time = [currentRemote currentSongLength];
406 if ( [df boolForKey:@"showNumber"] ) {
407 trackNumber = [currentRemote currentSongTrack];
408 trackTotal = [currentRemote currentAlbumTrackCount];
411 if ( [df boolForKey:@"showRating"] ) {
412 rating = ( [currentRemote currentSongRating] * 5 );
415 [statusWindowController showSongWindowWithTitle:title
419 trackNumber:trackNumber
420 trackTotal:trackTotal
423 title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
424 [statusWindowController showSongWindowWithTitle:title
434 - (void)showUpcomingSongs
436 int curPlaylist = [currentRemote currentPlaylistIndex];
437 int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
440 NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
441 int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
442 int curTrack = [currentRemote currentSongIndex];
445 for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
447 [songList addObject:[currentRemote songTitleAtIndex:i]];
451 [statusWindowController showUpcomingSongsWithTitles:songList];
454 [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
458 - (void)incrementVolume
460 float volume = [currentRemote volume];
465 [currentRemote setVolume:volume];
467 //Show volume status window
468 [statusWindowController showVolumeWindowWithLevel:volume];
471 - (void)decrementVolume
473 float volume = [currentRemote volume];
478 [currentRemote setVolume:volume];
480 //Show volume status window
481 [statusWindowController showVolumeWindowWithLevel:volume];
484 - (void)incrementRating
486 float rating = [currentRemote currentSongRating];
491 [currentRemote setCurrentSongRating:rating];
493 //Show rating status window
494 [statusWindowController showRatingWindowWithLevel:rating];
497 - (void)decrementRating
499 float rating = [currentRemote currentSongRating];
504 [currentRemote setCurrentSongRating:rating];
506 //Show rating status window
507 [statusWindowController showRatingWindowWithLevel:rating];
512 ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
514 switch (repeatMode) {
515 case ITMTRemotePlayerRepeatOff:
516 repeatMode = ITMTRemotePlayerRepeatAll;
518 case ITMTRemotePlayerRepeatAll:
519 repeatMode = ITMTRemotePlayerRepeatOne;
521 case ITMTRemotePlayerRepeatOne:
522 repeatMode = ITMTRemotePlayerRepeatOff;
525 [currentRemote setRepeatMode:repeatMode];
527 //Show loop status window
528 [statusWindowController showLoopWindowWithMode:repeatMode];
531 - (void)toggleShuffle
533 bool newShuffleEnabled = ![currentRemote shuffleEnabled];
534 [currentRemote setShuffleEnabled:newShuffleEnabled];
535 //Show shuffle status window
536 [statusWindowController showLoopWindowWithMode:newShuffleEnabled];
539 /*************************************************************************/
541 #pragma mark WORKSPACE NOTIFICATION HANDLERS
542 /*************************************************************************/
544 - (void)applicationLaunched:(NSNotification *)note
546 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
547 [currentRemote begin];
548 [self setLatestSongIdentifier:@""];
550 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
552 selector:@selector(timerUpdate)
554 repeats:YES] retain];
555 //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
557 playerRunningState = ITMTRemotePlayerRunning;
561 - (void)applicationTerminated:(NSNotification *)note
563 if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
564 [currentRemote halt];
565 [refreshTimer invalidate];
566 [refreshTimer release];
569 playerRunningState = ITMTRemotePlayerNotRunning;
574 /*************************************************************************/
576 #pragma mark NSApplication DELEGATE METHODS
577 /*************************************************************************/
579 - (void)applicationWillTerminate:(NSNotification *)note
582 [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
586 /*************************************************************************/
588 #pragma mark DEALLOCATION METHOD
589 /*************************************************************************/
593 [self applicationTerminated:nil];
594 [statusItem release];
595 [statusWindowController release];
596 [menuController release];