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