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