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