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