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