Handshake is attempted at launch and when the username is changed. Lowered timeout...
[MenuTunes.git] / PreferencesController.m
1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "MenuController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import "StatusWindow.h"
7 #import "StatusWindowController.h"
8 #import "CustomMenuTableView.h"
9 #import "AudioscrobblerController.h"
10
11 #import <Security/Security.h>
12
13 #import <netinet/in.h>
14 #import <arpa/inet.h>
15 #import <openssl/sha.h>
16 #import <sys/types.h>
17 #import <sys/stat.h>
18
19 #import <ITKit/ITLoginItem.h>
20
21 #import <ITKit/ITHotKeyCenter.h>
22 #import <ITKit/ITKeyCombo.h>
23 #import <ITKit/ITKeyComboPanel.h>
24 #import <ITKit/ITWindowPositioning.h>
25 #import <ITKit/ITKeyBroadcaster.h>
26
27 #import <ITKit/ITTSWBackgroundView.h>
28 #import <ITKit/ITWindowEffect.h>
29 #import <ITKit/ITCutWindowEffect.h>
30 #import <ITKit/ITDissolveWindowEffect.h>
31 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
32 #import <ITKit/ITSlideVerticallyWindowEffect.h>
33 #import <ITKit/ITPivotWindowEffect.h>
34
35
36 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
37 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE "MenuTunes: Audioscrobbler"
38 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
39
40 /*************************************************************************/
41 #pragma mark -
42 #pragma mark PRIVATE INTERFACE
43 /*************************************************************************/
44
45 @interface PreferencesController (Private)
46 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
47 + (BOOL)keychainItemExistsForUser:(NSString *)user;
48 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
49 + (BOOL)deleteKeychainItemForUser:(NSString *)user;
50 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
51
52 - (void)setupWindow;
53 - (void)setupCustomizationTables;
54 - (void)setupMenuItems;
55 - (void)setupUI;
56 - (void)setupScreenPopup;
57 - (void)setStatusWindowEntryEffect:(Class)effectClass;
58 - (void)setStatusWindowExitEffect:(Class)effectClass;
59 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
60 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
61 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
62 - (IBAction)changeMenus:(id)sender;
63 @end
64
65
66 @implementation PreferencesController
67
68
69 /*************************************************************************/
70 #pragma mark -
71 #pragma mark STATIC VARIABLES
72 /*************************************************************************/
73
74 static PreferencesController *prefs = nil;
75
76 /*************************************************************************/
77 #pragma mark -
78 #pragma mark STATIC KEYCHAIN SUPPORT METHODS
79 /*************************************************************************/
80
81 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user
82 {
83         SecKeychainSearchRef search;
84         SecKeychainItemRef item;
85         OSStatus status;
86         SecKeychainAttribute attributes[3];
87         SecKeychainAttributeList list;
88
89         ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
90         attributes[0].tag = kSecAccountItemAttr;
91         attributes[0].data = (char *)[user UTF8String];
92         attributes[0].length = [user length];
93         attributes[1].tag = kSecDescriptionItemAttr;
94         attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
95         attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
96         attributes[2].tag = kSecLabelItemAttr;
97         attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
98         attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
99         list.count = 3;
100         list.attr = attributes;
101
102         status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
103
104         if (status != noErr) {
105                 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
106         }
107         
108         status = SecKeychainSearchCopyNext(search, &item);
109         
110         if (status != noErr) {
111                 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
112                 item = nil;
113         }
114         
115     CFRelease(search);
116         return item;
117 }
118
119 + (BOOL)keychainItemExistsForUser:(NSString *)user
120 {
121         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
122         BOOL exists = (item != nil);
123         if (item) {
124                 CFRelease(item);
125         }
126         return exists;
127 }
128
129 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
130 {
131         SecKeychainItemRef item;
132         OSStatus status;
133         SecKeychainAttribute attributes[3];
134         SecKeychainAttributeList list;
135
136         ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
137         attributes[0].tag = kSecAccountItemAttr;
138         attributes[0].data = (char *)[user UTF8String];
139         attributes[0].length = [user length];
140         attributes[1].tag = kSecDescriptionItemAttr;
141         attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
142         attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
143         attributes[2].tag = kSecLabelItemAttr;
144         attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
145         attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
146         list.count = 3;
147         list.attr = attributes;
148
149         status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
150         if (status != noErr) {
151                 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
152         }
153         return (status == noErr);
154 }
155
156 + (BOOL)deleteKeychainItemForUser:(NSString *)user
157 {
158         OSStatus status = errSecNotAvailable;
159         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
160         if (item != nil) {
161                 status = SecKeychainItemDelete(item);
162                 if (status != noErr) {
163                         ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
164                 }
165                 CFRelease(item);
166         }
167         return (status == noErr);
168 }
169
170 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
171 {
172         OSStatus status = errSecNotAvailable;
173         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
174         if (item != nil) {
175                 status = SecKeychainItemModifyContent(item, NULL, [password length], [password cString]);
176                 if (status != noErr) {
177                         ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
178                 }
179                 CFRelease(item);
180         }
181         return (status == noErr);
182 }
183
184 + (NSString *)getKeychainItemPasswordForUser:(NSString *)user
185 {
186         OSStatus status = errSecNotAvailable;
187         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
188         NSString *pass = nil;
189         if (item != nil) {
190                 UInt32 length;
191                 char *buffer;
192                 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
193                 if (status != noErr) {
194                         ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
195                 } else {
196                         if ([NSString respondsToSelector:@selector(stringWithCString:encoding:)]) {
197                                 pass = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
198                         } else {
199                                 pass = [NSString stringWithCString:buffer];
200                         }
201                 }
202                 if (status != noErr) {
203                         ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
204                 }
205                 CFRelease(item);
206         }
207         return pass;
208 }
209
210 /*************************************************************************/
211 #pragma mark -
212 #pragma mark INITIALIZATION METHODS
213 /*************************************************************************/
214
215 + (PreferencesController *)sharedPrefs;
216 {
217     if (! prefs) {
218         prefs = [[self alloc] init];
219     }
220     return prefs;
221 }
222
223 - (id)init
224 {
225     if ( (self = [super init]) ) {
226         ITDebugLog(@"Preferences initialized.");
227         df = [[NSUserDefaults standardUserDefaults] retain];
228         
229         effectClasses = [[ITWindowEffect effectClasses] retain];
230         
231         hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
232                                                        @"NextTrack",
233                                                        @"PrevTrack",
234                                                        @"FastForward",
235                                                        @"Rewind",
236                                                        @"ShowPlayer",
237                                                        @"TrackInfo",
238                                                        @"UpcomingSongs",
239                                                        @"IncrementVolume",
240                                                        @"DecrementVolume",
241                                                        @"IncrementRating",
242                                                        @"DecrementRating",
243                                                        @"ToggleShuffle",
244                                                        @"ToggleLoop",
245                                                                                                            @"ToggleShufflability",
246                                                        @"PopupMenu",
247                                                        @"SetRating0",
248                                                        @"SetRating1",
249                                                        @"SetRating2",
250                                                        @"SetRating3",
251                                                        @"SetRating4",
252                                                        @"SetRating5",
253                                                        nil];
254         
255         hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
256                                                        @"Next Track",
257                                                        @"Previous Track",
258                                                        @"Fast Forward",
259                                                        @"Rewind",
260                                                        @"Show Player",
261                                                        @"Track Info",
262                                                        @"Upcoming Songs",
263                                                        @"Increment Volume",
264                                                        @"Decrement Volume",
265                                                        @"Increment Rating",
266                                                        @"Decrement Rating",
267                                                        @"Toggle Shuffle",
268                                                        @"Toggle Loop",
269                                                                                                            @"Toggle Song Included In Shuffle",
270                                                        @"Pop-up status menu",
271                                                        [NSString stringWithUTF8String:"Set Rating: â˜†â˜†â˜†â˜†â˜†"],
272                                                        [NSString stringWithUTF8String:"Set Rating: â˜…☆☆☆☆"],
273                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★☆☆☆"],
274                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★★☆☆"],
275                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★★★☆"],
276                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★★★★"],
277                                                        nil];
278         hotKeysDictionary = [[NSMutableDictionary alloc] init];
279         controller = nil;
280         
281         [self setupWindow];  // Load in the nib, and perform any initial setup.
282         [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
283     }
284     return self;
285 }
286
287
288 /*************************************************************************/
289 #pragma mark -
290 #pragma mark ACCESSOR METHODS
291 /*************************************************************************/
292
293 - (id)controller
294 {
295     return controller;
296 }
297
298 - (void)setController:(id)object
299 {
300     [controller autorelease];
301     controller = [object retain];
302 }
303
304
305 /*************************************************************************/
306 #pragma mark -
307 #pragma mark INSTANCE METHODS
308 /*************************************************************************/
309
310 - (BOOL)showPasswordPanel
311 {
312     [passwordPanel setLevel:NSStatusWindowLevel];
313     [passwordPanelOKButton setTitle:@"Connect"];
314     [passwordPanelTitle setStringValue:@"Password Required"];
315     [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
316     [passwordPanel setLevel:NSStatusWindowLevel];
317     [NSApp activateIgnoringOtherApps:YES];
318     [passwordPanel center];
319     [passwordPanel orderFrontRegardless];
320     [passwordPanel makeKeyWindow];
321     if ([NSApp runModalForWindow:passwordPanel]) {
322         return YES;
323     } else {
324         return NO;
325     }
326 }
327
328 - (BOOL)showInvalidPasswordPanel
329 {
330     [passwordPanel setLevel:NSStatusWindowLevel];
331     [passwordPanelOKButton setTitle:@"Retry"];
332     [passwordPanelTitle setStringValue:@"Invalid Password"];
333     [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"The password entered for access to the MenuTunes player named %@ at %@ is invalid.  Please provide a new password.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
334     [passwordPanel setLevel:NSStatusWindowLevel];
335     [NSApp activateIgnoringOtherApps:YES];
336     [passwordPanel center];
337     [passwordPanel orderFrontRegardless];
338     [passwordPanel makeKeyWindow];
339     if ([NSApp runModalForWindow:passwordPanel]) {
340         return YES;
341     } else {
342         return NO;
343     }
344 }
345
346 - (IBAction)showPrefsWindow:(id)sender
347 {
348     ITDebugLog(@"Showing preferences window.");
349     if (!myItems) {  // If menu array does not exist yet, then the window hasn't been setup.
350         ITDebugLog(@"Window doesn't exist, initial setup.");
351         [self setupCustomizationTables];  // Setup the DnD manu config tables.
352         [self setupMenuItems];  // Setup the arrays of menu items
353         [self setupUI]; // Sets up additional UI
354         [window setDelegate:self];
355         [menuTableView reloadData];
356         [hotKeysTableView reloadData];
357         [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
358         
359         //Change the launch player checkbox to the proper name
360         NS_DURING
361             [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
362         NS_HANDLER
363             [controller networkError:localException];
364         NS_ENDHANDLER
365     }
366
367     [self resetRemotePlayerTextFields];
368     [launchAtLoginCheckbox becomeFirstResponder];
369     [NSApp activateIgnoringOtherApps:YES];
370         if (![window isVisible]) {
371                 [window center];
372         }
373     [window orderFrontRegardless];
374     [window makeKeyWindow];
375 }
376
377 - (IBAction)changeGeneralSetting:(id)sender
378 {
379     ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
380     if ( [sender tag] == 1010) {
381         ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
382     } else if ( [sender tag] == 1020) {
383         [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
384     } else if ( [sender tag] == 1030) {
385         [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
386         if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
387             [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
388         }
389     } else if ( [sender tag] == 1040) {
390         // This will not be executed.  Song info always shows the title of the song.
391         // [df setBool:SENDER_STATE forKey:@"showName"];
392     } else if ( [sender tag] == 1050) {
393         [df setBool:SENDER_STATE forKey:@"showArtist"];
394     } else if ( [sender tag] == 1055) {
395         [df setBool:SENDER_STATE forKey:@"showComposer"];
396     } else if ( [sender tag] == 1060) {
397         [df setBool:SENDER_STATE forKey:@"showAlbum"];
398     } else if ( [sender tag] == 1070) {
399         [df setBool:SENDER_STATE forKey:@"showTime"];
400     } else if ( [sender tag] == 1080) {
401         [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
402     } else if ( [sender tag] == 1085) {
403         [df setBool:SENDER_STATE forKey:@"showPlayCount"];
404     } else if ( [sender tag] == 1090) {
405         [df setBool:SENDER_STATE forKey:@"showTrackRating"];
406     } else if ( [sender tag] == 1100) {
407         [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
408     } else if ( [sender tag] == 1110) {
409         [df setBool:SENDER_STATE forKey:@"runScripts"];
410         if (SENDER_STATE) {
411             [runScriptsCheckbox setState:NSOnState];
412             [showScriptsButton setEnabled:YES];
413         } else {
414             [showScriptsButton setEnabled:NO];
415         }
416     } else if ( [sender tag] == 1120) {
417         mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
418         [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
419     } else if ( [sender tag] == 6010) {
420                 //Toggle the other Audioscrobbler options
421                 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
422                 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
423                 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
424                 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
425         } else if ( [sender tag ] == 6015) {
426                 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
427                 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
428                 if ([newAccount length] == 0) {
429                         [PreferencesController deleteKeychainItemForUser:currentAccount];
430                 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
431                         [df setObject:newAccount forKey:@"audioscrobblerUser"];
432                         if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
433                                 //Delete the current keychain item if there is one
434                                 [PreferencesController deleteKeychainItemForUser:currentAccount];
435                         }
436                         [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
437                         [[AudioscrobblerController sharedController] attemptHandshake:YES];
438                 }
439         } else if ( [sender tag ] == 6030) {
440                 //Here we set the password for an existing keychain item or we create a new keychain item.
441                 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
442                         NSString *account = [df stringForKey:@"audioscrobblerUser"];
443                         if ([PreferencesController keychainItemExistsForUser:account]) {
444                                 //Update the current keychain item
445                                 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
446                         } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
447                                 //Create a new keychain item
448                                 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
449                         }
450                 }
451         } else if ( [sender tag] == 6045) {
452                 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
453         }
454     [df synchronize];
455 }
456
457 - (IBAction)changeSharingSetting:(id)sender
458 {
459     ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
460     if ( [sender tag] == 5010 ) {
461         BOOL state = SENDER_STATE;
462         [df setBool:state forKey:@"enableSharing"];
463         //Disable/enable the use of shared player options
464         [useSharedMenuTunesCheckbox setEnabled:!state];
465         [passwordTextField setEnabled:state];
466         [nameTextField setEnabled:state];
467         [selectSharedPlayerButton setEnabled:NO];
468         [controller setServerStatus:state]; //Set server status
469     } else if ( [sender tag] == 5015 ) {
470         [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
471         [[NetworkController sharedController] resetServerName];
472     } else if ( [sender tag] == 5030 ) {
473         //Set the server password
474         const char *instring = [[sender stringValue] UTF8String];
475         const char *password = "p4s5w0rdMT1.2";
476         char *result;
477         NSData *hashedPass, *passwordStringHash;
478         if ([[sender stringValue] length] == 0) {
479             [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
480             return;
481         }
482         result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
483         hashedPass = [NSData dataWithBytes:result length:strlen(result)];
484         result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
485         passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
486         if (![hashedPass isEqualToData:passwordStringHash]) {
487             [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
488             [sender setStringValue:@"p4s5w0rdMT1.2"];
489         }
490     } else if ( [sender tag] == 5040 ) {
491         BOOL state = SENDER_STATE;
492         [df setBool:state forKey:@"useSharedPlayer"];
493         //Disable/enable the use of sharing options
494         [shareMenuTunesCheckbox setEnabled:!state];
495         [passwordTextField setEnabled:NO];
496         [nameTextField setEnabled:NO];
497         [selectSharedPlayerButton setEnabled:state];
498         
499         if (state && ([controller connectToServer] == 1)) {
500             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
501             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
502         } else {
503             [selectedPlayerTextField setStringValue:@"No shared player selected."];
504             [locationTextField setStringValue:@"-"];
505             if ([[NetworkController sharedController] isConnectedToServer]) {
506                 [controller disconnectFromServer];
507             }
508             
509         }
510     } else if ( [sender tag] == 5050 ) {
511         //If no player is selected in the table view, turn off OK button.
512         if ([sender clickedRow] == -1 ) {
513             [sharingPanelOKButton setEnabled:NO];
514         } else {
515             [sharingPanelOKButton setEnabled:YES];
516         }
517     } else if ( [sender tag] == 5051 ) {
518         [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
519     } else if ( [sender tag] == 5060 ) {
520         //Set OK button state
521         if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
522             ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
523             [sharingPanelOKButton setEnabled:NO];
524         } else {
525             [sharingPanelOKButton setEnabled:YES];
526         }
527         //Show selection sheet
528         [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
529     } else if ( [sender tag] == 5100 ) {
530         //Change view
531         if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
532             NSRect frame = [selectPlayerSheet frame];
533             frame.origin.y -= 58;
534             frame.size.height = 273;
535             if ([sharingTableView selectedRow] == -1) {
536                 [sharingPanelOKButton setEnabled:NO];
537             }
538             [selectPlayerBox setContentView:zeroConfView];
539             [selectPlayerSheet setFrame:frame display:YES animate:YES];
540         } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
541             NSRect frame = [selectPlayerSheet frame];
542             frame.origin.y += 58;
543             frame.size.height = 215;
544             if ([[hostTextField stringValue] length] == 0) {
545                 [sharingPanelOKButton setEnabled:NO];
546             } else {
547                 [sharingPanelOKButton setEnabled:YES];
548             }
549             [selectPlayerBox setContentView:manualView];
550             [selectPlayerSheet setFrame:frame display:YES animate:YES];
551             [hostTextField selectText:nil];
552         }
553     } else if ( [sender tag] == 5150 ) {
554         const char *instring = [[sender stringValue] UTF8String];
555         char *result;
556         result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
557         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
558     } else if ( [sender tag] == 5110 ) {
559         //Cancel
560         [NSApp endSheet:selectPlayerSheet];
561         [selectPlayerSheet orderOut:nil];
562         if ([selectPlayerBox contentView] == manualView) {
563             [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
564         } else {
565         }
566     } else if ( [sender tag] == 5120 ) {
567         //OK, try to connect
568         [NSApp endSheet:selectPlayerSheet];
569         [selectPlayerSheet orderOut:nil];
570         
571         [self changeSharingSetting:clientPasswordTextField];
572         
573         if ([selectPlayerBox contentView] == manualView) {
574             [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
575         } else {
576             if ([sharingTableView selectedRow] > -1) {
577                 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
578             }
579         }
580         
581         if ([controller connectToServer] == 1) {
582             [useSharedMenuTunesCheckbox setState:NSOnState];
583             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
584             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
585         } else {
586             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);
587         }
588     } else if ( [sender tag] == 6010 ) {
589         //Cancel password entry
590         [passwordPanel orderOut:nil];
591         [NSApp stopModalWithCode:0];
592     } else if ( [sender tag] == 6020 ) {
593         //OK password entry, retry connect
594         const char *instring = [[passwordPanelTextField stringValue] UTF8String];
595         char *result;
596         result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
597         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
598         [passwordPanel orderOut:nil];
599         [NSApp stopModalWithCode:1];
600     }
601     [df synchronize];
602 }
603
604 - (IBAction)changeStatusWindowSetting:(id)sender
605 {
606     StatusWindow *sw = [StatusWindow sharedWindow];
607     ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
608     
609     if ( [sender tag] == 2010) {
610     
611         BOOL entryEffectValid = YES;
612         BOOL exitEffectValid  = YES;
613                 
614         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
615         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
616         [sw setVerticalPosition:[sender selectedRow]];
617         [sw setHorizontalPosition:[sender selectedColumn]];
618         
619         // Enable/disable the items in the popups.
620         [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
621                                      horizontalPosition:[sw horizontalPosition]];
622
623         // Make sure the effects support the new position.  
624         entryEffectValid = ( [self effect:[[sw entryEffect] class] 
625                  supportsVerticalPosition:[sw verticalPosition]
626                    withHorizontalPosition:[sw horizontalPosition]] );
627         exitEffectValid  = ( [self effect:[[sw exitEffect] class] 
628                  supportsVerticalPosition:[sw verticalPosition]
629                    withHorizontalPosition:[sw horizontalPosition]] );
630         
631         if ( ! entryEffectValid ) {
632             [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
633             [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
634         } else {
635             [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
636         }
637         
638         if ( ! exitEffectValid ) {
639             [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
640             [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
641         } else {
642             [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
643         }
644         
645         [(MainController *)controller showCurrentTrackInfo];
646         
647     } else if ( [sender tag] == 2020) {
648     
649         // Update screen selection.
650                 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
651                 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
652         [(MainController *)controller showCurrentTrackInfo];
653                 
654     } else if ( [sender tag] == 2030) {
655     
656         [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
657         [(MainController *)controller showCurrentTrackInfo];
658         
659     } else if ( [sender tag] == 2040) {
660     
661         [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
662         [(MainController *)controller showCurrentTrackInfo];
663         
664     } else if ( [sender tag] == 2050) {
665         float newTime = ( -([sender floatValue]) );
666         [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
667         [[sw entryEffect] setEffectTime:newTime];
668     } else if ( [sender tag] == 2060) {
669         float newTime = ( -([sender floatValue]) );
670         [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
671         [[sw exitEffect] setEffectTime:newTime];
672     } else if ( [sender tag] == 2070) {
673         [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
674         [sw setExitDelay:[sender floatValue]];
675     } else if ( [sender tag] == 2080) {
676         [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
677     } else if ( [sender tag] == 2090) {
678         
679         int setting = [sender indexOfSelectedItem];
680         
681         if ( setting == 0 ) {
682             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
683             [backgroundColorWell  setEnabled:NO];
684             [backgroundColorPopup setEnabled:NO];
685         } else if ( setting == 1 ) {
686             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
687             [backgroundColorWell  setEnabled:NO];
688             [backgroundColorPopup setEnabled:NO];
689         } else if ( setting == 2 ) {
690             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
691             [backgroundColorWell  setEnabled:YES];
692             [backgroundColorPopup setEnabled:YES];
693         }
694
695         [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
696         [(MainController *)controller showCurrentTrackInfo];
697         
698     } else if ( [sender tag] == 2091) {
699         [self setCustomColor:[sender color] updateWell:NO];
700         [(MainController *)controller showCurrentTrackInfo];
701     } else if ( [sender tag] == 2092) {
702         
703         int selectedItem = [sender indexOfSelectedItem];
704         
705         if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title.  Its first selectable item is 1.
706             [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
707         } else if ( selectedItem == 2 ) {
708             [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
709         } else if ( selectedItem == 3 ) {
710             [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
711         } else if ( selectedItem == 4 ) {
712             [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
713         } else if ( selectedItem == 5 ) {
714             [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
715         } else {
716             [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
717         }
718         [(MainController *)controller showCurrentTrackInfo];
719
720     } else if ( [sender tag] == 2095) {
721         [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
722         [(MainController *)controller showCurrentTrackInfo];
723     }
724     
725     [df synchronize];
726 }
727
728 - (void)registerDefaults
729 {
730     ITDebugLog(@"Registering defaults.");
731     [df setObject:[NSArray arrayWithObjects:
732         @"trackInfo",
733         @"separator",
734         @"playPause",
735         @"prevTrack",
736         @"nextTrack",
737         @"separator",
738         @"playlists",
739         @"upcomingSongs",
740         @"separator",
741         @"preferences",
742                 @"about",
743         @"quit",
744         nil] forKey:@"menu"];
745
746     [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
747     [df setInteger:5 forKey:@"SongsInAdvance"];
748 //  [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
749     [df setBool:YES forKey:@"showArtist"];
750     [df setBool:YES forKey:@"showAlbumArtwork"];
751     [df setBool:NO forKey:@"showAlbum"];
752     [df setBool:NO forKey:@"showComposer"];
753     [df setBool:NO forKey:@"showTime"];
754     [df setBool:NO forKey:@"showToolTip"];
755
756     [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
757     [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
758     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
759     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
760     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
761     [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
762     [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
763         [df setInteger:0 forKey:@"statusWindowScreenIndex"];
764     [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
765     [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
766     [df setBool:YES forKey:@"showSongInfoOnChange"];
767     
768     [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
769     
770     [df synchronize];
771     
772     if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
773         [[StatusWindowController sharedController] showSetupQueryWindow];
774     }
775 }
776
777 - (void)autoLaunchOK
778 {
779     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
780     [[StatusWindow sharedWindow] vanish:self];
781     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
782     
783     ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
784 }
785
786 - (void)autoLaunchCancel
787 {
788     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
789     [[StatusWindow sharedWindow] vanish:self];
790     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
791 }
792
793 - (void)deletePressedInTableView:(NSTableView *)tableView
794 {
795     if (tableView == menuTableView) {
796         int selRow = [tableView selectedRow];
797         ITDebugLog(@"Delete pressed in menu table view.");
798         if (selRow != - 1) {
799             NSString *object = [myItems objectAtIndex:selRow];
800             
801             if ([object isEqualToString:@"preferences"]) {
802                 NSBeep();
803                 return;
804             }
805             
806             if (![object isEqualToString:@"separator"])
807                 [availableItems addObject:object];
808             ITDebugLog(@"Removing object named %@", object);
809             [myItems removeObjectAtIndex:selRow];
810             [menuTableView reloadData];
811             [allTableView reloadData];
812         }
813         [self changeMenus:self];
814     }
815 }
816
817 - (void)resetRemotePlayerTextFields
818 {
819     if ([[NetworkController sharedController] isConnectedToServer]) {
820         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
821         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
822     } else {
823         [selectedPlayerTextField setStringValue:@"No shared player selected."];
824         [locationTextField setStringValue:@"-"];
825     }
826 }
827
828 /*************************************************************************/
829 #pragma mark -
830 #pragma mark HOTKEY SUPPORT METHODS
831 /*************************************************************************/
832
833 - (IBAction)clearHotKey:(id)sender
834 {
835     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
836     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
837     [controller setupHotKeys];
838     [hotKeysTableView reloadData];
839 }
840
841 - (IBAction)editHotKey:(id)sender
842 {
843     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
844     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
845     ITKeyCombo *keyCombo;
846     
847     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
848     [controller clearHotKeys];
849     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
850     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
851     if ([panel runModal] == NSOKButton) {
852         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
853         NSString *nextKey;
854         keyCombo = [panel keyCombo];
855         
856         //Check for duplicate key combo
857         while ( (nextKey = [keyEnumerator nextObject]) ) {
858             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
859                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
860                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
861                                    forKey:nextKey];
862                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
863                     forKey:nextKey];
864             }
865         }
866         
867         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
868         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
869         [controller setupHotKeys];
870         [hotKeysTableView reloadData];
871         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
872     } else {
873         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
874     }
875 }
876
877 - (void)hotKeysTableViewDoubleClicked:(id)sender
878 {
879     if ([sender clickedRow] > -1) {
880         [self editHotKey:sender];
881     }
882 }
883
884 /*************************************************************************/
885 #pragma mark -
886 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
887 /*************************************************************************/
888
889 - (void)setupWindow
890 {
891     ITDebugLog(@"Loading Preferences.nib.");
892     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
893         ITDebugLog(@"Failed to load Preferences.nib.");
894         NSBeep();
895         return;
896     }
897 }
898
899 - (void)setupCustomizationTables
900 {
901     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
902     ITDebugLog(@"Setting up table views.");
903     // Set the table view cells up
904     [imgCell setImageScaling:NSScaleNone];
905     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
906     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
907
908     // Register for drag and drop
909     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
910         @"MenuTableViewPboardType",
911         @"AllTableViewPboardType",
912         nil]];
913     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
914         @"MenuTableViewPboardType",
915         @"AllTableViewPboardType",
916         nil]];
917 }
918
919 - (void)setupMenuItems
920 {
921     NSEnumerator *itemEnum;
922     id            anItem;
923     ITDebugLog(@"Setting up table view arrays.");
924     // Set the list of items you can have.
925     availableItems = [[NSMutableArray alloc] initWithObjects:
926         @"separator",
927         @"trackInfo",
928         @"upcomingSongs",
929         @"playlists",
930         @"artists",
931         @"albums",
932         @"eqPresets",
933         @"songRating",
934         @"playPause",
935         @"nextTrack",
936         @"prevTrack",
937         @"fastForward",
938         @"rewind",
939         @"showPlayer",
940                 @"about",
941         @"quit",
942         nil];
943     
944     // Get our preferred menu
945     myItems = [[df arrayForKey:@"menu"] mutableCopy];
946     
947     // Delete items in the availableItems array that are already part of the menu
948     itemEnum = [myItems objectEnumerator];
949     while ( (anItem = [itemEnum nextObject]) ) {
950         if (![anItem isEqualToString:@"separator"]) {
951             [availableItems removeObject:anItem];
952         }
953     }
954     
955     // Items that show should a submenu image
956     submenuItems = [[NSArray alloc] initWithObjects:
957         @"upcomingSongs",
958         @"playlists",
959         @"artists",
960         @"albums",
961         @"eqPresets",
962         @"songRating",
963         nil];
964 }
965
966 - (void)setupUI
967 {
968     NSEnumerator   *keyArrayEnum;
969     NSString       *serverName;
970     NSData         *colorData;
971     int selectedBGStyle;
972     id anItem;
973         
974     [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
975     
976     ITDebugLog(@"Setting up preferences UI.");
977     // Fill in the number of songs in advance to show field
978     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
979     
980     // Fill hot key array
981     keyArrayEnum = [hotKeysArray objectEnumerator];
982     
983     while ( (anItem = [keyArrayEnum nextObject]) ) {
984         if ([df objectForKey:anItem]) {
985             ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
986             [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
987         } else {
988             [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
989         }
990     }
991     
992         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
993         [self setupScreenPopup];
994         
995     ITDebugLog(@"Setting up track info checkboxes.");
996     // Check current track info buttons
997     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
998     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
999     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
1000     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1001     [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1002     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1003     [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1004     [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1005     [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1006     [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1007     
1008     if ([df boolForKey:@"runScripts"]) {
1009         [runScriptsCheckbox setState:NSOnState];
1010         [showScriptsButton setEnabled:YES];
1011     } else {
1012         [showScriptsButton setEnabled:NO];
1013     }
1014     
1015     // Set the launch at login checkbox state
1016     ITDebugLog(@"Setting launch at login state.");
1017     if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1018         [launchAtLoginCheckbox setState:NSOnState];
1019     }
1020     
1021     // Set the launch player checkbox state
1022     ITDebugLog(@"Setting launch player with MenuTunes state.");
1023     [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1024     
1025     // Setup the positioning controls
1026     [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1027                              column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1028     
1029     // Setup effects controls
1030     // Populate the effects popups
1031     [appearanceEffectPopup setAutoenablesItems:NO];
1032     [vanishEffectPopup     setAutoenablesItems:NO];
1033     [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"] 
1034                                  horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1035     
1036     // Attempt to find the pref'd effect in the list.
1037     // If it's not there, use cut/dissolve.
1038     if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1039         [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1040     } else {
1041         [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1042     }
1043     
1044     if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1045         [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1046     } else {
1047         [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1048     }
1049     
1050     [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1051     [vanishSpeedSlider     setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1052     [vanishDelaySlider     setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1053
1054     // Setup General Controls
1055     selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1056     [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1057
1058     if ( selectedBGStyle == ITTSWBackgroundColored ) {
1059         [backgroundColorWell  setEnabled:YES];
1060         [backgroundColorPopup setEnabled:YES];
1061     } else {
1062         [backgroundColorWell  setEnabled:NO];
1063         [backgroundColorPopup setEnabled:NO];
1064     }
1065
1066     colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1067
1068     if ( colorData ) {
1069         [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1070     } else {
1071         [backgroundColorWell setColor:[NSColor blueColor]];
1072     }
1073     
1074     [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1075     
1076     [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1077
1078     // Setup the sharing controls
1079     if ([df boolForKey:@"enableSharing"]) {
1080         [shareMenuTunesCheckbox setState:NSOnState];
1081         [useSharedMenuTunesCheckbox setEnabled:NO];
1082         [selectSharedPlayerButton setEnabled:NO];
1083         [passwordTextField setEnabled:YES];
1084         [nameTextField setEnabled:YES];
1085     } else if ([df boolForKey:@"useSharedPlayer"]) {
1086         [useSharedMenuTunesCheckbox setState:NSOnState];
1087         [shareMenuTunesCheckbox setEnabled:NO];
1088         [selectSharedPlayerButton setEnabled:YES];
1089     }
1090     
1091         //Setup the Audioscrobbler controls
1092         if ([df boolForKey:@"audioscrobblerEnabled"]) {
1093                 [audioscrobblerEnabledCheckbox setState:NSOnState];
1094                 [audioscrobblerUserTextField setEnabled:YES];
1095                 [audioscrobblerPasswordTextField setEnabled:YES];
1096                 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1097         } else {
1098                 [audioscrobblerUserTextField setEnabled:NO];
1099                 [audioscrobblerPasswordTextField setEnabled:NO];
1100                 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1101         }
1102         NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1103         if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1104                 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1105                 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1106                 if (password != nil) {
1107                         [audioscrobblerPasswordTextField setStringValue:password];
1108                 }
1109         }
1110         [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1111         
1112     [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1113     
1114     serverName = [df stringForKey:@"sharedPlayerName"];
1115     if (!serverName || [serverName length] == 0) {
1116         serverName = @"MenuTunes Shared Player";
1117     }
1118     [nameTextField setStringValue:serverName];
1119     
1120     [selectPlayerBox setContentView:zeroConfView];
1121     if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1122         [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1123     } else {
1124         [passwordTextField setStringValue:@""];
1125     }
1126     if ([df stringForKey:@"sharedPlayerHost"]) {
1127         [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1128     }
1129     
1130     if ([[NetworkController sharedController] isConnectedToServer]) {
1131         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1132         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1133     } else {
1134         [selectedPlayerTextField setStringValue:@"No shared player selected."];
1135         [locationTextField setStringValue:@"-"];
1136     }
1137 }
1138
1139 - (void)setupScreenPopup
1140 {
1141         ITDebugLog(@"Setting up screen popup");
1142         NSArray *screens = [NSScreen screens];
1143         if ([screens count] > 1) {
1144                 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1145                 [screenPopup setEnabled:YES];
1146                 for (i = 0; i < [screens count]; i++) {
1147                         NSScreen *screen = [screens objectAtIndex:i];
1148                         if (![screen isEqual:[NSScreen mainScreen]]) {
1149                                 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1150                         }
1151                 }
1152                 [screenPopup selectItemAtIndex:index];
1153                 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1154         } else {
1155                 while ([screenPopup numberOfItems] > 1) {
1156                         [screenPopup removeItemAtIndex:1];
1157                 }
1158                 [screenPopup setEnabled:NO];
1159                 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1160         }
1161 }
1162
1163 - (void)setStatusWindowEntryEffect:(Class)effectClass
1164 {
1165     StatusWindow *sw = [StatusWindow sharedWindow];
1166     
1167     float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1168     [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1169     
1170     [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1171     [[sw entryEffect] setEffectTime:time];
1172 }
1173
1174 - (void)setStatusWindowExitEffect:(Class)effectClass
1175 {
1176     StatusWindow *sw = [StatusWindow sharedWindow];
1177     
1178     float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1179     [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1180     
1181     [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1182     [[sw exitEffect] setEffectTime:time];
1183 }
1184
1185 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1186 {
1187     [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1188     [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1189     
1190     if ( update ) {
1191         [backgroundColorWell setColor:color];
1192     }
1193 }
1194
1195 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1196 {
1197     NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1198     id anItem;
1199     
1200     [appearanceEffectPopup removeAllItems];
1201     [vanishEffectPopup     removeAllItems];
1202     
1203     while ( (anItem = [effectEnum nextObject]) ) {
1204         [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1205         [vanishEffectPopup     addItemWithTitle:[anItem effectName]];
1206         
1207         [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1208         [[vanishEffectPopup     lastItem] setRepresentedObject:anItem];
1209         
1210         if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1211             [[appearanceEffectPopup lastItem] setEnabled:YES];
1212             [[vanishEffectPopup     lastItem] setEnabled:YES];
1213         } else {
1214             [[appearanceEffectPopup lastItem] setEnabled:NO];
1215             [[vanishEffectPopup     lastItem] setEnabled:NO];
1216         }
1217     }
1218     
1219 }
1220
1221 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1222 {
1223     BOOL valid = NO;
1224     
1225     if ( vPos == ITWindowPositionTop ) {
1226         if ( hPos == ITWindowPositionLeft ) {
1227             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1228         } else if ( hPos == ITWindowPositionCenter ) {
1229             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1230         } else if ( hPos == ITWindowPositionRight ) {
1231             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1232         }
1233     } else if ( vPos == ITWindowPositionMiddle ) {
1234         if ( hPos == ITWindowPositionLeft ) {
1235             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1236         } else if ( hPos == ITWindowPositionCenter ) {
1237             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1238         } else if ( hPos == ITWindowPositionRight ) {
1239             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1240         }
1241     } else if ( vPos == ITWindowPositionBottom ) {
1242         if ( hPos == ITWindowPositionLeft ) {
1243             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1244         } else if ( hPos == ITWindowPositionCenter ) {
1245             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1246         } else if ( hPos == ITWindowPositionRight ) {
1247             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1248         }
1249     }
1250     
1251     return valid;
1252 }
1253
1254 - (IBAction)changeMenus:(id)sender
1255 {
1256     ITDebugLog(@"Synchronizing menus");
1257     [df setObject:myItems forKey:@"menu"];
1258     [df synchronize];
1259     
1260     [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1261     
1262     //If we're connected over a network, refresh the menu immediately
1263     if ([[NetworkController sharedController] isConnectedToServer]) {
1264         [controller timerUpdate];
1265     }
1266 }
1267
1268
1269 /*************************************************************************/
1270 #pragma mark -
1271 #pragma mark NSWindow DELEGATE METHODS
1272 /*************************************************************************/
1273
1274 - (void)windowWillClose:(NSNotification *)note
1275 {
1276     [(MainController *)controller closePreferences]; 
1277 }
1278
1279 /*************************************************************************/
1280 #pragma mark -
1281 #pragma mark NSTextField DELEGATE METHODS
1282 /*************************************************************************/
1283
1284 - (void)controlTextDidChange:(NSNotification*)note
1285 {
1286     if ([note object] == hostTextField) {
1287         if ([[hostTextField stringValue] length] == 0) {
1288             [sharingPanelOKButton setEnabled:NO];
1289         } else {
1290             [sharingPanelOKButton setEnabled:YES];
1291         }
1292     }
1293 }
1294
1295 /*************************************************************************/
1296 #pragma mark -
1297 #pragma mark NSTableView DATASOURCE METHODS
1298 /*************************************************************************/
1299
1300 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1301 {
1302     if (aTableView == menuTableView) {
1303         return [myItems count];
1304     } else if (aTableView == allTableView) {
1305         return [availableItems count];
1306     } else if (aTableView == hotKeysTableView) {
1307         return [hotKeysArray count];
1308     } else {
1309         return [[[NetworkController sharedController] remoteServices] count];
1310     }
1311 }
1312
1313 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1314 {
1315     if (aTableView == menuTableView) {
1316         NSString *object = [myItems objectAtIndex:rowIndex];
1317         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1318             if ([object isEqualToString:@"showPlayer"]) {
1319                 NSString *string = nil;
1320                 NS_DURING
1321                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1322                 NS_HANDLER
1323                     [controller networkError:localException];
1324                 NS_ENDHANDLER
1325                 return string;
1326             }
1327             return NSLocalizedString(object, @"ERROR");
1328         } else {
1329             if ([submenuItems containsObject:object])
1330             {
1331                 return [NSImage imageNamed:@"submenu"];
1332             } else {
1333                 return nil;
1334             }
1335         }
1336     } else if (aTableView == allTableView) {
1337         NSString *object = [availableItems objectAtIndex:rowIndex];
1338         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1339             if ([object isEqualToString:@"showPlayer"]) {
1340                 NSString *string = nil;
1341                 NS_DURING
1342                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1343                 NS_HANDLER
1344                     [controller networkError:localException];
1345                 NS_ENDHANDLER
1346                 return string;
1347             }
1348             return NSLocalizedString(object, @"ERROR");
1349         } else {
1350             if ([submenuItems containsObject:object]) {
1351                 return [NSImage imageNamed:@"submenu"];
1352             } else {
1353                 return nil;
1354             }
1355         }
1356     } else if (aTableView == hotKeysTableView) {
1357         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1358             return [hotKeyNamesArray objectAtIndex:rowIndex];
1359         } else {
1360             return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1361         }
1362     } else {
1363         return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1364     }
1365 }
1366
1367 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1368 {
1369     if (tableView == menuTableView) {
1370         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1371         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1372         return YES;
1373     }
1374     
1375     if (tableView == allTableView) {
1376         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1377         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1378         return YES;
1379     }
1380     return NO;
1381 }
1382
1383 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1384 {
1385     NSPasteboard *pb;
1386     int dragRow;
1387     NSString *dragData, *temp;
1388     
1389     pb = [info draggingPasteboard];
1390     
1391     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1392         dragData = [pb stringForType:@"MenuTableViewPboardType"];
1393         dragRow = [dragData intValue];
1394         temp = [myItems objectAtIndex:dragRow];
1395         
1396         if (tableView == menuTableView) {
1397             [myItems insertObject:temp atIndex:row];
1398             if (row > dragRow) {
1399                 [myItems removeObjectAtIndex:dragRow];
1400             } else {
1401                 [myItems removeObjectAtIndex:dragRow + 1];
1402             }
1403         } else if (tableView == allTableView) {
1404             if (![temp isEqualToString:@"separator"]) {
1405                 [availableItems addObject:temp];
1406             }
1407             [myItems removeObjectAtIndex:dragRow];
1408         }
1409     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1410         dragData = [pb stringForType:@"AllTableViewPboardType"];
1411         dragRow = [dragData intValue];
1412         temp = [availableItems objectAtIndex:dragRow];
1413         
1414         [myItems insertObject:temp atIndex:row];
1415         
1416         if (![temp isEqualToString:@"separator"]) {
1417             [availableItems removeObjectAtIndex:dragRow];
1418         }
1419     }
1420     
1421     [menuTableView reloadData];
1422     [allTableView reloadData];
1423     [self changeMenus:self];
1424     return YES;
1425 }
1426
1427 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1428 {
1429     if (tableView == allTableView) {
1430         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1431             return NSDragOperationNone;
1432         }
1433         
1434         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1435             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1436             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1437                 return NSDragOperationNone;
1438             }
1439         }
1440         
1441         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1442         return NSDragOperationGeneric;
1443     }
1444     
1445     if (operation == NSTableViewDropOn || row == -1)
1446     {
1447         return NSDragOperationNone;
1448     }
1449     return NSDragOperationGeneric;
1450 }
1451
1452
1453 /*************************************************************************/
1454 #pragma mark -
1455 #pragma mark DEALLOCATION METHODS
1456 /*************************************************************************/
1457
1458 - (void)dealloc
1459 {
1460         [[NSNotificationCenter defaultCenter] removeObserver:self];
1461     [hotKeysArray release];
1462     [hotKeysDictionary release];
1463     [effectClasses release];
1464     [menuTableView setDataSource:nil];
1465     [allTableView setDataSource:nil];
1466     [controller release];
1467     [availableItems release];
1468     [submenuItems release];
1469     [myItems release];
1470     [df release];
1471         [super dealloc];
1472 }
1473
1474 @end