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