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