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