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