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