Sizing is now in, with appropriate UI. Still working on the last of the positioning...
[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     } else if ( [sender tag] == 2095) {
473         [sw vanish:self];
474         [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
475     }
476     
477     [df synchronize];
478 }
479
480 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
481 {
482     [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
483     [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
484     
485     if ( update ) {
486         [backgroundColorWell setColor:color];
487     }
488 }
489
490 - (void)registerDefaults
491 {
492     BOOL found = NO;
493     NSMutableDictionary *loginWindow;
494     NSMutableArray *loginArray;
495     NSEnumerator *loginEnum;
496     id anItem;
497     ITDebugLog(@"Registering defaults.");
498     [df setObject:[NSArray arrayWithObjects:
499         @"trackInfo",
500         @"separator",
501         @"playPause",
502         @"prevTrack",
503         @"nextTrack",
504         @"separator",
505         @"playlists",
506         @"upcomingSongs",
507         @"separator",
508         @"preferences",
509         @"quit",
510         nil] forKey:@"menu"];
511
512     [df setInteger:5 forKey:@"SongsInAdvance"];
513     // [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
514     [df setBool:YES forKey:@"showArtist"];
515     [df setBool:NO forKey:@"showAlbum"];
516     [df setBool:NO forKey:@"showTime"];
517
518     [df setInteger:2100 forKey:@"statusWindowAppearanceEffect"];
519     [df setInteger:2101 forKey:@"statusWindowVanishEffect"];
520     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
521     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
522     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
523     [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
524     [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
525     [df setBool:YES forKey:@"showSongInfoOnChange"];
526     
527     [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
528     
529     [df synchronize];
530     
531     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
532     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
533     loginEnum = [loginArray objectEnumerator];
534
535     while ( (anItem = [loginEnum nextObject]) ) {
536         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
537             found = YES;
538         }
539     }
540     [loginWindow release];
541     
542     if (!found) {
543         [[StatusWindowController sharedController] showSetupQueryWindow];
544     }
545 }
546
547 - (void)autoLaunchOK
548 {
549     [[StatusWindow sharedWindow] setLocked:NO];
550     [[StatusWindow sharedWindow] vanish:self];
551     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
552     
553     [self setLaunchesAtLogin:YES];
554 }
555
556 - (void)autoLaunchCancel
557 {
558     [[StatusWindow sharedWindow] setLocked:NO];
559     [[StatusWindow sharedWindow] vanish:self];
560     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
561 }
562
563 - (void)deletePressedInTableView:(NSTableView *)tableView
564 {
565     if (tableView == menuTableView) {
566         int selRow = [tableView selectedRow];
567         ITDebugLog(@"Delete pressed in menu table view.");
568         if (selRow != - 1) {
569             NSString *object = [myItems objectAtIndex:selRow];
570             
571             if ([object isEqualToString:@"preferences"]) {
572                 NSBeep();
573                 return;
574             }
575             
576             if (![object isEqualToString:@"separator"])
577                 [availableItems addObject:object];
578             ITDebugLog(@"Removing object named %@", object);
579             [myItems removeObjectAtIndex:selRow];
580             [menuTableView reloadData];
581             [allTableView reloadData];
582         }
583         [self changeMenus:self];
584     }
585 }
586
587 - (void)resetRemotePlayerTextFields
588 {
589     if ([[NetworkController sharedController] isConnectedToServer]) {
590         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
591         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
592     } else {
593         [selectedPlayerTextField setStringValue:@"No shared player selected."];
594         [locationTextField setStringValue:@"-"];
595     }
596 }
597
598 /*************************************************************************/
599 #pragma mark -
600 #pragma mark HOTKEY SUPPORT METHODS
601 /*************************************************************************/
602
603 - (IBAction)clearHotKey:(id)sender
604 {
605     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
606     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
607     [controller setupHotKeys];
608     [hotKeysTableView reloadData];
609 }
610
611 - (IBAction)editHotKey:(id)sender
612 {
613     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
614     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
615     ITKeyCombo *keyCombo;
616     
617     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
618     [controller clearHotKeys];
619     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
620     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
621     if ([panel runModal] == NSOKButton) {
622         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
623         NSString *nextKey;
624         keyCombo = [panel keyCombo];
625         
626         //Check for duplicate key combo
627         while ( (nextKey = [keyEnumerator nextObject]) ) {
628             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
629                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
630                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
631                                    forKey:nextKey];
632                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
633                     forKey:nextKey];
634             }
635         }
636         
637         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
638         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
639         [controller setupHotKeys];
640         [hotKeysTableView reloadData];
641         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
642     } else {
643         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
644     }
645 }
646
647 - (void)hotKeysTableViewDoubleClicked:(id)sender
648 {
649     if ([sender clickedRow] > -1) {
650         [self editHotKey:sender];
651     }
652 }
653
654 /*************************************************************************/
655 #pragma mark -
656 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
657 /*************************************************************************/
658
659 - (void)setupWindow
660 {
661     ITDebugLog(@"Loading Preferences.nib.");
662     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
663         ITDebugLog(@"Failed to load Preferences.nib.");
664         NSBeep();
665         return;
666     }
667 }
668
669 - (void)setupCustomizationTables
670 {
671     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
672     ITDebugLog(@"Setting up table views.");
673     // Set the table view cells up
674     [imgCell setImageScaling:NSScaleNone];
675     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
676     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
677
678     // Register for drag and drop
679     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
680         @"MenuTableViewPboardType",
681         @"AllTableViewPboardType",
682         nil]];
683     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
684         @"MenuTableViewPboardType",
685         @"AllTableViewPboardType",
686         nil]];
687 }
688
689 - (void)setupMenuItems
690 {
691     NSEnumerator *itemEnum;
692     id            anItem;
693     ITDebugLog(@"Setting up table view arrays.");
694     // Set the list of items you can have.
695     availableItems = [[NSMutableArray alloc] initWithObjects:
696         @"separator",
697         @"trackInfo",
698         @"upcomingSongs",
699         @"playlists",
700         @"eqPresets",
701         @"songRating",
702         @"playPause",
703         @"nextTrack",
704         @"prevTrack",
705         @"fastForward",
706         @"rewind",
707         @"showPlayer",
708         @"quit",
709         nil];
710     
711     // Get our preferred menu
712     myItems = [[df arrayForKey:@"menu"] mutableCopy];
713     
714     // Delete items in the availableItems array that are already part of the menu
715     itemEnum = [myItems objectEnumerator];
716     while ( (anItem = [itemEnum nextObject]) ) {
717         if (![anItem isEqualToString:@"separator"]) {
718             [availableItems removeObject:anItem];
719         }
720     }
721     
722     // Items that show should a submenu image
723     submenuItems = [[NSArray alloc] initWithObjects:
724         @"upcomingSongs",
725         @"playlists",
726         @"eqPresets",
727         @"songRating",
728         nil];
729 }
730
731 - (void)setupUI
732 {
733     NSMutableDictionary *loginwindow;
734     NSMutableArray *loginarray;
735     NSEnumerator *loginEnum, *keyArrayEnum;
736     NSString *serverName;
737     NSData *colorData;
738     int selectedBGStyle;
739     id anItem;
740     
741     ITDebugLog(@"Setting up preferences UI.");
742     // Fill in the number of songs in advance to show field
743     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
744     
745     // Fill hot key array
746     keyArrayEnum = [hotKeysArray objectEnumerator];
747     
748     while ( (anItem = [keyArrayEnum nextObject]) ) {
749         if ([df objectForKey:anItem]) {
750             ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
751             [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
752         } else {
753             [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
754         }
755     }
756     
757     ITDebugLog(@"Setting up track info checkboxes.");
758     // Check current track info buttons
759     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
760     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
761     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
762     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
763     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
764     [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
765     [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
766     
767     // Set the launch at login checkbox state
768     ITDebugLog(@"Setting launch at login state.");
769     [df synchronize];
770     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
771     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
772     
773     loginEnum = [loginarray objectEnumerator];
774     while ( (anItem = [loginEnum nextObject]) ) {
775         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
776             [launchAtLoginCheckbox setState:NSOnState];
777         }
778     }
779     
780     // Set the launch player checkbox state
781     ITDebugLog(@"Setting launch player with MenuTunes state.");
782     [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
783     
784     // Setup the positioning controls
785     
786     // Setup effects controls
787     [appearanceEffectPopup selectItem:[appearanceEffectPopup itemAtIndex:[appearanceEffectPopup indexOfItemWithTag:[df integerForKey:@"statusWindowAppearanceEffect"]]]];
788     [vanishEffectPopup     selectItem:[vanishEffectPopup     itemAtIndex:[vanishEffectPopup     indexOfItemWithTag:[df integerForKey:@"statusWindowVanishEffect"]]]];
789     [appearanceSpeedSlider setFloatValue:-([df floatForKey:@"statusWindowAppearanceSpeed"])];
790     [vanishSpeedSlider     setFloatValue:-([df floatForKey:@"statusWindowVanishSpeed"])];
791     [vanishDelaySlider     setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
792
793     // Setup General Controls
794     
795     selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
796     [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
797
798     if ( selectedBGStyle == ITTSWBackgroundColored ) {
799         [backgroundColorWell  setEnabled:YES];
800         [backgroundColorPopup setEnabled:YES];
801     } else {
802         [backgroundColorWell  setEnabled:NO];
803         [backgroundColorPopup setEnabled:NO];
804     }
805
806     colorData = [df dataForKey:@"statusWindowBackgroundColor"];
807
808     if ( colorData ) {
809         [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
810     } else {
811         [backgroundColorWell setColor:[NSColor blueColor]];
812     }
813     
814     [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
815     
816     [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
817
818     // Setup the sharing controls
819     if ([df boolForKey:@"enableSharing"]) {
820         [shareMenuTunesCheckbox setState:NSOnState];
821         [useSharedMenuTunesCheckbox setEnabled:NO];
822         [selectSharedPlayerButton setEnabled:NO];
823         [passwordTextField setEnabled:YES];
824         [usePasswordCheckbox setEnabled:YES];
825         [nameTextField setEnabled:YES];
826     } else if ([df boolForKey:@"useSharedPlayer"]) {
827         [useSharedMenuTunesCheckbox setState:NSOnState];
828         [shareMenuTunesCheckbox setEnabled:NO];
829         [selectSharedPlayerButton setEnabled:YES];
830     }
831     
832     [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
833     
834     serverName = [df stringForKey:@"sharedPlayerName"];
835     if (!serverName || [serverName length] == 0) {
836         serverName = @"MenuTunes Shared Player";
837     }
838     [nameTextField setStringValue:serverName];
839     
840     [selectPlayerBox setContentView:zeroConfView];
841     [usePasswordCheckbox setState:([df boolForKey:@"enableSharingPassword"] ? NSOnState : NSOffState)];
842     if ([df dataForKey:@"sharedPlayerPassword"]) {
843         [passwordTextField setStringValue:@"password"];
844     }
845     if ([df stringForKey:@"sharedPlayerHost"]) {
846         [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
847     }
848     
849     if ([[NetworkController sharedController] isConnectedToServer]) {
850         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
851         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
852     } else {
853         [selectedPlayerTextField setStringValue:@"No shared player selected."];
854         [locationTextField setStringValue:@"-"];
855     }
856 }
857
858 - (IBAction)changeMenus:(id)sender
859 {
860     ITDebugLog(@"Synchronizing menus");
861     [df setObject:myItems forKey:@"menu"];
862     [df synchronize];
863 }
864
865 - (void)setLaunchesAtLogin:(BOOL)flag
866 {
867     NSMutableDictionary *loginwindow;
868     NSMutableArray *loginarray;
869     ITDebugLog(@"Setting launches at login: %i", flag);
870     [df synchronize];
871     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
872     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
873     
874     if (flag) {
875         NSDictionary *itemDict = [NSDictionary dictionaryWithObjectsAndKeys:
876         [[NSBundle mainBundle] bundlePath], @"Path",
877         [NSNumber numberWithInt:0], @"Hide", nil];
878         [loginarray addObject:itemDict];
879     } else {
880         int i;
881         for (i = 0; i < [loginarray count]; i++) {
882             NSDictionary *tempDict = [loginarray objectAtIndex:i];
883             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
884                 [loginarray removeObjectAtIndex:i];
885                 break;
886             }
887         }
888     }
889     [df setPersistentDomain:loginwindow forName:@"loginwindow"];
890     [df synchronize];
891     [loginwindow release];
892     ITDebugLog(@"Finished setting launches at login.");
893 }
894
895
896 /*************************************************************************/
897 #pragma mark -
898 #pragma mark NSWindow DELEGATE METHODS
899 /*************************************************************************/
900
901 - (void)windowWillClose:(NSNotification *)note
902 {
903     [(MainController *)controller closePreferences]; 
904 }
905
906 /*************************************************************************/
907 #pragma mark -
908 #pragma mark NSTextField DELEGATE METHODS
909 /*************************************************************************/
910
911 - (void)controlTextDidChange:(NSNotification*)note
912 {
913     if ([note object] == hostTextField) {
914         if ([[hostTextField stringValue] length] == 0) {
915             [sharingPanelOKButton setEnabled:NO];
916         } else {
917             [sharingPanelOKButton setEnabled:YES];
918         }
919     }
920 }
921
922 /*************************************************************************/
923 #pragma mark -
924 #pragma mark NSTableView DATASOURCE METHODS
925 /*************************************************************************/
926
927 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
928 {
929     if (aTableView == menuTableView) {
930         return [myItems count];
931     } else if (aTableView == allTableView) {
932         return [availableItems count];
933     } else if (aTableView == hotKeysTableView) {
934         return [hotKeysArray count];
935     } else {
936         return [[[NetworkController sharedController] remoteServices] count];
937     }
938 }
939
940 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
941 {
942     if (aTableView == menuTableView) {
943         NSString *object = [myItems objectAtIndex:rowIndex];
944         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
945             if ([object isEqualToString:@"showPlayer"]) {
946                 NSString *string = nil;
947                 NS_DURING
948                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
949                 NS_HANDLER
950                     [controller networkError:localException];
951                 NS_ENDHANDLER
952                 return string;
953             }
954             return NSLocalizedString(object, @"ERROR");
955         } else {
956             if ([submenuItems containsObject:object])
957             {
958                 return [NSImage imageNamed:@"submenu"];
959             } else {
960                 return nil;
961             }
962         }
963     } else if (aTableView == allTableView) {
964         NSString *object = [availableItems objectAtIndex:rowIndex];
965         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
966             if ([object isEqualToString:@"showPlayer"]) {
967                 NSString *string = nil;
968                 NS_DURING
969                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
970                 NS_HANDLER
971                     [controller networkError:localException];
972                 NS_ENDHANDLER
973                 return string;
974             }
975             return NSLocalizedString(object, @"ERROR");
976         } else {
977             if ([submenuItems containsObject:object]) {
978                 return [NSImage imageNamed:@"submenu"];
979             } else {
980                 return nil;
981             }
982         }
983     } else if (aTableView == hotKeysTableView) {
984         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
985             return [hotKeyNamesArray objectAtIndex:rowIndex];
986         } else {
987             return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
988         }
989     } else {
990         return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
991     }
992 }
993
994 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
995 {
996     if (tableView == menuTableView) {
997         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
998         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
999         return YES;
1000     }
1001     
1002     if (tableView == allTableView) {
1003         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1004         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1005         return YES;
1006     }
1007     return NO;
1008 }
1009
1010 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1011 {
1012     NSPasteboard *pb;
1013     int dragRow;
1014     NSString *dragData, *temp;
1015     
1016     pb = [info draggingPasteboard];
1017     
1018     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1019         dragData = [pb stringForType:@"MenuTableViewPboardType"];
1020         dragRow = [dragData intValue];
1021         temp = [myItems objectAtIndex:dragRow];
1022         
1023         if (tableView == menuTableView) {
1024             [myItems insertObject:temp atIndex:row];
1025             if (row > dragRow) {
1026                 [myItems removeObjectAtIndex:dragRow];
1027             } else {
1028                 [myItems removeObjectAtIndex:dragRow + 1];
1029             }
1030         } else if (tableView == allTableView) {
1031             if (![temp isEqualToString:@"separator"]) {
1032                 [availableItems addObject:temp];
1033             }
1034             [myItems removeObjectAtIndex:dragRow];
1035         }
1036     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1037         dragData = [pb stringForType:@"AllTableViewPboardType"];
1038         dragRow = [dragData intValue];
1039         temp = [availableItems objectAtIndex:dragRow];
1040         
1041         [myItems insertObject:temp atIndex:row];
1042         
1043         if (![temp isEqualToString:@"separator"]) {
1044             [availableItems removeObjectAtIndex:dragRow];
1045         }
1046     }
1047     
1048     [menuTableView reloadData];
1049     [allTableView reloadData];
1050     [self changeMenus:self];
1051     return YES;
1052 }
1053
1054 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1055 {
1056     if (tableView == allTableView) {
1057         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1058             return NSDragOperationNone;
1059         }
1060         
1061         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1062             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1063             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1064                 return NSDragOperationNone;
1065             }
1066         }
1067         
1068         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1069         return NSDragOperationGeneric;
1070     }
1071     
1072     if (operation == NSTableViewDropOn || row == -1)
1073     {
1074         return NSDragOperationNone;
1075     }
1076     return NSDragOperationGeneric;
1077 }
1078
1079
1080 /*************************************************************************/
1081 #pragma mark -
1082 #pragma mark DEALLOCATION METHODS
1083 /*************************************************************************/
1084
1085 - (void)dealloc
1086 {
1087     [hotKeysArray release];
1088     [hotKeysDictionary release];
1089     [menuTableView setDataSource:nil];
1090     [allTableView setDataSource:nil];
1091     [controller release];
1092     [availableItems release];
1093     [submenuItems release];
1094     [myItems release];
1095     [df release];
1096 }
1097
1098 @end