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