Prefs tweaks, and to-do's.
[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     [self cancelHotKey:sender];
463 }
464
465
466
467 /*************************************************************************/
468 #pragma mark -
469 #pragma mark HOTKEY SUPPORT METHODS
470 /*************************************************************************/
471
472 - (void)setHotKey:(NSString *)key
473 {
474     setHotKey = key;
475     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
476     [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
477 }
478
479 - (void)keyEvent:(NSNotification *)note
480 {
481     NSDictionary *info = [note userInfo];
482     short keyCode;
483     long modifiers;
484     KeyCombo *newCombo;
485     
486     keyCode = [[info objectForKey:@"KeyCode"] shortValue];
487     modifiers = [[info objectForKey:@"Modifiers"] longValue];
488     
489     newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
490     [self setKeyCombo:newCombo];
491 }
492
493 - (void)setKeyCombo:(KeyCombo *)newCombo
494 {
495     NSString *string;
496     [combo release];
497     combo = [newCombo copy];
498     
499     string = [combo userDisplayRep];
500     if (string == nil) {
501         string = @"";
502     }
503     [keyComboField setStringValue:string];
504 }
505
506
507 /*************************************************************************/
508 #pragma mark -
509 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
510 /*************************************************************************/
511
512 - (void)setupWindow
513 {
514     if ( ! [NSBundle loadNibNamed:@"Preferences" owner:self] ) {
515         NSLog( @"Failed to load Preferences.nib" );
516         NSBeep();
517         return;
518     }
519 }
520
521 - (void)setupCustomizationTables
522 {
523     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
524     
525     // Set the table view cells up
526     [imgCell setImageScaling:NSScaleNone];
527     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
528     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
529
530     // Register for drag and drop
531     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
532         @"MenuTableViewPboardType",
533         @"AllTableViewPboardType",
534         nil]];
535     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
536         @"MenuTableViewPboardType",
537         @"AllTableViewPboardType",
538         nil]];
539 }
540
541 - (void)setupMenuItems
542 {
543     NSEnumerator *itemEnum;
544     id            anItem;
545     // Set the list of items you can have.
546     availableItems = [[NSMutableArray alloc] initWithObjects:
547         @"Current Track Info",
548         @"Upcoming Songs",
549         @"Playlists",
550         @"EQ Presets",
551         @"Song Rating",
552         @"Play/Pause",
553         @"Next Track",
554         @"Previous Track",
555         @"Fast Forward",
556         @"Rewind",
557         @"Show Player",
558         @"<separator>",
559         nil];
560     
561     // Get our preferred menu
562     myItems = [[df arrayForKey:@"menu"] mutableCopy];
563     
564     // Delete items in the availableItems array that are already part of the menu
565     itemEnum = [myItems objectEnumerator];
566     while ( (anItem = [itemEnum nextObject]) ) {
567         if ( ! [anItem isEqualToString:@"<separator>"] ) {
568             [availableItems removeObject:anItem];
569         }
570     }
571     
572     // Items that show should a submenu image
573     submenuItems = [[NSArray alloc] initWithObjects:
574         @"Upcoming Songs",
575         @"Playlists",
576         @"EQ Presets",
577         @"Song Rating",
578         nil];
579 }
580
581 - (void)setupUI
582 {
583     NSMutableDictionary *loginwindow;
584     NSMutableArray *loginarray;
585     NSEnumerator *loginEnum;
586     id anItem;
587
588     // Fill in the number of songs in advance to show field
589     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
590     
591     // Fill in hot key buttons
592     if ([df objectForKey:@"PlayPause"]){
593         playPauseCombo = [df keyComboForKey:@"PlayPause"];
594         [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
595     } else {
596         playPauseCombo = [[KeyCombo alloc] init];
597     }
598     
599     if ([df objectForKey:@"NextTrack"]) {
600         nextTrackCombo = [df keyComboForKey:@"NextTrack"];
601         [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
602     } else {
603         nextTrackCombo = [[KeyCombo alloc] init];
604     }
605     
606     if ([df objectForKey:@"PrevTrack"]) {
607         prevTrackCombo = [df keyComboForKey:@"PrevTrack"];
608         [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
609     } else {
610         prevTrackCombo = [[KeyCombo alloc] init];
611     }
612     
613     if ([df objectForKey:@"TrackInfo"]) {
614         trackInfoCombo = [df keyComboForKey:@"TrackInfo"];
615         [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
616     } else {
617         trackInfoCombo = [[KeyCombo alloc] init];
618     }
619     
620     if ([df objectForKey:@"UpcomingSongs"]) {
621         upcomingSongsCombo = [df keyComboForKey:@"UpcomingSongs"];
622         [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
623     } else {
624         upcomingSongsCombo = [[KeyCombo alloc] init];
625     }
626     
627     if ([df objectForKey:@"IncrementVolume"]) {
628         volumeIncrementCombo = [df keyComboForKey:@"IncrementVolume"];
629         [volumeIncrementButton setTitle:[volumeIncrementCombo userDisplayRep]];
630     } else {
631         volumeIncrementCombo = [[KeyCombo alloc] init];
632     }
633     
634     if ([df objectForKey:@"DecrementVolume"]) {
635         volumeDecrementCombo = [df keyComboForKey:@"DecrementVolume"];
636         [volumeDecrementButton setTitle:[volumeDecrementCombo userDisplayRep]];
637     } else {
638         volumeDecrementCombo = [[KeyCombo alloc] init];
639     }
640     
641     if ([df objectForKey:@"IncrementRating"]) {
642         ratingIncrementCombo = [df keyComboForKey:@"IncrementRating"];
643         [ratingIncrementButton setTitle:[ratingIncrementCombo userDisplayRep]];
644     } else {
645         ratingIncrementCombo = [[KeyCombo alloc] init];
646     }
647     
648     if ([df objectForKey:@"DecrementRating"]) {
649         ratingDecrementCombo = [df keyComboForKey:@"DecrementRating"];
650         [ratingDecrementButton setTitle:[ratingDecrementCombo userDisplayRep]];
651     } else {
652         ratingDecrementCombo = [[KeyCombo alloc] init];
653     }
654     
655     if ([df objectForKey:@"ToggleLoop"]) {
656         toggleLoopCombo = [df keyComboForKey:@"ToggleLoop"];
657         [toggleLoopButton setTitle:[toggleLoopCombo userDisplayRep]];
658     } else {
659         toggleLoopCombo = [[KeyCombo alloc] init];
660     }
661     
662     if ([df objectForKey:@"ToggleShuffle"]) {
663         toggleShuffleCombo = [df keyComboForKey:@"ToggleShuffle"];
664         [toggleShuffleButton setTitle:[toggleShuffleCombo userDisplayRep]];
665     } else {
666         toggleShuffleCombo = [[KeyCombo alloc] init];
667     }
668     
669     // Check current track info buttons
670     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
671     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
672     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
673     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
674     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
675     
676     // Set the launch at login checkbox state
677     [df synchronize];
678     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
679     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
680     
681     loginEnum = [loginarray objectEnumerator];
682     while ( (anItem = [loginEnum nextObject]) ) {
683         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
684             [launchAtLoginCheckbox setState:NSOnState];
685         }
686     }
687 }
688
689 - (IBAction)changeMenus:(id)sender
690 {
691     [df setObject:myItems forKey:@"menu"];
692     [df synchronize];
693     [controller rebuildMenu];
694 }
695
696 - (void)setLaunchesAtLogin:(BOOL)flag
697 {
698     if ( flag ) {
699         NSMutableDictionary *loginwindow;
700         NSMutableArray *loginarray;
701         ComponentInstance temp = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);;
702         int i;
703         BOOL skip = NO;
704
705         [df synchronize];
706         loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
707         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
708
709         for (i = 0; i < [loginarray count]; i++) {
710             NSDictionary *tempDict = [loginarray objectAtIndex:i];
711             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
712                 skip = YES;
713             }
714         }
715
716         if (!skip) {
717             AEDesc scriptDesc, resultDesc;
718             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]];
719
720             AECreateDesc(typeChar, [script cString], [script cStringLength],
721                          &scriptDesc);
722
723             OSADoScript(temp, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
724
725             AEDisposeDesc(&scriptDesc);
726             AEDisposeDesc(&resultDesc);
727             CloseComponent(temp);
728         }
729
730     } else {
731         NSMutableDictionary *loginwindow;
732         NSMutableArray *loginarray;
733         int i;
734
735         [df synchronize];
736         loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
737         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
738
739         for (i = 0; i < [loginarray count]; i++) {
740             NSDictionary *tempDict = [loginarray objectAtIndex:i];
741             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
742                 [loginarray removeObjectAtIndex:i];
743                 [df setPersistentDomain:loginwindow forName:@"loginwindow"];
744                 [df synchronize];
745                 break;
746             }
747         }
748     }
749 }
750
751
752 /*************************************************************************/
753 #pragma mark -
754 #pragma mark NSWindow DELEGATE METHODS
755 /*************************************************************************/
756
757 - (void)windowWillClose:(NSNotification *)note
758 {
759     [(MainController *)controller closePreferences]; 
760 }
761
762
763 /*************************************************************************/
764 #pragma mark -
765 #pragma mark NSTableView DATASOURCE METHODS
766 /*************************************************************************/
767
768 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
769 {
770     if (aTableView == menuTableView) {
771         return [myItems count];
772     } else {
773         return [availableItems count];
774     }
775 }
776
777 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
778 {
779     if (aTableView == menuTableView) {
780         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
781             return [myItems objectAtIndex:rowIndex];
782         } else {
783             if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
784             {
785                 return [NSImage imageNamed:@"submenu"];
786             } else {
787                 return nil;
788             }
789         }
790     } else {
791         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
792             return [availableItems objectAtIndex:rowIndex];
793         } else {
794             if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]]) {
795                 return [NSImage imageNamed:@"submenu"];
796             } else {
797                 return nil;
798             }
799         }
800     }
801 }
802
803 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
804 {
805     if (tableView == menuTableView) {
806         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
807         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
808         return YES;
809     }
810     
811     if (tableView == allTableView) {
812         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
813         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
814         return YES;
815     }
816     return NO;
817 }
818
819 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
820 {
821     NSPasteboard *pb;
822     int dragRow;
823     NSString *dragData, *temp;
824     
825     pb = [info draggingPasteboard];
826     
827     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
828         dragData = [pb stringForType:@"MenuTableViewPboardType"];
829         dragRow = [dragData intValue];
830         temp = [myItems objectAtIndex:dragRow];
831         [myItems removeObjectAtIndex:dragRow];
832         
833         if (tableView == menuTableView) {
834             if (row > dragRow) {
835                 [myItems insertObject:temp atIndex:row - 1];
836             } else {
837                 [myItems insertObject:temp atIndex:row];
838             }
839         } else {
840             if (![temp isEqualToString:@"<separator>"]) {
841                 [availableItems addObject:temp];
842             }
843         }
844     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
845         dragData = [pb stringForType:@"AllTableViewPboardType"];
846         dragRow = [dragData intValue];
847         temp = [availableItems objectAtIndex:dragRow];
848         
849         if (![temp isEqualToString:@"<separator>"]) {
850             [availableItems removeObjectAtIndex:dragRow];
851         }
852         [myItems insertObject:temp atIndex:row];
853     }
854     
855     [menuTableView reloadData];
856     [allTableView reloadData];
857     [self changeMenus:self];
858     return YES;
859 }
860
861 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
862 {
863     if (tableView == allTableView) {
864         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
865             return NSDragOperationNone;
866         }
867         
868         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
869             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
870             if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"]) {
871                 return NSDragOperationNone;
872             }
873         }
874         
875         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
876         return NSDragOperationGeneric;
877     }
878     
879     if (operation == NSTableViewDropOn || row == -1)
880     {
881         return NSDragOperationNone;
882     }
883     
884     return NSDragOperationGeneric;
885 }
886
887
888 /*************************************************************************/
889 #pragma mark -
890 #pragma mark DEALLOCATION METHODS
891 /*************************************************************************/
892
893 - (void)dealloc
894 {
895     [self setKeyCombo:nil];
896     [playPauseCombo release];
897     [nextTrackCombo release];
898     [prevTrackCombo release];
899     [trackInfoCombo release];
900     [upcomingSongsCombo release];
901     [keyComboPanel release];
902     [menuTableView setDataSource:nil];
903     [allTableView setDataSource:nil];
904     [controller release];
905     [availableItems release];
906     [submenuItems release];
907     [myItems release];
908     [df release];
909 }
910
911
912 @end