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