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