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