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