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