Lots of changes....
[MenuTunes.git] / MenuController.m
1 //
2 //  MenuController.m
3 //  MenuTunes
4 //
5 //  Created by Joseph Spiros on Wed Apr 30 2003.
6 //  Copyright (c) 2003 iThink Software. All rights reserved.
7 //
8
9 #import "MenuController.h"
10 #import "NewMainController.h"
11 #import "HotKeyCenter.h"
12 #import "KeyCombo.h"
13
14 @interface MenuController (SubmenuMethods)
15 - (NSMenu *)ratingMenu;
16 - (NSMenu *)upcomingSongsMenu;
17 - (NSMenu *)playlistsMenu;
18 - (NSMenu *)eqMenu;
19 - (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers
20         onItem:(NSMenuItem *)item;
21 @end
22
23 @implementation MenuController
24
25 - (id)init
26 {
27     if ( (self = [super init]) ) {
28         _menuLayout = [[NSMutableArray alloc] initWithCapacity:0];
29     }
30     return self;
31 }
32
33 - (NSMenu *)menu
34 {
35     NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
36     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
37     NSArray *menuArray = [defaults arrayForKey:@"menu"];
38     NSEnumerator *enumerator = [menuArray objectEnumerator];
39     NSString *nextObject;
40     NSMenuItem *tempItem;
41     NSEnumerator *itemEnum;
42     KeyCombo *keyCombo;
43     
44     //Get the information
45     _currentPlaylist = [currentRemote currentPlaylistIndex];
46     _playingRadio = ([currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist);
47     
48     //Kill the old submenu items
49     if ( (tempItem = [_currentMenu itemWithTag:1]) ) {
50         [tempItem setSubmenu:nil];
51     }
52     
53     if ( (tempItem = [_currentMenu itemWithTag:2]) ) {
54         [tempItem setSubmenu:nil];
55     }
56     
57     if ( (tempItem = [_currentMenu itemWithTag:3]) ) {
58         [tempItem setSubmenu:nil];
59     }
60     
61     if ( (tempItem = [_currentMenu itemWithTag:4]) ) {
62         [tempItem setSubmenu:nil];
63     }
64     
65     //create our menu
66     while ( (nextObject = [enumerator nextObject]) ) {
67         //Main menu items
68         if ([nextObject isEqualToString:@"Play/Pause"]) {
69             tempItem = [menu addItemWithTitle:@"Play"
70                     action:@selector(performMainMenuAction:)
71                     keyEquivalent:@""];
72             [tempItem setTag:MTMenuPlayPauseItem];
73             [tempItem setTarget:self];
74             
75             if ( (keyCombo = [[HotKeyCenter sharedCenter] keyComboForName:@"PlayPause"]) ) {
76                 [self setKeyEquivalentForCode:[keyCombo keyCode]
77                         andModifiers:[keyCombo modifiers]
78                         onItem:tempItem];
79             }
80             
81             switch ([currentRemote playerPlayingState]) {
82                 case ITMTRemotePlayerPlaying:
83                     [tempItem setTitle:@"Pause"];
84                 break;
85                 case ITMTRemotePlayerRewinding:
86                 case ITMTRemotePlayerForwarding:
87                     [tempItem setTitle:@"Resume"];
88                 break;
89                 default:
90                 break;
91             }
92         } else if ([nextObject isEqualToString:@"Next Track"]) {
93             tempItem = [menu addItemWithTitle:@"Next Track"
94                     action:@selector(performMainMenuAction:)
95                     keyEquivalent:@""];
96             
97             if ( (keyCombo = [[HotKeyCenter sharedCenter] keyComboForName:@"NextTrack"]) ) {
98                 [self setKeyEquivalentForCode:[keyCombo keyCode]
99                         andModifiers:[keyCombo modifiers]
100                         onItem:tempItem];
101             }
102             
103             if (_currentPlaylist) {
104                 [tempItem setTag:MTMenuNextTrackItem];
105                 [tempItem setTarget:self];
106             }
107         } else if ([nextObject isEqualToString:@"Previous Track"]) {
108             tempItem = [menu addItemWithTitle:@"Previous Track"
109                     action:@selector(performMainMenuAction:)
110                     keyEquivalent:@""];
111             
112             if ( (keyCombo = [[HotKeyCenter sharedCenter] keyComboForName:@"PrevTrack"]) ) {
113                 [self setKeyEquivalentForCode:[keyCombo keyCode]
114                         andModifiers:[keyCombo modifiers]
115                         onItem:tempItem];
116             }
117             
118             if (_currentPlaylist) {
119                 [tempItem setTag:MTMenuPreviousTrackItem];
120                 [tempItem setTarget:self];
121             }
122         } else if ([nextObject isEqualToString:@"Fast Forward"]) {
123             tempItem = [menu addItemWithTitle:@"Fast Forward"
124                     action:@selector(performMainMenuAction:)
125                     keyEquivalent:@""];
126             if (_currentPlaylist) {
127                 [tempItem setTag:MTMenuFastForwardItem];
128                 [tempItem setTarget:self];
129             }
130         } else if ([nextObject isEqualToString:@"Rewind"]) {
131             tempItem = [menu addItemWithTitle:@"Rewind"
132                     action:@selector(performMainMenuAction:)
133                     keyEquivalent:@""];
134             if (_currentPlaylist) {
135                 [tempItem setTag:MTMenuRewindItem];
136                 [tempItem setTarget:self];
137             }
138         } else if ([nextObject isEqualToString:@"Preferences"]) {
139             tempItem = [menu addItemWithTitle:@"Preferences..."
140                     action:@selector(performMainMenuAction:)
141                     keyEquivalent:@""];
142             [tempItem setTag:MTMenuPreferencesItem];
143             [tempItem setTarget:self];
144         } else if ([nextObject isEqualToString:@"Quit"]) {
145             tempItem = [menu addItemWithTitle:@"Quit"
146                     action:@selector(performMainMenuAction:)
147                     keyEquivalent:@""];
148             [tempItem setTag:MTMenuQuitItem];
149             [tempItem setTarget:self];
150         } else if ([nextObject isEqualToString:@"Current Track Info"]) {
151             //Handle playing radio too
152             if (_currentPlaylist) {
153                 NSString *title = [currentRemote currentSongTitle];
154                 
155                 [menu addItemWithTitle:@"Now Playing" action:NULL keyEquivalent:@""];
156                 
157                 if ([title length] > 0) {
158                     [menu addItemWithTitle:[NSString stringWithFormat:@"         %@", title]
159                             action:nil
160                             keyEquivalent:@""];
161                 }
162                 
163                 if ([defaults boolForKey:@"showAlbum"]) {
164                     [menu addItemWithTitle:[NSString stringWithFormat:@"         %@", [currentRemote currentSongAlbum]]
165                             action:nil
166                             keyEquivalent:@""];
167                 }
168                 
169                 if ([defaults boolForKey:@"showArtist"]) {
170                     [menu addItemWithTitle:[NSString stringWithFormat:@"         %@", [currentRemote currentSongArtist]]
171                             action:nil
172                             keyEquivalent:@""];
173                 }
174                 
175                 if ([defaults boolForKey:@"showTrackNumber"]) {
176                     [menu addItemWithTitle:[NSString stringWithFormat:@"         Track %i", [currentRemote currentSongTrack]]
177                             action:nil
178                             keyEquivalent:@""];
179                 }
180                 
181                 if ([defaults boolForKey:@"showTime"]) {
182                     int left = [[currentRemote currentSongRemaining] intValue];
183                     NSString *remaining = [NSString stringWithFormat:@"%i:%02i", left / 60, left % 60];
184                     [menu addItemWithTitle:[NSString stringWithFormat:@"         %@/%@", remaining, [currentRemote currentSongLength]]
185                             action:nil
186                             keyEquivalent:@""];
187                 }
188             } else {
189                 [menu addItemWithTitle:@"No Song" action:NULL keyEquivalent:@""];
190             }
191         } else if ([nextObject isEqualToString:@"<separator>"]) {
192             [menu addItem:[NSMenuItem separatorItem]];
193         //Submenu items
194         } else if ([nextObject isEqualToString:@"Song Rating"]) {
195             tempItem = [menu addItemWithTitle:@"Song Rating"
196                     action:nil
197                     keyEquivalent:@""];
198             [tempItem setSubmenu:_ratingMenu];
199             [tempItem setTag:1];
200             
201             itemEnum = [[_ratingMenu itemArray] objectEnumerator];
202             while ( (tempItem = [itemEnum nextObject]) ) {
203                 [tempItem setState:NSOffState];
204             }
205             
206             [[_ratingMenu itemAtIndex:([currentRemote currentSongRating] * 5)] setState:NSOnState];
207             if (_playingRadio || !_currentPlaylist) {
208                 [tempItem setEnabled:NO];
209             }
210         } else if ([nextObject isEqualToString:@"Upcoming Songs"]) {
211             tempItem = [menu addItemWithTitle:@"Upcoming Songs"
212                     action:nil
213                     keyEquivalent:@""];
214             [tempItem setSubmenu:_upcomingSongsMenu];
215             [tempItem setTag:2];
216             if (_playingRadio || !_currentPlaylist) {
217                 [tempItem setEnabled:NO];
218             }
219         } else if ([nextObject isEqualToString:@"Playlists"]) {
220             tempItem = [menu addItemWithTitle:@"Playlists"
221                     action:nil
222                     keyEquivalent:@""];
223             [tempItem setSubmenu:_playlistsMenu];
224             [tempItem setTag:3];
225         } else if ([nextObject isEqualToString:@"EQ Presets"]) {
226             tempItem = [menu addItemWithTitle:@"EQ Presets"
227                     action:nil
228                     keyEquivalent:@""];
229             [tempItem setSubmenu:_eqMenu];
230             [tempItem setTag:4];
231             
232             itemEnum = [[_eqMenu itemArray] objectEnumerator];
233             while ( (tempItem = [itemEnum nextObject]) ) {
234                 [tempItem setState:NSOffState];
235             }
236             [[_eqMenu itemAtIndex:([currentRemote currentEQPresetIndex] - 1)] setState:NSOnState];
237         }
238     }
239     [_currentMenu release];
240     _currentMenu = menu;
241     return _currentMenu;
242 }
243
244 - (NSMenu *)menuForNoPlayer
245 {
246     NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
247     NSMenuItem *tempItem;
248     tempItem = [menu addItemWithTitle:[NSString stringWithFormat:@"Open %@", [[[MainController sharedController] currentRemote] playerSimpleName]] action:@selector(performMainMenuAction:) keyEquivalent:@""];
249     [tempItem setTag:MTMenuShowPlayerItem];
250     [tempItem setTarget:self];
251     [menu addItem:[NSMenuItem separatorItem]];
252     tempItem = [menu addItemWithTitle:@"Preferences" action:@selector(performMainMenuAction:) keyEquivalent:@""];
253     [tempItem setTag:MTMenuPreferencesItem];
254     [tempItem setTarget:self];
255     tempItem = [menu addItemWithTitle:@"Quit" action:@selector(performMainMenuAction:) keyEquivalent:@""];
256     [tempItem setTag:MTMenuQuitItem];
257     [tempItem setTarget:self];
258     return [menu autorelease];
259 }
260
261 - (void)rebuildSubmenus
262 {
263     currentRemote = [[MainController sharedController] currentRemote];
264     _currentPlaylist = [currentRemote currentPlaylistIndex];
265     _currentTrack = [currentRemote currentSongIndex];
266     _playingRadio = ([currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist);
267     
268     [_ratingMenu release];
269     [_upcomingSongsMenu release];
270     [_playlistsMenu release];
271     [_eqMenu release];
272     _ratingMenu = [self ratingMenu];
273     _upcomingSongsMenu = [self upcomingSongsMenu];
274     _playlistsMenu = [self playlistsMenu];
275     _eqMenu = [self eqMenu];
276 }
277
278 - (NSMenu *)ratingMenu
279 {
280     NSMenu *ratingMenu = [[NSMenu alloc] initWithTitle:@""];
281     if (_currentPlaylist && !_playingRadio) {
282         NSEnumerator *itemEnum;
283         id  anItem;
284         int itemTag = 0;
285         SEL itemSelector = @selector(performRatingMenuAction:);
286         
287         [ratingMenu addItemWithTitle:[NSString stringWithUTF8String:"☆☆☆☆☆"] action:nil keyEquivalent:@""];
288         [ratingMenu addItemWithTitle:[NSString stringWithUTF8String:"★☆☆☆☆"] action:nil keyEquivalent:@""];
289         [ratingMenu addItemWithTitle:[NSString stringWithUTF8String:"★★☆☆☆"] action:nil keyEquivalent:@""];
290         [ratingMenu addItemWithTitle:[NSString stringWithUTF8String:"★★★☆☆"] action:nil keyEquivalent:@""];
291         [ratingMenu addItemWithTitle:[NSString stringWithUTF8String:"★★★★☆"] action:nil keyEquivalent:@""];
292         [ratingMenu addItemWithTitle:[NSString stringWithUTF8String:"★★★★★"] action:nil keyEquivalent:@""];
293         
294         itemEnum = [[ratingMenu itemArray] objectEnumerator];
295         while ( (anItem = [itemEnum nextObject]) ) {
296             [anItem setAction:itemSelector];
297             [anItem setTarget:self];
298             [anItem setTag:itemTag];
299             itemTag += 20;
300         }
301     }
302     return ratingMenu;
303 }
304
305 - (NSMenu *)upcomingSongsMenu
306 {
307     NSMenu *upcomingSongsMenu = [[NSMenu alloc] initWithTitle:@""];
308     int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:_currentPlaylist];
309     int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"];
310     
311     if (_currentPlaylist && !_playingRadio) {
312         if (numSongs > 0) {
313             int i;
314             
315             for (i = _currentTrack + 1; i <= _currentTrack + numSongsInAdvance; i++) {
316                 if (i <= numSongs) {
317                     NSString *curSong = [currentRemote songTitleAtIndex:i];
318                     NSMenuItem *songItem;
319                     songItem = [upcomingSongsMenu addItemWithTitle:curSong action:@selector(performUpcomingSongsMenuAction:) keyEquivalent:@""];
320                     [songItem setTag:i];
321                     [songItem setTarget:self];
322                 } else {
323                     break;
324                 }
325             }
326         }
327     }
328     return upcomingSongsMenu;
329 }
330
331 - (NSMenu *)playlistsMenu
332 {
333     NSMenu *playlistsMenu = [[NSMenu alloc] initWithTitle:@""];
334     NSArray *playlists = [currentRemote playlists];
335     NSMenuItem *tempItem;
336     int i;
337     
338     for (i = 0; i < [playlists count]; i++) {
339         tempItem = [playlistsMenu addItemWithTitle:[playlists objectAtIndex:i] action:@selector(performPlaylistMenuAction:) keyEquivalent:@""];
340         [tempItem setTag:i + 1];
341         [tempItem setTarget:self];
342     }
343     
344     if (!_playingRadio && _currentPlaylist) {
345         [[playlistsMenu itemAtIndex:_currentPlaylist - 1] setState:NSOnState];
346     }
347     return playlistsMenu;
348 }
349
350 - (NSMenu *)eqMenu
351 {
352     NSMenu *eqMenu = [[NSMenu alloc] initWithTitle:@""];
353     NSArray *eqPresets = [currentRemote eqPresets];
354     NSMenuItem *tempItem;
355     int i;
356     
357     for (i = 0; i < [eqPresets count]; i++) {
358         NSString *name;
359         if ( ( name = [eqPresets objectAtIndex:i] ) ) {
360             tempItem = [eqMenu addItemWithTitle:name action:@selector(performEqualizerMenuAction:) keyEquivalent:@""];
361             [tempItem setTag:i];
362             [tempItem setTarget:self];
363         }
364     }
365     return eqMenu;
366 }
367
368 - (void)performMainMenuAction:(id)sender
369 {
370     switch ( [sender tag] )
371     {
372         case MTMenuPlayPauseItem:
373             NSLog(@"MenuController: Play/Pause");
374             [[MainController sharedController] playPause];
375             break;
376         case MTMenuFastForwardItem:
377             NSLog(@"MenuController: Fast Forward");
378             [[MainController sharedController] fastForward];
379             break;
380         case MTMenuRewindItem:
381             NSLog(@"MenuController: Rewind");
382             [[MainController sharedController] rewind];
383             break;
384         case MTMenuPreviousTrackItem:
385             NSLog(@"MenuController: Previous Track");
386             [[MainController sharedController] prevSong];
387             break;
388         case MTMenuNextTrackItem:
389             NSLog(@"MenuController: Next Track");
390             [[MainController sharedController] nextSong];
391             break;
392         case MTMenuShowPlayerItem:
393             NSLog(@"MainController: Show Main Interface");
394             [[MainController sharedController] showPlayer];
395             break;
396         case MTMenuPreferencesItem:
397             NSLog(@"MenuController: Preferences...");
398             [[MainController sharedController] showPreferences];
399             break;
400         case MTMenuQuitItem:
401             NSLog(@"MenuController: Quit");
402             [[MainController sharedController] quitMenuTunes];
403             break;
404         default:
405             NSLog(@"MenuController: Unimplemented Menu Item OR Child-bearing Menu Item");
406             break;
407     }
408 }
409
410 - (void)performRatingMenuAction:(id)sender
411 {
412     [[MainController sharedController] selectSongRating:[sender tag]];
413 }
414
415 - (void)performPlaylistMenuAction:(id)sender
416 {
417     [[MainController sharedController] selectPlaylistAtIndex:[sender tag]];
418 }
419
420 - (void)performEqualizerMenuAction:(id)sender
421 {
422     [[MainController sharedController] selectEQPresetAtIndex:[sender tag]];
423 }
424
425 - (void)performUpcomingSongsMenuAction:(id)sender
426 {
427     [[MainController sharedController] selectSongAtIndex:[sender tag]];
428 }
429
430 - (void)updateMenu
431 {
432     
433     [_currentMenu update];
434 }
435
436 - (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
437 {
438     return YES;
439 }
440
441 - (NSString *)systemUIColor
442 {
443     NSDictionary *tmpDict;
444     NSNumber *tmpNumber;
445     if ( (tmpDict = [NSDictionary dictionaryWithContentsOfFile:[@"~/Library/Preferences/.GlobalPreferences.plist" stringByExpandingTildeInPath]]) ) {
446         if ( (tmpNumber = [tmpDict objectForKey:@"AppleAquaColorVariant"]) ) {
447             if ( ([tmpNumber intValue] == 1) ) {
448                 return @"Aqua";
449             } else {
450                 return @"Graphite";
451             }
452         } else {
453             return @"Aqua";
454         }
455     } else {
456         return @"Aqua";
457     }
458 }
459
460 - (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers
461         onItem:(NSMenuItem *)item
462 {
463     unichar charcode = 'a';
464     int i;
465     long cocoaModifiers = 0;
466     static long carbonToCocoa[6][2] = 
467     {
468         { cmdKey, NSCommandKeyMask },
469         { optionKey, NSAlternateKeyMask },
470         { controlKey, NSControlKeyMask },
471         { shiftKey, NSShiftKeyMask },
472     };
473     
474     for (i = 0; i < 6; i++) {
475         if (modifiers & carbonToCocoa[i][0]) {
476             cocoaModifiers += carbonToCocoa[i][1];
477         }
478     }
479     [item setKeyEquivalentModifierMask:cocoaModifiers];
480     
481     //Missing key combos for some keys. Must find them later.
482     switch (code)
483     {
484         case 36:
485             charcode = '\r';
486         break;
487         
488         case 48:
489             charcode = '\t';
490         break;
491         
492         //Space -- ARGH!
493         case 49:
494         {
495             // Haven't tested this, though it should work.
496             // This doesn't work. :'(
497             unichar buffer;
498             [[NSString stringWithString:@"Space"] getCharacters:&buffer];
499             charcode = buffer;
500             /*MenuRef menuRef = _NSGetCarbonMenu([item menu]);
501             NSLog(@"%@", menuRef);
502             SetMenuItemCommandKey(menuRef, 0, NO, 49);
503             SetMenuItemModifiers(menuRef, 0, kMenuNoCommandModifier);
504             SetMenuItemKeyGlyph(menuRef, 0, kMenuBlankGlyph);
505             charcode = 'b';*/
506             
507         }
508         break;
509         
510         case 51:
511             charcode = NSDeleteFunctionKey;
512         break;
513         
514         case 53:
515             charcode = '\e';
516         break;
517         
518         case 71:
519             charcode = '\e';
520         break;
521         
522         case 76:
523             charcode = '\r';
524         break;
525         
526         case 96:
527             charcode = NSF5FunctionKey;
528         break;
529         
530         case 97:
531             charcode = NSF6FunctionKey;
532         break;
533         
534         case 98:
535             charcode = NSF7FunctionKey;
536         break;
537         
538         case 99:
539             charcode = NSF3FunctionKey;
540         break;
541         
542         case 100:
543             charcode = NSF8FunctionKey;
544         break;
545         
546         case 101:
547             charcode = NSF9FunctionKey;
548         break;
549         
550         case 103:
551             charcode = NSF11FunctionKey;
552         break;
553         
554         case 105:
555             charcode = NSF3FunctionKey;
556         break;
557         
558         case 107:
559             charcode = NSF14FunctionKey;
560         break;
561         
562         case 109:
563             charcode = NSF10FunctionKey;
564         break;
565         
566         case 111:
567             charcode = NSF12FunctionKey;
568         break;
569         
570         case 113:
571             charcode = NSF13FunctionKey;
572         break;
573         
574         case 114:
575             charcode = NSInsertFunctionKey;
576         break;
577         
578         case 115:
579             charcode = NSHomeFunctionKey;
580         break;
581         
582         case 116:
583             charcode = NSPageUpFunctionKey;
584         break;
585         
586         case 117:
587             charcode = NSDeleteFunctionKey;
588         break;
589         
590         case 118:
591             charcode = NSF4FunctionKey;
592         break;
593         
594         case 119:
595             charcode = NSEndFunctionKey;
596         break;
597         
598         case 120:
599             charcode = NSF2FunctionKey;
600         break;
601         
602         case 121:
603             charcode = NSPageDownFunctionKey;
604         break;
605         
606         case 122:
607             charcode = NSF1FunctionKey;
608         break;
609         
610         case 123:
611             charcode = NSLeftArrowFunctionKey;
612         break;
613         
614         case 124:
615             charcode = NSRightArrowFunctionKey;
616         break;
617         
618         case 125:
619             charcode = NSDownArrowFunctionKey;
620         break;
621         
622         case 126:
623             charcode = NSUpArrowFunctionKey;
624         break;
625     }
626     
627     if (charcode == 'a') {
628         unsigned long state;
629         long keyTrans;
630         char charCode;
631         Ptr kchr;
632         state = 0;
633         kchr = (Ptr) GetScriptVariable(smCurrentScript, smKCHRCache);
634         keyTrans = KeyTranslate(kchr, code, &state);
635         charCode = keyTrans;
636         [item setKeyEquivalent:[NSString stringWithCString:&charCode length:1]];
637     } else if (charcode != 'b') {
638         [item setKeyEquivalent:[NSString stringWithCharacters:&charcode length:1]];
639     }
640 }
641
642 @end