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