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