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