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