Fixed the toggle loop problem.
[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 performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
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     [self timerUpdate];
216 }
217
218 - (void)nextSong
219 {
220     [currentRemote goToNextSong];
221     
222     [self timerUpdate];
223 }
224
225 - (void)prevSong
226 {
227     [currentRemote goToPreviousSong];
228     
229     [self timerUpdate];
230 }
231
232 - (void)fastForward
233 {
234     [currentRemote forward];
235     
236     [self timerUpdate];
237 }
238
239 - (void)rewind
240 {
241     [currentRemote rewind];
242     
243     [self timerUpdate];
244 }
245
246 - (void)selectPlaylistAtIndex:(int)index
247 {
248     [currentRemote switchToPlaylistAtIndex:index];
249     
250     [self timerUpdate];
251 }
252
253 - (void)selectSongAtIndex:(int)index
254 {
255     [currentRemote switchToSongAtIndex:index];
256     
257     [self timerUpdate];
258 }
259
260 - (void)selectSongRating:(int)rating
261 {
262     [currentRemote setCurrentSongRating:(float)rating / 100.0];
263     
264     [self timerUpdate];
265 }
266
267 - (void)selectEQPresetAtIndex:(int)index
268 {
269     [currentRemote switchToEQAtIndex:index];
270     
271     [self timerUpdate];
272 }
273
274 - (void)showPlayer
275 {
276     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
277         [currentRemote showPrimaryInterface];
278     } else {
279         if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
280             NSLog(@"MenuTunes: Error Launching Player");
281         }
282     }
283 }
284
285 - (void)showPreferences
286 {
287     [[PreferencesController sharedPrefs] setController:self];
288     [[PreferencesController sharedPrefs] showPrefsWindow:self];
289 }
290
291 - (void)quitMenuTunes
292 {
293     [NSApp terminate:self];
294 }
295
296 //
297 //
298
299 - (void)closePreferences
300 {
301     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
302         [self setupHotKeys];
303     }
304 }
305
306 - (ITMTRemote *)currentRemote
307 {
308     return currentRemote;
309 }
310
311 //
312 //
313 // Hot key setup
314 //
315 //
316
317 - (void)clearHotKeys
318 {
319     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
320     ITHotKey *nextHotKey;
321     
322     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
323         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
324     }
325 }
326
327 - (void)setupHotKeys
328 {
329     ITHotKey *hotKey;
330     
331     if ([df objectForKey:@"PlayPause"] != nil) {
332         hotKey = [[ITHotKey alloc] init];
333         [hotKey setName:@"PlayPause"];
334         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
335         [hotKey setTarget:self];
336         [hotKey setAction:@selector(playPause)];
337         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
338     }
339     
340     if ([df objectForKey:@"NextTrack"] != nil) {
341         hotKey = [[ITHotKey alloc] init];
342         [hotKey setName:@"NextTrack"];
343         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
344         [hotKey setTarget:self];
345         [hotKey setAction:@selector(nextSong)];
346         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
347     }
348     
349     if ([df objectForKey:@"PrevTrack"] != nil) {
350         hotKey = [[ITHotKey alloc] init];
351         [hotKey setName:@"PrevTrack"];
352         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
353         [hotKey setTarget:self];
354         [hotKey setAction:@selector(prevSong)];
355         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
356     }
357     
358     if ([df objectForKey:@"ShowPlayer"] != nil) {
359         hotKey = [[ITHotKey alloc] init];
360         [hotKey setName:@"ShowPlayer"];
361         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
362         [hotKey setTarget:self];
363         [hotKey setAction:@selector(showPlayer)];
364         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
365     }
366     
367     if ([df objectForKey:@"TrackInfo"] != nil) {
368         hotKey = [[ITHotKey alloc] init];
369         [hotKey setName:@"TrackInfo"];
370         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
371         [hotKey setTarget:self];
372         [hotKey setAction:@selector(showCurrentTrackInfo)];
373         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
374     }
375     
376     if ([df objectForKey:@"UpcomingSongs"] != nil) {
377         hotKey = [[ITHotKey alloc] init];
378         [hotKey setName:@"UpcomingSongs"];
379         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
380         [hotKey setTarget:self];
381         [hotKey setAction:@selector(showUpcomingSongs)];
382         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
383     }
384     
385     if ([df objectForKey:@"ToggleLoop"] != nil) {
386         hotKey = [[ITHotKey alloc] init];
387         [hotKey setName:@"ToggleLoop"];
388         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
389         [hotKey setTarget:self];
390         [hotKey setAction:@selector(toggleLoop)];
391         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
392     }
393     
394     if ([df objectForKey:@"ToggleShuffle"] != nil) {
395         hotKey = [[ITHotKey alloc] init];
396         [hotKey setName:@"ToggleShuffle"];
397         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
398         [hotKey setTarget:self];
399         [hotKey setAction:@selector(toggleShuffle)];
400         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
401     }
402     
403     if ([df objectForKey:@"IncrementVolume"] != nil) {
404         hotKey = [[ITHotKey alloc] init];
405         [hotKey setName:@"IncrementVolume"];
406         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
407         [hotKey setTarget:self];
408         [hotKey setAction:@selector(incrementVolume)];
409         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
410     }
411     
412     if ([df objectForKey:@"DecrementVolume"] != nil) {
413         hotKey = [[ITHotKey alloc] init];
414         [hotKey setName:@"DecrementVolume"];
415         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
416         [hotKey setTarget:self];
417         [hotKey setAction:@selector(decrementVolume)];
418         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
419     }
420     
421     if ([df objectForKey:@"IncrementRating"] != nil) {
422         hotKey = [[ITHotKey alloc] init];
423         [hotKey setName:@"IncrementRating"];
424         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
425         [hotKey setTarget:self];
426         [hotKey setAction:@selector(incrementRating)];
427         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
428     }
429     
430     if ([df objectForKey:@"DecrementRating"] != nil) {
431         hotKey = [[ITHotKey alloc] init];
432         [hotKey setName:@"DecrementRating"];
433         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
434         [hotKey setTarget:self];
435         [hotKey setAction:@selector(decrementRating)];
436         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
437     }
438 }
439
440 - (void)showCurrentTrackInfo
441 {
442     ITMTRemotePlayerSource  source      = [currentRemote currentSource];
443     NSString               *title       = [currentRemote currentSongTitle];
444     NSString               *album       = nil;
445     NSString               *artist      = nil;
446     NSString               *time        = nil;
447     int                     trackNumber = 0;
448     int                     trackTotal  = 0;
449     int                     rating      = -1;
450     
451     if ( title ) {
452
453         if ( [df boolForKey:@"showAlbum"] ) {
454             album = [currentRemote currentSongAlbum];
455         }
456
457         if ( [df boolForKey:@"showArtist"] ) {
458             artist = [currentRemote currentSongArtist];
459         }
460
461         if ( [df boolForKey:@"showTime"] ) {
462             time = [currentRemote currentSongLength];
463         }
464
465         if ( [df boolForKey:@"showNumber"] ) {
466             trackNumber = [currentRemote currentSongTrack];
467             trackTotal  = [currentRemote currentAlbumTrackCount];
468         }
469
470         if ( [df boolForKey:@"showRating"] ) {
471             rating = ( [currentRemote currentSongRating] * 5 );
472         }
473         
474     } else {
475         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
476     }
477
478     [statusWindowController showSongInfoWindowWithSource:source
479                                                    title:title
480                                                    album:album
481                                                   artist:artist
482                                                     time:time
483                                              trackNumber:trackNumber
484                                               trackTotal:trackTotal
485                                                   rating:rating];
486 }
487
488 - (void)showUpcomingSongs
489 {
490     int curPlaylist = [currentRemote currentPlaylistIndex];
491     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
492
493     if (numSongs > 0) {
494         NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
495         int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
496         int curTrack = [currentRemote currentSongIndex];
497         int i;
498
499         for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
500             if (i <= numSongs) {
501                 [songList addObject:[currentRemote songTitleAtIndex:i]];
502             }
503         }
504         
505         [statusWindowController showUpcomingSongsWindowWithTitles:songList];
506         
507     } else {
508         [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
509     }
510 }
511
512 - (void)incrementVolume
513 {
514     float volume  = [currentRemote volume];
515     float dispVol = volume;
516     
517     volume  += 0.110;
518     dispVol += 0.100;
519     
520     if (volume > 1.0) {
521         volume  = 1.0;
522         dispVol = 1.0;
523     }
524
525     [currentRemote setVolume:volume];
526
527  // Show volume status window
528     [statusWindowController showVolumeWindowWithLevel:dispVol];
529 }
530
531 - (void)decrementVolume
532 {
533     float volume  = [currentRemote volume];
534     float dispVol = volume;
535     
536     volume  -= 0.090;
537     dispVol -= 0.100;
538
539     if (volume < 0.0) {
540         volume  = 0.0;
541         dispVol = 0.0;
542     }
543     
544     [currentRemote setVolume:volume];
545     
546     //Show volume status window
547     [statusWindowController showVolumeWindowWithLevel:dispVol];
548 }
549
550 - (void)incrementRating
551 {
552     float rating = [currentRemote currentSongRating];
553     rating += 0.2;
554     if (rating > 1.0) {
555         rating = 1.0;
556     }
557     [currentRemote setCurrentSongRating:rating];
558     
559     //Show rating status window
560     [statusWindowController showRatingWindowWithRating:rating];
561 }
562
563 - (void)decrementRating
564 {
565     float rating = [currentRemote currentSongRating];
566     rating -= 0.2;
567     if (rating < 0.0) {
568         rating = 0.0;
569     }
570     [currentRemote setCurrentSongRating:rating];
571     
572     //Show rating status window
573     [statusWindowController showRatingWindowWithRating:rating];
574 }
575
576 - (void)toggleLoop
577 {
578     ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode];
579     switch (repeatMode) {
580         case ITMTRemotePlayerRepeatOff:
581             repeatMode = ITMTRemotePlayerRepeatAll;
582         break;
583         case ITMTRemotePlayerRepeatAll:
584             repeatMode = ITMTRemotePlayerRepeatOne;
585         break;
586         case ITMTRemotePlayerRepeatOne:
587             repeatMode = ITMTRemotePlayerRepeatOff;
588         break;
589     }
590     [currentRemote setRepeatMode:repeatMode];
591     
592     //Show loop status window
593     [statusWindowController showRepeatWindowWithMode:repeatMode];
594 }
595
596 - (void)toggleShuffle
597 {
598     bool newShuffleEnabled = ![currentRemote shuffleEnabled];
599     [currentRemote setShuffleEnabled:newShuffleEnabled];
600     //Show shuffle status window
601     [statusWindowController showRepeatWindowWithMode:newShuffleEnabled];
602 }
603
604 /*************************************************************************/
605 #pragma mark -
606 #pragma mark WORKSPACE NOTIFICATION HANDLERS
607 /*************************************************************************/
608
609 - (void)applicationLaunched:(NSNotification *)note
610 {
611     if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
612         [currentRemote begin];
613         [self setLatestSongIdentifier:@""];
614         [self timerUpdate];
615         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
616                              target:self
617                              selector:@selector(timerUpdate)
618                              userInfo:nil
619                              repeats:YES] retain];
620         //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
621         [self setupHotKeys];
622         playerRunningState = ITMTRemotePlayerRunning;
623     }
624 }
625
626  - (void)applicationTerminated:(NSNotification *)note
627  {
628      if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
629         [currentRemote halt];
630         [refreshTimer invalidate];
631         [refreshTimer release];
632         refreshTimer = nil;
633         [self clearHotKeys];
634         playerRunningState = ITMTRemotePlayerNotRunning;
635      }
636  }
637
638
639 /*************************************************************************/
640 #pragma mark -
641 #pragma mark NSApplication DELEGATE METHODS
642 /*************************************************************************/
643
644 - (void)applicationWillTerminate:(NSNotification *)note
645 {
646     [self clearHotKeys];
647     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
648 }
649
650
651 /*************************************************************************/
652 #pragma mark -
653 #pragma mark DEALLOCATION METHOD
654 /*************************************************************************/
655
656 - (void)dealloc
657 {
658     [self applicationTerminated:nil];
659     [statusItem release];
660     [statusWindowController release];
661     [menuController release];
662     [super dealloc];
663 }
664
665
666 @end