Fixed my dorkiness with making unneeded helper methods in
[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         [sw setHorizontalPosition:[sender selectedColumn]];
368         [sw setVerticalPosition:[sender selectedRow]];
369         // update the window's position here
370     } else if ( [sender tag] == 2020) {
371         // update screen selection
372     } else if ( [sender tag] == 2030) {
373         int effectTag = [[sender selectedItem] tag];
374         float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
375         [df setInteger:effectTag forKey:@"statusWindowAppearanceEffect"];
376
377         if ( effectTag == 2100 ) {
378             [sw setEntryEffect:[[[ITCutWindowEffect alloc] initWithWindow:sw] autorelease]];
379         } else if ( effectTag == 2101 ) {
380             [sw setEntryEffect:[[[ITDissolveWindowEffect alloc] initWithWindow:sw] autorelease]];
381         } else if ( effectTag == 2102 ) {
382             [sw setEntryEffect:[[[ITSlideVerticallyWindowEffect alloc] initWithWindow:sw] autorelease]];
383         } else if ( effectTag == 2103 ) {
384             [sw setEntryEffect:[[[ITSlideHorizontallyWindowEffect alloc] initWithWindow:sw] autorelease]];
385         } else if ( effectTag == 2104 ) {
386             [sw setEntryEffect:[[[ITPivotWindowEffect alloc] initWithWindow:sw] autorelease]];
387         }
388
389         [[sw entryEffect] setEffectTime:time];
390         
391     } else if ( [sender tag] == 2040) {
392         int effectTag = [[sender selectedItem] tag];
393         float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
394         
395         [df setInteger:[[sender selectedItem] tag] forKey:@"statusWindowVanishEffect"];
396         
397         if ( effectTag == 2100 ) {
398             [sw setExitEffect:[[[ITCutWindowEffect alloc] initWithWindow:sw] autorelease]];
399         } else if ( effectTag == 2101 ) {
400             [sw setExitEffect:[[[ITDissolveWindowEffect alloc] initWithWindow:sw] autorelease]];
401         } else if ( effectTag == 2102 ) {
402             [sw setExitEffect:[[[ITSlideVerticallyWindowEffect alloc] initWithWindow:sw] autorelease]];
403         } else if ( effectTag == 2103 ) {
404             [sw setExitEffect:[[[ITSlideHorizontallyWindowEffect alloc] initWithWindow:sw] autorelease]];
405         } else if ( effectTag == 2104 ) {
406             [sw setExitEffect:[[[ITPivotWindowEffect alloc] initWithWindow:sw] autorelease]];
407         }
408
409         [[sw exitEffect] setEffectTime:time];
410
411     } else if ( [sender tag] == 2050) {
412         float newTime = (-([sender floatValue]));
413         [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
414         [[sw entryEffect] setEffectTime:newTime];
415     } else if ( [sender tag] == 2060) {
416         float newTime = (-([sender floatValue]));
417         [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
418         [[sw exitEffect] setEffectTime:newTime];
419     } else if ( [sender tag] == 2070) {
420         [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
421         [sw setExitDelay:[sender floatValue]];
422     } else if ( [sender tag] == 2080) {
423         [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
424     }
425     [df synchronize];
426 }
427
428 - (void)registerDefaults
429 {
430     BOOL found = NO;
431     NSMutableDictionary *loginWindow;
432     NSMutableArray *loginArray;
433     NSEnumerator *loginEnum;
434     id anItem;
435     ITDebugLog(@"Registering defaults.");
436     [df setObject:[NSArray arrayWithObjects:
437         @"trackInfo",
438         @"separator",
439         @"playPause",
440         @"prevTrack",
441         @"nextTrack",
442         @"separator",
443         @"playlists",
444         @"upcomingSongs",
445         @"separator",
446         @"preferences",
447         @"quit",
448         nil] forKey:@"menu"];
449
450     [df setInteger:5 forKey:@"SongsInAdvance"];
451     // [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
452     [df setBool:YES forKey:@"showArtist"];
453     [df setBool:NO forKey:@"showAlbum"];
454     [df setBool:NO forKey:@"showTime"];
455
456     [df setInteger:2100 forKey:@"statusWindowAppearanceEffect"];
457     [df setInteger:2101 forKey:@"statusWindowVanishEffect"];
458     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
459     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
460     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
461     [df setBool:YES forKey:@"showSongInfoOnChange"];
462
463     [df synchronize];
464     
465     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
466     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
467     loginEnum = [loginArray objectEnumerator];
468
469     while ( (anItem = [loginEnum nextObject]) ) {
470         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
471             found = YES;
472         }
473     }
474     [loginWindow release];
475     
476     if (!found) {
477         [[StatusWindowController sharedController] showSetupQueryWindow];
478     }
479 }
480
481 - (void)autoLaunchOK
482 {
483     [[StatusWindow sharedWindow] setLocked:NO];
484     [[StatusWindow sharedWindow] vanish:self];
485     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
486     
487     [self setLaunchesAtLogin:YES];
488 }
489
490 - (void)autoLaunchCancel
491 {
492     [[StatusWindow sharedWindow] setLocked:NO];
493     [[StatusWindow sharedWindow] vanish:self];
494     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
495 }
496
497 - (void)deletePressedInTableView:(NSTableView *)tableView
498 {
499     if (tableView == menuTableView) {
500         int selRow = [tableView selectedRow];
501         ITDebugLog(@"Delete pressed in menu table view.");
502         if (selRow != - 1) {
503             NSString *object = [myItems objectAtIndex:selRow];
504             
505             if ([object isEqualToString:@"preferences"]) {
506                 NSBeep();
507                 return;
508             }
509             
510             if (![object isEqualToString:@"separator"])
511                 [availableItems addObject:object];
512             ITDebugLog(@"Removing object named %@", object);
513             [myItems removeObjectAtIndex:selRow];
514             [menuTableView reloadData];
515             [allTableView reloadData];
516         }
517         [self changeMenus:self];
518     }
519 }
520
521 - (void)resetRemotePlayerTextFields
522 {
523     if ([[NetworkController sharedController] isConnectedToServer]) {
524         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
525         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
526     } else {
527         [selectedPlayerTextField setStringValue:@"No shared player selected."];
528         [locationTextField setStringValue:@"-"];
529     }
530 }
531
532 /*************************************************************************/
533 #pragma mark -
534 #pragma mark HOTKEY SUPPORT METHODS
535 /*************************************************************************/
536
537 - (IBAction)clearHotKey:(id)sender
538 {
539     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
540     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
541     [controller setupHotKeys];
542     [hotKeysTableView reloadData];
543 }
544
545 - (IBAction)editHotKey:(id)sender
546 {
547     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
548     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
549     ITKeyCombo *keyCombo;
550     
551     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
552     [controller clearHotKeys];
553     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
554     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
555     if ([panel runModal] == NSOKButton) {
556         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
557         NSString *nextKey;
558         keyCombo = [panel keyCombo];
559         
560         //Check for duplicate key combo
561         while ( (nextKey = [keyEnumerator nextObject]) ) {
562             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
563                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
564                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
565                                    forKey:nextKey];
566                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
567                     forKey:nextKey];
568             }
569         }
570         
571         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
572         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
573         [controller setupHotKeys];
574         [hotKeysTableView reloadData];
575         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
576     } else {
577         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
578     }
579 }
580
581 - (void)hotKeysTableViewDoubleClicked:(id)sender
582 {
583     if ([sender clickedRow] > -1) {
584         [self editHotKey:sender];
585     }
586 }
587
588 /*************************************************************************/
589 #pragma mark -
590 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
591 /*************************************************************************/
592
593 - (void)setupWindow
594 {
595     ITDebugLog(@"Loading Preferences.nib.");
596     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
597         ITDebugLog(@"Failed to load Preferences.nib.");
598         NSBeep();
599         return;
600     }
601 }
602
603 - (void)setupCustomizationTables
604 {
605     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
606     ITDebugLog(@"Setting up table views.");
607     // Set the table view cells up
608     [imgCell setImageScaling:NSScaleNone];
609     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
610     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
611
612     // Register for drag and drop
613     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
614         @"MenuTableViewPboardType",
615         @"AllTableViewPboardType",
616         nil]];
617     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
618         @"MenuTableViewPboardType",
619         @"AllTableViewPboardType",
620         nil]];
621 }
622
623 - (void)setupMenuItems
624 {
625     NSEnumerator *itemEnum;
626     id            anItem;
627     ITDebugLog(@"Setting up table view arrays.");
628     // Set the list of items you can have.
629     availableItems = [[NSMutableArray alloc] initWithObjects:
630         @"separator",
631         @"trackInfo",
632         @"upcomingSongs",
633         @"playlists",
634         @"eqPresets",
635         @"songRating",
636         @"playPause",
637         @"nextTrack",
638         @"prevTrack",
639         @"fastForward",
640         @"rewind",
641         @"showPlayer",
642         @"quit",
643         nil];
644     
645     // Get our preferred menu
646     myItems = [[df arrayForKey:@"menu"] mutableCopy];
647     
648     // Delete items in the availableItems array that are already part of the menu
649     itemEnum = [myItems objectEnumerator];
650     while ( (anItem = [itemEnum nextObject]) ) {
651         if (![anItem isEqualToString:@"separator"]) {
652             [availableItems removeObject:anItem];
653         }
654     }
655     
656     // Items that show should a submenu image
657     submenuItems = [[NSArray alloc] initWithObjects:
658         @"upcomingSongs",
659         @"playlists",
660         @"eqPresets",
661         @"songRating",
662         nil];
663 }
664
665 - (void)setupUI
666 {
667     NSMutableDictionary *loginwindow;
668     NSMutableArray *loginarray;
669     NSEnumerator *loginEnum, *keyArrayEnum;
670     NSString *serverName;
671     id anItem;
672     
673     ITDebugLog(@"Setting up preferences UI.");
674     // Fill in the number of songs in advance to show field
675     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
676     
677     // Fill hot key array
678     keyArrayEnum = [hotKeysArray objectEnumerator];
679     
680     while ( (anItem = [keyArrayEnum nextObject]) ) {
681         if ([df objectForKey:anItem]) {
682             ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
683             [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
684         } else {
685             [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
686         }
687     }
688     
689     ITDebugLog(@"Setting up track info checkboxes.");
690     // Check current track info buttons
691     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
692     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
693     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
694     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
695     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
696     [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
697     [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
698     
699     // Set the launch at login checkbox state
700     ITDebugLog(@"Setting launch at login state.");
701     [df synchronize];
702     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
703     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
704     
705     loginEnum = [loginarray objectEnumerator];
706     while ( (anItem = [loginEnum nextObject]) ) {
707         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
708             [launchAtLoginCheckbox setState:NSOnState];
709         }
710     }
711     
712     // Set the launch player checkbox state
713     ITDebugLog(@"Setting launch player with MenuTunes state.");
714     [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
715     
716     // Setup the positioning controls
717     
718     // Setup effects controls
719     [appearanceEffectPopup selectItem:[appearanceEffectPopup itemAtIndex:[appearanceEffectPopup indexOfItemWithTag:[df integerForKey:@"statusWindowAppearanceEffect"]]]];
720     [vanishEffectPopup     selectItem:[vanishEffectPopup     itemAtIndex:[vanishEffectPopup     indexOfItemWithTag:[df integerForKey:@"statusWindowVanishEffect"]]]];
721     [appearanceSpeedSlider setFloatValue:-([df floatForKey:@"statusWindowAppearanceSpeed"])];
722     [vanishSpeedSlider     setFloatValue:-([df floatForKey:@"statusWindowVanishSpeed"])];
723     [vanishDelaySlider     setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
724     [showOnChangeCheckbox  setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
725     
726     // Setup the sharing controls
727     if ([df boolForKey:@"enableSharing"]) {
728         [shareMenuTunesCheckbox setState:NSOnState];
729         [useSharedMenuTunesCheckbox setEnabled:NO];
730         [selectSharedPlayerButton setEnabled:NO];
731         [passwordTextField setEnabled:YES];
732         [usePasswordCheckbox setEnabled:YES];
733         [nameTextField setEnabled:YES];
734     } else if ([df boolForKey:@"useSharedPlayer"]) {
735         [useSharedMenuTunesCheckbox setState:NSOnState];
736         [shareMenuTunesCheckbox setEnabled:NO];
737         [selectSharedPlayerButton setEnabled:YES];
738     }
739     
740     [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
741     
742     serverName = [df stringForKey:@"sharedPlayerName"];
743     if (!serverName || [serverName length] == 0) {
744         serverName = @"MenuTunes Shared Player";
745     }
746     [nameTextField setStringValue:serverName];
747     
748     [selectPlayerBox setContentView:zeroConfView];
749     [usePasswordCheckbox setState:([df boolForKey:@"enableSharingPassword"] ? NSOnState : NSOffState)];
750     if ([df dataForKey:@"sharedPlayerPassword"]) {
751         [passwordTextField setStringValue:@"password"];
752     }
753     if ([df stringForKey:@"sharedPlayerHost"]) {
754         [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
755     }
756     
757     if ([[NetworkController sharedController] isConnectedToServer]) {
758         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
759         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
760     } else {
761         [selectedPlayerTextField setStringValue:@"No shared player selected."];
762         [locationTextField setStringValue:@"-"];
763     }
764 }
765
766 - (IBAction)changeMenus:(id)sender
767 {
768     ITDebugLog(@"Synchronizing menus");
769     [df setObject:myItems forKey:@"menu"];
770     [df synchronize];
771 }
772
773 - (void)setLaunchesAtLogin:(BOOL)flag
774 {
775     NSMutableDictionary *loginwindow;
776     NSMutableArray *loginarray;
777     ITDebugLog(@"Setting launches at login: %i", flag);
778     [df synchronize];
779     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
780     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
781     
782     if (flag) {
783         NSDictionary *itemDict = [NSDictionary dictionaryWithObjectsAndKeys:
784         [[NSBundle mainBundle] bundlePath], @"Path",
785         [NSNumber numberWithInt:0], @"Hide", nil];
786         [loginarray addObject:itemDict];
787     } else {
788         int i;
789         for (i = 0; i < [loginarray count]; i++) {
790             NSDictionary *tempDict = [loginarray objectAtIndex:i];
791             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
792                 [loginarray removeObjectAtIndex:i];
793                 break;
794             }
795         }
796     }
797     [df setPersistentDomain:loginwindow forName:@"loginwindow"];
798     [df synchronize];
799     [loginwindow release];
800     ITDebugLog(@"Finished setting launches at login.");
801 }
802
803
804 /*************************************************************************/
805 #pragma mark -
806 #pragma mark NSWindow DELEGATE METHODS
807 /*************************************************************************/
808
809 - (void)windowWillClose:(NSNotification *)note
810 {
811     [(MainController *)controller closePreferences]; 
812 }
813
814 /*************************************************************************/
815 #pragma mark -
816 #pragma mark NSTextField DELEGATE METHODS
817 /*************************************************************************/
818
819 - (void)controlTextDidChange:(NSNotification*)note
820 {
821     if ([note object] == hostTextField) {
822         if ([[hostTextField stringValue] length] == 0) {
823             [sharingPanelOKButton setEnabled:NO];
824         } else {
825             [sharingPanelOKButton setEnabled:YES];
826         }
827     }
828 }
829
830 /*************************************************************************/
831 #pragma mark -
832 #pragma mark NSTableView DATASOURCE METHODS
833 /*************************************************************************/
834
835 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
836 {
837     if (aTableView == menuTableView) {
838         return [myItems count];
839     } else if (aTableView == allTableView) {
840         return [availableItems count];
841     } else if (aTableView == hotKeysTableView) {
842         return [hotKeysArray count];
843     } else {
844         return [[[NetworkController sharedController] remoteServices] count];
845     }
846 }
847
848 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
849 {
850     if (aTableView == menuTableView) {
851         NSString *object = [myItems objectAtIndex:rowIndex];
852         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
853             if ([object isEqualToString:@"showPlayer"]) {
854                 NSString *string;
855                 NS_DURING
856                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
857                 NS_HANDLER
858                     [controller networkError:localException];
859                 NS_ENDHANDLER
860                 return string;
861             }
862             return NSLocalizedString(object, @"ERROR");
863         } else {
864             if ([submenuItems containsObject:object])
865             {
866                 return [NSImage imageNamed:@"submenu"];
867             } else {
868                 return nil;
869             }
870         }
871     } else if (aTableView == allTableView) {
872         NSString *object = [availableItems objectAtIndex:rowIndex];
873         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
874             if ([object isEqualToString:@"showPlayer"]) {
875                 NSString *string;
876                 NS_DURING
877                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
878                 NS_HANDLER
879                     [controller networkError:localException];
880                 NS_ENDHANDLER
881                 return string;
882             }
883             return NSLocalizedString(object, @"ERROR");
884         } else {
885             if ([submenuItems containsObject:object]) {
886                 return [NSImage imageNamed:@"submenu"];
887             } else {
888                 return nil;
889             }
890         }
891     } else if (aTableView == hotKeysTableView) {
892         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
893             return [hotKeyNamesArray objectAtIndex:rowIndex];
894         } else {
895             return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
896         }
897     } else {
898         return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
899     }
900 }
901
902 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
903 {
904     if (tableView == menuTableView) {
905         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
906         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
907         return YES;
908     }
909     
910     if (tableView == allTableView) {
911         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
912         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
913         return YES;
914     }
915     return NO;
916 }
917
918 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
919 {
920     NSPasteboard *pb;
921     int dragRow;
922     NSString *dragData, *temp;
923     
924     pb = [info draggingPasteboard];
925     
926     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
927         dragData = [pb stringForType:@"MenuTableViewPboardType"];
928         dragRow = [dragData intValue];
929         temp = [myItems objectAtIndex:dragRow];
930         
931         if (tableView == menuTableView) {
932             [myItems insertObject:temp atIndex:row];
933             if (row > dragRow) {
934                 [myItems removeObjectAtIndex:dragRow];
935             } else {
936                 [myItems removeObjectAtIndex:dragRow + 1];
937             }
938         } else if (tableView == allTableView) {
939             if (![temp isEqualToString:@"separator"]) {
940                 [availableItems addObject:temp];
941             }
942             [myItems removeObjectAtIndex:dragRow];
943         }
944     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
945         dragData = [pb stringForType:@"AllTableViewPboardType"];
946         dragRow = [dragData intValue];
947         temp = [availableItems objectAtIndex:dragRow];
948         
949         [myItems insertObject:temp atIndex:row];
950         
951         if (![temp isEqualToString:@"separator"]) {
952             [availableItems removeObjectAtIndex:dragRow];
953         }
954     }
955     
956     [menuTableView reloadData];
957     [allTableView reloadData];
958     [self changeMenus:self];
959     return YES;
960 }
961
962 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
963 {
964     if (tableView == allTableView) {
965         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
966             return NSDragOperationNone;
967         }
968         
969         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
970             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
971             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
972                 return NSDragOperationNone;
973             }
974         }
975         
976         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
977         return NSDragOperationGeneric;
978     }
979     
980     if (operation == NSTableViewDropOn || row == -1)
981     {
982         return NSDragOperationNone;
983     }
984     return NSDragOperationGeneric;
985 }
986
987
988 /*************************************************************************/
989 #pragma mark -
990 #pragma mark DEALLOCATION METHODS
991 /*************************************************************************/
992
993 - (void)dealloc
994 {
995     [hotKeysArray release];
996     [hotKeysDictionary release];
997     [menuTableView setDataSource:nil];
998     [allTableView setDataSource:nil];
999     [controller release];
1000     [availableItems release];
1001     [submenuItems release];
1002     [myItems release];
1003     [df release];
1004 }
1005
1006 @end