Moo.
[MenuTunes.git] / MainController.m
1 #import "NewMainController.h"
2 #import "MenuController.h"
3 #import "PreferencesController.h"
4 #import "HotKeyCenter.h"
5 #import "StatusWindowController.h"
6 #import "StatusItemHack.h"
7
8 @interface MainController(Private)
9 - (ITMTRemote *)loadRemote;
10 - (void)setupHotKeys;
11 - (void)timerUpdate;
12 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
13 - (void)showCurrentTrackInfo;
14 - (void)applicationLaunched:(NSNotification *)note;
15 - (void)applicationTerminated:(NSNotification *)note;
16 @end
17
18 static MainController *sharedController;
19
20 @implementation MainController
21
22 + (MainController *)sharedController
23 {
24     return sharedController;
25 }
26
27 /*************************************************************************/
28 #pragma mark -
29 #pragma mark INITIALIZATION/DEALLOCATION METHODS
30 /*************************************************************************/
31
32 - (id)init
33 {
34     if ( ( self = [super init] ) ) {
35         sharedController = self;
36         
37         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
38         statusWindowController = [[StatusWindowController alloc] init];
39         menuController = [[MenuController alloc] init];
40         df = [[NSUserDefaults standardUserDefaults] retain];
41     }
42     return self;
43 }
44
45 - (void)applicationDidFinishLaunching:(NSNotification *)note
46 {
47     currentRemote = [self loadRemote];
48     [currentRemote begin];
49     
50     //Setup for notification of the remote player launching or quitting
51     [[[NSWorkspace sharedWorkspace] notificationCenter]
52             addObserver:self
53             selector:@selector(applicationTerminated:)
54             name:NSWorkspaceDidTerminateApplicationNotification
55             object:nil];
56     
57     [[[NSWorkspace sharedWorkspace] notificationCenter]
58             addObserver:self
59             selector:@selector(applicationLaunched:)
60             name:NSWorkspaceDidLaunchApplicationNotification
61             object:nil];
62     
63     if ( ! [df objectForKey:@"menu"] ) {  // If this is nil, defaults have never been registered.
64         [[PreferencesController sharedPrefs] registerDefaults];
65     }
66     
67     [StatusItemHack install];
68     statusItem = [[ITStatusItem alloc]
69             initWithStatusBar:[NSStatusBar systemStatusBar]
70             withLength:NSSquareStatusItemLength];
71     
72     if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
73         [self applicationLaunched:nil];
74     } else {
75         [self applicationTerminated:nil];
76     }
77     
78     [statusItem setImage:[NSImage imageNamed:@"menu"]];
79     [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]];
80 }
81
82 - (ITMTRemote *)loadRemote
83 {
84     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
85     
86     if (folderPath) {
87         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
88         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
89         NSString     *bundlePath;
90
91         while ( (bundlePath = [enumerator nextObject]) ) {
92             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
93
94             if (remoteBundle) {
95                 Class remoteClass = [remoteBundle principalClass];
96
97                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
98                     [remoteClass isKindOfClass:[NSObject class]]) {
99
100                     id remote = [remoteClass remote];
101                     [remoteArray addObject:remote];
102                 }
103             }
104         }
105
106 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
107 //          if ( [remoteArray count] > 1 ) {
108 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
109 //          }
110 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
111 //      }
112     }
113 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
114     return [remoteArray objectAtIndex:0];
115 }
116
117 /*************************************************************************/
118 #pragma mark -
119 #pragma mark INSTANCE METHODS
120 /*************************************************************************/
121
122 - (void)startTimerInNewThread
123 {
124     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
125     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
126     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
127                              target:self
128                              selector:@selector(timerUpdate)
129                              userInfo:nil
130                              repeats:YES] retain];
131     [runLoop run];
132     [pool release];
133 }
134
135 - (BOOL)songIsPlaying
136 {
137     return ( ! ([[currentRemote currentSongUniqueIdentifier] isEqualToString:@"0-0"]) );
138 }
139
140 - (BOOL)radioIsPlaying
141 {
142     return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
143 }
144
145 - (BOOL)songChanged
146 {
147     return ( ! [[currentRemote currentSongUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
148 }
149
150 - (NSString *)latestSongIdentifier
151 {
152     return _latestSongIdentifier;
153 }
154
155 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
156 {
157     [_latestSongIdentifier autorelease];
158     _latestSongIdentifier = [newIdentifier copy];
159 }
160
161 - (void)timerUpdate
162 {
163     //This huge if statement is being nasty
164     /*if ( ( [self songChanged] ) ||
165          ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) ||
166          ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) )*/
167     
168     if ([self songChanged]) {
169         [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]];
170         latestPlaylistClass = [currentRemote currentPlaylistClass];
171         [menuController rebuildSubmenus];
172         
173         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
174             [self showCurrentTrackInfo];
175         }
176     }
177 }
178
179 - (void)menuClicked
180 {
181     if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
182         [statusItem setMenu:[menuController menu]];
183     } else {
184         [statusItem setMenu:[menuController menuForNoPlayer]];
185     }
186 }
187
188 //
189 //
190 // Menu Selectors
191 //
192 //
193
194 - (void)playPause
195 {
196     ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
197     
198     if (state == ITMTRemotePlayerPlaying) {
199         [currentRemote pause];
200     } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
201         [currentRemote pause];
202         [currentRemote play];
203     } else {
204         [currentRemote play];
205     }
206 }
207
208 - (void)nextSong
209 {
210     [currentRemote goToNextSong];
211 }
212
213 - (void)prevSong
214 {
215     [currentRemote goToPreviousSong];
216 }
217
218 - (void)fastForward
219 {
220     [currentRemote forward];
221 }
222
223 - (void)rewind
224 {
225     [currentRemote rewind];
226 }
227
228 - (void)selectPlaylistAtIndex:(int)index
229 {
230     [currentRemote switchToPlaylistAtIndex:index];
231 }
232
233 - (void)selectSongAtIndex:(int)index
234 {
235     [currentRemote switchToSongAtIndex:index];
236 }
237
238 - (void)selectSongRating:(int)rating
239 {
240     [currentRemote setCurrentSongRating:(float)rating / 100.0];
241 }
242
243 - (void)selectEQPresetAtIndex:(int)index
244 {
245     [currentRemote switchToEQAtIndex:index];
246 }
247
248 - (void)showPlayer
249 {
250     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
251         [currentRemote showPrimaryInterface];
252     } else {
253         if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
254             NSLog(@"Error Launching Player");
255         }
256     }
257 }
258
259 - (void)showPreferences
260 {
261     [[PreferencesController sharedPrefs] setController:self];
262     [[PreferencesController sharedPrefs] showPrefsWindow:self];
263 }
264
265 - (void)quitMenuTunes
266 {
267     [NSApp terminate:self];
268 }
269
270 //
271 //
272
273 - (void)closePreferences
274 {
275     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
276         [self setupHotKeys];
277     }
278 }
279
280 - (ITMTRemote *)currentRemote
281 {
282     return currentRemote;
283 }
284
285 //
286 //
287 // Hot key setup
288 //
289 //
290
291 - (void)clearHotKeys
292 {
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"];
305 }
306
307 - (void)setupHotKeys
308 {
309     if ([df objectForKey:@"PlayPause"] != nil) {
310         [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
311                 combo:[df keyComboForKey:@"PlayPause"]
312                 target:self action:@selector(playPause)];
313     }
314     
315     if ([df objectForKey:@"NextTrack"] != nil) {
316         [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
317                 combo:[df keyComboForKey:@"NextTrack"]
318                 target:self action:@selector(nextSong)];
319     }
320     
321     if ([df objectForKey:@"PrevTrack"] != nil) {
322         [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
323                 combo:[df keyComboForKey:@"PrevTrack"]
324                 target:self action:@selector(prevSong)];
325     }
326     
327     if ([df objectForKey:@"ShowPlayer"] != nil) {
328         [[HotKeyCenter sharedCenter] addHotKey:@"ShowPlayer"
329                 combo:[df keyComboForKey:@"ShowPlayer"]
330                 target:self action:@selector(showPlayer)];
331     }
332     
333     if ([df objectForKey:@"TrackInfo"] != nil) {
334         [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
335                 combo:[df keyComboForKey:@"TrackInfo"]
336                 target:self action:@selector(showCurrentTrackInfo)];
337     }
338     
339     if ([df objectForKey:@"UpcomingSongs"] != nil) {
340         [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
341                combo:[df keyComboForKey:@"UpcomingSongs"]
342                target:self action:@selector(showUpcomingSongs)];
343     }
344     
345     if ([df objectForKey:@"ToggleLoop"] != nil) {
346         [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop"
347                combo:[df keyComboForKey:@"ToggleLoop"]
348                target:self action:@selector(toggleLoop)];
349     }
350     
351     if ([df objectForKey:@"ToggleShuffle"] != nil) {
352         [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle"
353                combo:[df keyComboForKey:@"ToggleShuffle"]
354                target:self action:@selector(toggleShuffle)];
355     }
356     
357     if ([df objectForKey:@"IncrementVolume"] != nil) {
358         [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume"
359                combo:[df keyComboForKey:@"IncrementVolume"]
360                target:self action:@selector(incrementVolume)];
361     }
362     
363     if ([df objectForKey:@"DecrementVolume"] != nil) {
364         [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume"
365                combo:[df keyComboForKey:@"DecrementVolume"]
366                target:self action:@selector(decrementVolume)];
367     }
368     
369     if ([df objectForKey:@"IncrementRating"] != nil) {
370         [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating"
371                combo:[df keyComboForKey:@"IncrementRating"]
372                target:self action:@selector(incrementRating)];
373     }
374     
375     if ([df objectForKey:@"DecrementRating"] != nil) {
376         [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating"
377                combo:[df keyComboForKey:@"DecrementRating"]
378                target:self action:@selector(decrementRating)];
379     }
380 }
381
382 - (void)showCurrentTrackInfo
383 {
384     NSString *title = [currentRemote currentSongTitle];
385
386     if ( title ) {
387         NSString *album       = nil;
388         NSString *artist      = nil;
389         NSString *time        = nil;
390         int       trackNumber = 0;
391         int       trackTotal  = 0;
392         int       rating      = 0;
393
394         if ( [df boolForKey:@"showAlbum"] ) {
395             album = [currentRemote currentSongAlbum];
396         }
397
398         if ( [df boolForKey:@"showArtist"] ) {
399             artist = [currentRemote currentSongArtist];
400         }
401
402         if ( [df boolForKey:@"showTime"] ) {
403             time = [currentRemote currentSongLength];
404         }
405
406         if ( [df boolForKey:@"showNumber"] ) {
407             trackNumber = [currentRemote currentSongTrack];
408             trackTotal  = [currentRemote currentAlbumTrackCount];
409         }
410
411         if ( [df boolForKey:@"showRating"] ) {
412             rating = ( [currentRemote currentSongRating] * 5 );
413         }
414
415         [statusWindowController showSongWindowWithTitle:title
416                                                   album:album
417                                                  artist:artist
418                                                    time:time
419                                             trackNumber:trackNumber
420                                              trackTotal:trackTotal
421                                                  rating:rating];
422     } else {
423         title = @"No song is playing.";
424         [statusWindowController showSongWindowWithTitle:title
425                                                   album:nil
426                                                  artist:nil
427                                                    time:nil
428                                             trackNumber:0
429                                              trackTotal:0
430                                                  rating:0];
431     }
432 }
433
434 - (void)showUpcomingSongs
435 {
436     int curPlaylist = [currentRemote currentPlaylistIndex];
437     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
438
439     if (numSongs > 0) {
440         NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
441         int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
442         int curTrack = [currentRemote currentSongIndex];
443         int i;
444
445         for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
446             if (i <= numSongs) {
447                 [songList addObject:[currentRemote songTitleAtIndex:i]];
448             }
449         }
450         
451         [statusWindowController showUpcomingSongsWithTitles:songList];
452         
453     } else {
454         [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:@"No upcoming songs."]];
455     }
456 }
457
458 - (void)incrementVolume
459 {
460     float volume = [currentRemote volume];
461     volume += 0.2;
462     if (volume > 1.0) {
463         volume = 1.0;
464     }
465     [currentRemote setVolume:volume];
466     
467     //Show volume status window
468     [statusWindowController showVolumeWindowWithLevel:volume];
469 }
470
471 - (void)decrementVolume
472 {
473     float volume = [currentRemote volume];
474     volume -= 0.2;
475     if (volume < 0.0) {
476         volume = 0.0;
477     }
478     [currentRemote setVolume:volume];
479     
480     //Show volume status window
481     [statusWindowController showVolumeWindowWithLevel:volume];
482 }
483
484 - (void)incrementRating
485 {
486     float rating = [currentRemote currentSongRating];
487     rating += 0.2;
488     if (rating > 1.0) {
489         rating = 1.0;
490     }
491     [currentRemote setCurrentSongRating:rating];
492     
493     //Show rating status window
494     [statusWindowController showRatingWindowWithLevel:rating];
495 }
496
497 - (void)decrementRating
498 {
499     float rating = [currentRemote currentSongRating];
500     rating -= 0.2;
501     if (rating < 0.0) {
502         rating = 0.0;
503     }
504     [currentRemote setCurrentSongRating:rating];
505     
506     //Show rating status window
507     [statusWindowController showRatingWindowWithLevel:rating];
508 }
509
510 - (void)toggleLoop
511 {
512     ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
513     
514     switch (repeatMode) {
515         case ITMTRemotePlayerRepeatOff:
516             repeatMode = ITMTRemotePlayerRepeatAll;
517         break;
518         case ITMTRemotePlayerRepeatAll:
519             repeatMode = ITMTRemotePlayerRepeatOne;
520         break;
521         case ITMTRemotePlayerRepeatOne:
522             repeatMode = ITMTRemotePlayerRepeatOff;
523         break;
524     }
525     [currentRemote setRepeatMode:repeatMode];
526     
527     //Show loop status window
528     [statusWindowController showLoopWindowWithMode:repeatMode];
529 }
530
531 - (void)toggleShuffle
532 {
533     bool newShuffleEnabled = ![currentRemote shuffleEnabled];
534     [currentRemote setShuffleEnabled:newShuffleEnabled];
535     //Show shuffle status window
536     [statusWindowController showLoopWindowWithMode:newShuffleEnabled];
537 }
538
539 /*************************************************************************/
540 #pragma mark -
541 #pragma mark WORKSPACE NOTIFICATION HANDLERS
542 /*************************************************************************/
543
544 - (void)applicationLaunched:(NSNotification *)note
545 {
546     if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
547         [currentRemote begin];
548         [self setLatestSongIdentifier:@""];
549         [self timerUpdate];
550         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
551                              target:self
552                              selector:@selector(timerUpdate)
553                              userInfo:nil
554                              repeats:YES] retain];
555         //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
556         [self setupHotKeys];
557         playerRunningState = ITMTRemotePlayerRunning;
558     }
559 }
560
561  - (void)applicationTerminated:(NSNotification *)note
562  {
563      if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
564         [currentRemote halt];
565         [refreshTimer invalidate];
566         [refreshTimer release];
567         refreshTimer = nil;
568         [self clearHotKeys];
569         playerRunningState = ITMTRemotePlayerNotRunning;
570      }
571  }
572
573
574 /*************************************************************************/
575 #pragma mark -
576 #pragma mark NSApplication DELEGATE METHODS
577 /*************************************************************************/
578
579 - (void)applicationWillTerminate:(NSNotification *)note
580 {
581     [self clearHotKeys];
582     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
583 }
584
585
586 /*************************************************************************/
587 #pragma mark -
588 #pragma mark DEALLOCATION METHOD
589 /*************************************************************************/
590
591 - (void)dealloc
592 {
593     [self applicationTerminated:nil];
594     [statusItem release];
595     [statusWindowController release];
596     [menuController release];
597     [super dealloc];
598 }
599
600
601 @end