f0ee9ade6f06c0af755e7fc28badb2ae7e47793c
[MenuTunes.git] / MainController.m
1 #import "NewMainController.h"
2 #import "PreferencesController.h"
3 #import "HotKeyCenter.h"
4 #import "StatusWindowController.h"
5
6 @interface MainController(Private)
7 - (ITMTRemote *)loadRemote;
8 - (void)setupHotKeys;
9 - (void)timerUpdate;
10 @end
11
12 @implementation MainController
13
14 /*************************************************************************/
15 #pragma mark -
16 #pragma mark INITIALIZATION/DEALLOCATION METHODS
17 /*************************************************************************/
18
19 - (id)init
20 {
21     if ( ( self = [super init] ) ) {
22         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
23         statusWindowController = [[StatusWindowController alloc] init];
24         df = [[NSUserDefaults standardUserDefaults] retain];
25         [self setLatestSongIdentifier:@"0-0"];
26     }
27     return self;
28 }
29
30 - (void)applicationDidFinishLaunching:(NSNotification *)note
31 {
32     currentRemote = [self loadRemote];
33     [currentRemote begin];
34     
35     //Setup for notification of the remote player launching or quitting
36     [[[NSWorkspace sharedWorkspace] notificationCenter]
37             addObserver:self
38             selector:@selector(applicationTerminated:)
39             name:NSWorkspaceDidTerminateApplicationNotification
40             object:nil];
41     
42     [[[NSWorkspace sharedWorkspace] notificationCenter]
43             addObserver:self
44             selector:@selector(applicationLaunched:)
45             name:NSWorkspaceDidLaunchApplicationNotification
46             object:nil];
47
48     if ( ! [df objectForKey:@"menu"] ) {  // If this is nil, defaults have never been registered.
49         [[PreferencesController sharedPrefs] registerDefaults];
50     }
51     
52     statusItem = [[ITStatusItem alloc]
53             initWithStatusBar:[NSStatusBar systemStatusBar]
54             withLength:NSSquareStatusItemLength];
55     
56     [statusItem setImage:[NSImage imageNamed:@"menu"]];
57     [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]];
58 }
59
60 - (ITMTRemote *)loadRemote
61 {
62     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
63     
64     if (folderPath) {
65         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
66         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
67         NSString     *bundlePath;
68
69         while ( (bundlePath = [enumerator nextObject]) ) {
70             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
71
72             if (remoteBundle) {
73                 Class remoteClass = [remoteBundle principalClass];
74
75                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
76                     [remoteClass isKindOfClass:[NSObject class]]) {
77
78                     id remote = [remoteClass remote];
79                     [remoteArray addObject:remote];
80                 }
81             }
82         }
83
84 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
85 //          if ( [remoteArray count] > 1 ) {
86 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
87 //          }
88 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
89 //      }
90     }
91 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
92     return [remoteArray objectAtIndex:0];
93 }
94
95 /*************************************************************************/
96 #pragma mark -
97 #pragma mark INSTANCE METHODS
98 /*************************************************************************/
99
100 - (void)startTimerInNewThread
101 {
102     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
103     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
104     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
105                              target:self
106                              selector:@selector(timerUpdate)
107                              userInfo:nil
108                              repeats:YES] retain];
109     [runLoop run];
110     [pool release];
111 }
112
113 - (BOOL)songIsPlaying
114 {
115     return ( ! ([[currentRemote currentSongUniqueIdentifier] isEqualToString:@"0-0"]) );
116 }
117
118 - (BOOL)radioIsPlaying
119 {
120     return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist );
121 }
122
123 - (BOOL)songChanged
124 {
125     return ( ! [[currentRemote currentSongUniqueIdentifier] isEqualToString:_latestSongIdentifier] );
126 }
127
128 - (NSString *)latestSongIdentifier
129 {
130     return _latestSongIdentifier;
131 }
132
133 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
134 {
135     [_latestSongIdentifier autorelease];
136     _latestSongIdentifier = [newIdentifier copy];
137 }
138
139 - (void)timerUpdate
140 {
141     if ( ( [self songChanged] ) ||
142          ( ([self radioIsPlaying]) && (latestPlaylistClass != ITMTRemotePlayerRadioPlaylist) ) ||
143          ( (! [self radioIsPlaying]) && (latestPlaylistClass == ITMTRemotePlayerRadioPlaylist) ) ) {
144         //[statusItem setMenu:[self menu]];
145         [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]];
146         latestPlaylistClass = [currentRemote currentPlaylistClass];
147         
148         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
149             [self showCurrentTrackInfo];
150         }
151     }
152 /*    
153     //Update Play/Pause menu item
154     if (playPauseItem){
155         if ([currentRemote playerPlayingState] == ITMTRemotePlayerPlaying) {
156             [playPauseItem setTitle:@"Pause"];
157         } else {
158             [playPauseItem setTitle:@"Play"];
159         }
160     }
161 */
162 }
163
164 /*
165 //
166 //
167 // Menu Selectors
168 //
169 //
170
171 - (void)selectSong:(id)sender
172 {
173     [currentRemote switchToSongAtIndex:[[sender representedObject] intValue]];
174 }
175 */
176 - (void)selectPlaylist:(id)sender
177 {
178     int playlist = [sender tag];
179     [currentRemote switchToPlaylistAtIndex:playlist];
180 }
181 /*
182 - (void)selectEQPreset:(id)sender
183 {
184     int curSet = [currentRemote currentEQPresetIndex];
185     int item = [sender tag];
186     
187     [currentRemote switchToEQAtIndex:item];
188     [[eqMenu itemAtIndex:curSet - 1] setState:NSOffState];
189     [[eqMenu itemAtIndex:item] setState:NSOnState];
190 }
191 */
192 /*
193 - (void)selectSongRating:(id)sender
194 {
195     int newRating = [sender tag];
196 //  [[ratingMenu itemAtIndex:lastSongRating] setState:NSOffState];
197     [sender setState:NSOnState];
198     [currentRemote setCurrentSongRating:(float)newRating / 100.0];
199     lastSongRating = newRating / 20;
200 }
201 */
202 /*
203 - (void)playPause:(id)sender
204 {
205     ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
206     
207     if (state == ITMTRemotePlayerPlaying) {
208         [currentRemote pause];
209         [playPauseItem setTitle:@"Play"];
210     } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
211         [currentRemote pause];
212         [currentRemote play];
213     } else {
214         [currentRemote play];
215         [playPauseItem setTitle:@"Pause"];
216     }
217 }
218
219 - (void)nextSong:(id)sender
220 {
221     [currentRemote goToNextSong];
222 }
223
224 - (void)prevSong:(id)sender
225 {
226     [currentRemote goToPreviousSong];
227 }
228
229 - (void)fastForward:(id)sender
230 {
231     [currentRemote forward];
232     [playPauseItem setTitle:@"Play"];
233 }
234
235 - (void)rewind:(id)sender
236 {
237     [currentRemote rewind];
238     [playPauseItem setTitle:@"Play"];
239 }
240 */
241
242 //
243 //
244 - (void)quitMenuTunes:(id)sender
245 {
246     [NSApp terminate:self];
247 }
248
249 - (void)showPlayer:(id)sender
250 {
251     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
252         [currentRemote showPrimaryInterface];
253     } else {
254         if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
255             NSLog(@"Error Launching Player");
256         }
257     }
258 }
259
260 - (void)showPreferences:(id)sender
261 {
262     [[PreferencesController sharedPrefs] setController:self];
263     [[PreferencesController sharedPrefs] showPrefsWindow:self];
264 }
265
266 - (void)closePreferences
267 {
268     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
269         [self setupHotKeys];
270     }
271 }
272
273 - (ITMTRemote *)currentRemote
274 {
275     return currentRemote;
276 }
277
278 //
279 //
280 // Hot key setup
281 //
282 //
283
284 - (void)clearHotKeys
285 {
286     [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
287     [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
288     [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
289     [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
290     [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
291     [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleLoop"];
292     [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleShuffle"];
293     [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementVolume"];
294     [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementVolume"];
295     [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementRating"];
296     [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementRating"];
297 }
298
299 - (void)setupHotKeys
300 {
301     if ([df objectForKey:@"PlayPause"] != nil) {
302         [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
303                 combo:[df keyComboForKey:@"PlayPause"]
304                 target:self action:@selector(playPause:)];
305     }
306     
307     if ([df objectForKey:@"NextTrack"] != nil) {
308         [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
309                 combo:[df keyComboForKey:@"NextTrack"]
310                 target:self action:@selector(nextSong:)];
311     }
312     
313     if ([df objectForKey:@"PrevTrack"] != nil) {
314         [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
315                 combo:[df keyComboForKey:@"PrevTrack"]
316                 target:self action:@selector(prevSong:)];
317     }
318     
319     if ([df objectForKey:@"TrackInfo"] != nil) {
320         [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
321                 combo:[df keyComboForKey:@"TrackInfo"]
322                 target:self action:@selector(showCurrentTrackInfo)];
323     }
324     
325     if ([df objectForKey:@"UpcomingSongs"] != nil) {
326         [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
327                combo:[df keyComboForKey:@"UpcomingSongs"]
328                target:self action:@selector(showUpcomingSongs)];
329     }
330     
331     if ([df objectForKey:@"ToggleLoop"] != nil) {
332         [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop"
333                combo:[df keyComboForKey:@"ToggleLoop"]
334                target:self action:NULL/*Set this to something*/];
335     }
336     
337     if ([df objectForKey:@"ToggleShuffle"] != nil) {
338         [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle"
339                combo:[df keyComboForKey:@"ToggleShuffle"]
340                target:self action:NULL/*Set this to something*/];
341     }
342     
343     if ([df objectForKey:@"IncrementVolume"] != nil) {
344         [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume"
345                combo:[df keyComboForKey:@"IncrementVolume"]
346                target:self action:NULL/*Set this to something*/];
347     }
348     
349     if ([df objectForKey:@"DecrementVolume"] != nil) {
350         [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume"
351                combo:[df keyComboForKey:@"DecrementVolume"]
352                target:self action:NULL/*Set this to something*/];
353     }
354     
355     if ([df objectForKey:@"IncrementRating"] != nil) {
356         [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating"
357                combo:[df keyComboForKey:@"IncrementRating"]
358                target:self action:NULL/*Set this to something*/];
359     }
360     
361     if ([df objectForKey:@"DecrementRating"] != nil) {
362         [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating"
363                combo:[df keyComboForKey:@"DecrementRating"]
364                target:self action:NULL/*Set this to something*/];
365     }
366 }
367
368 - (void)showCurrentTrackInfo
369 {
370     NSString *title = [currentRemote currentSongTitle];
371
372     if ( title ) {
373         NSString *album       = nil;
374         NSString *artist      = nil;
375         NSString *time        = nil;
376         int       trackNumber = 0;
377         int       trackTotal  = 0;
378         int       rating      = 0;
379
380         if ( [df boolForKey:@"showAlbum"] ) {
381             album = [currentRemote currentSongAlbum];
382         }
383
384         if ( [df boolForKey:@"showArtist"] ) {
385             artist = [currentRemote currentSongArtist];
386         }
387
388         if ( [df boolForKey:@"showTime"] ) {
389             time = [currentRemote currentSongLength];
390         }
391
392         if ( [df boolForKey:@"showNumber"] ) {
393             trackNumber = [currentRemote currentSongTrack];
394             trackTotal  = [currentRemote currentAlbumTrackCount];
395         }
396
397         if ( [df boolForKey:@"showRating"] ) {
398             rating = ( [currentRemote currentSongRating] * 5 );
399         }
400
401         [statusWindowController showSongWindowWithTitle:title
402                                                   album:album
403                                                  artist:artist
404                                                    time:time
405                                             trackNumber:trackNumber
406                                              trackTotal:trackTotal
407                                                  rating:rating];
408     } else {
409         title = @"No song is playing.";
410         [statusWindowController showSongWindowWithTitle:title
411                                                   album:nil
412                                                  artist:nil
413                                                    time:nil
414                                             trackNumber:0
415                                              trackTotal:0
416                                                  rating:0];
417     }
418 }
419
420 - (void)showUpcomingSongs
421 {
422     int curPlaylist = [currentRemote currentPlaylistIndex];
423     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
424
425     if (numSongs > 0) {
426         NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
427         int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
428         int curTrack = [currentRemote currentSongIndex];
429         int i;
430
431         for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
432             if (i <= numSongs) {
433                 [songList addObject:[currentRemote songTitleAtIndex:i]];
434             }
435         }
436         
437         [statusWindowController showUpcomingSongsWithTitles:songList];
438         
439     } else {
440         [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:@"No upcoming songs."]];
441     }
442 }
443
444 /*************************************************************************/
445 #pragma mark -
446 #pragma mark WORKSPACE NOTIFICATION HANDLERS
447 /*************************************************************************/
448
449 - (void)applicationLaunched:(NSNotification *)note
450 {
451     if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
452         [NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
453         [self setupHotKeys];
454         playerRunningState = ITMTRemotePlayerRunning;
455     }
456 }
457
458  - (void)applicationTerminated:(NSNotification *)note
459  {
460      if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
461 /*
462          NSMenu *notRunningMenu = [[NSMenu alloc] initWithTitle:@""];
463          [notRunningMenu addItemWithTitle:[NSString stringWithFormat:@"Open %@", [currentRemote playerSimpleName]] action:@selector(showPlayer:) keyEquivalent:@""];
464          [notRunningMenu addItem:[NSMenuItem separatorItem]];
465          [notRunningMenu addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""];
466          [notRunningMenu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""];
467 */
468          [refreshTimer invalidate];
469          [refreshTimer release];
470          refreshTimer = nil;
471          [self clearHotKeys];
472          playerRunningState = ITMTRemotePlayerNotRunning;
473
474          [statusItem setMenu:[self menuForNoPlayer]];
475      }
476  }
477
478
479 /*************************************************************************/
480 #pragma mark -
481 #pragma mark NSApplication DELEGATE METHODS
482 /*************************************************************************/
483
484 - (void)applicationWillTerminate:(NSNotification *)note
485 {
486     [self clearHotKeys];
487     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
488 }
489
490
491 /*************************************************************************/
492 #pragma mark -
493 #pragma mark DEALLOCATION METHOD
494 /*************************************************************************/
495
496 - (void)dealloc
497 {
498     if (refreshTimer) {
499         [refreshTimer invalidate];
500         [refreshTimer release];
501         refreshTimer = nil;
502     }
503     
504     [currentRemote halt];
505     [statusItem release];
506     [statusWindowController release];
507     [super dealloc];
508 }
509
510
511 @end