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