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