Tweaked the image position in the status window a bit. Added base of the
[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:NO forKey:@"showAlbum"];
544     [df setBool:NO forKey:@"showTime"];
545
546     [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
547     [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
548     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
549     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
550     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
551     [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
552     [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
553     [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
554     [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
555     [df setBool:YES forKey:@"showSongInfoOnChange"];
556     
557     [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
558     
559     [df synchronize];
560     
561     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
562     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
563     loginEnum = [loginArray objectEnumerator];
564
565     while ( (anItem = [loginEnum nextObject]) ) {
566         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
567             found = YES;
568         }
569     }
570     [loginWindow release];
571     
572     if (!found) {
573         [[StatusWindowController sharedController] showSetupQueryWindow];
574     }
575 }
576
577 - (void)autoLaunchOK
578 {
579     [[StatusWindow sharedWindow] setLocked:NO];
580     [[StatusWindow sharedWindow] vanish:self];
581     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
582     
583     [self setLaunchesAtLogin:YES];
584 }
585
586 - (void)autoLaunchCancel
587 {
588     [[StatusWindow sharedWindow] setLocked:NO];
589     [[StatusWindow sharedWindow] vanish:self];
590     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
591 }
592
593 - (void)deletePressedInTableView:(NSTableView *)tableView
594 {
595     if (tableView == menuTableView) {
596         int selRow = [tableView selectedRow];
597         ITDebugLog(@"Delete pressed in menu table view.");
598         if (selRow != - 1) {
599             NSString *object = [myItems objectAtIndex:selRow];
600             
601             if ([object isEqualToString:@"preferences"]) {
602                 NSBeep();
603                 return;
604             }
605             
606             if (![object isEqualToString:@"separator"])
607                 [availableItems addObject:object];
608             ITDebugLog(@"Removing object named %@", object);
609             [myItems removeObjectAtIndex:selRow];
610             [menuTableView reloadData];
611             [allTableView reloadData];
612         }
613         [self changeMenus:self];
614     }
615 }
616
617 - (void)resetRemotePlayerTextFields
618 {
619     if ([[NetworkController sharedController] isConnectedToServer]) {
620         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
621         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
622     } else {
623         [selectedPlayerTextField setStringValue:@"No shared player selected."];
624         [locationTextField setStringValue:@"-"];
625     }
626 }
627
628 /*************************************************************************/
629 #pragma mark -
630 #pragma mark HOTKEY SUPPORT METHODS
631 /*************************************************************************/
632
633 - (IBAction)clearHotKey:(id)sender
634 {
635     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
636     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
637     [controller setupHotKeys];
638     [hotKeysTableView reloadData];
639 }
640
641 - (IBAction)editHotKey:(id)sender
642 {
643     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
644     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
645     ITKeyCombo *keyCombo;
646     
647     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
648     [controller clearHotKeys];
649     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
650     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
651     if ([panel runModal] == NSOKButton) {
652         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
653         NSString *nextKey;
654         keyCombo = [panel keyCombo];
655         
656         //Check for duplicate key combo
657         while ( (nextKey = [keyEnumerator nextObject]) ) {
658             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
659                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
660                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
661                                    forKey:nextKey];
662                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
663                     forKey:nextKey];
664             }
665         }
666         
667         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
668         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
669         [controller setupHotKeys];
670         [hotKeysTableView reloadData];
671         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
672     } else {
673         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
674     }
675 }
676
677 - (void)hotKeysTableViewDoubleClicked:(id)sender
678 {
679     if ([sender clickedRow] > -1) {
680         [self editHotKey:sender];
681     }
682 }
683
684 /*************************************************************************/
685 #pragma mark -
686 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
687 /*************************************************************************/
688
689 - (void)setupWindow
690 {
691     ITDebugLog(@"Loading Preferences.nib.");
692     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
693         ITDebugLog(@"Failed to load Preferences.nib.");
694         NSBeep();
695         return;
696     }
697 }
698
699 - (void)setupCustomizationTables
700 {
701     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
702     ITDebugLog(@"Setting up table views.");
703     // Set the table view cells up
704     [imgCell setImageScaling:NSScaleNone];
705     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
706     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
707
708     // Register for drag and drop
709     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
710         @"MenuTableViewPboardType",
711         @"AllTableViewPboardType",
712         nil]];
713     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
714         @"MenuTableViewPboardType",
715         @"AllTableViewPboardType",
716         nil]];
717 }
718
719 - (void)setupMenuItems
720 {
721     NSEnumerator *itemEnum;
722     id            anItem;
723     ITDebugLog(@"Setting up table view arrays.");
724     // Set the list of items you can have.
725     availableItems = [[NSMutableArray alloc] initWithObjects:
726         @"separator",
727         @"trackInfo",
728         @"upcomingSongs",
729         @"playlists",
730         @"eqPresets",
731         @"songRating",
732         @"playPause",
733         @"nextTrack",
734         @"prevTrack",
735         @"fastForward",
736         @"rewind",
737         @"showPlayer",
738         @"quit",
739         nil];
740     
741     // Get our preferred menu
742     myItems = [[df arrayForKey:@"menu"] mutableCopy];
743     
744     // Delete items in the availableItems array that are already part of the menu
745     itemEnum = [myItems objectEnumerator];
746     while ( (anItem = [itemEnum nextObject]) ) {
747         if (![anItem isEqualToString:@"separator"]) {
748             [availableItems removeObject:anItem];
749         }
750     }
751     
752     // Items that show should a submenu image
753     submenuItems = [[NSArray alloc] initWithObjects:
754         @"upcomingSongs",
755         @"playlists",
756         @"eqPresets",
757         @"songRating",
758         nil];
759 }
760
761 - (void)setupUI
762 {
763     NSMutableDictionary *loginwindow;
764     NSMutableArray      *loginarray;
765     NSEnumerator   *loginEnum;
766     NSEnumerator   *keyArrayEnum;
767     NSString       *serverName;
768     NSData         *colorData;
769     int selectedBGStyle;
770     id anItem;
771     
772     ITDebugLog(@"Setting up preferences UI.");
773     // Fill in the number of songs in advance to show field
774     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
775     
776     // Fill hot key array
777     keyArrayEnum = [hotKeysArray objectEnumerator];
778     
779     while ( (anItem = [keyArrayEnum nextObject]) ) {
780         if ([df objectForKey:anItem]) {
781             ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
782             [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
783         } else {
784             [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
785         }
786     }
787     
788     ITDebugLog(@"Setting up track info checkboxes.");
789     // Check current track info buttons
790     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
791     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
792     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
793     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
794     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
795     [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
796     [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
797     [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
798     
799     if ([df boolForKey:@"runScripts"]) {
800         [runScriptsCheckbox setState:NSOnState];
801         [showScriptsButton setEnabled:YES];
802     } else {
803         [showScriptsButton setEnabled:NO];
804     }
805     
806     // Set the launch at login checkbox state
807     ITDebugLog(@"Setting launch at login state.");
808     [df synchronize];
809     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
810     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
811     
812     loginEnum = [loginarray objectEnumerator];
813     while ( (anItem = [loginEnum nextObject]) ) {
814         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
815             [launchAtLoginCheckbox setState:NSOnState];
816         }
817     }
818     
819     // Set the launch player checkbox state
820     ITDebugLog(@"Setting launch player with MenuTunes state.");
821     [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
822     
823     // Setup the positioning controls
824     [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
825                              column:[df integerForKey:@"statusWindowHorizontalPosition"]];
826     
827     // Setup effects controls
828     // Populate the effects popups
829     [appearanceEffectPopup setAutoenablesItems:NO];
830     [vanishEffectPopup     setAutoenablesItems:NO];
831     [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"] 
832                                  horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
833     
834     // Attempt to find the pref'd effect in the list.
835     // If it's not there, use cut/dissolve.
836     if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
837         [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
838     } else {
839         [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
840     }
841     
842     if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
843         [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
844     } else {
845         [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
846     }
847     
848     [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
849     [vanishSpeedSlider     setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
850     [vanishDelaySlider     setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
851
852     // Setup General Controls
853     selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
854     [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
855
856     if ( selectedBGStyle == ITTSWBackgroundColored ) {
857         [backgroundColorWell  setEnabled:YES];
858         [backgroundColorPopup setEnabled:YES];
859     } else {
860         [backgroundColorWell  setEnabled:NO];
861         [backgroundColorPopup setEnabled:NO];
862     }
863
864     colorData = [df dataForKey:@"statusWindowBackgroundColor"];
865
866     if ( colorData ) {
867         [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
868     } else {
869         [backgroundColorWell setColor:[NSColor blueColor]];
870     }
871     
872     [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
873     
874     [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
875
876     // Setup the sharing controls
877     if ([df boolForKey:@"enableSharing"]) {
878         [shareMenuTunesCheckbox setState:NSOnState];
879         [useSharedMenuTunesCheckbox setEnabled:NO];
880         [selectSharedPlayerButton setEnabled:NO];
881         [passwordTextField setEnabled:YES];
882         [nameTextField setEnabled:YES];
883     } else if ([df boolForKey:@"useSharedPlayer"]) {
884         [useSharedMenuTunesCheckbox setState:NSOnState];
885         [shareMenuTunesCheckbox setEnabled:NO];
886         [selectSharedPlayerButton setEnabled:YES];
887     }
888     
889     [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
890     
891     serverName = [df stringForKey:@"sharedPlayerName"];
892     if (!serverName || [serverName length] == 0) {
893         serverName = @"MenuTunes Shared Player";
894     }
895     [nameTextField setStringValue:serverName];
896     
897     [selectPlayerBox setContentView:zeroConfView];
898     if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
899         [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
900     } else {
901         [passwordTextField setStringValue:@""];
902     }
903     if ([df stringForKey:@"sharedPlayerHost"]) {
904         [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
905     }
906     
907     if ([[NetworkController sharedController] isConnectedToServer]) {
908         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
909         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
910     } else {
911         [selectedPlayerTextField setStringValue:@"No shared player selected."];
912         [locationTextField setStringValue:@"-"];
913     }
914 }
915
916 - (void)setStatusWindowEntryEffect:(Class)effectClass
917 {
918     StatusWindow *sw = [StatusWindow sharedWindow];
919     
920     float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
921     [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
922     
923     [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
924     [[sw entryEffect] setEffectTime:time];
925 }
926
927 - (void)setStatusWindowExitEffect:(Class)effectClass
928 {
929     StatusWindow *sw = [StatusWindow sharedWindow];
930     
931     float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
932     [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
933     
934     [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
935     [[sw exitEffect] setEffectTime:time];
936 }
937
938 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
939 {
940     [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
941     [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
942     
943     if ( update ) {
944         [backgroundColorWell setColor:color];
945     }
946 }
947
948 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
949 {
950     NSEnumerator *effectEnum = [effectClasses objectEnumerator];
951     id anItem;
952     
953     [appearanceEffectPopup removeAllItems];
954     [vanishEffectPopup     removeAllItems];
955     
956     while ( (anItem = [effectEnum nextObject]) ) {
957         [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
958         [vanishEffectPopup     addItemWithTitle:[anItem effectName]];
959         
960         [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
961         [[vanishEffectPopup     lastItem] setRepresentedObject:anItem];
962         
963         if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
964             [[appearanceEffectPopup lastItem] setEnabled:YES];
965             [[vanishEffectPopup     lastItem] setEnabled:YES];
966         } else {
967             [[appearanceEffectPopup lastItem] setEnabled:NO];
968             [[vanishEffectPopup     lastItem] setEnabled:NO];
969         }
970     }
971     
972 }
973
974 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
975 {
976     BOOL valid = NO;
977     
978     if ( vPos == ITWindowPositionTop ) {
979         if ( hPos == ITWindowPositionLeft ) {
980             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
981         } else if ( hPos == ITWindowPositionCenter ) {
982             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
983         } else if ( hPos == ITWindowPositionRight ) {
984             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
985         }
986     } else if ( vPos == ITWindowPositionMiddle ) {
987         if ( hPos == ITWindowPositionLeft ) {
988             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
989         } else if ( hPos == ITWindowPositionCenter ) {
990             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
991         } else if ( hPos == ITWindowPositionRight ) {
992             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
993         }
994     } else if ( vPos == ITWindowPositionBottom ) {
995         if ( hPos == ITWindowPositionLeft ) {
996             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
997         } else if ( hPos == ITWindowPositionCenter ) {
998             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
999         } else if ( hPos == ITWindowPositionRight ) {
1000             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1001         }
1002     }
1003     
1004     return valid;
1005 }
1006
1007 - (IBAction)changeMenus:(id)sender
1008 {
1009     ITDebugLog(@"Synchronizing menus");
1010     [df setObject:myItems forKey:@"menu"];
1011     [df synchronize];
1012     
1013     //If we're connected over a network, refresh the menu immediately
1014     if ([[NetworkController sharedController] isConnectedToServer]) {
1015         [controller timerUpdate];
1016     }
1017 }
1018
1019 - (void)setLaunchesAtLogin:(BOOL)flag
1020 {
1021     NSMutableDictionary *loginwindow;
1022     NSMutableArray *loginarray;
1023     ITDebugLog(@"Setting launches at login: %i", flag);
1024     [df synchronize];
1025     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
1026     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
1027     
1028     if (flag) {
1029         NSDictionary *itemDict = [NSDictionary dictionaryWithObjectsAndKeys:
1030         [[NSBundle mainBundle] bundlePath], @"Path",
1031         [NSNumber numberWithInt:0], @"Hide", nil];
1032         [loginarray addObject:itemDict];
1033     } else {
1034         int i;
1035         for (i = 0; i < [loginarray count]; i++) {
1036             NSDictionary *tempDict = [loginarray objectAtIndex:i];
1037             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
1038                 [loginarray removeObjectAtIndex:i];
1039                 break;
1040             }
1041         }
1042     }
1043     [df setPersistentDomain:loginwindow forName:@"loginwindow"];
1044     [df synchronize];
1045     [loginwindow release];
1046     ITDebugLog(@"Finished setting launches at login.");
1047 }
1048
1049
1050 /*************************************************************************/
1051 #pragma mark -
1052 #pragma mark NSWindow DELEGATE METHODS
1053 /*************************************************************************/
1054
1055 - (void)windowWillClose:(NSNotification *)note
1056 {
1057     [(MainController *)controller closePreferences]; 
1058 }
1059
1060 /*************************************************************************/
1061 #pragma mark -
1062 #pragma mark NSTextField DELEGATE METHODS
1063 /*************************************************************************/
1064
1065 - (void)controlTextDidChange:(NSNotification*)note
1066 {
1067     if ([note object] == hostTextField) {
1068         if ([[hostTextField stringValue] length] == 0) {
1069             [sharingPanelOKButton setEnabled:NO];
1070         } else {
1071             [sharingPanelOKButton setEnabled:YES];
1072         }
1073     }
1074 }
1075
1076 /*************************************************************************/
1077 #pragma mark -
1078 #pragma mark NSTableView DATASOURCE METHODS
1079 /*************************************************************************/
1080
1081 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1082 {
1083     if (aTableView == menuTableView) {
1084         return [myItems count];
1085     } else if (aTableView == allTableView) {
1086         return [availableItems count];
1087     } else if (aTableView == hotKeysTableView) {
1088         return [hotKeysArray count];
1089     } else {
1090         return [[[NetworkController sharedController] remoteServices] count];
1091     }
1092 }
1093
1094 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1095 {
1096     if (aTableView == menuTableView) {
1097         NSString *object = [myItems objectAtIndex:rowIndex];
1098         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1099             if ([object isEqualToString:@"showPlayer"]) {
1100                 NSString *string = nil;
1101                 NS_DURING
1102                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1103                 NS_HANDLER
1104                     [controller networkError:localException];
1105                 NS_ENDHANDLER
1106                 return string;
1107             }
1108             return NSLocalizedString(object, @"ERROR");
1109         } else {
1110             if ([submenuItems containsObject:object])
1111             {
1112                 return [NSImage imageNamed:@"submenu"];
1113             } else {
1114                 return nil;
1115             }
1116         }
1117     } else if (aTableView == allTableView) {
1118         NSString *object = [availableItems objectAtIndex:rowIndex];
1119         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1120             if ([object isEqualToString:@"showPlayer"]) {
1121                 NSString *string = nil;
1122                 NS_DURING
1123                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1124                 NS_HANDLER
1125                     [controller networkError:localException];
1126                 NS_ENDHANDLER
1127                 return string;
1128             }
1129             return NSLocalizedString(object, @"ERROR");
1130         } else {
1131             if ([submenuItems containsObject:object]) {
1132                 return [NSImage imageNamed:@"submenu"];
1133             } else {
1134                 return nil;
1135             }
1136         }
1137     } else if (aTableView == hotKeysTableView) {
1138         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1139             return [hotKeyNamesArray objectAtIndex:rowIndex];
1140         } else {
1141             return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1142         }
1143     } else {
1144         return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1145     }
1146 }
1147
1148 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1149 {
1150     if (tableView == menuTableView) {
1151         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1152         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1153         return YES;
1154     }
1155     
1156     if (tableView == allTableView) {
1157         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1158         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1159         return YES;
1160     }
1161     return NO;
1162 }
1163
1164 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1165 {
1166     NSPasteboard *pb;
1167     int dragRow;
1168     NSString *dragData, *temp;
1169     
1170     pb = [info draggingPasteboard];
1171     
1172     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1173         dragData = [pb stringForType:@"MenuTableViewPboardType"];
1174         dragRow = [dragData intValue];
1175         temp = [myItems objectAtIndex:dragRow];
1176         
1177         if (tableView == menuTableView) {
1178             [myItems insertObject:temp atIndex:row];
1179             if (row > dragRow) {
1180                 [myItems removeObjectAtIndex:dragRow];
1181             } else {
1182                 [myItems removeObjectAtIndex:dragRow + 1];
1183             }
1184         } else if (tableView == allTableView) {
1185             if (![temp isEqualToString:@"separator"]) {
1186                 [availableItems addObject:temp];
1187             }
1188             [myItems removeObjectAtIndex:dragRow];
1189         }
1190     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1191         dragData = [pb stringForType:@"AllTableViewPboardType"];
1192         dragRow = [dragData intValue];
1193         temp = [availableItems objectAtIndex:dragRow];
1194         
1195         [myItems insertObject:temp atIndex:row];
1196         
1197         if (![temp isEqualToString:@"separator"]) {
1198             [availableItems removeObjectAtIndex:dragRow];
1199         }
1200     }
1201     
1202     [menuTableView reloadData];
1203     [allTableView reloadData];
1204     [self changeMenus:self];
1205     return YES;
1206 }
1207
1208 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1209 {
1210     if (tableView == allTableView) {
1211         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1212             return NSDragOperationNone;
1213         }
1214         
1215         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1216             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1217             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1218                 return NSDragOperationNone;
1219             }
1220         }
1221         
1222         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1223         return NSDragOperationGeneric;
1224     }
1225     
1226     if (operation == NSTableViewDropOn || row == -1)
1227     {
1228         return NSDragOperationNone;
1229     }
1230     return NSDragOperationGeneric;
1231 }
1232
1233
1234 /*************************************************************************/
1235 #pragma mark -
1236 #pragma mark DEALLOCATION METHODS
1237 /*************************************************************************/
1238
1239 - (void)dealloc
1240 {
1241     [hotKeysArray release];
1242     [hotKeysDictionary release];
1243     [effectClasses release];
1244     [menuTableView setDataSource:nil];
1245     [allTableView setDataSource:nil];
1246     [controller release];
1247     [availableItems release];
1248     [submenuItems release];
1249     [myItems release];
1250     [df release];
1251 }
1252
1253 @end