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