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