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