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