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