Adding a new proxy object for the networking. It's broken right now, but
[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         SHA1(instring, strlen(instring), &result);
205         hashedPass = [NSData dataWithBytes:&result length:strlen(&result)];
206         SHA1(password, strlen(password), &result);
207         passwordStringHash = [NSData dataWithBytes:&result length:strlen(&result)];
208         if (![hashedPass isEqualToData:passwordStringHash]) {
209             [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
210         }
211     } else if ( [sender tag] == 5040 ) {
212         BOOL state = SENDER_STATE;
213         [df setBool:state forKey:@"useSharedPlayer"];
214         //Disable/enable the use of sharing options
215         [shareMenuTunesCheckbox setEnabled:!state];
216         [usePasswordCheckbox setEnabled:NO];
217         [passwordTextField setEnabled:NO];
218         [nameTextField setEnabled:NO];
219         [selectSharedPlayerButton setEnabled:state];
220         
221         if (state) {
222             [controller connectToServer];
223         } else {
224             [controller disconnectFromServer];
225         }
226     } else if ( [sender tag] == 5050 ) {
227         //Do nothing on table view click
228     } else if ( [sender tag] == 5051 ) {
229         [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
230     } else if ( [sender tag] == 5060 ) {
231         //Show selection sheet
232         [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
233     } else if ( [sender tag] == 5100 ) {
234         //Change view
235         if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
236             NSRect frame = [selectPlayerSheet frame];
237             frame.origin.y -= 58;
238             frame.size.height = 273;
239             [selectPlayerBox setContentView:zeroConfView];
240             [selectPlayerSheet setFrame:frame display:YES animate:YES];
241         } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
242             NSRect frame = [selectPlayerSheet frame];
243             frame.origin.y += 58;
244             frame.size.height = 215;
245             //[window makeFirstResponder:hostTextField];
246             [selectPlayerBox setContentView:manualView];
247             [selectPlayerSheet setFrame:frame display:YES animate:YES];
248             [hostTextField selectText:nil];
249         }
250     } else if ( [sender tag] == 5150 ) {
251         const char *instring = [[sender stringValue] UTF8String];
252         unsigned char result;
253         SHA1(instring, strlen(instring), &result);
254         [df setObject:[NSData dataWithBytes:&result length:strlen(&result)] forKey:@"connectPassword"];
255     } else if ( [sender tag] == 5110 ) {
256         //Cancel
257         [NSApp endSheet:selectPlayerSheet];
258         [selectPlayerSheet orderOut:nil];
259         if ([selectPlayerBox contentView] == manualView) {
260             [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
261         } else {
262         }
263     } else if ( [sender tag] == 5120 ) {
264         //OK, try to connect
265         [NSApp endSheet:selectPlayerSheet];
266         [selectPlayerSheet orderOut:nil];
267         
268         if ([selectPlayerBox contentView] == manualView) {
269             [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
270         } else {
271             if ([sharingTableView selectedRow] > -1) {
272                 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
273             }
274         }
275         
276         if ([controller connectToServer]) {
277             [useSharedMenuTunesCheckbox setState:NSOnState];
278             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
279             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
280         } else {
281             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);
282         }
283     }
284     [df synchronize];
285 }
286
287 - (IBAction)changeStatusWindowSetting:(id)sender
288 {
289     StatusWindow *sw = [StatusWindow sharedWindow];
290     ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
291     if ( [sender tag] == 2010) {
292         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
293         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
294         // update the window's position here
295     } else if ( [sender tag] == 2020) {
296         // update screen selection
297     } else if ( [sender tag] == 2030) {
298         int effectTag = [[sender selectedItem] tag];
299         float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
300         [df setInteger:effectTag forKey:@"statusWindowAppearanceEffect"];
301
302         if ( effectTag == 2100 ) {
303             [sw setEntryEffect:[[[ITCutWindowEffect alloc] initWithWindow:sw] autorelease]];
304         } else if ( effectTag == 2101 ) {
305             [sw setEntryEffect:[[[ITDissolveWindowEffect alloc] initWithWindow:sw] autorelease]];
306         } else if ( effectTag == 2102 ) {
307             [sw setEntryEffect:[[[ITSlideVerticallyWindowEffect alloc] initWithWindow:sw] autorelease]];
308         } else if ( effectTag == 2103 ) {
309             [sw setEntryEffect:[[[ITSlideHorizontallyWindowEffect alloc] initWithWindow:sw] autorelease]];
310         } else if ( effectTag == 2104 ) {
311             NSLog(@"dflhgldf");
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         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
799             return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
800         } else {
801             return @"X";
802         }
803     }
804 }
805
806 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
807 {
808     if (tableView == menuTableView) {
809         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
810         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
811         return YES;
812     }
813     
814     if (tableView == allTableView) {
815         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
816         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
817         return YES;
818     }
819     return NO;
820 }
821
822 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
823 {
824     NSPasteboard *pb;
825     int dragRow;
826     NSString *dragData, *temp;
827     
828     pb = [info draggingPasteboard];
829     
830     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
831         dragData = [pb stringForType:@"MenuTableViewPboardType"];
832         dragRow = [dragData intValue];
833         temp = [myItems objectAtIndex:dragRow];
834         
835         if (tableView == menuTableView) {
836             [myItems insertObject:temp atIndex:row];
837             if (row > dragRow) {
838                 [myItems removeObjectAtIndex:dragRow];
839             } else {
840                 [myItems removeObjectAtIndex:dragRow + 1];
841             }
842         } else if (tableView == allTableView) {
843             if (![temp isEqualToString:@"separator"]) {
844                 [availableItems addObject:temp];
845             }
846             [myItems removeObjectAtIndex:dragRow];
847         }
848     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
849         dragData = [pb stringForType:@"AllTableViewPboardType"];
850         dragRow = [dragData intValue];
851         temp = [availableItems objectAtIndex:dragRow];
852         
853         [myItems insertObject:temp atIndex:row];
854         
855         if (![temp isEqualToString:@"separator"]) {
856             [availableItems removeObjectAtIndex:dragRow];
857         }
858     }
859     
860     [menuTableView reloadData];
861     [allTableView reloadData];
862     [self changeMenus:self];
863     return YES;
864 }
865
866 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
867 {
868     if (tableView == allTableView) {
869         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
870             return NSDragOperationNone;
871         }
872         
873         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
874             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
875             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
876                 return NSDragOperationNone;
877             }
878         }
879         
880         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
881         return NSDragOperationGeneric;
882     }
883     
884     if (operation == NSTableViewDropOn || row == -1)
885     {
886         return NSDragOperationNone;
887     }
888     return NSDragOperationGeneric;
889 }
890
891
892 /*************************************************************************/
893 #pragma mark -
894 #pragma mark DEALLOCATION METHODS
895 /*************************************************************************/
896
897 - (void)dealloc
898 {
899     [hotKeysArray release];
900     [hotKeysDictionary release];
901     [menuTableView setDataSource:nil];
902     [allTableView setDataSource:nil];
903     [controller release];
904     [availableItems release];
905     [submenuItems release];
906     [myItems release];
907     [df release];
908 }
909
910 @end