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