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