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