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