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