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