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