Added more the MenuController. All of the main menu things are added in now.
[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         [self setLatestSongIdentifier:[currentRemote currentSongUniqueIdentifier]];
160         latestPlaylistClass = [currentRemote currentPlaylistClass];
161         
162         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
163             [self showCurrentTrackInfo];
164         }
165     }
166 }
167
168 - (void)menuClicked
169 {
170     if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) {
171         [statusItem setMenu:[menuController menu]];
172     } else {
173         [statusItem setMenu:[menuController menuForNoPlayer]];
174     }
175 }
176
177 //
178 //
179 // Menu Selectors
180 //
181 //
182
183 - (void)playPause
184 {
185     ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState];
186     
187     if (state == ITMTRemotePlayerPlaying) {
188         [currentRemote pause];
189     } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
190         [currentRemote pause];
191         [currentRemote play];
192     } else {
193         [currentRemote play];
194     }
195 }
196
197 - (void)nextSong
198 {
199     [currentRemote goToNextSong];
200 }
201
202 - (void)prevSong
203 {
204     [currentRemote goToPreviousSong];
205 }
206
207 - (void)fastForward
208 {
209     [currentRemote forward];
210 }
211
212 - (void)rewind
213 {
214     [currentRemote rewind];
215 }
216
217 - (void)selectPlaylistAtIndex:(int)index
218 {
219     [currentRemote switchToPlaylistAtIndex:index];
220 }
221
222 - (void)selectSongAtIndex:(int)index
223 {
224     [currentRemote switchToSongAtIndex:index];
225 }
226
227 - (void)selectSongRating:(int)rating
228 {
229     [currentRemote setCurrentSongRating:(float)rating / 100.0];
230 }
231
232 - (void)selectEQPresetAtIndex:(int)index
233 {
234     [currentRemote switchToEQAtIndex:index];
235 }
236
237 - (void)showPreferences
238 {
239     [[PreferencesController sharedPrefs] setController:self];
240     [[PreferencesController sharedPrefs] showPrefsWindow:self];
241 }
242
243 - (void)quitMenuTunes
244 {
245     [NSApp terminate:self];
246 }
247
248 //
249 //
250
251 - (void)showPlayer:(id)sender
252 {
253     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
254         [currentRemote showPrimaryInterface];
255     } else {
256         if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) {
257             NSLog(@"Error Launching Player");
258         }
259     }
260 }
261
262 - (void)closePreferences
263 {
264     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
265         [self setupHotKeys];
266     }
267 }
268
269 - (ITMTRemote *)currentRemote
270 {
271     return currentRemote;
272 }
273
274 //
275 //
276 // Hot key setup
277 //
278 //
279
280 - (void)clearHotKeys
281 {
282     [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
283     [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
284     [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
285     [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
286     [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
287     [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleLoop"];
288     [[HotKeyCenter sharedCenter] removeHotKey:@"ToggleShuffle"];
289     [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementVolume"];
290     [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementVolume"];
291     [[HotKeyCenter sharedCenter] removeHotKey:@"IncrementRating"];
292     [[HotKeyCenter sharedCenter] removeHotKey:@"DecrementRating"];
293 }
294
295 - (void)setupHotKeys
296 {
297     if ([df objectForKey:@"PlayPause"] != nil) {
298         [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
299                 combo:[df keyComboForKey:@"PlayPause"]
300                 target:self action:@selector(playPause:)];
301     }
302     
303     if ([df objectForKey:@"NextTrack"] != nil) {
304         [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
305                 combo:[df keyComboForKey:@"NextTrack"]
306                 target:self action:@selector(nextSong:)];
307     }
308     
309     if ([df objectForKey:@"PrevTrack"] != nil) {
310         [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
311                 combo:[df keyComboForKey:@"PrevTrack"]
312                 target:self action:@selector(prevSong:)];
313     }
314     
315     if ([df objectForKey:@"TrackInfo"] != nil) {
316         [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
317                 combo:[df keyComboForKey:@"TrackInfo"]
318                 target:self action:@selector(showCurrentTrackInfo)];
319     }
320     
321     if ([df objectForKey:@"UpcomingSongs"] != nil) {
322         [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
323                combo:[df keyComboForKey:@"UpcomingSongs"]
324                target:self action:@selector(showUpcomingSongs)];
325     }
326     
327     if ([df objectForKey:@"ToggleLoop"] != nil) {
328         [[HotKeyCenter sharedCenter] addHotKey:@"ToggleLoop"
329                combo:[df keyComboForKey:@"ToggleLoop"]
330                target:self action:NULL/*Set this to something*/];
331     }
332     
333     if ([df objectForKey:@"ToggleShuffle"] != nil) {
334         [[HotKeyCenter sharedCenter] addHotKey:@"ToggleShuffle"
335                combo:[df keyComboForKey:@"ToggleShuffle"]
336                target:self action:NULL/*Set this to something*/];
337     }
338     
339     if ([df objectForKey:@"IncrementVolume"] != nil) {
340         [[HotKeyCenter sharedCenter] addHotKey:@"IncrementVolume"
341                combo:[df keyComboForKey:@"IncrementVolume"]
342                target:self action:NULL/*Set this to something*/];
343     }
344     
345     if ([df objectForKey:@"DecrementVolume"] != nil) {
346         [[HotKeyCenter sharedCenter] addHotKey:@"DecrementVolume"
347                combo:[df keyComboForKey:@"DecrementVolume"]
348                target:self action:NULL/*Set this to something*/];
349     }
350     
351     if ([df objectForKey:@"IncrementRating"] != nil) {
352         [[HotKeyCenter sharedCenter] addHotKey:@"IncrementRating"
353                combo:[df keyComboForKey:@"IncrementRating"]
354                target:self action:NULL/*Set this to something*/];
355     }
356     
357     if ([df objectForKey:@"DecrementRating"] != nil) {
358         [[HotKeyCenter sharedCenter] addHotKey:@"DecrementRating"
359                combo:[df keyComboForKey:@"DecrementRating"]
360                target:self action:NULL/*Set this to something*/];
361     }
362 }
363
364 - (void)showCurrentTrackInfo
365 {
366     NSString *title = [currentRemote currentSongTitle];
367
368     if ( title ) {
369         NSString *album       = nil;
370         NSString *artist      = nil;
371         NSString *time        = nil;
372         int       trackNumber = 0;
373         int       trackTotal  = 0;
374         int       rating      = 0;
375
376         if ( [df boolForKey:@"showAlbum"] ) {
377             album = [currentRemote currentSongAlbum];
378         }
379
380         if ( [df boolForKey:@"showArtist"] ) {
381             artist = [currentRemote currentSongArtist];
382         }
383
384         if ( [df boolForKey:@"showTime"] ) {
385             time = [currentRemote currentSongLength];
386         }
387
388         if ( [df boolForKey:@"showNumber"] ) {
389             trackNumber = [currentRemote currentSongTrack];
390             trackTotal  = [currentRemote currentAlbumTrackCount];
391         }
392
393         if ( [df boolForKey:@"showRating"] ) {
394             rating = ( [currentRemote currentSongRating] * 5 );
395         }
396
397         [statusWindowController showSongWindowWithTitle:title
398                                                   album:album
399                                                  artist:artist
400                                                    time:time
401                                             trackNumber:trackNumber
402                                              trackTotal:trackTotal
403                                                  rating:rating];
404     } else {
405         title = @"No song is playing.";
406         [statusWindowController showSongWindowWithTitle:title
407                                                   album:nil
408                                                  artist:nil
409                                                    time:nil
410                                             trackNumber:0
411                                              trackTotal:0
412                                                  rating:0];
413     }
414 }
415
416 - (void)showUpcomingSongs
417 {
418     int curPlaylist = [currentRemote currentPlaylistIndex];
419     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
420
421     if (numSongs > 0) {
422         NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5];
423         int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
424         int curTrack = [currentRemote currentSongIndex];
425         int i;
426
427         for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
428             if (i <= numSongs) {
429                 [songList addObject:[currentRemote songTitleAtIndex:i]];
430             }
431         }
432         
433         [statusWindowController showUpcomingSongsWithTitles:songList];
434         
435     } else {
436         [statusWindowController showUpcomingSongsWithTitles:[NSArray arrayWithObject:@"No upcoming songs."]];
437     }
438 }
439
440 /*************************************************************************/
441 #pragma mark -
442 #pragma mark WORKSPACE NOTIFICATION HANDLERS
443 /*************************************************************************/
444
445 - (void)applicationLaunched:(NSNotification *)note
446 {
447     if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
448         [NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
449         [self setupHotKeys];
450         playerRunningState = ITMTRemotePlayerRunning;
451     }
452 }
453
454  - (void)applicationTerminated:(NSNotification *)note
455  {
456      if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) {
457 /*
458          NSMenu *notRunningMenu = [[NSMenu alloc] initWithTitle:@""];
459          [notRunningMenu addItemWithTitle:[NSString stringWithFormat:@"Open %@", [currentRemote playerSimpleName]] action:@selector(showPlayer:) keyEquivalent:@""];
460          [notRunningMenu addItem:[NSMenuItem separatorItem]];
461          [notRunningMenu addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""];
462          [notRunningMenu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""];
463 */
464          [refreshTimer invalidate];
465          [refreshTimer release];
466          refreshTimer = nil;
467          [self clearHotKeys];
468          playerRunningState = ITMTRemotePlayerNotRunning;
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     [menuController release];
502     [super dealloc];
503 }
504
505
506 @end