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