Removed the Views.
[MenuTunes.git] / MenuTunes.m
1 /*
2 Things to do:
3 ¥ Make preferences window pretty
4 ¥ Optimize
5 ¥ Apple Events! Apple Events! Apple Events!
6 */
7
8 #import "MenuTunes.h"
9 #import "PreferencesController.h"
10 #import "HotKeyCenter.h"
11 #import "StatusWindowController.h"
12
13 @interface MenuTunes(Private)
14 - (void)registerDefaultsIfNeeded;
15 - (void)updateMenu;
16 - (void)rebuildUpcomingSongsMenu;
17 - (void)rebuildPlaylistMenu;
18 - (void)rebuildEQPresetsMenu;
19 - (void)setupHotKeys;
20 - (NSString *)runScriptAndReturnResult:(NSString *)script;
21 - (void)timerUpdate;
22 - (void)sendAEWithEventClass:(AEEventClass)eventClass andEventID:(AEEventID)eventID;
23
24 @end
25
26 @implementation MenuTunes
27
28 /*************************************************************************/
29 #pragma mark -
30 #pragma mark INITIALIZATION METHODS
31 /*************************************************************************/
32
33 - (void)applicationDidFinishLaunching:(NSNotification *)note
34 {
35     asComponent = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);
36
37     [self registerDefaultsIfNeeded];
38     
39     menu = [[NSMenu alloc] initWithTitle:@""];
40     iTunesPSN = [self iTunesPSN]; //Get PSN of iTunes if it's running
41     
42     if (!((iTunesPSN.highLongOfPSN == kNoProcess) && (iTunesPSN.lowLongOfPSN == 0)))
43     {
44         [self rebuildMenu];
45         refreshTimer = [NSTimer scheduledTimerWithTimeInterval:3.5
46                                                         target:self
47                                                       selector:@selector(timerUpdate)
48                                                       userInfo:nil
49                                                        repeats:YES];
50     }
51     else
52     {
53         menu = [[NSMenu alloc] initWithTitle:@""];
54         [[menu addItemWithTitle:@"Open iTunes" action:@selector(openiTunes:) keyEquivalent:@""] setTarget:self];
55         [[menu addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""] setTarget:self];
56         [[menu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""] setTarget:self];
57         [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(iTunesLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
58         refreshTimer = nil;
59     }
60     
61     statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
62     [[[statusItem _button] cell] setType:NSNullCellType];
63     [statusItem setImage:[NSImage imageNamed:@"menu"]];
64     [[statusItem _button] setAlternateImage:[NSImage imageNamed:@"selected_image"]];
65     [statusItem setHighlightMode:YES];
66     [statusItem setMenu:menu];
67     // Below line of code is for creating builds for Beta Testers
68     // [statusItem setToolTip:@"This Nontransferable Beta (Built on __DATE__) of iThink Software's MenuTunes is Registered to: Beta Tester (betatester@somedomain.com)."];
69     [statusItem retain];
70 }
71
72
73 /*************************************************************************/
74 #pragma mark -
75 #pragma mark INSTANCE METHODS
76 /*************************************************************************/
77
78 - (void)registerDefaultsIfNeeded
79 {
80     if (![[NSUserDefaults standardUserDefaults] objectForKey:@"menu"]) {
81         [[NSUserDefaults standardUserDefaults] setObject:
82             [NSArray arrayWithObjects:
83                 @"Play/Pause",
84                 @"Next Track",
85                 @"Previous Track",
86                 @"Fast Forward",
87                 @"Rewind",
88                 @"<separator>",
89                 @"Upcoming Songs",
90                 @"Playlists",
91                 @"<separator>",
92                 @"PreferencesÉ",
93                 @"Quit",
94                 @"<separator>",
95                 @"Current Track Info",
96                 nil] forKey:@"menu"];
97     }
98 }
99
100 //Recreate the status item menu
101 - (void)rebuildMenu
102 {
103     NSArray *myMenu = [[NSUserDefaults standardUserDefaults] arrayForKey:@"menu"];
104     int i;
105     
106     trackInfoIndex = -1;
107     didHaveAlbumName = ([[self runScriptAndReturnResult:@"return album of current track"] length] > 0);
108
109     while ([menu numberOfItems] > 0) {
110         [menu removeItemAtIndex:0];
111     }
112     
113     playPauseMenuItem = nil;
114     upcomingSongsItem = nil;
115     playlistItem = nil;
116     [playlistMenu release];
117     playlistMenu = nil;
118     eqItem = nil;
119     [eqMenu release];
120     eqMenu = nil;
121     
122     for (i = 0; i < [myMenu count]; i++) {
123         NSString *item = [myMenu objectAtIndex:i];
124         if ([item isEqualToString:@"Play/Pause"]) {
125             playPauseMenuItem = [menu addItemWithTitle:@"Play"
126                                                 action:@selector(playPause:)
127                                          keyEquivalent:@""];
128             [playPauseMenuItem setTarget:self];
129         } else if ([item isEqualToString:@"Next Track"]) {
130             [[menu addItemWithTitle:@"Next Track"
131                              action:@selector(nextSong:)
132                       keyEquivalent:@""] setTarget:self];
133         } else if ([item isEqualToString:@"Previous Track"]) {
134             [[menu addItemWithTitle:@"Previous Track"
135                              action:@selector(prevSong:)
136                       keyEquivalent:@""] setTarget:self];
137         } else if ([item isEqualToString:@"Fast Forward"]) {
138             [[menu addItemWithTitle:@"Fast Forward"
139                              action:@selector(fastForward:)
140                       keyEquivalent:@""] setTarget:self];
141         } else if ([item isEqualToString:@"Rewind"]) {
142             [[menu addItemWithTitle:@"Rewind"
143                              action:@selector(rewind:)
144                       keyEquivalent:@""] setTarget:self];
145         } else if ([item isEqualToString:@"Upcoming Songs"]) {
146             upcomingSongsItem = [menu addItemWithTitle:@"Upcoming Songs"
147                                                 action:nil
148                                          keyEquivalent:@""];
149         } else if ([item isEqualToString:@"Playlists"]) {
150             playlistItem = [menu addItemWithTitle:@"Playlists"
151                                            action:nil
152                                     keyEquivalent:@""];
153         } else if ([item isEqualToString:@"EQ Presets"]) {
154             eqItem = [menu addItemWithTitle:@"EQ Presets"
155                                      action:nil
156                               keyEquivalent:@""];
157         } else if ([item isEqualToString:@"PreferencesÉ"]) {
158             [[menu addItemWithTitle:@"PreferencesÉ"
159                              action:@selector(showPreferences:)
160                       keyEquivalent:@""] setTarget:self];
161         } else if ([item isEqualToString:@"Quit"]) {
162             [[menu addItemWithTitle:@"Quit"
163                              action:@selector(quitMenuTunes:)
164                       keyEquivalent:@""] setTarget:self];
165         } else if ([item isEqualToString:@"Current Track Info"]) {
166             trackInfoIndex = [menu numberOfItems];
167             [menu addItemWithTitle:@"No Song"
168                             action:nil
169                      keyEquivalent:@""];
170         } else if ([item isEqualToString:@"<separator>"]) {
171             [menu addItem:[NSMenuItem separatorItem]];
172         }
173     }
174     
175     curTrackIndex = -1; //Force update of everything
176     [self timerUpdate]; //Updates dynamic info in the menu
177     
178     [self clearHotKeys];
179     [self setupHotKeys];
180 }
181
182 //Updates the menu with current player state, song, and upcoming songs
183 - (void)updateMenu
184 {
185     NSString *curAlbumName = [self runScriptAndReturnResult:@"return album of current track"];
186     NSMenuItem *menuItem;
187     if ((iTunesPSN.highLongOfPSN == kNoProcess) && (iTunesPSN.lowLongOfPSN == 0)) {
188         return;
189     }
190     
191     if (upcomingSongsItem) {
192         [self rebuildUpcomingSongsMenu];
193     }
194     if (playlistItem) {
195         [self rebuildPlaylistMenu];
196     }
197     if (eqItem) {
198         [self rebuildEQPresetsMenu];
199     }
200     
201     if (trackInfoIndex > -1)
202     {
203         NSString *curSongName;
204         curSongName = [self runScriptAndReturnResult:@"return name of current track"];
205         if ([curSongName length] > 0) {
206             int index = [menu indexOfItemWithTitle:@"Now Playing"];
207             
208             if (index > -1) {
209                 [menu removeItemAtIndex:index + 1];
210                 if (didHaveAlbumName) {
211                     [menu removeItemAtIndex:index + 1];
212                 }
213             }
214             if (!isPlayingRadio) {
215                 if ([curAlbumName length] > 0) {
216                     menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curAlbumName]
217                                                         action:nil
218                                                         keyEquivalent:@""];
219                     [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
220                     [menuItem release];
221                 }
222             }
223             menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"  %@", curSongName]
224                                                 action:nil
225                                                 keyEquivalent:@""];
226             [menu insertItem:menuItem atIndex:trackInfoIndex + 1];
227             [menuItem release];
228             
229             if (index == -1) {
230                 menuItem = [[NSMenuItem alloc] initWithTitle:@"Now Playing" action:nil keyEquivalent:@""];
231                 [menu removeItemAtIndex:[menu indexOfItemWithTitle:@"No Song"]];
232                 [menu insertItem:menuItem atIndex:trackInfoIndex];
233                 [menuItem release];
234             }
235             
236         } else if ([menu indexOfItemWithTitle:@"No Song"] == -1) {
237             [menu removeItemAtIndex:trackInfoIndex];
238             [menu removeItemAtIndex:trackInfoIndex];
239             
240             if (didHaveAlbumName) {
241                 [menu removeItemAtIndex:trackInfoIndex];
242             }
243             
244             menuItem = [[NSMenuItem alloc] initWithTitle:@"No Song" action:nil keyEquivalent:@""];
245             [menu insertItem:menuItem atIndex:trackInfoIndex];
246             [menuItem release];
247         }
248     }
249     didHaveAlbumName = (([curAlbumName length] > 0) ? YES : NO);
250 }
251
252 //Rebuild the upcoming songs submenu. Can be improved a lot.
253 - (void)rebuildUpcomingSongsMenu
254 {
255     int numSongs = [[self runScriptAndReturnResult:@"return number of tracks in current playlist"] intValue];
256     int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
257     if (!isPlayingRadio) {
258         if (numSongs > 0) {
259             int curTrack = [[self runScriptAndReturnResult:@"return index of current track"] intValue];
260             int i;
261             
262             [upcomingSongsMenu release];
263             upcomingSongsMenu = [[NSMenu alloc] initWithTitle:@""];
264             [upcomingSongsItem setSubmenu:upcomingSongsMenu];
265             [upcomingSongsItem setEnabled:YES];
266             
267             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
268                 if (i <= numSongs) {
269                     NSString *curSong = [self runScriptAndReturnResult:[NSString stringWithFormat:@"return name of track %i of current playlist", i]];
270                     NSMenuItem *songItem;
271                     songItem = [[NSMenuItem alloc] initWithTitle:curSong action:@selector(playTrack:) keyEquivalent:@""];
272                     [songItem setTarget:self];
273                     [songItem setRepresentedObject:[NSNumber numberWithInt:i]];
274                     [upcomingSongsMenu addItem:songItem];
275                     [songItem release];
276                 } else {
277                     break;
278                 }
279             }
280         }
281     } else {
282         [upcomingSongsItem setSubmenu:nil];
283         [upcomingSongsItem setEnabled:NO];
284     }
285 }
286
287 - (void)rebuildPlaylistMenu
288 {
289     int numPlaylists = [[self runScriptAndReturnResult:@"return number of playlists"] intValue];
290     int i, curPlaylist = [[self runScriptAndReturnResult:@"return index of current playlist"] intValue];
291     
292     if (isPlayingRadio)
293     {
294         curPlaylist = 0;
295     }
296     
297     if (playlistMenu && (numPlaylists == [playlistMenu numberOfItems]))
298         return;
299     
300     [playlistMenu release];
301     playlistMenu = [[NSMenu alloc] initWithTitle:@""];
302     
303     for (i = 1; i <= numPlaylists; i++) {
304         NSString *playlistName = [self runScriptAndReturnResult:[NSString stringWithFormat:@"return name of playlist %i", i]];
305         NSMenuItem *tempItem;
306         tempItem = [[NSMenuItem alloc] initWithTitle:playlistName action:@selector(selectPlaylist:) keyEquivalent:@""];
307         [tempItem setTarget:self];
308         [tempItem setRepresentedObject:[NSNumber numberWithInt:i]];
309         [playlistMenu addItem:tempItem];
310         [tempItem release];
311     }
312     [playlistItem setSubmenu:playlistMenu];
313     
314     if (curPlaylist) {
315         [[playlistMenu itemAtIndex:curPlaylist - 1] setState:NSOnState];
316     }
317 }
318
319 //Build a menu with the list of all available EQ presets
320 - (void)rebuildEQPresetsMenu
321 {
322     int numSets = [[self runScriptAndReturnResult:@"return number of EQ presets"] intValue];
323     int i;
324     
325     if (eqMenu && (numSets == [eqMenu numberOfItems]))
326         return;
327     
328     [eqMenu release];
329     eqMenu = [[NSMenu alloc] initWithTitle:@""];
330     
331     for (i = 1; i <= numSets; i++) {
332         NSString *setName = [self runScriptAndReturnResult:[NSString stringWithFormat:@"return name of EQ preset %i", i]];
333         NSMenuItem *tempItem;
334         tempItem = [[NSMenuItem alloc] initWithTitle:setName action:@selector(selectEQPreset:) keyEquivalent:@""];
335         [tempItem setTarget:self];
336         [tempItem setRepresentedObject:[NSNumber numberWithInt:i]];
337         [eqMenu addItem:tempItem];
338         [tempItem release];
339     }
340     [eqItem setSubmenu:eqMenu];
341     
342     [[eqMenu itemAtIndex:[[self runScriptAndReturnResult:@"return index of current EQ preset"] intValue] - 1] setState:NSOnState];
343 }
344
345 - (void)clearHotKeys
346 {
347     [[HotKeyCenter sharedCenter] removeHotKey:@"PlayPause"];
348     [[HotKeyCenter sharedCenter] removeHotKey:@"NextTrack"];
349     [[HotKeyCenter sharedCenter] removeHotKey:@"PrevTrack"];
350     [[HotKeyCenter sharedCenter] removeHotKey:@"TrackInfo"];
351     [[HotKeyCenter sharedCenter] removeHotKey:@"UpcomingSongs"];
352 }
353
354 - (void)setupHotKeys
355 {
356     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
357     
358     if ([defaults objectForKey:@"PlayPause"] != nil) {
359         [[HotKeyCenter sharedCenter] addHotKey:@"PlayPause"
360                 combo:[defaults keyComboForKey:@"PlayPause"]
361                 target:self action:@selector(playPause:)];
362     }
363     
364     if ([defaults objectForKey:@"NextTrack"] != nil) {
365         [[HotKeyCenter sharedCenter] addHotKey:@"NextTrack"
366                 combo:[defaults keyComboForKey:@"NextTrack"]
367                 target:self action:@selector(nextSong:)];
368     }
369     
370     if ([defaults objectForKey:@"PrevTrack"] != nil) {
371         [[HotKeyCenter sharedCenter] addHotKey:@"PrevTrack"
372                 combo:[defaults keyComboForKey:@"PrevTrack"]
373                 target:self action:@selector(prevSong:)];
374     }
375     
376     if ([defaults objectForKey:@"TrackInfo"] != nil) {
377         [[HotKeyCenter sharedCenter] addHotKey:@"TrackInfo"
378                 combo:[defaults keyComboForKey:@"TrackInfo"]
379                 target:self action:@selector(showCurrentTrackInfo)];
380     }
381     
382     if ([defaults objectForKey:@"UpcomingSongs"] != nil) {
383         [[HotKeyCenter sharedCenter] addHotKey:@"UpcomingSongs"
384                combo:[defaults keyComboForKey:@"UpcomingSongs"]
385                target:self action:@selector(showUpcomingSongs)];
386     }
387 }
388
389 //Runs an AppleScript and returns the result as an NSString after stripping quotes, if needed. It takes in script and automatically adds the tell iTunes and end tell statements.
390 - (NSString *)runScriptAndReturnResult:(NSString *)script
391 {
392     AEDesc scriptDesc, resultDesc;
393     Size length;
394     NSString *result;
395     Ptr buffer;
396     
397     script = [NSString stringWithFormat:@"tell application \"iTunes\"\n%@\nend tell", script];
398     
399     AECreateDesc(typeChar, [script cString], [script cStringLength], 
400 &scriptDesc);
401     
402     OSADoScript(asComponent, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
403     
404     length = AEGetDescDataSize(&resultDesc);
405     buffer = malloc(length);
406     
407     AEGetDescData(&resultDesc, buffer, length);
408     AEDisposeDesc(&scriptDesc);
409     AEDisposeDesc(&resultDesc);
410     result = [NSString stringWithCString:buffer length:length];
411     if ( (! [result isEqualToString:@""])      &&
412          ([result characterAtIndex:0] == '\"') &&
413          ([result characterAtIndex:[result length] - 1] == '\"') ) {
414         result = [result substringWithRange:NSMakeRange(1, [result length] - 2)];
415     }
416     free(buffer);
417     buffer = nil;
418     return result;
419 }
420
421 //Called when the timer fires.
422 - (void)timerUpdate
423 {
424     int pid;
425     if (GetProcessPID(&iTunesPSN, &pid) == noErr) {
426         int trackPlayingIndex = [[self runScriptAndReturnResult:@"return index of current track"] intValue];
427         
428         if (trackPlayingIndex != curTrackIndex) {
429             bool wasPlayingRadio = isPlayingRadio;
430             isPlayingRadio = [[self runScriptAndReturnResult:@"return class of current playlist"] isEqualToString:@"radio tuner playlist"];
431             if (isPlayingRadio && !wasPlayingRadio) {
432                 int i;
433                 for (i = 0; i < [playlistMenu numberOfItems]; i++)
434                 {
435                     [[playlistMenu itemAtIndex:i] setState:NSOffState];
436                 }
437             }
438             if (wasPlayingRadio) {
439                 NSMenuItem *temp = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
440                 [menu insertItem:temp atIndex:trackInfoIndex + 1];
441                 [temp release];
442             }
443             [self updateMenu];
444             curTrackIndex = trackPlayingIndex;
445         }
446         else
447         {
448             int playlist = [[self runScriptAndReturnResult:@"return index of current playlist"] intValue];
449             if (playlist != curPlaylistIndex) {
450                 bool wasPlayingRadio = isPlayingRadio;
451                 isPlayingRadio = [[self runScriptAndReturnResult:@"return class of current playlist"] isEqualToString:@"radio tuner playlist"];
452                 if (isPlayingRadio && !wasPlayingRadio) {
453                     int i;
454                     for (i = 0; i < [playlistMenu numberOfItems]; i++)
455                     {
456                         [[playlistMenu itemAtIndex:i] setState:NSOffState];
457                     }
458                 }
459                 if (wasPlayingRadio) {
460                     NSMenuItem *temp = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
461                     [menu insertItem:temp atIndex:trackInfoIndex + 1];
462                     [temp release];
463                 }
464                 [self updateMenu];
465                 curTrackIndex = trackPlayingIndex;
466                 curPlaylistIndex = playlist;
467             }
468         }
469         //Update Play/Pause menu item
470         if (playPauseMenuItem){
471             if ([[self runScriptAndReturnResult:@"return player state"] isEqualToString:@"playing"]) {
472                 [playPauseMenuItem setTitle:@"Pause"];
473             } else {
474                 [playPauseMenuItem setTitle:@"Play"];
475             }
476         }
477     } else {
478         [menu release];
479         menu = [[NSMenu alloc] initWithTitle:@""];
480         [[menu addItemWithTitle:@"Open iTunes" action:@selector(openiTunes:) keyEquivalent:@""] setTarget:self];
481         [[menu addItemWithTitle:@"Preferences" action:@selector(showPreferences:) keyEquivalent:@""] setTarget:self];
482         [[menu addItemWithTitle:@"Quit" action:@selector(quitMenuTunes:) keyEquivalent:@""] setTarget:self];
483         [statusItem setMenu:menu];
484         
485         [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(iTunesLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
486         [refreshTimer invalidate];
487         refreshTimer = nil;
488         [self clearHotKeys];
489     }
490 }
491
492 - (void)iTunesLaunched:(NSNotification *)note
493 {
494     NSDictionary *info = [note userInfo];
495     
496     iTunesPSN.highLongOfPSN = [[info objectForKey:@"NSApplicationProcessSerialNumberHigh"] longValue];
497     iTunesPSN.lowLongOfPSN = [[info objectForKey:@"NSApplicationProcessSerialNumberLow"] longValue];
498     
499     //Restart the timer
500     refreshTimer = [NSTimer scheduledTimerWithTimeInterval:3.5 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES]; 
501     
502     [self rebuildMenu]; //Rebuild the menu since no songs will be playing
503     [statusItem setMenu:menu]; //Set the menu back to the main one
504     [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
505 }
506
507 //Return the PSN of iTunes, if it's running
508 - (ProcessSerialNumber)iTunesPSN
509 {
510     ProcessSerialNumber procNum;
511     procNum.highLongOfPSN = kNoProcess;
512     procNum.lowLongOfPSN = 0;
513     
514     while ( (GetNextProcess(&procNum) == noErr) ) {
515         CFStringRef procName;
516         if ( (CopyProcessName(&procNum, &procName) == noErr) ) {
517             if ([(NSString *)procName isEqualToString:@"iTunes"]) {
518                 return procNum;
519             }
520             [(NSString *)procName release];
521         }
522     }
523     return procNum;
524 }
525
526 //Send an AppleEvent with a given event ID
527 - (void)sendAEWithEventClass:(AEEventClass)eventClass 
528 andEventID:(AEEventID)eventID
529 {
530     OSType iTunesType = 'hook';
531     AppleEvent event, reply;
532     
533     AEBuildAppleEvent(eventClass, eventID, typeApplSignature, &iTunesType, sizeof(iTunesType), kAutoGenerateReturnID, kAnyTransactionID, &event, nil, "");
534     
535     AESend(&event, &reply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil);
536     AEDisposeDesc(&event);
537     AEDisposeDesc(&reply);
538 }
539
540 //
541 // Selectors - called from status item menu
542 //
543
544 - (void)playTrack:(id)sender
545 {
546     [self runScriptAndReturnResult:[NSString stringWithFormat:@"play track %i of current playlist", [[sender representedObject] intValue]]];
547     [self updateMenu];
548 }
549
550 - (void)selectPlaylist:(id)sender
551 {
552     int playlist = [[sender representedObject] intValue];
553     if (!isPlayingRadio) {
554         int curPlaylist = [[self runScriptAndReturnResult:@"return index of current playlist"] intValue];
555         [[playlistMenu itemAtIndex:curPlaylist - 1] setState:NSOffState];
556     }
557     [self runScriptAndReturnResult:[NSString stringWithFormat:@"play playlist %i", playlist]];
558     [[playlistMenu itemAtIndex:playlist - 1] setState:NSOnState];
559 }
560
561 - (void)selectEQPreset:(id)sender
562 {
563     int curSet = [[self runScriptAndReturnResult:@"return index of current EQ preset"] intValue];
564     int item = [[sender representedObject] intValue];
565     [self runScriptAndReturnResult:[NSString stringWithFormat:@"set current EQ preset to EQ preset %i", item]];
566     [self runScriptAndReturnResult:@"set EQ enabled to 1"];
567     [[eqMenu itemAtIndex:curSet - 1] setState:NSOffState];
568     [[eqMenu itemAtIndex:item - 1] setState:NSOnState];
569 }
570
571 - (void)playPause:(id)sender
572 {
573     NSString *state = [self runScriptAndReturnResult:@"return player state"];
574     if ([state isEqualToString:@"playing"]) {
575         [self sendAEWithEventClass:'hook' andEventID:'Paus'];
576         [playPauseMenuItem setTitle:@"Play"];
577     } else if ([state isEqualToString:@"fast forwarding"] || [state 
578 isEqualToString:@"rewinding"]) {
579         [self sendAEWithEventClass:'hook' andEventID:'Paus'];
580         [self sendAEWithEventClass:'hook' andEventID:'Play'];
581     } else {
582         [self sendAEWithEventClass:'hook' andEventID:'Play'];
583         [playPauseMenuItem setTitle:@"Pause"];
584     }
585 }
586
587 - (void)nextSong:(id)sender
588 {
589     [self sendAEWithEventClass:'hook' andEventID:'Next'];
590 }
591
592 - (void)prevSong:(id)sender
593 {
594     [self sendAEWithEventClass:'hook' andEventID:'Prev'];
595 }
596
597 - (void)fastForward:(id)sender
598 {
599     [self sendAEWithEventClass:'hook' andEventID:'Fast'];
600 }
601
602 - (void)rewind:(id)sender
603 {
604     [self sendAEWithEventClass:'hook' andEventID:'Rwnd'];
605 }
606
607 - (void)quitMenuTunes:(id)sender
608 {
609     [NSApp terminate:self];
610 }
611
612 - (void)openiTunes:(id)sender
613 {
614     [[NSWorkspace sharedWorkspace] launchApplication:@"iTunes"];
615 }
616
617 - (void)showPreferences:(id)sender
618 {
619     if (!prefsController) {
620         prefsController = [[PreferencesController alloc] initWithMenuTunes:self];
621         [self clearHotKeys];
622     }
623 }
624
625
626 - (void)closePreferences
627 {
628     if (!((iTunesPSN.highLongOfPSN == kNoProcess) && (iTunesPSN.lowLongOfPSN == 0))) {
629         [self setupHotKeys];
630     }
631     [prefsController release];
632     prefsController = nil;
633 }
634
635 //
636 //
637 // Show Current Track Info And Show Upcoming Songs Floaters
638 //
639 //
640
641 - (void)showCurrentTrackInfo
642 {
643     NSString *trackName = [self runScriptAndReturnResult:@"return name of current track"];
644     if (!statusController && [trackName length]) {
645         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
646         NSString *stringToShow = @"";
647         int lines = 1;
648         
649         if ([defaults boolForKey:@"showName"]) {
650             if ([defaults boolForKey:@"showArtist"]) {
651                 NSString *trackArtist = [self runScriptAndReturnResult:@"return artist of current track"];
652                 trackName = [NSString stringWithFormat:@"%@ - %@", trackArtist, trackName];
653             }
654             stringToShow = [stringToShow stringByAppendingString:trackName];
655             stringToShow = [stringToShow stringByAppendingString:@"\n"];
656             if ([trackName length] > 38) {
657                 lines++;
658             }
659             lines++;
660         }
661         
662         if ([defaults boolForKey:@"showAlbum"]) {
663             NSString *trackAlbum = [self runScriptAndReturnResult:@"return album of current track"];
664             if ([trackAlbum length]) {
665                 stringToShow = [stringToShow stringByAppendingString:trackAlbum];
666                 stringToShow = [stringToShow stringByAppendingString:@"\n"];
667                 lines++;
668             }
669         }
670         
671         if ([defaults boolForKey:@"showTime"]) {
672             NSString *trackTime = [self runScriptAndReturnResult:@"return time of current track"];
673             if ([trackTime length]) {
674                 stringToShow = [NSString stringWithFormat:@"%@Total Time: %@\n", stringToShow, trackTime];
675                 lines++;
676             }
677         }
678         
679         {
680             int trackTimeLeft = [[self runScriptAndReturnResult:@"return (duration of current track) - player position"] intValue];
681             int minutes = trackTimeLeft / 60, seconds = trackTimeLeft % 60;
682             if (seconds < 10) {
683                 stringToShow = [stringToShow stringByAppendingString:
684                             [NSString stringWithFormat:@"Time Remaining: %i:0%i", minutes, seconds]];
685             } else {
686                 stringToShow = [stringToShow stringByAppendingString:
687                             [NSString stringWithFormat:@"Time Remaining: %i:%i", minutes, seconds]];
688             }
689         }
690         
691         statusController = [[StatusWindowController alloc] init];
692         [statusController setTrackInfo:stringToShow lines:lines];
693         [NSTimer scheduledTimerWithTimeInterval:3.0
694                                          target:self
695                                        selector:@selector(fadeAndCloseStatusWindow)
696                                        userInfo:nil
697                                         repeats:NO];
698     }
699 }
700
701 - (void)showUpcomingSongs
702 {
703     if (!statusController) {
704         int numSongs = [[self runScriptAndReturnResult:@"return number of tracks in current playlist"] intValue];
705         
706         if (numSongs > 0) {
707             int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
708             int curTrack = [[self runScriptAndReturnResult:@"return index of current track"] intValue];
709             int i;
710             NSString *songs = @"";
711             
712             statusController = [[StatusWindowController alloc] init];
713             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
714                 if (i <= numSongs) {
715                     NSString *curSong = [self runScriptAndReturnResult:
716                         [NSString stringWithFormat:@"return name of track %i of current playlist", i]];
717                     songs = [songs stringByAppendingString:curSong];
718                     songs = [songs stringByAppendingString:@"\n"];
719                 }
720             }
721             [statusController setUpcomingSongs:songs numSongs:numSongsInAdvance];
722             [NSTimer scheduledTimerWithTimeInterval:3.0
723                                              target:self
724                                            selector:@selector(fadeAndCloseStatusWindow)
725                                            userInfo:nil
726                                             repeats:NO];
727         }
728     }
729 }
730
731 - (void)fadeAndCloseStatusWindow
732 {
733     [statusController fadeWindowOut];
734     [statusController release];
735     statusController = nil;
736 }
737
738 /*************************************************************************/
739 #pragma mark -
740 #pragma mark NSApplication DELEGATE METHODS
741 /*************************************************************************/
742
743 - (void)applicationWillTerminate:(NSNotification *)note
744 {
745     [self clearHotKeys];
746     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
747 }
748
749
750 /*************************************************************************/
751 #pragma mark -
752 #pragma mark DEALLOCATION METHODS
753 /*************************************************************************/
754
755 - (void)dealloc
756 {
757     if (refreshTimer) {
758         [refreshTimer invalidate];
759         refreshTimer = nil;
760     }
761     CloseComponent(asComponent);
762     [statusItem release];
763     [menu release];
764 //  [view release];
765     [super dealloc];
766 }
767
768
769 @end