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