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