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"
11 #import <openssl/sha.h>
15 #import <ITFoundation/ITFoundation.h>
17 #import <ITKit/ITHotKeyCenter.h>
18 #import <ITKit/ITKeyCombo.h>
19 #import <ITKit/ITKeyComboPanel.h>
20 #import <ITKit/ITWindowPositioning.h>
21 #import <ITKit/ITKeyBroadcaster.h>
23 #import <ITKit/ITTSWBackgroundView.h>
24 #import <ITKit/ITWindowEffect.h>
25 #import <ITKit/ITCutWindowEffect.h>
26 #import <ITKit/ITDissolveWindowEffect.h>
27 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
28 #import <ITKit/ITSlideVerticallyWindowEffect.h>
29 #import <ITKit/ITPivotWindowEffect.h>
32 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
34 /*************************************************************************/
36 #pragma mark PRIVATE INTERFACE
37 /*************************************************************************/
39 @interface PreferencesController (Private)
41 - (void)setupCustomizationTables;
42 - (void)setupMenuItems;
44 - (void)setStatusWindowEntryEffect:(Class)effectClass;
45 - (void)setStatusWindowExitEffect:(Class)effectClass;
46 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
47 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
48 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
49 - (IBAction)changeMenus:(id)sender;
50 - (void)setLaunchesAtLogin:(BOOL)flag;
54 @implementation PreferencesController
57 /*************************************************************************/
59 #pragma mark STATIC VARIABLES
60 /*************************************************************************/
62 static PreferencesController *prefs = nil;
65 /*************************************************************************/
67 #pragma mark INITIALIZATION METHODS
68 /*************************************************************************/
70 + (PreferencesController *)sharedPrefs;
73 prefs = [[self alloc] init];
80 if ( (self = [super init]) ) {
81 ITDebugLog(@"Preferences initialized.");
82 df = [[NSUserDefaults standardUserDefaults] retain];
84 effectClasses = [[ITWindowEffect effectClasses] retain];
86 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
100 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
113 hotKeysDictionary = [[NSMutableDictionary alloc] init];
116 [self setupWindow]; // Load in the nib, and perform any initial setup.
117 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
123 /*************************************************************************/
125 #pragma mark ACCESSOR METHODS
126 /*************************************************************************/
133 - (void)setController:(id)object
135 [controller autorelease];
136 controller = [object retain];
140 /*************************************************************************/
142 #pragma mark INSTANCE METHODS
143 /*************************************************************************/
145 - (BOOL)showPasswordPanel
147 [passwordPanel setLevel:NSStatusWindowLevel];
148 [passwordPanelOKButton setTitle:@"Connect"];
149 [passwordPanelTitle setStringValue:@"Password Required"];
150 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
151 [passwordPanel setLevel:NSStatusWindowLevel];
152 [NSApp activateIgnoringOtherApps:YES];
153 [passwordPanel center];
154 [passwordPanel orderFrontRegardless];
155 [passwordPanel makeKeyWindow];
156 if ([NSApp runModalForWindow:passwordPanel]) {
163 - (BOOL)showInvalidPasswordPanel
165 [passwordPanel setLevel:NSStatusWindowLevel];
166 [passwordPanelOKButton setTitle:@"Retry"];
167 [passwordPanelTitle setStringValue:@"Invalid Password"];
168 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"The password entered for access to the MenuTunes player named %@ at %@ is invalid. Please provide a new password.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
169 [passwordPanel setLevel:NSStatusWindowLevel];
170 [NSApp activateIgnoringOtherApps:YES];
171 [passwordPanel center];
172 [passwordPanel orderFrontRegardless];
173 [passwordPanel makeKeyWindow];
174 if ([NSApp runModalForWindow:passwordPanel]) {
181 - (IBAction)showPrefsWindow:(id)sender
183 ITDebugLog(@"Showing preferences window.");
184 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
185 ITDebugLog(@"Window doesn't exist, initial setup.");
186 [self setupCustomizationTables]; // Setup the DnD manu config tables.
187 [self setupMenuItems]; // Setup the arrays of menu items
188 [self setupUI]; // Sets up additional UI
189 [window setDelegate:self];
190 [menuTableView reloadData];
191 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
193 //Change the launch player checkbox to the proper name
195 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
197 [controller networkError:localException];
201 [self resetRemotePlayerTextFields];
202 [launchAtLoginCheckbox becomeFirstResponder];
203 [NSApp activateIgnoringOtherApps:YES];
205 [window orderFrontRegardless];
206 [window makeKeyWindow];
209 - (IBAction)showTestWindow:(id)sender
211 [controller showTestWindow];
214 - (IBAction)changeGeneralSetting:(id)sender
216 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
217 if ( [sender tag] == 1010) {
218 [self setLaunchesAtLogin:SENDER_STATE];
219 } else if ( [sender tag] == 1020) {
220 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
221 } else if ( [sender tag] == 1030) {
222 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
224 } else if ( [sender tag] == 1040) {
225 // This will not be executed. Song info always shows the title of the song.
226 // [df setBool:SENDER_STATE forKey:@"showName"];
227 } else if ( [sender tag] == 1050) {
228 [df setBool:SENDER_STATE forKey:@"showArtist"];
229 } else if ( [sender tag] == 1060) {
230 [df setBool:SENDER_STATE forKey:@"showAlbum"];
231 } else if ( [sender tag] == 1070) {
232 [df setBool:SENDER_STATE forKey:@"showTime"];
233 } else if ( [sender tag] == 1080) {
234 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
235 } else if ( [sender tag] == 1090) {
236 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
237 } else if ( [sender tag] == 1100) {
238 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
239 } else if ( [sender tag] == 1110) {
240 [df setBool:SENDER_STATE forKey:@"runScripts"];
242 [runScriptsCheckbox setState:NSOnState];
243 [showScriptsButton setEnabled:YES];
245 [showScriptsButton setEnabled:NO];
247 } else if ( [sender tag] == 1120) {
248 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
249 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
254 - (IBAction)changeSharingSetting:(id)sender
256 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
257 if ( [sender tag] == 5010 ) {
258 BOOL state = SENDER_STATE;
259 [df setBool:state forKey:@"enableSharing"];
260 //Disable/enable the use of shared player options
261 [useSharedMenuTunesCheckbox setEnabled:!state];
262 [passwordTextField setEnabled:state];
263 [nameTextField setEnabled:state];
264 [selectSharedPlayerButton setEnabled:NO];
265 [controller setServerStatus:state]; //Set server status
266 } else if ( [sender tag] == 5015 ) {
267 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
268 [[NetworkController sharedController] resetServerName];
269 } else if ( [sender tag] == 5030 ) {
270 //Set the server password
271 const char *instring = [[sender stringValue] UTF8String];
272 const char *password = "p4s5w0rdMT1.2";
273 unsigned char *result;
274 NSData *hashedPass, *passwordStringHash;
275 if ([[sender stringValue] length] == 0) {
276 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
279 result = SHA1(instring, strlen(instring), NULL);
280 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
281 result = SHA1(password, strlen(password), NULL);
282 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
283 if (![hashedPass isEqualToData:passwordStringHash]) {
284 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
285 [sender setStringValue:@"p4s5w0rdMT1.2"];
287 } else if ( [sender tag] == 5040 ) {
288 BOOL state = SENDER_STATE;
289 [df setBool:state forKey:@"useSharedPlayer"];
290 //Disable/enable the use of sharing options
291 [shareMenuTunesCheckbox setEnabled:!state];
292 [passwordTextField setEnabled:NO];
293 [nameTextField setEnabled:NO];
294 [selectSharedPlayerButton setEnabled:state];
296 if (state && ([controller connectToServer] == 1)) {
297 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
298 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
300 [selectedPlayerTextField setStringValue:@"No shared player selected."];
301 [locationTextField setStringValue:@"-"];
302 if ([[NetworkController sharedController] isConnectedToServer]) {
303 [controller disconnectFromServer];
307 } else if ( [sender tag] == 5050 ) {
308 //If no player is selected in the table view, turn off OK button.
309 if ([sender clickedRow] == -1 ) {
310 [sharingPanelOKButton setEnabled:NO];
312 [sharingPanelOKButton setEnabled:YES];
314 } else if ( [sender tag] == 5051 ) {
315 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
316 } else if ( [sender tag] == 5060 ) {
317 //Set OK button state
318 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
319 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
320 [sharingPanelOKButton setEnabled:NO];
322 [sharingPanelOKButton setEnabled:YES];
324 //Show selection sheet
325 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
326 } else if ( [sender tag] == 5100 ) {
328 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
329 NSRect frame = [selectPlayerSheet frame];
330 frame.origin.y -= 58;
331 frame.size.height = 273;
332 if ([sharingTableView selectedRow] == -1) {
333 [sharingPanelOKButton setEnabled:NO];
335 [selectPlayerBox setContentView:zeroConfView];
336 [selectPlayerSheet setFrame:frame display:YES animate:YES];
337 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
338 NSRect frame = [selectPlayerSheet frame];
339 frame.origin.y += 58;
340 frame.size.height = 215;
341 if ([[hostTextField stringValue] length] == 0) {
342 [sharingPanelOKButton setEnabled:NO];
344 [sharingPanelOKButton setEnabled:YES];
346 [selectPlayerBox setContentView:manualView];
347 [selectPlayerSheet setFrame:frame display:YES animate:YES];
348 [hostTextField selectText:nil];
350 } else if ( [sender tag] == 5150 ) {
351 const char *instring = [[sender stringValue] UTF8String];
352 unsigned char *result;
353 result = SHA1(instring, strlen(instring), NULL);
354 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
355 } else if ( [sender tag] == 5110 ) {
357 [NSApp endSheet:selectPlayerSheet];
358 [selectPlayerSheet orderOut:nil];
359 if ([selectPlayerBox contentView] == manualView) {
360 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
363 } else if ( [sender tag] == 5120 ) {
365 [NSApp endSheet:selectPlayerSheet];
366 [selectPlayerSheet orderOut:nil];
368 [self changeSharingSetting:clientPasswordTextField];
370 if ([selectPlayerBox contentView] == manualView) {
371 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
373 if ([sharingTableView selectedRow] > -1) {
374 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
378 if ([controller connectToServer] == 1) {
379 [useSharedMenuTunesCheckbox setState:NSOnState];
380 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
381 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
383 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);
385 } else if ( [sender tag] == 6010 ) {
386 //Cancel password entry
387 [passwordPanel orderOut:nil];
388 [NSApp stopModalWithCode:0];
389 } else if ( [sender tag] == 6020 ) {
390 //OK password entry, retry connect
391 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
392 unsigned char *result;
393 result = SHA1(instring, strlen(instring), NULL);
394 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
395 [passwordPanel orderOut:nil];
396 [NSApp stopModalWithCode:1];
401 - (IBAction)changeStatusWindowSetting:(id)sender
403 StatusWindow *sw = [StatusWindow sharedWindow];
404 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
406 if ( [sender tag] == 2010) {
408 BOOL entryEffectValid = YES;
409 BOOL exitEffectValid = YES;
411 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
412 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
413 [sw setVerticalPosition:[sender selectedRow]];
414 [sw setHorizontalPosition:[sender selectedColumn]];
416 // Enable/disable the items in the popups.
417 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
418 horizontalPosition:[sw horizontalPosition]];
420 // Make sure the effects support the new position.
421 entryEffectValid = ( [self effect:[[sw entryEffect] class]
422 supportsVerticalPosition:[sw verticalPosition]
423 withHorizontalPosition:[sw horizontalPosition]] );
424 exitEffectValid = ( [self effect:[[sw exitEffect] class]
425 supportsVerticalPosition:[sw verticalPosition]
426 withHorizontalPosition:[sw horizontalPosition]] );
428 if ( ! entryEffectValid ) {
429 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
430 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
432 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
435 if ( ! exitEffectValid ) {
436 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
437 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
439 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
442 [(MainController *)controller showCurrentTrackInfo];
444 } else if ( [sender tag] == 2020) {
446 // Update screen selection.
448 } else if ( [sender tag] == 2030) {
450 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
452 } else if ( [sender tag] == 2040) {
454 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
456 } else if ( [sender tag] == 2050) {
457 float newTime = ( -([sender floatValue]) );
458 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
459 [[sw entryEffect] setEffectTime:newTime];
460 } else if ( [sender tag] == 2060) {
461 float newTime = ( -([sender floatValue]) );
462 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
463 [[sw exitEffect] setEffectTime:newTime];
464 } else if ( [sender tag] == 2070) {
465 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
466 [sw setExitDelay:[sender floatValue]];
467 } else if ( [sender tag] == 2080) {
468 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
469 } else if ( [sender tag] == 2090) {
471 int setting = [sender indexOfSelectedItem];
473 if ( setting == 0 ) {
474 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
475 [backgroundColorWell setEnabled:NO];
476 [backgroundColorPopup setEnabled:NO];
477 } else if ( setting == 1 ) {
478 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
479 [backgroundColorWell setEnabled:NO];
480 [backgroundColorPopup setEnabled:NO];
481 } else if ( setting == 2 ) {
482 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
483 [backgroundColorWell setEnabled:YES];
484 [backgroundColorPopup setEnabled:YES];
487 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
489 } else if ( [sender tag] == 2091) {
490 [self setCustomColor:[sender color] updateWell:NO];
491 } else if ( [sender tag] == 2092) {
493 int selectedItem = [sender indexOfSelectedItem];
495 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
496 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
497 } else if ( selectedItem == 2 ) {
498 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
499 } else if ( selectedItem == 3 ) {
500 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
501 } else if ( selectedItem == 4 ) {
502 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
503 } else if ( selectedItem == 5 ) {
504 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
506 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
509 } else if ( [sender tag] == 2095) {
510 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
511 [(MainController *)controller showCurrentTrackInfo];
517 - (void)registerDefaults
520 NSMutableDictionary *loginWindow;
521 NSMutableArray *loginArray;
522 NSEnumerator *loginEnum;
524 ITDebugLog(@"Registering defaults.");
525 [df setObject:[NSArray arrayWithObjects:
537 nil] forKey:@"menu"];
539 [df setInteger:1200 forKey:@"appVersion"];
540 [df setInteger:5 forKey:@"SongsInAdvance"];
541 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
542 [df setBool:YES forKey:@"showArtist"];
543 [df setBool:NO forKey:@"showAlbum"];
544 [df setBool:NO forKey:@"showTime"];
546 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
547 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
548 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
549 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
550 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
551 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
552 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
553 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
554 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
555 [df setBool:YES forKey:@"showSongInfoOnChange"];
557 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
561 loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
562 loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
563 loginEnum = [loginArray objectEnumerator];
565 while ( (anItem = [loginEnum nextObject]) ) {
566 if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
570 [loginWindow release];
573 [[StatusWindowController sharedController] showSetupQueryWindow];
579 [[StatusWindow sharedWindow] setLocked:NO];
580 [[StatusWindow sharedWindow] vanish:self];
581 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
583 [self setLaunchesAtLogin:YES];
586 - (void)autoLaunchCancel
588 [[StatusWindow sharedWindow] setLocked:NO];
589 [[StatusWindow sharedWindow] vanish:self];
590 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
593 - (void)deletePressedInTableView:(NSTableView *)tableView
595 if (tableView == menuTableView) {
596 int selRow = [tableView selectedRow];
597 ITDebugLog(@"Delete pressed in menu table view.");
599 NSString *object = [myItems objectAtIndex:selRow];
601 if ([object isEqualToString:@"preferences"]) {
606 if (![object isEqualToString:@"separator"])
607 [availableItems addObject:object];
608 ITDebugLog(@"Removing object named %@", object);
609 [myItems removeObjectAtIndex:selRow];
610 [menuTableView reloadData];
611 [allTableView reloadData];
613 [self changeMenus:self];
617 - (void)resetRemotePlayerTextFields
619 if ([[NetworkController sharedController] isConnectedToServer]) {
620 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
621 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
623 [selectedPlayerTextField setStringValue:@"No shared player selected."];
624 [locationTextField setStringValue:@"-"];
628 /*************************************************************************/
630 #pragma mark HOTKEY SUPPORT METHODS
631 /*************************************************************************/
633 - (IBAction)clearHotKey:(id)sender
635 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
636 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
637 [controller setupHotKeys];
638 [hotKeysTableView reloadData];
641 - (IBAction)editHotKey:(id)sender
643 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
644 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
645 ITKeyCombo *keyCombo;
647 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
648 [controller clearHotKeys];
649 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
650 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
651 if ([panel runModal] == NSOKButton) {
652 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
654 keyCombo = [panel keyCombo];
656 //Check for duplicate key combo
657 while ( (nextKey = [keyEnumerator nextObject]) ) {
658 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
659 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
660 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
662 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
667 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
668 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
669 [controller setupHotKeys];
670 [hotKeysTableView reloadData];
671 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
673 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
677 - (void)hotKeysTableViewDoubleClicked:(id)sender
679 if ([sender clickedRow] > -1) {
680 [self editHotKey:sender];
684 /*************************************************************************/
686 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
687 /*************************************************************************/
691 ITDebugLog(@"Loading Preferences.nib.");
692 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
693 ITDebugLog(@"Failed to load Preferences.nib.");
699 - (void)setupCustomizationTables
701 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
702 ITDebugLog(@"Setting up table views.");
703 // Set the table view cells up
704 [imgCell setImageScaling:NSScaleNone];
705 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
706 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
708 // Register for drag and drop
709 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
710 @"MenuTableViewPboardType",
711 @"AllTableViewPboardType",
713 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
714 @"MenuTableViewPboardType",
715 @"AllTableViewPboardType",
719 - (void)setupMenuItems
721 NSEnumerator *itemEnum;
723 ITDebugLog(@"Setting up table view arrays.");
724 // Set the list of items you can have.
725 availableItems = [[NSMutableArray alloc] initWithObjects:
741 // Get our preferred menu
742 myItems = [[df arrayForKey:@"menu"] mutableCopy];
744 // Delete items in the availableItems array that are already part of the menu
745 itemEnum = [myItems objectEnumerator];
746 while ( (anItem = [itemEnum nextObject]) ) {
747 if (![anItem isEqualToString:@"separator"]) {
748 [availableItems removeObject:anItem];
752 // Items that show should a submenu image
753 submenuItems = [[NSArray alloc] initWithObjects:
763 NSMutableDictionary *loginwindow;
764 NSMutableArray *loginarray;
765 NSEnumerator *loginEnum;
766 NSEnumerator *keyArrayEnum;
767 NSString *serverName;
772 ITDebugLog(@"Setting up preferences UI.");
773 // Fill in the number of songs in advance to show field
774 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
776 // Fill hot key array
777 keyArrayEnum = [hotKeysArray objectEnumerator];
779 while ( (anItem = [keyArrayEnum nextObject]) ) {
780 if ([df objectForKey:anItem]) {
781 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
782 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
784 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
788 ITDebugLog(@"Setting up track info checkboxes.");
789 // Check current track info buttons
790 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
791 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
792 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
793 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
794 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
795 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
796 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
797 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
799 if ([df boolForKey:@"runScripts"]) {
800 [runScriptsCheckbox setState:NSOnState];
801 [showScriptsButton setEnabled:YES];
803 [showScriptsButton setEnabled:NO];
806 // Set the launch at login checkbox state
807 ITDebugLog(@"Setting launch at login state.");
809 loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
810 loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
812 loginEnum = [loginarray objectEnumerator];
813 while ( (anItem = [loginEnum nextObject]) ) {
814 if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
815 [launchAtLoginCheckbox setState:NSOnState];
819 // Set the launch player checkbox state
820 ITDebugLog(@"Setting launch player with MenuTunes state.");
821 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
823 // Setup the positioning controls
824 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
825 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
827 // Setup effects controls
828 // Populate the effects popups
829 [appearanceEffectPopup setAutoenablesItems:NO];
830 [vanishEffectPopup setAutoenablesItems:NO];
831 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
832 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
834 // Attempt to find the pref'd effect in the list.
835 // If it's not there, use cut/dissolve.
836 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
837 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
839 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
842 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
843 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
845 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
848 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
849 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
850 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
852 // Setup General Controls
853 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
854 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
856 if ( selectedBGStyle == ITTSWBackgroundColored ) {
857 [backgroundColorWell setEnabled:YES];
858 [backgroundColorPopup setEnabled:YES];
860 [backgroundColorWell setEnabled:NO];
861 [backgroundColorPopup setEnabled:NO];
864 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
867 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
869 [backgroundColorWell setColor:[NSColor blueColor]];
872 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
874 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
876 // Setup the sharing controls
877 if ([df boolForKey:@"enableSharing"]) {
878 [shareMenuTunesCheckbox setState:NSOnState];
879 [useSharedMenuTunesCheckbox setEnabled:NO];
880 [selectSharedPlayerButton setEnabled:NO];
881 [passwordTextField setEnabled:YES];
882 [nameTextField setEnabled:YES];
883 } else if ([df boolForKey:@"useSharedPlayer"]) {
884 [useSharedMenuTunesCheckbox setState:NSOnState];
885 [shareMenuTunesCheckbox setEnabled:NO];
886 [selectSharedPlayerButton setEnabled:YES];
889 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
891 serverName = [df stringForKey:@"sharedPlayerName"];
892 if (!serverName || [serverName length] == 0) {
893 serverName = @"MenuTunes Shared Player";
895 [nameTextField setStringValue:serverName];
897 [selectPlayerBox setContentView:zeroConfView];
898 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
899 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
901 [passwordTextField setStringValue:@""];
903 if ([df stringForKey:@"sharedPlayerHost"]) {
904 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
907 if ([[NetworkController sharedController] isConnectedToServer]) {
908 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
909 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
911 [selectedPlayerTextField setStringValue:@"No shared player selected."];
912 [locationTextField setStringValue:@"-"];
916 - (void)setStatusWindowEntryEffect:(Class)effectClass
918 StatusWindow *sw = [StatusWindow sharedWindow];
920 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
921 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
923 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
924 [[sw entryEffect] setEffectTime:time];
927 - (void)setStatusWindowExitEffect:(Class)effectClass
929 StatusWindow *sw = [StatusWindow sharedWindow];
931 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
932 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
934 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
935 [[sw exitEffect] setEffectTime:time];
938 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
940 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
941 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
944 [backgroundColorWell setColor:color];
948 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
950 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
953 [appearanceEffectPopup removeAllItems];
954 [vanishEffectPopup removeAllItems];
956 while ( (anItem = [effectEnum nextObject]) ) {
957 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
958 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
960 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
961 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
963 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
964 [[appearanceEffectPopup lastItem] setEnabled:YES];
965 [[vanishEffectPopup lastItem] setEnabled:YES];
967 [[appearanceEffectPopup lastItem] setEnabled:NO];
968 [[vanishEffectPopup lastItem] setEnabled:NO];
974 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
978 if ( vPos == ITWindowPositionTop ) {
979 if ( hPos == ITWindowPositionLeft ) {
980 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
981 } else if ( hPos == ITWindowPositionCenter ) {
982 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
983 } else if ( hPos == ITWindowPositionRight ) {
984 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
986 } else if ( vPos == ITWindowPositionMiddle ) {
987 if ( hPos == ITWindowPositionLeft ) {
988 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
989 } else if ( hPos == ITWindowPositionCenter ) {
990 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
991 } else if ( hPos == ITWindowPositionRight ) {
992 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
994 } else if ( vPos == ITWindowPositionBottom ) {
995 if ( hPos == ITWindowPositionLeft ) {
996 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
997 } else if ( hPos == ITWindowPositionCenter ) {
998 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
999 } else if ( hPos == ITWindowPositionRight ) {
1000 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1007 - (IBAction)changeMenus:(id)sender
1009 ITDebugLog(@"Synchronizing menus");
1010 [df setObject:myItems forKey:@"menu"];
1013 //If we're connected over a network, refresh the menu immediately
1014 if ([[NetworkController sharedController] isConnectedToServer]) {
1015 [controller timerUpdate];
1019 - (void)setLaunchesAtLogin:(BOOL)flag
1021 NSMutableDictionary *loginwindow;
1022 NSMutableArray *loginarray;
1023 ITDebugLog(@"Setting launches at login: %i", flag);
1025 loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
1026 loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
1029 NSDictionary *itemDict = [NSDictionary dictionaryWithObjectsAndKeys:
1030 [[NSBundle mainBundle] bundlePath], @"Path",
1031 [NSNumber numberWithInt:0], @"Hide", nil];
1032 [loginarray addObject:itemDict];
1035 for (i = 0; i < [loginarray count]; i++) {
1036 NSDictionary *tempDict = [loginarray objectAtIndex:i];
1037 if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
1038 [loginarray removeObjectAtIndex:i];
1043 [df setPersistentDomain:loginwindow forName:@"loginwindow"];
1045 [loginwindow release];
1046 ITDebugLog(@"Finished setting launches at login.");
1050 /*************************************************************************/
1052 #pragma mark NSWindow DELEGATE METHODS
1053 /*************************************************************************/
1055 - (void)windowWillClose:(NSNotification *)note
1057 [(MainController *)controller closePreferences];
1060 /*************************************************************************/
1062 #pragma mark NSTextField DELEGATE METHODS
1063 /*************************************************************************/
1065 - (void)controlTextDidChange:(NSNotification*)note
1067 if ([note object] == hostTextField) {
1068 if ([[hostTextField stringValue] length] == 0) {
1069 [sharingPanelOKButton setEnabled:NO];
1071 [sharingPanelOKButton setEnabled:YES];
1076 /*************************************************************************/
1078 #pragma mark NSTableView DATASOURCE METHODS
1079 /*************************************************************************/
1081 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1083 if (aTableView == menuTableView) {
1084 return [myItems count];
1085 } else if (aTableView == allTableView) {
1086 return [availableItems count];
1087 } else if (aTableView == hotKeysTableView) {
1088 return [hotKeysArray count];
1090 return [[[NetworkController sharedController] remoteServices] count];
1094 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1096 if (aTableView == menuTableView) {
1097 NSString *object = [myItems objectAtIndex:rowIndex];
1098 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1099 if ([object isEqualToString:@"showPlayer"]) {
1100 NSString *string = nil;
1102 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1104 [controller networkError:localException];
1108 return NSLocalizedString(object, @"ERROR");
1110 if ([submenuItems containsObject:object])
1112 return [NSImage imageNamed:@"submenu"];
1117 } else if (aTableView == allTableView) {
1118 NSString *object = [availableItems objectAtIndex:rowIndex];
1119 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1120 if ([object isEqualToString:@"showPlayer"]) {
1121 NSString *string = nil;
1123 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1125 [controller networkError:localException];
1129 return NSLocalizedString(object, @"ERROR");
1131 if ([submenuItems containsObject:object]) {
1132 return [NSImage imageNamed:@"submenu"];
1137 } else if (aTableView == hotKeysTableView) {
1138 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1139 return [hotKeyNamesArray objectAtIndex:rowIndex];
1141 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1144 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1148 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1150 if (tableView == menuTableView) {
1151 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1152 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1156 if (tableView == allTableView) {
1157 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1158 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1164 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1168 NSString *dragData, *temp;
1170 pb = [info draggingPasteboard];
1172 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1173 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1174 dragRow = [dragData intValue];
1175 temp = [myItems objectAtIndex:dragRow];
1177 if (tableView == menuTableView) {
1178 [myItems insertObject:temp atIndex:row];
1179 if (row > dragRow) {
1180 [myItems removeObjectAtIndex:dragRow];
1182 [myItems removeObjectAtIndex:dragRow + 1];
1184 } else if (tableView == allTableView) {
1185 if (![temp isEqualToString:@"separator"]) {
1186 [availableItems addObject:temp];
1188 [myItems removeObjectAtIndex:dragRow];
1190 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1191 dragData = [pb stringForType:@"AllTableViewPboardType"];
1192 dragRow = [dragData intValue];
1193 temp = [availableItems objectAtIndex:dragRow];
1195 [myItems insertObject:temp atIndex:row];
1197 if (![temp isEqualToString:@"separator"]) {
1198 [availableItems removeObjectAtIndex:dragRow];
1202 [menuTableView reloadData];
1203 [allTableView reloadData];
1204 [self changeMenus:self];
1208 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1210 if (tableView == allTableView) {
1211 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1212 return NSDragOperationNone;
1215 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1216 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1217 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1218 return NSDragOperationNone;
1222 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1223 return NSDragOperationGeneric;
1226 if (operation == NSTableViewDropOn || row == -1)
1228 return NSDragOperationNone;
1230 return NSDragOperationGeneric;
1234 /*************************************************************************/
1236 #pragma mark DEALLOCATION METHODS
1237 /*************************************************************************/
1241 [hotKeysArray release];
1242 [hotKeysDictionary release];
1243 [effectClasses release];
1244 [menuTableView setDataSource:nil];
1245 [allTableView setDataSource:nil];
1246 [controller release];
1247 [availableItems release];
1248 [submenuItems release];