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