Now removing items from the submission queue after success.
[MenuTunes.git] / PreferencesController.m
1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "MenuController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import "StatusWindow.h"
7 #import "StatusWindowController.h"
8 #import "CustomMenuTableView.h"
9 #import "AudioscrobblerController.h"
10
11 #import <Security/Security.h>
12
13 #import <netinet/in.h>
14 #import <arpa/inet.h>
15 #import <openssl/sha.h>
16 #import <sys/types.h>
17 #import <sys/stat.h>
18
19 #import <ITKit/ITLoginItem.h>
20
21 #import <ITKit/ITHotKeyCenter.h>
22 #import <ITKit/ITKeyCombo.h>
23 #import <ITKit/ITKeyComboPanel.h>
24 #import <ITKit/ITWindowPositioning.h>
25 #import <ITKit/ITKeyBroadcaster.h>
26
27 #import <ITKit/ITTSWBackgroundView.h>
28 #import <ITKit/ITWindowEffect.h>
29 #import <ITKit/ITCutWindowEffect.h>
30 #import <ITKit/ITDissolveWindowEffect.h>
31 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
32 #import <ITKit/ITSlideVerticallyWindowEffect.h>
33 #import <ITKit/ITPivotWindowEffect.h>
34
35
36 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
37 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE "MenuTunes: Audioscrobbler"
38 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
39
40 /*************************************************************************/
41 #pragma mark -
42 #pragma mark PRIVATE INTERFACE
43 /*************************************************************************/
44
45 @interface PreferencesController (Private)
46 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
47 + (BOOL)keychainItemExistsForUser:(NSString *)user;
48 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
49 + (BOOL)deleteKeychainItemForUser:(NSString *)user;
50 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
51
52 - (void)setupWindow;
53 - (void)setupCustomizationTables;
54 - (void)setupMenuItems;
55 - (void)setupUI;
56 - (void)setupScreenPopup;
57 - (void)setStatusWindowEntryEffect:(Class)effectClass;
58 - (void)setStatusWindowExitEffect:(Class)effectClass;
59 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
60 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
61 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
62 - (IBAction)changeMenus:(id)sender;
63 @end
64
65
66 @implementation PreferencesController
67
68
69 /*************************************************************************/
70 #pragma mark -
71 #pragma mark STATIC VARIABLES
72 /*************************************************************************/
73
74 static PreferencesController *prefs = nil;
75
76 /*************************************************************************/
77 #pragma mark -
78 #pragma mark STATIC KEYCHAIN SUPPORT METHODS
79 /*************************************************************************/
80
81 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user
82 {
83         SecKeychainSearchRef search;
84         SecKeychainItemRef item;
85         OSStatus status;
86         SecKeychainAttribute attributes[3];
87         SecKeychainAttributeList list;
88
89         if ((user == nil) || ([user length] == 0)) {
90                 return nil;
91         }
92
93         ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
94         attributes[0].tag = kSecAccountItemAttr;
95         attributes[0].data = (char *)[user UTF8String];
96         attributes[0].length = [user length];
97         attributes[1].tag = kSecDescriptionItemAttr;
98         attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
99         attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
100         attributes[2].tag = kSecLabelItemAttr;
101         attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
102         attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
103         list.count = 3;
104         list.attr = attributes;
105
106         status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
107
108         if (status != noErr) {
109                 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
110         }
111         
112         status = SecKeychainSearchCopyNext(search, &item);
113         
114         if (status != noErr) {
115                 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
116                 item = nil;
117         }
118         
119     CFRelease(search);
120         return item;
121 }
122
123 + (BOOL)keychainItemExistsForUser:(NSString *)user
124 {
125         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
126         BOOL exists = (item != nil);
127         if (item) {
128                 CFRelease(item);
129         }
130         return exists;
131 }
132
133 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
134 {
135         SecKeychainItemRef item;
136         OSStatus status;
137         SecKeychainAttribute attributes[3];
138         SecKeychainAttributeList list;
139
140         ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
141         attributes[0].tag = kSecAccountItemAttr;
142         attributes[0].data = (char *)[user UTF8String];
143         attributes[0].length = [user length];
144         attributes[1].tag = kSecDescriptionItemAttr;
145         attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
146         attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
147         attributes[2].tag = kSecLabelItemAttr;
148         attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
149         attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
150         list.count = 3;
151         list.attr = attributes;
152
153         status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
154         if (status != noErr) {
155                 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
156         }
157         return (status == noErr);
158 }
159
160 + (BOOL)deleteKeychainItemForUser:(NSString *)user
161 {
162         OSStatus status = errSecNotAvailable;
163         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
164         if (item != nil) {
165                 status = SecKeychainItemDelete(item);
166                 if (status != noErr) {
167                         ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
168                 }
169                 CFRelease(item);
170         }
171         return (status == noErr);
172 }
173
174 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
175 {
176         OSStatus status = errSecNotAvailable;
177         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
178         if (item != nil) {
179                 status = SecKeychainItemModifyContent(item, NULL, [password length], [password cString]);
180                 if (status != noErr) {
181                         ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
182                 }
183                 CFRelease(item);
184         }
185         return (status == noErr);
186 }
187
188 + (NSString *)getKeychainItemPasswordForUser:(NSString *)user
189 {
190         OSStatus status = errSecNotAvailable;
191         SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
192         NSString *pass = nil;
193         if (item != nil) {
194                 UInt32 length;
195                 char *buffer;
196                 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
197                 if (status != noErr) {
198                         ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
199                 } else {
200                         if ([NSString respondsToSelector:@selector(stringWithCString:encoding:)]) {
201                                 pass = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
202                         } else {
203                                 pass = [NSString stringWithCString:buffer];
204                         }
205                 }
206                 if (status != noErr) {
207                         ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
208                 }
209                 CFRelease(item);
210         }
211         return pass;
212 }
213
214 /*************************************************************************/
215 #pragma mark -
216 #pragma mark INITIALIZATION METHODS
217 /*************************************************************************/
218
219 + (PreferencesController *)sharedPrefs;
220 {
221     if (! prefs) {
222         prefs = [[self alloc] init];
223     }
224     return prefs;
225 }
226
227 - (id)init
228 {
229     if ( (self = [super init]) ) {
230         ITDebugLog(@"Preferences initialized.");
231         df = [[NSUserDefaults standardUserDefaults] retain];
232         
233         effectClasses = [[ITWindowEffect effectClasses] retain];
234         
235         hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
236                                                        @"NextTrack",
237                                                        @"PrevTrack",
238                                                        @"FastForward",
239                                                        @"Rewind",
240                                                        @"ShowPlayer",
241                                                        @"TrackInfo",
242                                                        @"UpcomingSongs",
243                                                        @"IncrementVolume",
244                                                        @"DecrementVolume",
245                                                        @"IncrementRating",
246                                                        @"DecrementRating",
247                                                        @"ToggleShuffle",
248                                                        @"ToggleLoop",
249                                                                                                            @"ToggleShufflability",
250                                                        @"PopupMenu",
251                                                        @"SetRating0",
252                                                        @"SetRating1",
253                                                        @"SetRating2",
254                                                        @"SetRating3",
255                                                        @"SetRating4",
256                                                        @"SetRating5",
257                                                        nil];
258         
259         hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
260                                                        @"Next Track",
261                                                        @"Previous Track",
262                                                        @"Fast Forward",
263                                                        @"Rewind",
264                                                        @"Show Player",
265                                                        @"Track Info",
266                                                        @"Upcoming Songs",
267                                                        @"Increment Volume",
268                                                        @"Decrement Volume",
269                                                        @"Increment Rating",
270                                                        @"Decrement Rating",
271                                                        @"Toggle Shuffle",
272                                                        @"Toggle Loop",
273                                                                                                            @"Toggle Song Included In Shuffle",
274                                                        @"Pop-up status menu",
275                                                        [NSString stringWithUTF8String:"Set Rating: â˜†â˜†â˜†â˜†â˜†"],
276                                                        [NSString stringWithUTF8String:"Set Rating: â˜…☆☆☆☆"],
277                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★☆☆☆"],
278                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★★☆☆"],
279                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★★★☆"],
280                                                        [NSString stringWithUTF8String:"Set Rating: â˜…★★★★"],
281                                                        nil];
282         hotKeysDictionary = [[NSMutableDictionary alloc] init];
283         controller = nil;
284         
285         [self setupWindow];  // Load in the nib, and perform any initial setup.
286         [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
287     }
288     return self;
289 }
290
291
292 /*************************************************************************/
293 #pragma mark -
294 #pragma mark ACCESSOR METHODS
295 /*************************************************************************/
296
297 - (id)controller
298 {
299     return controller;
300 }
301
302 - (void)setController:(id)object
303 {
304     [controller autorelease];
305     controller = [object retain];
306 }
307
308
309 /*************************************************************************/
310 #pragma mark -
311 #pragma mark INSTANCE METHODS
312 /*************************************************************************/
313
314 - (BOOL)showPasswordPanel
315 {
316     [passwordPanel setLevel:NSStatusWindowLevel];
317     [passwordPanelOKButton setTitle:@"Connect"];
318     [passwordPanelTitle setStringValue:@"Password Required"];
319     [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
320     [passwordPanel setLevel:NSStatusWindowLevel];
321     [NSApp activateIgnoringOtherApps:YES];
322     [passwordPanel center];
323     [passwordPanel orderFrontRegardless];
324     [passwordPanel makeKeyWindow];
325     if ([NSApp runModalForWindow:passwordPanel]) {
326         return YES;
327     } else {
328         return NO;
329     }
330 }
331
332 - (BOOL)showInvalidPasswordPanel
333 {
334     [passwordPanel setLevel:NSStatusWindowLevel];
335     [passwordPanelOKButton setTitle:@"Retry"];
336     [passwordPanelTitle setStringValue:@"Invalid Password"];
337     [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]]];
338     [passwordPanel setLevel:NSStatusWindowLevel];
339     [NSApp activateIgnoringOtherApps:YES];
340     [passwordPanel center];
341     [passwordPanel orderFrontRegardless];
342     [passwordPanel makeKeyWindow];
343     if ([NSApp runModalForWindow:passwordPanel]) {
344         return YES;
345     } else {
346         return NO;
347     }
348 }
349
350 - (IBAction)showPrefsWindow:(id)sender
351 {
352     ITDebugLog(@"Showing preferences window.");
353     if (!myItems) {  // If menu array does not exist yet, then the window hasn't been setup.
354         ITDebugLog(@"Window doesn't exist, initial setup.");
355         [self setupCustomizationTables];  // Setup the DnD manu config tables.
356         [self setupMenuItems];  // Setup the arrays of menu items
357         [self setupUI]; // Sets up additional UI
358         [window setDelegate:self];
359         [menuTableView reloadData];
360         [hotKeysTableView reloadData];
361         [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
362         
363         //Change the launch player checkbox to the proper name
364         NS_DURING
365             [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
366         NS_HANDLER
367             [controller networkError:localException];
368         NS_ENDHANDLER
369     }
370
371     [self resetRemotePlayerTextFields];
372     [launchAtLoginCheckbox becomeFirstResponder];
373     [NSApp activateIgnoringOtherApps:YES];
374         if (![window isVisible]) {
375                 [window center];
376         }
377     [window orderFrontRegardless];
378     [window makeKeyWindow];
379 }
380
381 - (IBAction)changeGeneralSetting:(id)sender
382 {
383     ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
384     if ( [sender tag] == 1010) {
385         ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
386     } else if ( [sender tag] == 1020) {
387         [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
388     } else if ( [sender tag] == 1030) {
389         [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
390         if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
391             [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
392         }
393     } else if ( [sender tag] == 1040) {
394         // This will not be executed.  Song info always shows the title of the song.
395         // [df setBool:SENDER_STATE forKey:@"showName"];
396     } else if ( [sender tag] == 1050) {
397         [df setBool:SENDER_STATE forKey:@"showArtist"];
398     } else if ( [sender tag] == 1055) {
399         [df setBool:SENDER_STATE forKey:@"showComposer"];
400     } else if ( [sender tag] == 1060) {
401         [df setBool:SENDER_STATE forKey:@"showAlbum"];
402     } else if ( [sender tag] == 1070) {
403         [df setBool:SENDER_STATE forKey:@"showTime"];
404     } else if ( [sender tag] == 1080) {
405         [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
406     } else if ( [sender tag] == 1085) {
407         [df setBool:SENDER_STATE forKey:@"showPlayCount"];
408     } else if ( [sender tag] == 1090) {
409         [df setBool:SENDER_STATE forKey:@"showTrackRating"];
410     } else if ( [sender tag] == 1100) {
411         [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
412     } else if ( [sender tag] == 1110) {
413         [df setBool:SENDER_STATE forKey:@"runScripts"];
414         if (SENDER_STATE) {
415             [runScriptsCheckbox setState:NSOnState];
416             [showScriptsButton setEnabled:YES];
417         } else {
418             [showScriptsButton setEnabled:NO];
419         }
420     } else if ( [sender tag] == 1120) {
421         mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
422         [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
423     } else if ( [sender tag] == 6010) {
424                 //Toggle the other Audioscrobbler options
425                 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
426                 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
427                 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
428                 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
429         } else if ( [sender tag ] == 6015) {
430                 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
431                 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
432                 if ([newAccount length] == 0) {
433                         [PreferencesController deleteKeychainItemForUser:currentAccount];
434                 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
435                         [df setObject:newAccount forKey:@"audioscrobblerUser"];
436                         if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
437                                 //Delete the current keychain item if there is one
438                                 [PreferencesController deleteKeychainItemForUser:currentAccount];
439                         }
440                         [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
441                         [[AudioscrobblerController sharedController] attemptHandshake:YES];
442                 }
443         } else if ( [sender tag ] == 6030) {
444                 //Here we set the password for an existing keychain item or we create a new keychain item.
445                 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
446                         NSString *account = [df stringForKey:@"audioscrobblerUser"];
447                         if ([PreferencesController keychainItemExistsForUser:account]) {
448                                 //Update the current keychain item
449                                 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
450                         } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
451                                 //Create a new keychain item
452                                 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
453                         }
454                 }
455         } else if ( [sender tag] == 6045) {
456                 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
457         }
458     [df synchronize];
459 }
460
461 - (IBAction)changeSharingSetting:(id)sender
462 {
463     ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
464     if ( [sender tag] == 5010 ) {
465         BOOL state = SENDER_STATE;
466         [df setBool:state forKey:@"enableSharing"];
467         //Disable/enable the use of shared player options
468         [useSharedMenuTunesCheckbox setEnabled:!state];
469         [passwordTextField setEnabled:state];
470         [nameTextField setEnabled:state];
471         [selectSharedPlayerButton setEnabled:NO];
472         [controller setServerStatus:state]; //Set server status
473     } else if ( [sender tag] == 5015 ) {
474         [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
475         [[NetworkController sharedController] resetServerName];
476     } else if ( [sender tag] == 5030 ) {
477         //Set the server password
478         const char *instring = [[sender stringValue] UTF8String];
479         const char *password = "p4s5w0rdMT1.2";
480         char *result;
481         NSData *hashedPass, *passwordStringHash;
482         if ([[sender stringValue] length] == 0) {
483             [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
484             return;
485         }
486         result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
487         hashedPass = [NSData dataWithBytes:result length:strlen(result)];
488         result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
489         passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
490         if (![hashedPass isEqualToData:passwordStringHash]) {
491             [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
492             [sender setStringValue:@"p4s5w0rdMT1.2"];
493         }
494     } else if ( [sender tag] == 5040 ) {
495         BOOL state = SENDER_STATE;
496         [df setBool:state forKey:@"useSharedPlayer"];
497         //Disable/enable the use of sharing options
498         [shareMenuTunesCheckbox setEnabled:!state];
499         [passwordTextField setEnabled:NO];
500         [nameTextField setEnabled:NO];
501         [selectSharedPlayerButton setEnabled:state];
502         
503         if (state && ([controller connectToServer] == 1)) {
504             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
505             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
506         } else {
507             [selectedPlayerTextField setStringValue:@"No shared player selected."];
508             [locationTextField setStringValue:@"-"];
509             if ([[NetworkController sharedController] isConnectedToServer]) {
510                 [controller disconnectFromServer];
511             }
512             
513         }
514     } else if ( [sender tag] == 5050 ) {
515         //If no player is selected in the table view, turn off OK button.
516         if ([sender clickedRow] == -1 ) {
517             [sharingPanelOKButton setEnabled:NO];
518         } else {
519             [sharingPanelOKButton setEnabled:YES];
520         }
521     } else if ( [sender tag] == 5051 ) {
522         [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
523     } else if ( [sender tag] == 5060 ) {
524         //Set OK button state
525         if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
526             ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
527             [sharingPanelOKButton setEnabled:NO];
528         } else {
529             [sharingPanelOKButton setEnabled:YES];
530         }
531         //Show selection sheet
532         [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
533     } else if ( [sender tag] == 5100 ) {
534         //Change view
535         if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
536             NSRect frame = [selectPlayerSheet frame];
537             frame.origin.y -= 58;
538             frame.size.height = 273;
539             if ([sharingTableView selectedRow] == -1) {
540                 [sharingPanelOKButton setEnabled:NO];
541             }
542             [selectPlayerBox setContentView:zeroConfView];
543             [selectPlayerSheet setFrame:frame display:YES animate:YES];
544         } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
545             NSRect frame = [selectPlayerSheet frame];
546             frame.origin.y += 58;
547             frame.size.height = 215;
548             if ([[hostTextField stringValue] length] == 0) {
549                 [sharingPanelOKButton setEnabled:NO];
550             } else {
551                 [sharingPanelOKButton setEnabled:YES];
552             }
553             [selectPlayerBox setContentView:manualView];
554             [selectPlayerSheet setFrame:frame display:YES animate:YES];
555             [hostTextField selectText:nil];
556         }
557     } else if ( [sender tag] == 5150 ) {
558         const char *instring = [[sender stringValue] UTF8String];
559         char *result;
560         result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
561         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
562     } else if ( [sender tag] == 5110 ) {
563         //Cancel
564         [NSApp endSheet:selectPlayerSheet];
565         [selectPlayerSheet orderOut:nil];
566         if ([selectPlayerBox contentView] == manualView) {
567             [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
568         } else {
569         }
570     } else if ( [sender tag] == 5120 ) {
571         //OK, try to connect
572         [NSApp endSheet:selectPlayerSheet];
573         [selectPlayerSheet orderOut:nil];
574         
575         [self changeSharingSetting:clientPasswordTextField];
576         
577         if ([selectPlayerBox contentView] == manualView) {
578             [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
579         } else {
580             if ([sharingTableView selectedRow] > -1) {
581                 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
582             }
583         }
584         
585         if ([controller connectToServer] == 1) {
586             [useSharedMenuTunesCheckbox setState:NSOnState];
587             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
588             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
589         } else {
590             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);
591         }
592     } else if ( [sender tag] == 6010 ) {
593         //Cancel password entry
594         [passwordPanel orderOut:nil];
595         [NSApp stopModalWithCode:0];
596     } else if ( [sender tag] == 6020 ) {
597         //OK password entry, retry connect
598         const char *instring = [[passwordPanelTextField stringValue] UTF8String];
599         char *result;
600         result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
601         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
602         [passwordPanel orderOut:nil];
603         [NSApp stopModalWithCode:1];
604     }
605     [df synchronize];
606 }
607
608 - (IBAction)changeStatusWindowSetting:(id)sender
609 {
610     StatusWindow *sw = [StatusWindow sharedWindow];
611     ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
612     
613     if ( [sender tag] == 2010) {
614     
615         BOOL entryEffectValid = YES;
616         BOOL exitEffectValid  = YES;
617                 
618         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
619         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
620         [sw setVerticalPosition:[sender selectedRow]];
621         [sw setHorizontalPosition:[sender selectedColumn]];
622         
623         // Enable/disable the items in the popups.
624         [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
625                                      horizontalPosition:[sw horizontalPosition]];
626
627         // Make sure the effects support the new position.  
628         entryEffectValid = ( [self effect:[[sw entryEffect] class] 
629                  supportsVerticalPosition:[sw verticalPosition]
630                    withHorizontalPosition:[sw horizontalPosition]] );
631         exitEffectValid  = ( [self effect:[[sw exitEffect] class] 
632                  supportsVerticalPosition:[sw verticalPosition]
633                    withHorizontalPosition:[sw horizontalPosition]] );
634         
635         if ( ! entryEffectValid ) {
636             [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
637             [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
638         } else {
639             [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
640         }
641         
642         if ( ! exitEffectValid ) {
643             [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
644             [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
645         } else {
646             [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
647         }
648         
649         [(MainController *)controller showCurrentTrackInfo];
650         
651     } else if ( [sender tag] == 2020) {
652     
653         // Update screen selection.
654                 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
655                 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
656         [(MainController *)controller showCurrentTrackInfo];
657                 
658     } else if ( [sender tag] == 2030) {
659     
660         [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
661         [(MainController *)controller showCurrentTrackInfo];
662         
663     } else if ( [sender tag] == 2040) {
664     
665         [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
666         [(MainController *)controller showCurrentTrackInfo];
667         
668     } else if ( [sender tag] == 2050) {
669         float newTime = ( -([sender floatValue]) );
670         [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
671         [[sw entryEffect] setEffectTime:newTime];
672     } else if ( [sender tag] == 2060) {
673         float newTime = ( -([sender floatValue]) );
674         [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
675         [[sw exitEffect] setEffectTime:newTime];
676     } else if ( [sender tag] == 2070) {
677         [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
678         [sw setExitDelay:[sender floatValue]];
679     } else if ( [sender tag] == 2080) {
680         [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
681     } else if ( [sender tag] == 2090) {
682         
683         int setting = [sender indexOfSelectedItem];
684         
685         if ( setting == 0 ) {
686             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
687             [backgroundColorWell  setEnabled:NO];
688             [backgroundColorPopup setEnabled:NO];
689         } else if ( setting == 1 ) {
690             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
691             [backgroundColorWell  setEnabled:NO];
692             [backgroundColorPopup setEnabled:NO];
693         } else if ( setting == 2 ) {
694             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
695             [backgroundColorWell  setEnabled:YES];
696             [backgroundColorPopup setEnabled:YES];
697         }
698
699         [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
700         [(MainController *)controller showCurrentTrackInfo];
701         
702     } else if ( [sender tag] == 2091) {
703         [self setCustomColor:[sender color] updateWell:NO];
704         [(MainController *)controller showCurrentTrackInfo];
705     } else if ( [sender tag] == 2092) {
706         
707         int selectedItem = [sender indexOfSelectedItem];
708         
709         if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title.  Its first selectable item is 1.
710             [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
711         } else if ( selectedItem == 2 ) {
712             [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
713         } else if ( selectedItem == 3 ) {
714             [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
715         } else if ( selectedItem == 4 ) {
716             [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
717         } else if ( selectedItem == 5 ) {
718             [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
719         } else {
720             [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
721         }
722         [(MainController *)controller showCurrentTrackInfo];
723
724     } else if ( [sender tag] == 2095) {
725         [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
726         [(MainController *)controller showCurrentTrackInfo];
727     }
728     
729     [df synchronize];
730 }
731
732 - (void)registerDefaults
733 {
734     ITDebugLog(@"Registering defaults.");
735     [df setObject:[NSArray arrayWithObjects:
736         @"trackInfo",
737         @"separator",
738         @"playPause",
739         @"prevTrack",
740         @"nextTrack",
741         @"separator",
742         @"playlists",
743         @"upcomingSongs",
744         @"separator",
745         @"preferences",
746                 @"about",
747         @"quit",
748         nil] forKey:@"menu"];
749
750     [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
751     [df setInteger:5 forKey:@"SongsInAdvance"];
752 //  [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
753     [df setBool:YES forKey:@"showArtist"];
754     [df setBool:YES forKey:@"showAlbumArtwork"];
755     [df setBool:NO forKey:@"showAlbum"];
756     [df setBool:NO forKey:@"showComposer"];
757     [df setBool:NO forKey:@"showTime"];
758     [df setBool:NO forKey:@"showToolTip"];
759
760     [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
761     [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
762     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
763     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
764     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
765     [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
766     [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
767         [df setInteger:0 forKey:@"statusWindowScreenIndex"];
768     [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
769     [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
770     [df setBool:YES forKey:@"showSongInfoOnChange"];
771     
772     [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
773     
774     [df synchronize];
775     
776     if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
777         [[StatusWindowController sharedController] showSetupQueryWindow];
778     }
779 }
780
781 - (void)autoLaunchOK
782 {
783     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
784     [[StatusWindow sharedWindow] vanish:self];
785     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
786     
787     ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
788 }
789
790 - (void)autoLaunchCancel
791 {
792     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
793     [[StatusWindow sharedWindow] vanish:self];
794     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
795 }
796
797 - (void)deletePressedInTableView:(NSTableView *)tableView
798 {
799     if (tableView == menuTableView) {
800         int selRow = [tableView selectedRow];
801         ITDebugLog(@"Delete pressed in menu table view.");
802         if (selRow != - 1) {
803             NSString *object = [myItems objectAtIndex:selRow];
804             
805             if ([object isEqualToString:@"preferences"]) {
806                 NSBeep();
807                 return;
808             }
809             
810             if (![object isEqualToString:@"separator"])
811                 [availableItems addObject:object];
812             ITDebugLog(@"Removing object named %@", object);
813             [myItems removeObjectAtIndex:selRow];
814             [menuTableView reloadData];
815             [allTableView reloadData];
816         }
817         [self changeMenus:self];
818     }
819 }
820
821 - (void)resetRemotePlayerTextFields
822 {
823     if ([[NetworkController sharedController] isConnectedToServer]) {
824         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
825         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
826     } else {
827         [selectedPlayerTextField setStringValue:@"No shared player selected."];
828         [locationTextField setStringValue:@"-"];
829     }
830 }
831
832 /*************************************************************************/
833 #pragma mark -
834 #pragma mark HOTKEY SUPPORT METHODS
835 /*************************************************************************/
836
837 - (IBAction)clearHotKey:(id)sender
838 {
839     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
840     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
841     [controller setupHotKeys];
842     [hotKeysTableView reloadData];
843 }
844
845 - (IBAction)editHotKey:(id)sender
846 {
847     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
848     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
849     ITKeyCombo *keyCombo;
850     
851     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
852     [controller clearHotKeys];
853     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
854     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
855     if ([panel runModal] == NSOKButton) {
856         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
857         NSString *nextKey;
858         keyCombo = [panel keyCombo];
859         
860         //Check for duplicate key combo
861         while ( (nextKey = [keyEnumerator nextObject]) ) {
862             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
863                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
864                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
865                                    forKey:nextKey];
866                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
867                     forKey:nextKey];
868             }
869         }
870         
871         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
872         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
873         [controller setupHotKeys];
874         [hotKeysTableView reloadData];
875         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
876     } else {
877         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
878     }
879 }
880
881 - (void)hotKeysTableViewDoubleClicked:(id)sender
882 {
883     if ([sender clickedRow] > -1) {
884         [self editHotKey:sender];
885     }
886 }
887
888 /*************************************************************************/
889 #pragma mark -
890 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
891 /*************************************************************************/
892
893 - (void)setupWindow
894 {
895     ITDebugLog(@"Loading Preferences.nib.");
896     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
897         ITDebugLog(@"Failed to load Preferences.nib.");
898         NSBeep();
899         return;
900     }
901 }
902
903 - (void)setupCustomizationTables
904 {
905     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
906     ITDebugLog(@"Setting up table views.");
907     // Set the table view cells up
908     [imgCell setImageScaling:NSScaleNone];
909     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
910     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
911
912     // Register for drag and drop
913     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
914         @"MenuTableViewPboardType",
915         @"AllTableViewPboardType",
916         nil]];
917     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
918         @"MenuTableViewPboardType",
919         @"AllTableViewPboardType",
920         nil]];
921 }
922
923 - (void)setupMenuItems
924 {
925     NSEnumerator *itemEnum;
926     id            anItem;
927     ITDebugLog(@"Setting up table view arrays.");
928     // Set the list of items you can have.
929     availableItems = [[NSMutableArray alloc] initWithObjects:
930         @"separator",
931         @"trackInfo",
932         @"upcomingSongs",
933         @"playlists",
934         @"artists",
935         @"albums",
936         @"eqPresets",
937         @"songRating",
938         @"playPause",
939         @"nextTrack",
940         @"prevTrack",
941         @"fastForward",
942         @"rewind",
943         @"showPlayer",
944                 @"about",
945         @"quit",
946         nil];
947     
948     // Get our preferred menu
949     myItems = [[df arrayForKey:@"menu"] mutableCopy];
950     
951     // Delete items in the availableItems array that are already part of the menu
952     itemEnum = [myItems objectEnumerator];
953     while ( (anItem = [itemEnum nextObject]) ) {
954         if (![anItem isEqualToString:@"separator"]) {
955             [availableItems removeObject:anItem];
956         }
957     }
958     
959     // Items that show should a submenu image
960     submenuItems = [[NSArray alloc] initWithObjects:
961         @"upcomingSongs",
962         @"playlists",
963         @"artists",
964         @"albums",
965         @"eqPresets",
966         @"songRating",
967         nil];
968 }
969
970 - (void)setupUI
971 {
972     NSEnumerator   *keyArrayEnum;
973     NSString       *serverName;
974     NSData         *colorData;
975     int selectedBGStyle;
976     id anItem;
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 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 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