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