Changing hotkeys in the prefs now writes to the prefs and rebuilds the menu.
[MenuTunes.git] / PreferencesController.m
1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "HotKeyCenter.h"
4 #import <ITKit/ITWindowPositioning.h>
5
6 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
7
8 /*************************************************************************/
9 #pragma mark -
10 #pragma mark PRIVATE INTERFACE
11 /*************************************************************************/
12
13 @interface PreferencesController (Private)
14 - (void)setupWindow;
15 - (void)setupCustomizationTables;
16 - (void)setupMenuItems;
17 - (void)setupUI;
18 - (IBAction)changeMenus:(id)sender;
19 - (void)setLaunchesAtLogin:(BOOL)flag;
20 @end
21
22
23 @implementation PreferencesController
24
25
26 /*************************************************************************/
27 #pragma mark -
28 #pragma mark STATIC VARIABLES
29 /*************************************************************************/
30
31 static PreferencesController *prefs = nil;
32
33
34 /*************************************************************************/
35 #pragma mark -
36 #pragma mark INITIALIZATION METHODS
37 /*************************************************************************/
38
39 + (PreferencesController *)sharedPrefs;
40 {
41     if (! prefs) {
42         prefs = [[self alloc] init];
43     }
44     return prefs;
45 }
46
47 - (id)init
48 {
49     if ( (self = [super init]) ) {
50         df = [[NSUserDefaults standardUserDefaults] retain];
51         controller = nil;
52     }
53     return self;
54 }
55
56
57 /*************************************************************************/
58 #pragma mark -
59 #pragma mark ACCESSOR METHODS
60 /*************************************************************************/
61
62 - (id)controller
63 {
64     return controller;
65 }
66
67 - (void)setController:(id)object
68 {
69     [controller autorelease];
70     controller = [object retain];
71 }
72
73
74 /*************************************************************************/
75 #pragma mark -
76 #pragma mark INSTANCE METHODS
77 /*************************************************************************/
78
79 - (IBAction)showPrefsWindow:(id)sender
80 {
81     if (! window) {  // If window does not exist yet, then the nib hasn't been loaded.
82         [self setupWindow];  // Load in the nib, and perform any initial setup.
83         [self setupCustomizationTables];  // Setup the DnD manu config tables.
84         [self setupMenuItems];  // Setup the arrays of menu items
85         [self setupUI]; // Sets up additional UI
86         [window setDelegate:self];
87     }
88     
89     [window setLevel:NSStatusWindowLevel];
90     [window center];
91     [window makeKeyAndOrderFront:self];
92     [NSApp activateIgnoringOtherApps:YES];
93 }
94
95 - (IBAction)changeGeneralSetting:(id)sender
96 {
97     BOOL rebuildRequired = NO;
98
99     if ( [sender tag] == 1010) {
100         [self setLaunchesAtLogin:SENDER_STATE];
101     } else if ( [sender tag] == 1020) {
102         [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
103     } else if ( [sender tag] == 1030) {
104         [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
105         rebuildRequired = YES;
106     } else if ( [sender tag] == 1040) {
107         // This will not be executed.  Song info always shows the title of the song.
108         // [df setBool:SENDER_STATE forKey:@"showName"];
109         // rebuildRequired = YES;
110     } else if ( [sender tag] == 1050) {
111         [df setBool:SENDER_STATE forKey:@"showArtist"];
112         rebuildRequired = YES;
113     } else if ( [sender tag] == 1060) {
114         [df setBool:SENDER_STATE forKey:@"showAlbum"];
115         rebuildRequired = YES;
116     } else if ( [sender tag] == 1070) {
117         [df setBool:SENDER_STATE forKey:@"showTime"];
118         rebuildRequired = YES;
119     } else if ( [sender tag] == 1080) {
120         [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
121         rebuildRequired = YES;
122     } else if ( [sender tag] == 1090) {
123         [df setBool:SENDER_STATE forKey:@"showTrackRating"];
124         rebuildRequired = YES;
125     }
126
127     if ( rebuildRequired ) {
128         [controller rebuildMenu];
129         // redraw song info status window, or upcoming songs here
130     }
131
132     [df synchronize];
133 }
134
135 - (IBAction)changeStatusWindowSetting:(id)sender
136 {
137     if ( [sender tag] == 2010) {
138         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
139         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
140         // update the window's position here
141     } else if ( [sender tag] == 2020) {
142         // update screen selection
143     } else if ( [sender tag] == 2030) {
144         // Update appearance effect
145     } else if ( [sender tag] == 2040) {
146         // Update Vanish Effect
147     } else if ( [sender tag] == 2050) {
148         // Update appearance speed
149     } else if ( [sender tag] == 2060) {
150         // Update vanish speed
151     } else if ( [sender tag] == 2070) {
152         // Update vanish delay
153     } else if ( [sender tag] == 2080) {
154         // Update "Song Info window when song changes" setting.
155     }
156 }
157
158 - (IBAction)changeHotKey:(id)sender
159 {
160     switch ([sender tag])
161     {
162         case 4010:
163             [self setKeyCombo:playPauseCombo];
164             [self setHotKey:@"PlayPause"];
165             break;
166         case 4020:
167             [self setKeyCombo:nextTrackCombo];
168             [self setHotKey:@"NextTrack"];
169             break;
170         case 4030:
171             [self setKeyCombo:prevTrackCombo];
172             [self setHotKey:@"PrevTrack"];
173             break;
174         case 4040:
175             [self setKeyCombo:toggleLoopCombo];
176             [self setHotKey:@"ToggleLoop"];
177             break;
178         case 4050:
179             [self setKeyCombo:toggleShuffleCombo];
180             [self setHotKey:@"ToggleShuffle"];
181             break;
182         case 4060:
183             [self setKeyCombo:trackInfoCombo];
184             [self setHotKey:@"TrackInfo"];
185             break;
186         case 4070:
187             [self setKeyCombo:upcomingSongsCombo];
188             [self setHotKey:@"UpcomingSongs"];
189             break;
190         case 4080:
191             [self setKeyCombo:volumeIncrementCombo];
192             [self setHotKey:@"IncrementVolume"];
193             break;
194         case 4090:
195             [self setKeyCombo:volumeDecrementCombo];
196             [self setHotKey:@"DecrementVolume"];
197             break;
198         case 4100:
199             [self setKeyCombo:ratingIncrementCombo];
200             [self setHotKey:@"IncrementRating"];
201             break;
202         case 4110:
203             [self setKeyCombo:ratingDecrementCombo];
204             [self setHotKey:@"DecrementRating"];
205             break;
206     }
207 }
208
209 - (void)registerDefaults
210 {
211     BOOL found = NO;
212     NSMutableDictionary *loginWindow;
213     NSMutableArray *loginArray;
214     NSEnumerator *loginEnum;
215     id anItem;
216
217     [df setObject:[NSArray arrayWithObjects:
218         @"Play/Pause",
219         @"Next Track",
220         @"Previous Track",
221         @"Fast Forward",
222         @"Rewind",
223         @"Show Player",
224         @"<separator>",
225         @"Upcoming Songs",
226         @"Playlists",
227         @"Song Rating",
228         @"<separator>",
229         @"PreferencesÉ",
230         @"Quit",
231         @"<separator>",
232         @"Current Track Info",
233         nil] forKey:@"menu"];
234
235     [df setInteger:5 forKey:@"SongsInAdvance"];
236     // [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
237     [df setBool:YES forKey:@"showArtist"];
238     [df setBool:NO forKey:@"showAlbum"];
239     [df setBool:NO forKey:@"showTime"];
240
241     [df synchronize];
242     
243     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
244     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
245     loginEnum = [loginArray objectEnumerator];
246
247     while ( (anItem = [loginEnum nextObject]) ) {
248         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
249             found = YES;
250         }
251     }
252
253     [loginWindow release];
254     
255     // This is teh sux
256     // We must fix it so it is no longer suxy
257     if (!found) {
258         if (NSRunInformationalAlertPanel(@"Auto-launch MenuTunes", @"Would you like MenuTunes to automatically launch at login?", @"Yes", @"No", nil) == NSOKButton) {
259             AEDesc scriptDesc, resultDesc;
260             NSString *script = [NSString stringWithFormat:@"tell application \"System Events\"\nmake new login item at end of login items with properties {path:\"%@\", kind:\"APPLICATION\"}\nend tell", [[NSBundle mainBundle] bundlePath]];
261             ComponentInstance asComponent = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);
262
263             AECreateDesc(typeChar, [script cString], [script cStringLength],
264                          &scriptDesc);
265
266             OSADoScript(asComponent, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
267
268             AEDisposeDesc(&scriptDesc);
269             AEDisposeDesc(&resultDesc);
270
271             CloseComponent(asComponent);
272         }
273     }
274 }
275
276 - (IBAction)cancelHotKey:(id)sender
277 {
278     [[NSNotificationCenter defaultCenter] removeObserver:self];
279     [NSApp endSheet:keyComboPanel];
280     [keyComboPanel orderOut:nil];
281 }
282
283 - (IBAction)clearHotKey:(id)sender
284 {
285     [self setKeyCombo:[KeyCombo clearKeyCombo]];
286 }
287
288 - (IBAction)okHotKey:(id)sender
289 {
290     NSString *string = [combo userDisplayRep];
291     
292     if (string == nil) {
293         string = @"";
294     }
295     if ([setHotKey isEqualToString:@"PlayPause"]) {
296         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
297             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo] ||
298             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
299             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
300             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) &&
301             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
302             
303             [window setLevel:NSNormalWindowLevel];
304             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
305             [window setLevel:NSStatusWindowLevel];
306             return;
307         }
308         playPauseCombo = [combo copy];
309         [playPauseButton setTitle:string];
310     } else if ([setHotKey isEqualToString:@"NextTrack"]) {
311         if (([combo isEqual:playPauseCombo] || [combo isEqual:prevTrackCombo] ||
312             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo] ||
313             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
314             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
315             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
316             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
317             
318             [window setLevel:NSNormalWindowLevel];
319             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
320             [window setLevel:NSStatusWindowLevel];
321             return;
322         }
323         nextTrackCombo = [combo copy];
324         [nextTrackButton setTitle:string];
325     } else if ([setHotKey isEqualToString:@"PrevTrack"]) {
326         if (([combo isEqual:nextTrackCombo] || [combo isEqual:playPauseCombo] ||
327             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo] ||
328             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
329             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
330             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
331             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
332             
333             [window setLevel:NSNormalWindowLevel];
334             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
335             [window setLevel:NSStatusWindowLevel];
336             return;
337         }
338         prevTrackCombo = [combo copy];
339         [previousTrackButton setTitle:string];
340     } else if ([setHotKey isEqualToString:@"TrackInfo"]) {
341         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
342             [combo isEqual:playPauseCombo] || [combo isEqual:upcomingSongsCombo] ||
343             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
344             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
345             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
346             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
347             
348             [window setLevel:NSNormalWindowLevel];
349             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
350             [window setLevel:NSStatusWindowLevel];
351             return;
352         }
353         trackInfoCombo = [combo copy];
354         [trackInfoButton setTitle:string];
355     } else if ([setHotKey isEqualToString:@"UpcomingSongs"]) {
356         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
357             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
358             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
359             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
360             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
361             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
362             
363             [window setLevel:NSNormalWindowLevel];
364             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
365             [window setLevel:NSStatusWindowLevel];
366             return;
367         }
368         upcomingSongsCombo = [combo copy];
369         [upcomingSongsButton setTitle:string];
370     //THE NEW COMBOS!
371     } else if ([setHotKey isEqualToString:@"IncrementVolume"]) {
372         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
373             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
374             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
375             [combo isEqual:upcomingSongsCombo] || [combo isEqual:volumeDecrementCombo] ||
376             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
377             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
378             
379             [window setLevel:NSNormalWindowLevel];
380             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
381             [window setLevel:NSStatusWindowLevel];
382             return;
383         }
384         volumeIncrementCombo = [combo copy];
385         [volumeIncrementButton setTitle:string];
386     } else if ([setHotKey isEqualToString:@"DecrementVolume"]) {
387         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
388             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
389             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
390             [combo isEqual:volumeIncrementCombo] || [combo isEqual:upcomingSongsCombo] ||
391             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
392             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
393             
394             [window setLevel:NSNormalWindowLevel];
395             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
396             [window setLevel:NSStatusWindowLevel];
397             return;
398         }
399         volumeDecrementCombo = [combo copy];
400         [volumeDecrementButton setTitle:string];
401     } else if ([setHotKey isEqualToString:@"IncrementRating"]) {
402         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
403             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
404             [combo isEqual:upcomingSongsCombo] || [combo isEqual:ratingDecrementCombo] ||
405             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
406             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
407             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
408             
409             [window setLevel:NSNormalWindowLevel];
410             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
411             [window setLevel:NSStatusWindowLevel];
412             return;
413         }
414         ratingIncrementCombo = [combo copy];
415         [ratingIncrementButton setTitle:string];
416     } else if ([setHotKey isEqualToString:@"DecrementRating"]) {
417         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
418             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
419             [combo isEqual:ratingIncrementCombo] || [combo isEqual:upcomingSongsCombo] ||
420             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
421             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
422             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
423             
424             [window setLevel:NSNormalWindowLevel];
425             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
426             [window setLevel:NSStatusWindowLevel];
427             return;
428         }
429         ratingDecrementCombo = [combo copy];
430         [ratingDecrementButton setTitle:string];
431     } else if ([setHotKey isEqualToString:@"ToggleLoop"]) {
432         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
433             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
434             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
435             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
436             [combo isEqual:upcomingSongsCombo] || [combo isEqual:toggleShuffleCombo]) && 
437             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
438             
439             [window setLevel:NSNormalWindowLevel];
440             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
441             [window setLevel:NSStatusWindowLevel];
442             return;
443         }
444         toggleLoopCombo = [combo copy];
445         [toggleLoopButton setTitle:string];
446     } else if ([setHotKey isEqualToString:@"ToggleShuffle"]) {
447         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
448             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
449             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
450             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
451             [combo isEqual:toggleLoopCombo] || [combo isEqual:upcomingSongsCombo]) && 
452             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
453             
454             [window setLevel:NSNormalWindowLevel];
455             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
456             [window setLevel:NSStatusWindowLevel];
457             return;
458         }
459         toggleShuffleCombo = [combo copy];
460         [toggleShuffleButton setTitle:string];
461     }
462     [df setKeyCombo:combo forKey:setHotKey];
463     [controller rebuildMenu];
464     [self cancelHotKey:sender];
465 }
466
467
468
469 /*************************************************************************/
470 #pragma mark -
471 #pragma mark HOTKEY SUPPORT METHODS
472 /*************************************************************************/
473
474 - (void)setHotKey:(NSString *)key
475 {
476     setHotKey = key;
477     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
478     [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
479 }
480
481 - (void)keyEvent:(NSNotification *)note
482 {
483     NSDictionary *info = [note userInfo];
484     short keyCode;
485     long modifiers;
486     KeyCombo *newCombo;
487     
488     keyCode = [[info objectForKey:@"KeyCode"] shortValue];
489     modifiers = [[info objectForKey:@"Modifiers"] longValue];
490     
491     newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
492     [self setKeyCombo:newCombo];
493 }
494
495 - (void)setKeyCombo:(KeyCombo *)newCombo
496 {
497     NSString *string;
498     [combo release];
499     combo = [newCombo copy];
500     
501     string = [combo userDisplayRep];
502     if (string == nil) {
503         string = @"";
504     }
505     [keyComboField setStringValue:string];
506 }
507
508
509 /*************************************************************************/
510 #pragma mark -
511 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
512 /*************************************************************************/
513
514 - (void)setupWindow
515 {
516     if ( ! [NSBundle loadNibNamed:@"Preferences" owner:self] ) {
517         NSLog( @"Failed to load Preferences.nib" );
518         NSBeep();
519         return;
520     }
521 }
522
523 - (void)setupCustomizationTables
524 {
525     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
526     
527     // Set the table view cells up
528     [imgCell setImageScaling:NSScaleNone];
529     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
530     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
531
532     // Register for drag and drop
533     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
534         @"MenuTableViewPboardType",
535         @"AllTableViewPboardType",
536         nil]];
537     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
538         @"MenuTableViewPboardType",
539         @"AllTableViewPboardType",
540         nil]];
541 }
542
543 - (void)setupMenuItems
544 {
545     NSEnumerator *itemEnum;
546     id            anItem;
547     // Set the list of items you can have.
548     availableItems = [[NSMutableArray alloc] initWithObjects:
549         @"Current Track Info",
550         @"Upcoming Songs",
551         @"Playlists",
552         @"EQ Presets",
553         @"Song Rating",
554         @"Play/Pause",
555         @"Next Track",
556         @"Previous Track",
557         @"Fast Forward",
558         @"Rewind",
559         @"Show Player",
560         @"<separator>",
561         nil];
562     
563     // Get our preferred menu
564     myItems = [[df arrayForKey:@"menu"] mutableCopy];
565     
566     // Delete items in the availableItems array that are already part of the menu
567     itemEnum = [myItems objectEnumerator];
568     while ( (anItem = [itemEnum nextObject]) ) {
569         if ( ! [anItem isEqualToString:@"<separator>"] ) {
570             [availableItems removeObject:anItem];
571         }
572     }
573     
574     // Items that show should a submenu image
575     submenuItems = [[NSArray alloc] initWithObjects:
576         @"Upcoming Songs",
577         @"Playlists",
578         @"EQ Presets",
579         @"Song Rating",
580         nil];
581 }
582
583 - (void)setupUI
584 {
585     NSMutableDictionary *loginwindow;
586     NSMutableArray *loginarray;
587     NSEnumerator *loginEnum;
588     id anItem;
589
590     // Fill in the number of songs in advance to show field
591     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
592     
593     // Fill in hot key buttons
594     if ([df objectForKey:@"PlayPause"]){
595         playPauseCombo = [df keyComboForKey:@"PlayPause"];
596         [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
597     } else {
598         playPauseCombo = [[KeyCombo alloc] init];
599     }
600     
601     if ([df objectForKey:@"NextTrack"]) {
602         nextTrackCombo = [df keyComboForKey:@"NextTrack"];
603         [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
604     } else {
605         nextTrackCombo = [[KeyCombo alloc] init];
606     }
607     
608     if ([df objectForKey:@"PrevTrack"]) {
609         prevTrackCombo = [df keyComboForKey:@"PrevTrack"];
610         [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
611     } else {
612         prevTrackCombo = [[KeyCombo alloc] init];
613     }
614     
615     if ([df objectForKey:@"TrackInfo"]) {
616         trackInfoCombo = [df keyComboForKey:@"TrackInfo"];
617         [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
618     } else {
619         trackInfoCombo = [[KeyCombo alloc] init];
620     }
621     
622     if ([df objectForKey:@"UpcomingSongs"]) {
623         upcomingSongsCombo = [df keyComboForKey:@"UpcomingSongs"];
624         [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
625     } else {
626         upcomingSongsCombo = [[KeyCombo alloc] init];
627     }
628     
629     if ([df objectForKey:@"IncrementVolume"]) {
630         volumeIncrementCombo = [df keyComboForKey:@"IncrementVolume"];
631         [volumeIncrementButton setTitle:[volumeIncrementCombo userDisplayRep]];
632     } else {
633         volumeIncrementCombo = [[KeyCombo alloc] init];
634     }
635     
636     if ([df objectForKey:@"DecrementVolume"]) {
637         volumeDecrementCombo = [df keyComboForKey:@"DecrementVolume"];
638         [volumeDecrementButton setTitle:[volumeDecrementCombo userDisplayRep]];
639     } else {
640         volumeDecrementCombo = [[KeyCombo alloc] init];
641     }
642     
643     if ([df objectForKey:@"IncrementRating"]) {
644         ratingIncrementCombo = [df keyComboForKey:@"IncrementRating"];
645         [ratingIncrementButton setTitle:[ratingIncrementCombo userDisplayRep]];
646     } else {
647         ratingIncrementCombo = [[KeyCombo alloc] init];
648     }
649     
650     if ([df objectForKey:@"DecrementRating"]) {
651         ratingDecrementCombo = [df keyComboForKey:@"DecrementRating"];
652         [ratingDecrementButton setTitle:[ratingDecrementCombo userDisplayRep]];
653     } else {
654         ratingDecrementCombo = [[KeyCombo alloc] init];
655     }
656     
657     if ([df objectForKey:@"ToggleLoop"]) {
658         toggleLoopCombo = [df keyComboForKey:@"ToggleLoop"];
659         [toggleLoopButton setTitle:[toggleLoopCombo userDisplayRep]];
660     } else {
661         toggleLoopCombo = [[KeyCombo alloc] init];
662     }
663     
664     if ([df objectForKey:@"ToggleShuffle"]) {
665         toggleShuffleCombo = [df keyComboForKey:@"ToggleShuffle"];
666         [toggleShuffleButton setTitle:[toggleShuffleCombo userDisplayRep]];
667     } else {
668         toggleShuffleCombo = [[KeyCombo alloc] init];
669     }
670     
671     // Check current track info buttons
672     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
673     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
674     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
675     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
676     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
677     
678     // Set the launch at login checkbox state
679     [df synchronize];
680     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
681     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
682     
683     loginEnum = [loginarray objectEnumerator];
684     while ( (anItem = [loginEnum nextObject]) ) {
685         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
686             [launchAtLoginCheckbox setState:NSOnState];
687         }
688     }
689 }
690
691 - (IBAction)changeMenus:(id)sender
692 {
693     [df setObject:myItems forKey:@"menu"];
694     [df synchronize];
695     [controller rebuildMenu];
696 }
697
698 - (void)setLaunchesAtLogin:(BOOL)flag
699 {
700     if ( flag ) {
701         NSMutableDictionary *loginwindow;
702         NSMutableArray *loginarray;
703         ComponentInstance temp = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);;
704         int i;
705         BOOL skip = NO;
706
707         [df synchronize];
708         loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
709         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
710
711         for (i = 0; i < [loginarray count]; i++) {
712             NSDictionary *tempDict = [loginarray objectAtIndex:i];
713             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
714                 skip = YES;
715             }
716         }
717
718         if (!skip) {
719             AEDesc scriptDesc, resultDesc;
720             NSString *script = [NSString stringWithFormat:@"tell application \"System Events\"\nmake new login item at end of login items with properties {path:\"%@\", kind:\"APPLICATION\"}\nend tell", [[NSBundle mainBundle] bundlePath]];
721
722             AECreateDesc(typeChar, [script cString], [script cStringLength],
723                          &scriptDesc);
724
725             OSADoScript(temp, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
726
727             AEDisposeDesc(&scriptDesc);
728             AEDisposeDesc(&resultDesc);
729             CloseComponent(temp);
730         }
731
732     } else {
733         NSMutableDictionary *loginwindow;
734         NSMutableArray *loginarray;
735         int i;
736
737         [df synchronize];
738         loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
739         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
740
741         for (i = 0; i < [loginarray count]; i++) {
742             NSDictionary *tempDict = [loginarray objectAtIndex:i];
743             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
744                 [loginarray removeObjectAtIndex:i];
745                 [df setPersistentDomain:loginwindow forName:@"loginwindow"];
746                 [df synchronize];
747                 break;
748             }
749         }
750     }
751 }
752
753
754 /*************************************************************************/
755 #pragma mark -
756 #pragma mark NSWindow DELEGATE METHODS
757 /*************************************************************************/
758
759 - (void)windowWillClose:(NSNotification *)note
760 {
761     [(MainController *)controller closePreferences]; 
762 }
763
764
765 /*************************************************************************/
766 #pragma mark -
767 #pragma mark NSTableView DATASOURCE METHODS
768 /*************************************************************************/
769
770 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
771 {
772     if (aTableView == menuTableView) {
773         return [myItems count];
774     } else {
775         return [availableItems count];
776     }
777 }
778
779 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
780 {
781     if (aTableView == menuTableView) {
782         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
783             NSString *object = [myItems objectAtIndex:rowIndex];
784             if ([object isEqualToString:@"Show Player"]) {
785                 return [NSString stringWithFormat:@"Show %@", [[controller currentRemote] playerSimpleName]];
786             }
787             return object;
788         } else {
789             if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
790             {
791                 return [NSImage imageNamed:@"submenu"];
792             } else {
793                 return nil;
794             }
795         }
796     } else {
797         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
798             return [availableItems objectAtIndex:rowIndex];
799         } else {
800             if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]]) {
801                 return [NSImage imageNamed:@"submenu"];
802             } else {
803                 return nil;
804             }
805         }
806     }
807 }
808
809 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
810 {
811     if (tableView == menuTableView) {
812         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
813         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
814         return YES;
815     }
816     
817     if (tableView == allTableView) {
818         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
819         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
820         return YES;
821     }
822     return NO;
823 }
824
825 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
826 {
827     NSPasteboard *pb;
828     int dragRow;
829     NSString *dragData, *temp;
830     
831     pb = [info draggingPasteboard];
832     
833     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
834         dragData = [pb stringForType:@"MenuTableViewPboardType"];
835         dragRow = [dragData intValue];
836         temp = [myItems objectAtIndex:dragRow];
837         [myItems removeObjectAtIndex:dragRow];
838         
839         if (tableView == menuTableView) {
840             if (row > dragRow) {
841                 [myItems insertObject:temp atIndex:row - 1];
842             } else {
843                 [myItems insertObject:temp atIndex:row];
844             }
845         } else {
846             if (![temp isEqualToString:@"<separator>"]) {
847                 [availableItems addObject:temp];
848             }
849         }
850     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
851         dragData = [pb stringForType:@"AllTableViewPboardType"];
852         dragRow = [dragData intValue];
853         temp = [availableItems objectAtIndex:dragRow];
854         
855         if (![temp isEqualToString:@"<separator>"]) {
856             [availableItems removeObjectAtIndex:dragRow];
857         }
858         [myItems insertObject:temp atIndex:row];
859     }
860     
861     [menuTableView reloadData];
862     [allTableView reloadData];
863     [self changeMenus:self];
864     return YES;
865 }
866
867 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
868 {
869     if (tableView == allTableView) {
870         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
871             return NSDragOperationNone;
872         }
873         
874         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
875             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
876             if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"]) {
877                 return NSDragOperationNone;
878             }
879         }
880         
881         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
882         return NSDragOperationGeneric;
883     }
884     
885     if (operation == NSTableViewDropOn || row == -1)
886     {
887         return NSDragOperationNone;
888     }
889     
890     return NSDragOperationGeneric;
891 }
892
893
894 /*************************************************************************/
895 #pragma mark -
896 #pragma mark DEALLOCATION METHODS
897 /*************************************************************************/
898
899 - (void)dealloc
900 {
901     [self setKeyCombo:nil];
902     [playPauseCombo release];
903     [nextTrackCombo release];
904     [prevTrackCombo release];
905     [trackInfoCombo release];
906     [upcomingSongsCombo release];
907     [keyComboPanel release];
908     [menuTableView setDataSource:nil];
909     [allTableView setDataSource:nil];
910     [controller release];
911     [availableItems release];
912     [submenuItems release];
913     [myItems release];
914     [df release];
915 }
916
917
918 @end