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