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