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