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