Plugin API changes
[MenuTunes.git] / MenuTunes.m
1 #import "MenuTunes.h"
2 #import "PreferencesController.h"
3 #import "HotKeyCenter.h"
4 #import "StatusWindow.h"
5
6 @interface MenuTunes(Private)
7 - (ITMTRemote *)loadRemote;
8 - (void)updateMenu;
9 - (void)rebuildUpcomingSongsMenu;
10 - (void)rebuildPlaylistMenu;
11 - (void)rebuildEQPresetsMenu;
12 - (void)setupHotKeys;
13 - (void)timerUpdate;
14 - (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers
15         onItem:(NSMenuItem *)item;
16
17 @end
18
19 @implementation MenuTunes
20
21 /*************************************************************************/
22 #pragma mark -
23 #pragma mark INITIALIZATION METHODS
24 /*************************************************************************/
25
26 - (id)init
27 {
28     if ( ( self = [super init] ) ) {
29         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
30         statusWindow = [StatusWindow sharedWindow];
31     }
32     return self;
33 }
34
35 - (void)applicationDidFinishLaunching:(NSNotification *)note
36 {
37     currentRemote = [self loadRemote];
38     [currentRemote begin];
39     
40     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(remotePlayerTerminated:) name:@"ITMTRemoteAppDidTerminateNotification" object:nil];
41     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(remotePlayerLaunched:) name:@"ITMTRemoteAppDidLaunchNotification" object:nil];
42     
43     [self registerDefaultsIfNeeded];
44     
45     menu = [[NSMenu alloc] initWithTitle:@""];
46     
47     if ( ( [currentRemote remotePlayerStatus] == ITMTRemotePlayerRunning ) ) {
48         [self remotePlayerLaunched:nil];
49     } else {
50         [self remotePlayerTerminated:nil];
51     }
52     
53     statusItem = [[ITStatusItem alloc] initWithStatusBar:[NSStatusBar systemStatusBar]
54                                               withLength:NSSquareStatusItemLength];
55     
56     [statusItem setImage:[NSImage imageNamed:@"menu"]];
57     [statusItem setAlternateImage:[NSImage imageNamed:@"selected_image"]];
58     [statusItem setMenu:menu];
59     // Below line of code is for creating builds for Beta Testers
60     // [statusItem setToolTip:@[NSString stringWithFormat:@"This Nontransferable Beta (Built on %s) of iThink Software's MenuTunes is Registered to: Beta Tester (betatester@somedomain.com).",__DATE__]];
61 }
62
63 - (ITMTRemote *)loadRemote
64 {
65     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
66     
67     if (folderPath) {
68         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
69         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
70         NSString     *bundlePath;
71
72         while ( (bundlePath = [enumerator nextObject]) ) {
73             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
74
75             if (remoteBundle) {
76                 Class remoteClass = [remoteBundle principalClass];
77
78                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
79                     [remoteClass isKindOfClass:[NSObject class]]) {
80
81                     id remote = [remoteClass remote];
82                     [remoteArray addObject:remote];
83                 }
84             }
85         }
86
87 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
88 //          if ( [remoteArray count] > 1 ) {
89 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
90 //          }
91 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
92 //      }
93     }
94 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
95     return [remoteArray objectAtIndex:0];
96 }
97
98
99 /*************************************************************************/
100 #pragma mark -
101 #pragma mark INSTANCE METHODS
102 /*************************************************************************/
103
104 - (void)registerDefaultsIfNeeded
105 {
106     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
107     if (![defaults objectForKey:@"menu"]) {
108         BOOL found = NO;
109         NSMutableDictionary *loginwindow;
110         NSMutableArray *loginarray;
111         int i;
112         
113         [defaults setObject:
114             [NSArray arrayWithObjects:
115                 @"Play/Pause",
116                 @"Next Track",
117                 @"Previous Track",
118                 @"Fast Forward",
119                 @"Rewind",
120                 @"<separator>",
121                 @"Upcoming Songs",
122                 @"Playlists",
123                 @"Song Rating",
124                 @"<separator>",
125                 @"PreferencesÉ",
126                 @"Quit",
127                 @"<separator>",
128                 @"Current Track Info",
129                 nil] forKey:@"menu"];
130         
131         [defaults synchronize];
132         loginwindow = [[defaults persistentDomainForName:@"loginwindow"] mutableCopy];
133         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
134         
135         for (i = 0; i < [loginarray count]; i++) {
136             NSDictionary *tempDict = [loginarray objectAtIndex:i];
137             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
138                 found = YES;
139             }
140         }
141         
142         //
143         //This is teh sux
144         //We must fix it so it is no longer suxy
145         if (!found) {
146             if (NSRunInformationalAlertPanel(@"Auto-launch MenuTunes", @"Would you like MenuTunes to automatically launch at login?", @"Yes", @"No", nil) == NSOKButton) {
147                 AEDesc scriptDesc, resultDesc;
148                 NSString *script = [NSString stringWithFormat:@"tell application \"System Events\"\nmake new login item at end of login items with properties {path:\"%@\", kind:\"APPLICATION\"}\nend tell", [[NSBundle mainBundle] bundlePath]];
149                 ComponentInstance asComponent = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);
150                 
151                 AECreateDesc(typeChar, [script cString], [script cStringLength], 
152             &scriptDesc);
153                 
154                 OSADoScript(asComponent, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
155                 
156                 AEDisposeDesc(&scriptDesc);
157                 AEDisposeDesc(&resultDesc);
158                 
159                 CloseComponent(asComponent);
160             }
161         }
162     }
163     
164     if (![defaults integerForKey:@"SongsInAdvance"])
165     {
166         [defaults setInteger:5 forKey:@"SongsInAdvance"];
167     }
168     
169     if (![defaults objectForKey:@"showName"]) {
170         [defaults setBool:YES forKey:@"showName"];
171     }
172     
173     if (![defaults objectForKey:@"showArtist"]) {
174         [defaults setBool:YES forKey:@"showArtist"];
175     }
176     
177     if (![defaults objectForKey:@"showAlbum"]) {
178         [defaults setBool:NO forKey:@"showAlbum"];
179     }
180     
181     if (![defaults objectForKey:@"showTime"]) {
182         [defaults setBool:NO forKey:@"showTime"];
183     }
184 }
185
186 //Recreate the status item menu
187 - (void)rebuildMenu
188 {
189     NSArray *myMenu = [[NSUserDefaults standardUserDefaults] arrayForKey:@"menu"];
190     int i;
191     
192     trackInfoIndex = -1;
193     lastSongIndex = -1;
194     lastPlaylistIndex = -1;
195     didHaveAlbumName = ([[currentRemote currentSongAlbum] length] > 0);
196     didHaveArtistName = ([[currentRemote currentSongArtist] length] > 0);
197     
198     while ([menu numberOfItems] > 0) {
199         [menu removeItemAtIndex:0];
200     }
201     
202     playPauseMenuItem = nil;
203     upcomingSongsItem = nil;
204     playlistItem = nil;
205     [playlistMenu release];
206     playlistMenu = nil;
207     eqItem = nil;
208     [eqMenu release];
209     eqMenu = nil;
210     
211     for (i = 0; i < [myMenu count]; i++) {
212         NSString *item = [myMenu objectAtIndex:i];
213         if ([item isEqualToString:@"Play/Pause"]) {
214             KeyCombo *tempCombo = [[NSUserDefaults standardUserDefaults] keyComboForKey:@"PlayPause"];
215             playPauseMenuItem = [menu addItemWithTitle:@"Play"
216                                     action:@selector(playPause:)
217                                     keyEquivalent:@""];
218             [playPauseMenuItem setTarget:self];
219             
220             if (tempCombo) {
221                 [self setKeyEquivalentForCode:[tempCombo keyCode]
222                     andModifiers:[tempCombo modifiers] onItem:playPauseMenuItem];
223                 [tempCombo release];
224             }
225         } else if ([item isEqualToString:@"Next Track"]) {
226             KeyCombo *tempCombo = [[NSUserDefaults standardUserDefaults] keyComboForKey:@"NextTrack"];
227             NSMenuItem *nextTrack = [menu addItemWithTitle:@"Next Track"
228                                         action:@selector(nextSong:)
229                                         keyEquivalent:@""];
230             
231             [nextTrack setTarget:self];
232             if (tempCombo) {
233                 [self setKeyEquivalentForCode:[tempCombo keyCode]
234                     andModifiers:[tempCombo modifiers] onItem:nextTrack];
235                 [tempCombo release];
236             }
237         } else if ([item isEqualToString:@"Previous Track"]) {
238             KeyCombo *tempCombo = [[NSUserDefaults standardUserDefaults] keyComboForKey:@"PrevTrack"];
239             NSMenuItem *prevTrack = [menu addItemWithTitle:@"Previous Track"
240                                         action:@selector(prevSong:)
241                                         keyEquivalent:@""];
242             
243             [prevTrack setTarget:self];
244             if (tempCombo) {
245                 [self setKeyEquivalentForCode:[tempCombo keyCode]
246                     andModifiers:[tempCombo modifiers] onItem:prevTrack];
247                 [tempCombo release];
248             }
249         } else if ([item isEqualToString:@"Fast Forward"]) {
250             [[menu addItemWithTitle:@"Fast Forward"
251                     action:@selector(fastForward:)
252                     keyEquivalent:@""] setTarget:self];
253         } else if ([item isEqualToString:@"Rewind"]) {
254             [[menu addItemWithTitle:@"Rewind"
255                     action:@selector(rewind:)
256                     keyEquivalent:@""] setTarget:self];
257         } else if ([item isEqualToString:@"Upcoming Songs"]) {
258             upcomingSongsItem = [menu addItemWithTitle:@"Upcoming Songs"
259                     action:nil
260                     keyEquivalent:@""];
261         } else if ([item isEqualToString:@"Playlists"]) {
262             playlistItem = [menu addItemWithTitle:@"Playlists"
263                     action:nil
264                     keyEquivalent:@""];
265         } else if ([item isEqualToString:@"EQ Presets"]) {
266             eqItem = [menu addItemWithTitle:@"EQ Presets"
267                     action:nil
268                     keyEquivalent:@""];
269         } else if ([item isEqualToString:@"PreferencesÉ"]) {
270             [[menu addItemWithTitle:@"PreferencesÉ"
271                     action:@selector(showPreferences:)
272                     keyEquivalent:@""] setTarget:self];
273         } else if ([item isEqualToString:@"Quit"]) {
274             [[menu addItemWithTitle:@"Quit"
275                     action:@selector(quitMenuTunes:)
276                     keyEquivalent:@""] setTarget:self];
277         } else if ([item isEqualToString:@"Current Track Info"]) {
278             trackInfoIndex = [menu numberOfItems];
279             [menu addItemWithTitle:@"No Song"
280                     action:nil
281                     keyEquivalent:@""];
282         } else if ([item isEqualToString:@"Song Rating"]) {
283             NSMenu *ratingSubmenu = [[NSMenu alloc] initWithTitle:@""];
284             unichar whiteStar = 'o';//2606;
285             unichar blackStar = 'x';//2605;
286             NSString *whiteStarString = [NSString stringWithCharacters:&whiteStar
287                                             length:1];
288             NSString *blackStarString = [NSString stringWithCharacters:&blackStar
289                                             length:1];
290             NSString *string = @"";
291             int i;
292             
293             for (i = 0; i < 5; i++) {
294                 string = [string stringByAppendingString:whiteStarString];
295             }
296             for (i = 0; i < 6; i++) {
297                 NSMenuItem *ratingItem;
298                 ratingItem = [ratingSubmenu addItemWithTitle:string action:@selector(setSongRating:) keyEquivalent:@""];
299                 [ratingItem setTarget:self];
300                 [ratingItem setTag:i * 20];
301                 string = [string substringToIndex:4];
302                 string = [blackStarString stringByAppendingString:string];
303             }
304             [[menu addItemWithTitle:@"Song Rating"
305                     action:nil
306                     keyEquivalent:@""] setSubmenu:ratingSubmenu];
307             [ratingSubmenu autorelease];
308         } else if ([item isEqualToString:@"<separator>"]) {
309             [menu addItem:[NSMenuItem separatorItem]];
310         }
311     }
312     
313     [self timerUpdate]; //Updates dynamic info in the menu
314     
315     [self clearHotKeys];
316     [self setupHotKeys];
317 }
318
319 //Updates the menu with current player state, song, and upcoming songs
320 - (void)updateMenu
321 {
322     NSMenuItem *menuItem;
323     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
324     
325     if ( ( isAppRunning = ITMTRemotePlayerNotRunning ) ) {
326         return;
327     }
328     
329     if (upcomingSongsItem) {
330         [self rebuildUpcomingSongsMenu];
331     }
332     
333     if (playlistItem) {
334         [self rebuildPlaylistMenu];
335     }
336     
337     if (eqItem) {
338         [self rebuildEQPresetsMenu];
339     }
340     
341     if (trackInfoIndex > -1) {
342         NSString *curSongName, *curAlbumName = @"", *curArtistName = @"";
343         curSongName = [currentRemote currentSongTitle];
344         
345         if ([defaults boolForKey:@"showAlbum"]) {
346             curAlbumName = [currentRemote currentSongAlbum];
347         }
348         
349         if ([defaults boolForKey:@"showArtist"]) {
350             curArtistName = [currentRemote currentSongArtist];
351         }
352         
353         if ([curSongName length] > 0) {
354             int index = [menu indexOfItemWithTitle:@"Now Playing"];
355             if (index > -1) {
356                 if ([defaults boolForKey:@"showName"]) {
357                     [menu removeItemAtIndex:index + 1];
358                 }
359                 if (didHaveAlbumName && [defaults boolForKey:@"showAlbum"]) {
360                     [menu removeItemAtIndex:index + 1];
361                 }
362                 if (didHaveArtistName && [defaults boolForKey:@"showArtist"]) {
363                     [menu removeItemAtIndex:index + 1];
364                 }
365                 if ([defaults boolForKey:@"showTime"]) {
366                     [menu removeItemAtIndex:index + 1];
367                 }
368             }
369             
370             if (!isPlayingRadio) {
371                 if ([defaults boolForKey:@"showTime"]) {
372                     menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", [currentRemote currentSongLength]]
373                             action:nil
374                             keyEquivalent:@""];
375                     [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
376                     [menuItem release];
377                 }
378                 
379                 if ([curArtistName length] > 0) {
380                     menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curArtistName]
381                             action:nil
382                             keyEquivalent:@""];
383                     [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
384                     [menuItem release];
385                 }
386                 
387                 if ([curAlbumName length] > 0) {
388                     menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curAlbumName]
389                             action:nil
390                             keyEquivalent:@""];
391                     [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
392                     [menuItem release];
393                 }
394             }
395             
396             if ([defaults boolForKey:@"showName"]) {
397                 menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curSongName]
398                             action:nil
399                             keyEquivalent:@""];
400                 [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
401                 [menuItem release];
402             }
403             
404             if (index == -1) {
405                 menuItem = [[NSMenuItem alloc] initWithTitle:@"Now Playing" action:nil keyEquivalent:@""];
406                 [menu removeItemAtIndex:[menu indexOfItemWithTitle:@"No Song"]];
407                 [menu insertItem:menuItem atIndex:trackInfoIndex];
408                 [menuItem release];
409             }
410         } else if ([menu indexOfItemWithTitle:@"No Song"] == -1) {
411             [menu removeItemAtIndex:trackInfoIndex];
412             
413             if ([defaults boolForKey:@"showName"] == YES) {
414                 [menu removeItemAtIndex:trackInfoIndex];
415             }
416             
417             if ([defaults boolForKey:@"showTime"] == YES) {
418                 [menu removeItemAtIndex:trackInfoIndex];
419             }
420             
421             if (didHaveArtistName && [defaults boolForKey:@"showArtist"]) {
422                 [menu removeItemAtIndex:trackInfoIndex];
423             }
424             
425             if (didHaveAlbumName && [defaults boolForKey:@"showAlbum"]) {
426                 [menu removeItemAtIndex:trackInfoIndex];
427             }
428             
429             menuItem = [[NSMenuItem alloc] initWithTitle:@"No Song" action:nil keyEquivalent:@""];
430             [menu insertItem:menuItem atIndex:trackInfoIndex];
431             [menuItem release];
432         }
433         
434         if ([defaults boolForKey:@"showArtist"]) {
435             didHaveArtistName = (([curArtistName length] > 0) ? YES : NO);
436         }
437             
438         if ([defaults boolForKey:@"showAlbum"]) {
439             didHaveAlbumName = (([curAlbumName length] > 0) ? YES : NO);
440         }
441     }
442 }
443
444 //Rebuild the upcoming songs submenu. Can be improved a lot.
445 - (void)rebuildUpcomingSongsMenu
446 {
447     int curIndex = [currentRemote currentPlaylistIndex];
448     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curIndex];
449     int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
450     
451     if (!isPlayingRadio) {
452         if (numSongs > 0) {
453             int curTrack = [currentRemote currentSongIndex];
454             int i;
455             
456             [upcomingSongsMenu release];
457             upcomingSongsMenu = [[NSMenu alloc] initWithTitle:@""];
458             [upcomingSongsItem setSubmenu:upcomingSongsMenu];
459             [upcomingSongsItem setEnabled:YES];
460             
461             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
462                 if (i <= numSongs) {
463                     NSString *curSong = [currentRemote songTitleAtIndex:i];
464                     NSMenuItem *songItem;
465                     songItem = [[NSMenuItem alloc] initWithTitle:curSong action:@selector(playTrack:) keyEquivalent:@""];
466                     [songItem setTarget:self];
467                     [songItem setRepresentedObject:[NSNumber numberWithInt:i]];
468                     [upcomingSongsMenu addItem:songItem];
469                     [songItem release];
470                 } else {
471                     break;
472                 }
473             }
474         }
475     } else {
476         [upcomingSongsItem setSubmenu:nil];
477         [upcomingSongsItem setEnabled:NO];
478     }
479 }
480
481 - (void)rebuildPlaylistMenu
482 {
483     NSArray *playlists = [currentRemote playlists];
484     int i, curPlaylist = [currentRemote currentPlaylistIndex];
485     
486     if (isPlayingRadio) {
487         curPlaylist = 0;
488     }
489     if (playlistMenu && ([playlists count] == [playlistMenu numberOfItems]))
490         return;
491     
492     [playlistMenu release];
493     playlistMenu = [[NSMenu alloc] initWithTitle:@""];
494     
495     for (i = 0; i < [playlists count]; i++) {
496         NSString *playlistName = [playlists objectAtIndex:i];
497         NSMenuItem *tempItem;
498         tempItem = [[NSMenuItem alloc] initWithTitle:playlistName action:@selector(selectPlaylist:) keyEquivalent:@""];
499         [tempItem setTarget:self];
500         [tempItem setRepresentedObject:[NSNumber numberWithInt:i + 1]];
501         [playlistMenu addItem:tempItem];
502         [tempItem release];
503     }
504     [playlistItem setSubmenu:playlistMenu];
505     [playlistItem setEnabled:YES];
506     
507     if (curPlaylist) {
508         [[playlistMenu itemAtIndex:curPlaylist - 1] setState:NSOnState];
509     }
510 }
511
512 //Build a menu with the list of all available EQ presets
513 - (void)rebuildEQPresetsMenu
514 {
515     NSArray *eqPresets = [currentRemote eqPresets];
516     NSMenuItem *enabledItem;
517     int i;
518     
519     if (eqMenu && ([[currentRemote eqPresets] count] == [eqMenu numberOfItems]))
520         return;
521     
522     [eqMenu release];
523     eqMenu = [[NSMenu alloc] initWithTitle:@""];
524     
525     enabledItem = [eqMenu addItemWithTitle:@"EQ Enabled"
526                           action:NULL
527                           keyEquivalent:@""];
528     [eqMenu addItem:[NSMenuItem separatorItem]];
529     
530     for (i = 0; i < [eqPresets count]; i++) {
531         NSString *setName = [eqPresets objectAtIndex:i];
532         NSMenuItem *tempItem;
533         if (setName) {
534         tempItem = [[NSMenuItem alloc] initWithTitle:setName action:@selector(selectEQPreset:) keyEquivalent:@""];
535         [tempItem setTarget:self];
536         [tempItem setRepresentedObject:[NSNumber numberWithInt:i]];
537         [eqMenu addItem:tempItem];
538         [tempItem release];
539         }
540     }
541     [eqItem setSubmenu:eqMenu];
542     
543     [[eqMenu itemAtIndex:[currentRemote currentEQPresetIndex] + 1] setState:NSOnState];
544 }
545
546 - (void)clearHotKeys
547 {
548     [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
549     [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
550     [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
551     [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
552     [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
553 }
554
555 - (void)setupHotKeys
556 {
557     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
558     
559     if ([defaults objectForKey:@"PlayPause"] != nil) {
560         [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
561                 combo:[defaults keyComboForKey:@"PlayPause"]
562                 target:self action:@selector(playPause:)];
563     }
564     
565     if ([defaults objectForKey:@"NextTrack"] != nil) {
566         [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
567                 combo:[defaults keyComboForKey:@"NextTrack"]
568                 target:self action:@selector(nextSong:)];
569     }
570     
571     if ([defaults objectForKey:@"PrevTrack"] != nil) {
572         [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
573                 combo:[defaults keyComboForKey:@"PrevTrack"]
574                 target:self action:@selector(prevSong:)];
575     }
576     
577     if ([defaults objectForKey:@"TrackInfo"] != nil) {
578         [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
579                 combo:[defaults keyComboForKey:@"TrackInfo"]
580                 target:self action:@selector(showCurrentTrackInfo)];
581     }
582     
583     if ([defaults objectForKey:@"UpcomingSongs"] != nil) {
584         [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
585                combo:[defaults keyComboForKey:@"UpcomingSongs"]
586                target:self action:@selector(showUpcomingSongs)];
587     }
588 }
589
590 //Called when the timer fires.
591 - (void)timerUpdate
592 {
593     int playlist = [currentRemote currentPlaylistIndex];
594     ITMTRemotePlayerState playerState = [currentRemote playerState];
595     
596     if ((playlist > 0) || playerState != stopped) {
597         int trackPlayingIndex = [currentRemote currentSongIndex];
598         
599         if (trackPlayingIndex != lastSongIndex) {
600             BOOL wasPlayingRadio = isPlayingRadio;
601             isPlayingRadio = [[currentRemote classOfPlaylistAtIndex:playlist] isEqualToString:@"radio tuner playlist"];
602             
603             if (isPlayingRadio && !wasPlayingRadio) {
604                 int i;
605                 for (i = 0; i < [playlistMenu numberOfItems]; i++)
606                 {
607                     [[playlistMenu itemAtIndex:i] setState:NSOffState];
608                 }
609             } else {
610                 [[playlistMenu itemAtIndex:playlist - 1] setState:NSOnState];
611             }
612             
613             if (wasPlayingRadio) {
614                 NSMenuItem *temp = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
615                 [menu insertItem:temp atIndex:trackInfoIndex + 1];
616                 [temp release];
617             }
618             
619             [self updateMenu];
620             lastSongIndex = trackPlayingIndex;
621         } else {
622             if (playlist != lastPlaylistIndex) {
623                 BOOL wasPlayingRadio = isPlayingRadio;
624                 isPlayingRadio = [[currentRemote classOfPlaylistAtIndex:playlist] isEqualToString:@"radio tuner playlist"];
625                 
626                 if (isPlayingRadio && !wasPlayingRadio) {
627                     int i;
628                     for (i = 0; i < [playlistMenu numberOfItems]; i++) {
629                         [[playlistMenu itemAtIndex:i] setState:NSOffState];
630                     }
631                 }
632                 
633                 if (wasPlayingRadio) {
634                     NSMenuItem *temp = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
635                     [menu insertItem:temp atIndex:trackInfoIndex + 1];
636                     [temp release];
637                 }
638                 
639                 if (!isPlayingRadio) {
640                     int i;
641                     for (i = 0; i < [playlistMenu numberOfItems]; i++)
642                     {
643                         [[playlistMenu itemAtIndex:i] setState:NSOffState];
644                     }
645                     [[playlistMenu itemAtIndex:playlist - 1] setState:NSOnState];
646                 }
647                 
648                 [self updateMenu];
649                 lastSongIndex = trackPlayingIndex;
650                 lastPlaylistIndex = playlist;
651             }
652         }
653         //Update Play/Pause menu item
654         if (playPauseMenuItem){
655             if (playerState == playing) {
656                 [playPauseMenuItem setTitle:@"Pause"];
657             } else {
658                 [playPauseMenuItem setTitle:@"Play"];
659             }
660         }
661     } else if ((lastPlaylistIndex > 0) && (playlist == 0)) {
662         NSMenuItem *menuItem;
663         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
664         //Remote the now playing item and add no song item
665         [menu removeItemAtIndex:trackInfoIndex];
666         
667         if ([defaults boolForKey:@"showName"] == YES) {
668             [menu removeItemAtIndex:trackInfoIndex];
669         }
670         
671         if ([defaults boolForKey:@"showTime"] == YES) {
672             [menu removeItemAtIndex:trackInfoIndex];
673         }
674         
675         if (didHaveArtistName && [defaults boolForKey:@"showArtist"]) {
676             [menu removeItemAtIndex:trackInfoIndex];
677         }
678         
679         if (didHaveAlbumName && [defaults boolForKey:@"showAlbum"]) {
680             [menu removeItemAtIndex:trackInfoIndex];
681         }
682         
683         [playPauseMenuItem setTitle:@"Play"];
684         
685         didHaveArtistName = NO;
686         didHaveAlbumName = NO;
687         lastPlaylistIndex = -1;
688         lastSongIndex = -1;
689         
690         [upcomingSongsItem setSubmenu:nil];
691         [upcomingSongsItem setEnabled:NO];
692         
693         menuItem = [[NSMenuItem alloc] initWithTitle:@"No Song" action:nil keyEquivalent:@""];
694         [menu insertItem:menuItem atIndex:trackInfoIndex];
695         [menuItem release];
696     }
697 }
698
699 - (void)remotePlayerLaunched:(NSNotification *)note
700 {
701     isAppRunning = ITMTRemotePlayerRunning;
702     
703     //Restart the timer
704     refreshTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES]; 
705     
706     [self rebuildMenu]; //Rebuild the menu since no songs will be playing
707     if (playlistItem) {
708         [self rebuildPlaylistMenu];
709     }
710     if (eqItem) {
711         [self rebuildEQPresetsMenu];
712     }
713     [statusItem setMenu:menu]; //Set the menu back to the main one
714 }
715
716 - (void)remotePlayerTerminated:(NSNotification *)note
717 {
718     isAppRunning = ITMTRemotePlayerNotRunning;
719     
720     [menu release];
721     menu = [[NSMenu alloc] initWithTitle:@""];
722     [menu addItemWithTitle:@"Audio Player" action:NULL keyEquivalent:@""];
723     [menu addItemWithTitle:@"Not Running" action:NULL keyEquivalent:@""];
724     [menu addItem:[NSMenuItem separatorItem]];
725     [[menu addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""] setTarget:self];
726     [[menu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""] setTarget:self];
727     [statusItem setMenu:menu];
728     
729     [refreshTimer invalidate];
730     refreshTimer = nil;
731     [self clearHotKeys];
732 }
733
734 //
735 //
736 // Selectors - called from status item menu
737 //
738 //
739
740 // Plugin dependent selectors
741
742 - (void)playTrack:(id)sender
743 {
744     [currentRemote switchToSongAtIndex:[[sender representedObject] intValue]];
745     [self updateMenu];
746 }
747
748 - (void)selectPlaylist:(id)sender
749 {
750     int playlist = [[sender representedObject] intValue];
751     if (!isPlayingRadio) {
752         int curPlaylist = [currentRemote currentPlaylistIndex];
753         if (curPlaylist > 0) {
754             [[playlistMenu itemAtIndex:curPlaylist - 1] setState:NSOffState];
755         }
756     }
757     [currentRemote switchToPlaylistAtIndex:playlist];
758     [[playlistMenu itemAtIndex:playlist - 1] setState:NSOnState];
759     [self updateMenu];
760 }
761
762 - (void)selectEQPreset:(id)sender
763 {
764     int curSet = [currentRemote currentEQPresetIndex];
765     int item = [[sender representedObject] intValue];
766     [currentRemote switchToEQAtIndex:item];
767     [[eqMenu itemAtIndex:curSet - 1] setState:NSOffState];
768     [[eqMenu itemAtIndex:item - 1] setState:NSOnState];
769 }
770
771 - (void)playPause:(id)sender
772 {
773     ITMTRemotePlayerState state = [currentRemote playerState];
774     
775     if (state == playing) {
776         [currentRemote pause];
777         [playPauseMenuItem setTitle:@"Play"];
778     } else if ((state == forwarding) || (state == rewinding)) {
779         [currentRemote pause];
780         [currentRemote play];
781     } else {
782         [currentRemote play];
783         [playPauseMenuItem setTitle:@"Pause"];
784     }
785 }
786
787 - (void)nextSong:(id)sender
788 {
789     [currentRemote goToNextSong];
790 }
791
792 - (void)prevSong:(id)sender
793 {
794     [currentRemote goToPreviousSong];
795 }
796
797 - (void)fastForward:(id)sender
798 {
799     [currentRemote fastForward];
800     [playPauseMenuItem setTitle:@"Play"];
801 }
802
803 - (void)rewind:(id)sender
804 {
805     [currentRemote rewind];
806     [playPauseMenuItem setTitle:@"Play"];
807 }
808
809 - (void)setSongRating:(id)sender
810 {
811     NSLog(@"%f", (float)[sender tag] / 100.0);
812     [currentRemote setCurrentSongRating:(float)[sender tag] / 100.0];
813 }
814
815 //
816 //
817 // Plugin independent selectors
818 //
819 //
820 - (void)quitMenuTunes:(id)sender
821 {
822     [NSApp terminate:self];
823 }
824
825 - (void)showPreferences:(id)sender
826 {
827     if (!prefsController) {
828         prefsController = [[PreferencesController alloc] initWithMenuTunes:self];
829         [self clearHotKeys];
830     }
831 }
832
833 - (void)closePreferences
834 {
835     if ( ( isAppRunning == ITMTRemotePlayerRunning) ) {
836         [self setupHotKeys];
837     }
838     [prefsController release];
839     prefsController = nil;
840 }
841
842 //
843 //
844 // Show Current Track Info And Show Upcoming Songs Floaters
845 //
846 //
847
848 - (void)showCurrentTrackInfo
849 {
850     NSString *trackName = [currentRemote currentSongTitle];
851     if (!statusWindow && [trackName length]) {
852         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
853         NSString *stringToShow = @"";
854         
855         if ([defaults boolForKey:@"showName"]) {
856             if ([defaults boolForKey:@"showArtist"]) {
857                 NSString *trackArtist = [currentRemote currentSongArtist];
858                 trackName = [NSString stringWithFormat:@"%@ - %@", trackArtist, trackName];
859             }
860             stringToShow = [stringToShow stringByAppendingString:trackName];
861             stringToShow = [stringToShow stringByAppendingString:@"\n"];
862         }
863         
864         if ([defaults boolForKey:@"showAlbum"]) {
865             NSString *trackAlbum = [currentRemote currentSongAlbum];
866             if ([trackAlbum length]) {
867                 stringToShow = [stringToShow stringByAppendingString:trackAlbum];
868                 stringToShow = [stringToShow stringByAppendingString:@"\n"];
869             }
870         }
871         
872         if ([defaults boolForKey:@"showTime"]) {
873             NSString *trackTime = [currentRemote currentSongLength];
874             if ([trackTime length]) {
875                 stringToShow = [NSString stringWithFormat:@"%@Total Time: %@\n", stringToShow, trackTime];
876             }
877         }
878         
879         {
880             int trackTimeLeft = [[currentRemote currentSongRemaining] intValue];
881             int minutes = trackTimeLeft / 60, seconds = trackTimeLeft % 60;
882             if (seconds < 10) {
883                 stringToShow = [stringToShow stringByAppendingString:
884                             [NSString stringWithFormat:@"Time Remaining: %i:0%i", minutes, seconds]];
885             } else {
886                 stringToShow = [stringToShow stringByAppendingString:
887                             [NSString stringWithFormat:@"Time Remaining: %i:%i", minutes, seconds]];
888             }
889         }
890         
891         [statusWindow setText:stringToShow];
892         [NSTimer scheduledTimerWithTimeInterval:3.0
893                     target:self
894                     selector:@selector(fadeAndCloseStatusWindow)
895                     userInfo:nil
896                     repeats:NO];
897     }
898 }
899
900 - (void)showUpcomingSongs
901 {
902     int curPlaylist = [currentRemote currentPlaylistIndex];
903     if (!statusWindow) {
904         int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist];
905         
906         if (numSongs > 0) {
907             int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
908             int curTrack = [currentRemote currentSongIndex];
909             int i;
910             NSString *songs = @"";
911             
912             statusWindow = [ITTransientStatusWindow sharedWindow];
913             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
914                 if (i <= numSongs) {
915                     NSString *curSong = [currentRemote songTitleAtIndex:i];
916                     songs = [songs stringByAppendingString:curSong];
917                     songs = [songs stringByAppendingString:@"\n"];
918                 }
919             }
920             [statusWindow setText:songs];
921             [NSTimer scheduledTimerWithTimeInterval:3.0
922                         target:self
923                         selector:@selector(fadeAndCloseStatusWindow)
924                         userInfo:nil
925                         repeats:NO];
926         }
927     }
928 }
929
930 - (void)fadeAndCloseStatusWindow
931 {
932     [statusWindow orderOut:self];
933 }
934
935 - (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers
936         onItem:(NSMenuItem *)item
937 {
938     unichar charcode = 'a';
939     int i;
940     long cocoaModifiers = 0;
941     static long carbonToCocoa[6][2] = 
942     {
943         { cmdKey, NSCommandKeyMask },
944         { optionKey, NSAlternateKeyMask },
945         { controlKey, NSControlKeyMask },
946         { shiftKey, NSShiftKeyMask },
947     };
948     
949     for (i = 0; i < 6; i++) {
950         if (modifiers & carbonToCocoa[i][0]) {
951             cocoaModifiers += carbonToCocoa[i][1];
952         }
953     }
954     [item setKeyEquivalentModifierMask:cocoaModifiers];
955     
956     //Missing key combos for some keys. Must find them later.
957     switch (code)
958     {
959         case 36:
960             charcode = '\r';
961         break;
962         
963         case 48:
964             charcode = '\t';
965         break;
966         
967         //Space -- ARGH!
968         case 49:
969         {
970             /*MenuRef menuRef = _NSGetCarbonMenu([item menu]);
971             NSLog(@"%@", menuRef);
972             SetMenuItemCommandKey(menuRef, 0, NO, 49);
973             SetMenuItemModifiers(menuRef, 0, kMenuNoCommandModifier);
974             SetMenuItemKeyGlyph(menuRef, 0, kMenuBlankGlyph);
975             charcode = 'b';*/
976         }
977         break;
978         
979         case 51:
980             charcode = NSDeleteFunctionKey;
981         break;
982         
983         case 53:
984             charcode = '\e';
985         break;
986         
987         case 71:
988             charcode = '\e';
989         break;
990         
991         case 76:
992             charcode = '\r';
993         break;
994         
995         case 96:
996             charcode = NSF5FunctionKey;
997         break;
998         
999         case 97:
1000             charcode = NSF6FunctionKey;
1001         break;
1002         
1003         case 98:
1004             charcode = NSF7FunctionKey;
1005         break;
1006         
1007         case 99:
1008             charcode = NSF3FunctionKey;
1009         break;
1010         
1011         case 100:
1012             charcode = NSF8FunctionKey;
1013         break;
1014         
1015         case 101:
1016             charcode = NSF9FunctionKey;
1017         break;
1018         
1019         case 103:
1020             charcode = NSF11FunctionKey;
1021         break;
1022         
1023         case 105:
1024             charcode = NSF3FunctionKey;
1025         break;
1026         
1027         case 107:
1028             charcode = NSF14FunctionKey;
1029         break;
1030         
1031         case 109:
1032             charcode = NSF10FunctionKey;
1033         break;
1034         
1035         case 111:
1036             charcode = NSF12FunctionKey;
1037         break;
1038         
1039         case 113:
1040             charcode = NSF13FunctionKey;
1041         break;
1042         
1043         case 114:
1044             charcode = NSInsertFunctionKey;
1045         break;
1046         
1047         case 115:
1048             charcode = NSHomeFunctionKey;
1049         break;
1050         
1051         case 116:
1052             charcode = NSPageUpFunctionKey;
1053         break;
1054         
1055         case 117:
1056             charcode = NSDeleteFunctionKey;
1057         break;
1058         
1059         case 118:
1060             charcode = NSF4FunctionKey;
1061         break;
1062         
1063         case 119:
1064             charcode = NSEndFunctionKey;
1065         break;
1066         
1067         case 120:
1068             charcode = NSF2FunctionKey;
1069         break;
1070         
1071         case 121:
1072             charcode = NSPageDownFunctionKey;
1073         break;
1074         
1075         case 122:
1076             charcode = NSF1FunctionKey;
1077         break;
1078         
1079         case 123:
1080             charcode = NSLeftArrowFunctionKey;
1081         break;
1082         
1083         case 124:
1084             charcode = NSRightArrowFunctionKey;
1085         break;
1086         
1087         case 125:
1088             charcode = NSDownArrowFunctionKey;
1089         break;
1090         
1091         case 126:
1092             charcode = NSUpArrowFunctionKey;
1093         break;
1094     }
1095     
1096     if (charcode == 'a') {
1097         unsigned long state;
1098         long keyTrans;
1099         char charCode;
1100         Ptr kchr;
1101         state = 0;
1102         kchr = (Ptr) GetScriptVariable(smCurrentScript, smKCHRCache);
1103         keyTrans = KeyTranslate(kchr, code, &state);
1104         charCode = keyTrans;
1105         [item setKeyEquivalent:[NSString stringWithCString:&charCode length:1]];
1106     } else if (charcode != 'b') {
1107         [item setKeyEquivalent:[NSString stringWithCharacters:&charcode length:1]];
1108     }
1109 }
1110
1111 /*************************************************************************/
1112 #pragma mark -
1113 #pragma mark NSApplication DELEGATE METHODS
1114 /*************************************************************************/
1115
1116 - (void)applicationWillTerminate:(NSNotification *)note
1117 {
1118     [self clearHotKeys];
1119     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1120 }
1121
1122
1123 /*************************************************************************/
1124 #pragma mark -
1125 #pragma mark DEALLOCATION METHODS
1126 /*************************************************************************/
1127
1128 - (void)dealloc
1129 {
1130     if (refreshTimer) {
1131         [refreshTimer invalidate];
1132         refreshTimer = nil;
1133     }
1134     [currentRemote halt];
1135     [statusItem release];
1136     [menu release];
1137 //  [view release];
1138     [super dealloc];
1139 }
1140
1141 @end