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