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