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