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