1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "MenuController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import "StatusWindow.h"
7 #import "StatusWindowController.h"
8 #import "CustomMenuTableView.h"
10 #import <Security/Security.h>
12 #import <netinet/in.h>
14 #import <openssl/sha.h>
18 #import <ITKit/ITLoginItem.h>
20 #import <ITKit/ITHotKeyCenter.h>
21 #import <ITKit/ITKeyCombo.h>
22 #import <ITKit/ITKeyComboPanel.h>
23 #import <ITKit/ITWindowPositioning.h>
24 #import <ITKit/ITKeyBroadcaster.h>
26 #import <ITKit/ITTSWBackgroundView.h>
27 #import <ITKit/ITWindowEffect.h>
28 #import <ITKit/ITCutWindowEffect.h>
29 #import <ITKit/ITDissolveWindowEffect.h>
30 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
31 #import <ITKit/ITSlideVerticallyWindowEffect.h>
32 #import <ITKit/ITPivotWindowEffect.h>
35 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
36 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE "MenuTunes: Audioscrobbler"
37 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
39 /*************************************************************************/
41 #pragma mark PRIVATE INTERFACE
42 /*************************************************************************/
44 @interface PreferencesController (Private)
45 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
46 + (BOOL)keychainItemExistsForUser:(NSString *)user;
47 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
48 + (BOOL)deleteKeychainItemForUser:(NSString *)user;
49 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
52 - (void)setupCustomizationTables;
53 - (void)setupMenuItems;
55 - (void)setupScreenPopup;
56 - (void)setStatusWindowEntryEffect:(Class)effectClass;
57 - (void)setStatusWindowExitEffect:(Class)effectClass;
58 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
59 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
60 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
61 - (IBAction)changeMenus:(id)sender;
65 @implementation PreferencesController
68 /*************************************************************************/
70 #pragma mark STATIC VARIABLES
71 /*************************************************************************/
73 static PreferencesController *prefs = nil;
75 /*************************************************************************/
77 #pragma mark STATIC KEYCHAIN SUPPORT METHODS
78 /*************************************************************************/
80 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user
82 SecKeychainSearchRef search;
83 SecKeychainItemRef item;
85 SecKeychainAttribute attributes[3];
86 SecKeychainAttributeList list;
88 ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
89 attributes[0].tag = kSecAccountItemAttr;
90 attributes[0].data = (char *)[user UTF8String];
91 attributes[0].length = [user length];
92 attributes[1].tag = kSecDescriptionItemAttr;
93 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
94 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
95 attributes[2].tag = kSecLabelItemAttr;
96 attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
97 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
99 list.attr = attributes;
101 status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
103 if (status != noErr) {
104 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
107 status = SecKeychainSearchCopyNext(search, &item);
109 if (status != noErr) {
110 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
118 + (BOOL)keychainItemExistsForUser:(NSString *)user
120 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
121 BOOL exists = (item != nil);
128 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
130 SecKeychainItemRef item;
132 SecKeychainAttribute attributes[3];
133 SecKeychainAttributeList list;
135 ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
136 attributes[0].tag = kSecAccountItemAttr;
137 attributes[0].data = (char *)[user UTF8String];
138 attributes[0].length = [user length];
139 attributes[1].tag = kSecDescriptionItemAttr;
140 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
141 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
142 attributes[2].tag = kSecLabelItemAttr;
143 attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
144 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
146 list.attr = attributes;
148 status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
149 if (status != noErr) {
150 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
152 return (status == noErr);
155 + (BOOL)deleteKeychainItemForUser:(NSString *)user
157 OSStatus status = errSecNotAvailable;
158 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
160 status = SecKeychainItemDelete(item);
161 if (status != noErr) {
162 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
166 return (status == noErr);
169 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
171 OSStatus status = errSecNotAvailable;
172 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
174 status = SecKeychainItemModifyContent(item, NULL, [password length], [password cString]);
175 if (status != noErr) {
176 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
180 return (status == noErr);
183 + (NSString *)getKeychainItemPasswordForUser:(NSString *)user
185 OSStatus status = errSecNotAvailable;
186 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
187 NSString *pass = nil;
191 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
192 if (status != noErr) {
193 ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
195 if ([NSString respondsToSelector:@selector(stringWithCString:encoding:)]) {
196 pass = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
198 pass = [NSString stringWithCString:buffer];
201 if (status != noErr) {
202 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
209 /*************************************************************************/
211 #pragma mark INITIALIZATION METHODS
212 /*************************************************************************/
214 + (PreferencesController *)sharedPrefs;
217 prefs = [[self alloc] init];
224 if ( (self = [super init]) ) {
225 ITDebugLog(@"Preferences initialized.");
226 df = [[NSUserDefaults standardUserDefaults] retain];
228 effectClasses = [[ITWindowEffect effectClasses] retain];
230 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
244 @"ToggleShufflability",
254 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
268 @"Toggle Song Included In Shuffle",
269 @"Pop-up status menu",
270 [NSString stringWithUTF8String:"Set Rating: ☆☆☆☆☆"],
271 [NSString stringWithUTF8String:"Set Rating: ★☆☆☆☆"],
272 [NSString stringWithUTF8String:"Set Rating: ★★☆☆☆"],
273 [NSString stringWithUTF8String:"Set Rating: ★★★☆☆"],
274 [NSString stringWithUTF8String:"Set Rating: ★★★★☆"],
275 [NSString stringWithUTF8String:"Set Rating: ★★★★★"],
277 hotKeysDictionary = [[NSMutableDictionary alloc] init];
280 [self setupWindow]; // Load in the nib, and perform any initial setup.
281 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
287 /*************************************************************************/
289 #pragma mark ACCESSOR METHODS
290 /*************************************************************************/
297 - (void)setController:(id)object
299 [controller autorelease];
300 controller = [object retain];
304 /*************************************************************************/
306 #pragma mark INSTANCE METHODS
307 /*************************************************************************/
309 - (BOOL)showPasswordPanel
311 [passwordPanel setLevel:NSStatusWindowLevel];
312 [passwordPanelOKButton setTitle:@"Connect"];
313 [passwordPanelTitle setStringValue:@"Password Required"];
314 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
315 [passwordPanel setLevel:NSStatusWindowLevel];
316 [NSApp activateIgnoringOtherApps:YES];
317 [passwordPanel center];
318 [passwordPanel orderFrontRegardless];
319 [passwordPanel makeKeyWindow];
320 if ([NSApp runModalForWindow:passwordPanel]) {
327 - (BOOL)showInvalidPasswordPanel
329 [passwordPanel setLevel:NSStatusWindowLevel];
330 [passwordPanelOKButton setTitle:@"Retry"];
331 [passwordPanelTitle setStringValue:@"Invalid Password"];
332 [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]]];
333 [passwordPanel setLevel:NSStatusWindowLevel];
334 [NSApp activateIgnoringOtherApps:YES];
335 [passwordPanel center];
336 [passwordPanel orderFrontRegardless];
337 [passwordPanel makeKeyWindow];
338 if ([NSApp runModalForWindow:passwordPanel]) {
345 - (IBAction)showPrefsWindow:(id)sender
347 ITDebugLog(@"Showing preferences window.");
348 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
349 ITDebugLog(@"Window doesn't exist, initial setup.");
350 [self setupCustomizationTables]; // Setup the DnD manu config tables.
351 [self setupMenuItems]; // Setup the arrays of menu items
352 [self setupUI]; // Sets up additional UI
353 [window setDelegate:self];
354 [menuTableView reloadData];
355 [hotKeysTableView reloadData];
356 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
358 //Change the launch player checkbox to the proper name
360 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
362 [controller networkError:localException];
366 [self resetRemotePlayerTextFields];
367 [launchAtLoginCheckbox becomeFirstResponder];
368 [NSApp activateIgnoringOtherApps:YES];
369 if (![window isVisible]) {
372 [window orderFrontRegardless];
373 [window makeKeyWindow];
376 - (IBAction)changeGeneralSetting:(id)sender
378 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
379 if ( [sender tag] == 1010) {
380 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
381 } else if ( [sender tag] == 1020) {
382 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
383 } else if ( [sender tag] == 1030) {
384 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
385 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
386 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
388 } else if ( [sender tag] == 1040) {
389 // This will not be executed. Song info always shows the title of the song.
390 // [df setBool:SENDER_STATE forKey:@"showName"];
391 } else if ( [sender tag] == 1050) {
392 [df setBool:SENDER_STATE forKey:@"showArtist"];
393 } else if ( [sender tag] == 1055) {
394 [df setBool:SENDER_STATE forKey:@"showComposer"];
395 } else if ( [sender tag] == 1060) {
396 [df setBool:SENDER_STATE forKey:@"showAlbum"];
397 } else if ( [sender tag] == 1070) {
398 [df setBool:SENDER_STATE forKey:@"showTime"];
399 } else if ( [sender tag] == 1080) {
400 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
401 } else if ( [sender tag] == 1085) {
402 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
403 } else if ( [sender tag] == 1090) {
404 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
405 } else if ( [sender tag] == 1100) {
406 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
407 } else if ( [sender tag] == 1110) {
408 [df setBool:SENDER_STATE forKey:@"runScripts"];
410 [runScriptsCheckbox setState:NSOnState];
411 [showScriptsButton setEnabled:YES];
413 [showScriptsButton setEnabled:NO];
415 } else if ( [sender tag] == 1120) {
416 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
417 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
418 } else if ( [sender tag] == 6010) {
419 //Toggle the other Audioscrobbler options
420 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
421 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
422 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
423 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
424 } else if ( [sender tag ] == 6015) {
425 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
426 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
427 if ([newAccount length] == 0) {
428 [PreferencesController deleteKeychainItemForUser:currentAccount];
429 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
430 [df setObject:newAccount forKey:@"audioscrobblerUser"];
431 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
432 //Delete the current keychain item if there is one
433 [PreferencesController deleteKeychainItemForUser:currentAccount];
435 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
437 } else if ( [sender tag ] == 6030) {
438 //Here we set the password for an existing keychain item or we create a new keychain item.
439 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
440 NSString *account = [df stringForKey:@"audioscrobblerUser"];
441 if ([PreferencesController keychainItemExistsForUser:account]) {
442 //Update the current keychain item
443 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
444 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
445 //Create a new keychain item
446 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
449 } else if ( [sender tag] == 6045) {
450 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
455 - (IBAction)changeSharingSetting:(id)sender
457 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
458 if ( [sender tag] == 5010 ) {
459 BOOL state = SENDER_STATE;
460 [df setBool:state forKey:@"enableSharing"];
461 //Disable/enable the use of shared player options
462 [useSharedMenuTunesCheckbox setEnabled:!state];
463 [passwordTextField setEnabled:state];
464 [nameTextField setEnabled:state];
465 [selectSharedPlayerButton setEnabled:NO];
466 [controller setServerStatus:state]; //Set server status
467 } else if ( [sender tag] == 5015 ) {
468 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
469 [[NetworkController sharedController] resetServerName];
470 } else if ( [sender tag] == 5030 ) {
471 //Set the server password
472 const char *instring = [[sender stringValue] UTF8String];
473 const char *password = "p4s5w0rdMT1.2";
475 NSData *hashedPass, *passwordStringHash;
476 if ([[sender stringValue] length] == 0) {
477 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
480 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
481 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
482 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
483 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
484 if (![hashedPass isEqualToData:passwordStringHash]) {
485 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
486 [sender setStringValue:@"p4s5w0rdMT1.2"];
488 } else if ( [sender tag] == 5040 ) {
489 BOOL state = SENDER_STATE;
490 [df setBool:state forKey:@"useSharedPlayer"];
491 //Disable/enable the use of sharing options
492 [shareMenuTunesCheckbox setEnabled:!state];
493 [passwordTextField setEnabled:NO];
494 [nameTextField setEnabled:NO];
495 [selectSharedPlayerButton setEnabled:state];
497 if (state && ([controller connectToServer] == 1)) {
498 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
499 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
501 [selectedPlayerTextField setStringValue:@"No shared player selected."];
502 [locationTextField setStringValue:@"-"];
503 if ([[NetworkController sharedController] isConnectedToServer]) {
504 [controller disconnectFromServer];
508 } else if ( [sender tag] == 5050 ) {
509 //If no player is selected in the table view, turn off OK button.
510 if ([sender clickedRow] == -1 ) {
511 [sharingPanelOKButton setEnabled:NO];
513 [sharingPanelOKButton setEnabled:YES];
515 } else if ( [sender tag] == 5051 ) {
516 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
517 } else if ( [sender tag] == 5060 ) {
518 //Set OK button state
519 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
520 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
521 [sharingPanelOKButton setEnabled:NO];
523 [sharingPanelOKButton setEnabled:YES];
525 //Show selection sheet
526 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
527 } else if ( [sender tag] == 5100 ) {
529 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
530 NSRect frame = [selectPlayerSheet frame];
531 frame.origin.y -= 58;
532 frame.size.height = 273;
533 if ([sharingTableView selectedRow] == -1) {
534 [sharingPanelOKButton setEnabled:NO];
536 [selectPlayerBox setContentView:zeroConfView];
537 [selectPlayerSheet setFrame:frame display:YES animate:YES];
538 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
539 NSRect frame = [selectPlayerSheet frame];
540 frame.origin.y += 58;
541 frame.size.height = 215;
542 if ([[hostTextField stringValue] length] == 0) {
543 [sharingPanelOKButton setEnabled:NO];
545 [sharingPanelOKButton setEnabled:YES];
547 [selectPlayerBox setContentView:manualView];
548 [selectPlayerSheet setFrame:frame display:YES animate:YES];
549 [hostTextField selectText:nil];
551 } else if ( [sender tag] == 5150 ) {
552 const char *instring = [[sender stringValue] UTF8String];
554 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
555 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
556 } else if ( [sender tag] == 5110 ) {
558 [NSApp endSheet:selectPlayerSheet];
559 [selectPlayerSheet orderOut:nil];
560 if ([selectPlayerBox contentView] == manualView) {
561 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
564 } else if ( [sender tag] == 5120 ) {
566 [NSApp endSheet:selectPlayerSheet];
567 [selectPlayerSheet orderOut:nil];
569 [self changeSharingSetting:clientPasswordTextField];
571 if ([selectPlayerBox contentView] == manualView) {
572 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
574 if ([sharingTableView selectedRow] > -1) {
575 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
579 if ([controller connectToServer] == 1) {
580 [useSharedMenuTunesCheckbox setState:NSOnState];
581 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
582 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
584 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);
586 } else if ( [sender tag] == 6010 ) {
587 //Cancel password entry
588 [passwordPanel orderOut:nil];
589 [NSApp stopModalWithCode:0];
590 } else if ( [sender tag] == 6020 ) {
591 //OK password entry, retry connect
592 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
594 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
595 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
596 [passwordPanel orderOut:nil];
597 [NSApp stopModalWithCode:1];
602 - (IBAction)changeStatusWindowSetting:(id)sender
604 StatusWindow *sw = [StatusWindow sharedWindow];
605 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
607 if ( [sender tag] == 2010) {
609 BOOL entryEffectValid = YES;
610 BOOL exitEffectValid = YES;
612 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
613 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
614 [sw setVerticalPosition:[sender selectedRow]];
615 [sw setHorizontalPosition:[sender selectedColumn]];
617 // Enable/disable the items in the popups.
618 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
619 horizontalPosition:[sw horizontalPosition]];
621 // Make sure the effects support the new position.
622 entryEffectValid = ( [self effect:[[sw entryEffect] class]
623 supportsVerticalPosition:[sw verticalPosition]
624 withHorizontalPosition:[sw horizontalPosition]] );
625 exitEffectValid = ( [self effect:[[sw exitEffect] class]
626 supportsVerticalPosition:[sw verticalPosition]
627 withHorizontalPosition:[sw horizontalPosition]] );
629 if ( ! entryEffectValid ) {
630 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
631 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
633 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
636 if ( ! exitEffectValid ) {
637 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
638 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
640 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
643 [(MainController *)controller showCurrentTrackInfo];
645 } else if ( [sender tag] == 2020) {
647 // Update screen selection.
648 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
649 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
650 [(MainController *)controller showCurrentTrackInfo];
652 } else if ( [sender tag] == 2030) {
654 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
655 [(MainController *)controller showCurrentTrackInfo];
657 } else if ( [sender tag] == 2040) {
659 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
660 [(MainController *)controller showCurrentTrackInfo];
662 } else if ( [sender tag] == 2050) {
663 float newTime = ( -([sender floatValue]) );
664 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
665 [[sw entryEffect] setEffectTime:newTime];
666 } else if ( [sender tag] == 2060) {
667 float newTime = ( -([sender floatValue]) );
668 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
669 [[sw exitEffect] setEffectTime:newTime];
670 } else if ( [sender tag] == 2070) {
671 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
672 [sw setExitDelay:[sender floatValue]];
673 } else if ( [sender tag] == 2080) {
674 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
675 } else if ( [sender tag] == 2090) {
677 int setting = [sender indexOfSelectedItem];
679 if ( setting == 0 ) {
680 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
681 [backgroundColorWell setEnabled:NO];
682 [backgroundColorPopup setEnabled:NO];
683 } else if ( setting == 1 ) {
684 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
685 [backgroundColorWell setEnabled:NO];
686 [backgroundColorPopup setEnabled:NO];
687 } else if ( setting == 2 ) {
688 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
689 [backgroundColorWell setEnabled:YES];
690 [backgroundColorPopup setEnabled:YES];
693 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
694 [(MainController *)controller showCurrentTrackInfo];
696 } else if ( [sender tag] == 2091) {
697 [self setCustomColor:[sender color] updateWell:NO];
698 [(MainController *)controller showCurrentTrackInfo];
699 } else if ( [sender tag] == 2092) {
701 int selectedItem = [sender indexOfSelectedItem];
703 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
704 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
705 } else if ( selectedItem == 2 ) {
706 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
707 } else if ( selectedItem == 3 ) {
708 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
709 } else if ( selectedItem == 4 ) {
710 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
711 } else if ( selectedItem == 5 ) {
712 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
714 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
716 [(MainController *)controller showCurrentTrackInfo];
718 } else if ( [sender tag] == 2095) {
719 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
720 [(MainController *)controller showCurrentTrackInfo];
726 - (void)registerDefaults
728 ITDebugLog(@"Registering defaults.");
729 [df setObject:[NSArray arrayWithObjects:
742 nil] forKey:@"menu"];
744 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
745 [df setInteger:5 forKey:@"SongsInAdvance"];
746 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
747 [df setBool:YES forKey:@"showArtist"];
748 [df setBool:YES forKey:@"showAlbumArtwork"];
749 [df setBool:NO forKey:@"showAlbum"];
750 [df setBool:NO forKey:@"showComposer"];
751 [df setBool:NO forKey:@"showTime"];
752 [df setBool:NO forKey:@"showToolTip"];
754 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
755 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
756 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
757 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
758 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
759 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
760 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
761 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
762 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
763 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
764 [df setBool:YES forKey:@"showSongInfoOnChange"];
766 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
770 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
771 [[StatusWindowController sharedController] showSetupQueryWindow];
777 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
778 [[StatusWindow sharedWindow] vanish:self];
779 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
781 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
784 - (void)autoLaunchCancel
786 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
787 [[StatusWindow sharedWindow] vanish:self];
788 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
791 - (void)deletePressedInTableView:(NSTableView *)tableView
793 if (tableView == menuTableView) {
794 int selRow = [tableView selectedRow];
795 ITDebugLog(@"Delete pressed in menu table view.");
797 NSString *object = [myItems objectAtIndex:selRow];
799 if ([object isEqualToString:@"preferences"]) {
804 if (![object isEqualToString:@"separator"])
805 [availableItems addObject:object];
806 ITDebugLog(@"Removing object named %@", object);
807 [myItems removeObjectAtIndex:selRow];
808 [menuTableView reloadData];
809 [allTableView reloadData];
811 [self changeMenus:self];
815 - (void)resetRemotePlayerTextFields
817 if ([[NetworkController sharedController] isConnectedToServer]) {
818 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
819 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
821 [selectedPlayerTextField setStringValue:@"No shared player selected."];
822 [locationTextField setStringValue:@"-"];
826 /*************************************************************************/
828 #pragma mark HOTKEY SUPPORT METHODS
829 /*************************************************************************/
831 - (IBAction)clearHotKey:(id)sender
833 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
834 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
835 [controller setupHotKeys];
836 [hotKeysTableView reloadData];
839 - (IBAction)editHotKey:(id)sender
841 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
842 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
843 ITKeyCombo *keyCombo;
845 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
846 [controller clearHotKeys];
847 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
848 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
849 if ([panel runModal] == NSOKButton) {
850 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
852 keyCombo = [panel keyCombo];
854 //Check for duplicate key combo
855 while ( (nextKey = [keyEnumerator nextObject]) ) {
856 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
857 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
858 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
860 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
865 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
866 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
867 [controller setupHotKeys];
868 [hotKeysTableView reloadData];
869 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
871 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
875 - (void)hotKeysTableViewDoubleClicked:(id)sender
877 if ([sender clickedRow] > -1) {
878 [self editHotKey:sender];
882 /*************************************************************************/
884 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
885 /*************************************************************************/
889 ITDebugLog(@"Loading Preferences.nib.");
890 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
891 ITDebugLog(@"Failed to load Preferences.nib.");
897 - (void)setupCustomizationTables
899 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
900 ITDebugLog(@"Setting up table views.");
901 // Set the table view cells up
902 [imgCell setImageScaling:NSScaleNone];
903 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
904 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
906 // Register for drag and drop
907 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
908 @"MenuTableViewPboardType",
909 @"AllTableViewPboardType",
911 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
912 @"MenuTableViewPboardType",
913 @"AllTableViewPboardType",
917 - (void)setupMenuItems
919 NSEnumerator *itemEnum;
921 ITDebugLog(@"Setting up table view arrays.");
922 // Set the list of items you can have.
923 availableItems = [[NSMutableArray alloc] initWithObjects:
942 // Get our preferred menu
943 myItems = [[df arrayForKey:@"menu"] mutableCopy];
945 // Delete items in the availableItems array that are already part of the menu
946 itemEnum = [myItems objectEnumerator];
947 while ( (anItem = [itemEnum nextObject]) ) {
948 if (![anItem isEqualToString:@"separator"]) {
949 [availableItems removeObject:anItem];
953 // Items that show should a submenu image
954 submenuItems = [[NSArray alloc] initWithObjects:
966 NSEnumerator *keyArrayEnum;
967 NSString *serverName;
972 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
974 ITDebugLog(@"Setting up preferences UI.");
975 // Fill in the number of songs in advance to show field
976 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
978 // Fill hot key array
979 keyArrayEnum = [hotKeysArray objectEnumerator];
981 while ( (anItem = [keyArrayEnum nextObject]) ) {
982 if ([df objectForKey:anItem]) {
983 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
984 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
986 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
990 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
991 [self setupScreenPopup];
993 ITDebugLog(@"Setting up track info checkboxes.");
994 // Check current track info buttons
995 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
996 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
997 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
998 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
999 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1000 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1001 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1002 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1003 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1004 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1006 if ([df boolForKey:@"runScripts"]) {
1007 [runScriptsCheckbox setState:NSOnState];
1008 [showScriptsButton setEnabled:YES];
1010 [showScriptsButton setEnabled:NO];
1013 // Set the launch at login checkbox state
1014 ITDebugLog(@"Setting launch at login state.");
1015 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1016 [launchAtLoginCheckbox setState:NSOnState];
1019 // Set the launch player checkbox state
1020 ITDebugLog(@"Setting launch player with MenuTunes state.");
1021 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1023 // Setup the positioning controls
1024 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1025 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1027 // Setup effects controls
1028 // Populate the effects popups
1029 [appearanceEffectPopup setAutoenablesItems:NO];
1030 [vanishEffectPopup setAutoenablesItems:NO];
1031 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1032 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1034 // Attempt to find the pref'd effect in the list.
1035 // If it's not there, use cut/dissolve.
1036 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1037 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1039 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1042 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1043 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1045 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1048 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1049 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1050 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1052 // Setup General Controls
1053 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1054 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1056 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1057 [backgroundColorWell setEnabled:YES];
1058 [backgroundColorPopup setEnabled:YES];
1060 [backgroundColorWell setEnabled:NO];
1061 [backgroundColorPopup setEnabled:NO];
1064 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1067 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1069 [backgroundColorWell setColor:[NSColor blueColor]];
1072 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1074 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1076 // Setup the sharing controls
1077 if ([df boolForKey:@"enableSharing"]) {
1078 [shareMenuTunesCheckbox setState:NSOnState];
1079 [useSharedMenuTunesCheckbox setEnabled:NO];
1080 [selectSharedPlayerButton setEnabled:NO];
1081 [passwordTextField setEnabled:YES];
1082 [nameTextField setEnabled:YES];
1083 } else if ([df boolForKey:@"useSharedPlayer"]) {
1084 [useSharedMenuTunesCheckbox setState:NSOnState];
1085 [shareMenuTunesCheckbox setEnabled:NO];
1086 [selectSharedPlayerButton setEnabled:YES];
1089 //Setup the Audioscrobbler controls
1090 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1091 [audioscrobblerEnabledCheckbox setState:NSOnState];
1092 [audioscrobblerUserTextField setEnabled:YES];
1093 [audioscrobblerPasswordTextField setEnabled:YES];
1094 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1096 [audioscrobblerUserTextField setEnabled:NO];
1097 [audioscrobblerPasswordTextField setEnabled:NO];
1098 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1100 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1101 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1102 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1103 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1104 if (password != nil) {
1105 [audioscrobblerPasswordTextField setStringValue:password];
1109 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1111 serverName = [df stringForKey:@"sharedPlayerName"];
1112 if (!serverName || [serverName length] == 0) {
1113 serverName = @"MenuTunes Shared Player";
1115 [nameTextField setStringValue:serverName];
1117 [selectPlayerBox setContentView:zeroConfView];
1118 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1119 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1121 [passwordTextField setStringValue:@""];
1123 if ([df stringForKey:@"sharedPlayerHost"]) {
1124 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1127 if ([[NetworkController sharedController] isConnectedToServer]) {
1128 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1129 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1131 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1132 [locationTextField setStringValue:@"-"];
1136 - (void)setupScreenPopup
1138 ITDebugLog(@"Setting up screen popup");
1139 NSArray *screens = [NSScreen screens];
1140 if ([screens count] > 1) {
1141 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1142 [screenPopup setEnabled:YES];
1143 for (i = 0; i < [screens count]; i++) {
1144 NSScreen *screen = [screens objectAtIndex:i];
1145 if (![screen isEqual:[NSScreen mainScreen]]) {
1146 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1149 [screenPopup selectItemAtIndex:index];
1150 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1152 while ([screenPopup numberOfItems] > 1) {
1153 [screenPopup removeItemAtIndex:1];
1155 [screenPopup setEnabled:NO];
1156 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1160 - (void)setStatusWindowEntryEffect:(Class)effectClass
1162 StatusWindow *sw = [StatusWindow sharedWindow];
1164 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1165 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1167 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1168 [[sw entryEffect] setEffectTime:time];
1171 - (void)setStatusWindowExitEffect:(Class)effectClass
1173 StatusWindow *sw = [StatusWindow sharedWindow];
1175 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1176 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1178 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1179 [[sw exitEffect] setEffectTime:time];
1182 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1184 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1185 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1188 [backgroundColorWell setColor:color];
1192 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1194 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1197 [appearanceEffectPopup removeAllItems];
1198 [vanishEffectPopup removeAllItems];
1200 while ( (anItem = [effectEnum nextObject]) ) {
1201 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1202 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1204 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1205 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1207 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1208 [[appearanceEffectPopup lastItem] setEnabled:YES];
1209 [[vanishEffectPopup lastItem] setEnabled:YES];
1211 [[appearanceEffectPopup lastItem] setEnabled:NO];
1212 [[vanishEffectPopup lastItem] setEnabled:NO];
1218 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1222 if ( vPos == ITWindowPositionTop ) {
1223 if ( hPos == ITWindowPositionLeft ) {
1224 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1225 } else if ( hPos == ITWindowPositionCenter ) {
1226 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1227 } else if ( hPos == ITWindowPositionRight ) {
1228 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1230 } else if ( vPos == ITWindowPositionMiddle ) {
1231 if ( hPos == ITWindowPositionLeft ) {
1232 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1233 } else if ( hPos == ITWindowPositionCenter ) {
1234 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1235 } else if ( hPos == ITWindowPositionRight ) {
1236 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1238 } else if ( vPos == ITWindowPositionBottom ) {
1239 if ( hPos == ITWindowPositionLeft ) {
1240 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1241 } else if ( hPos == ITWindowPositionCenter ) {
1242 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1243 } else if ( hPos == ITWindowPositionRight ) {
1244 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1251 - (IBAction)changeMenus:(id)sender
1253 ITDebugLog(@"Synchronizing menus");
1254 [df setObject:myItems forKey:@"menu"];
1257 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1259 //If we're connected over a network, refresh the menu immediately
1260 if ([[NetworkController sharedController] isConnectedToServer]) {
1261 [controller timerUpdate];
1266 /*************************************************************************/
1268 #pragma mark NSWindow DELEGATE METHODS
1269 /*************************************************************************/
1271 - (void)windowWillClose:(NSNotification *)note
1273 [(MainController *)controller closePreferences];
1276 /*************************************************************************/
1278 #pragma mark NSTextField DELEGATE METHODS
1279 /*************************************************************************/
1281 - (void)controlTextDidChange:(NSNotification*)note
1283 if ([note object] == hostTextField) {
1284 if ([[hostTextField stringValue] length] == 0) {
1285 [sharingPanelOKButton setEnabled:NO];
1287 [sharingPanelOKButton setEnabled:YES];
1292 /*************************************************************************/
1294 #pragma mark NSTableView DATASOURCE METHODS
1295 /*************************************************************************/
1297 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1299 if (aTableView == menuTableView) {
1300 return [myItems count];
1301 } else if (aTableView == allTableView) {
1302 return [availableItems count];
1303 } else if (aTableView == hotKeysTableView) {
1304 return [hotKeysArray count];
1306 return [[[NetworkController sharedController] remoteServices] count];
1310 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1312 if (aTableView == menuTableView) {
1313 NSString *object = [myItems objectAtIndex:rowIndex];
1314 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1315 if ([object isEqualToString:@"showPlayer"]) {
1316 NSString *string = nil;
1318 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1320 [controller networkError:localException];
1324 return NSLocalizedString(object, @"ERROR");
1326 if ([submenuItems containsObject:object])
1328 return [NSImage imageNamed:@"submenu"];
1333 } else if (aTableView == allTableView) {
1334 NSString *object = [availableItems objectAtIndex:rowIndex];
1335 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1336 if ([object isEqualToString:@"showPlayer"]) {
1337 NSString *string = nil;
1339 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1341 [controller networkError:localException];
1345 return NSLocalizedString(object, @"ERROR");
1347 if ([submenuItems containsObject:object]) {
1348 return [NSImage imageNamed:@"submenu"];
1353 } else if (aTableView == hotKeysTableView) {
1354 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1355 return [hotKeyNamesArray objectAtIndex:rowIndex];
1357 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1360 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1364 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1366 if (tableView == menuTableView) {
1367 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1368 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1372 if (tableView == allTableView) {
1373 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1374 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1380 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1384 NSString *dragData, *temp;
1386 pb = [info draggingPasteboard];
1388 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1389 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1390 dragRow = [dragData intValue];
1391 temp = [myItems objectAtIndex:dragRow];
1393 if (tableView == menuTableView) {
1394 [myItems insertObject:temp atIndex:row];
1395 if (row > dragRow) {
1396 [myItems removeObjectAtIndex:dragRow];
1398 [myItems removeObjectAtIndex:dragRow + 1];
1400 } else if (tableView == allTableView) {
1401 if (![temp isEqualToString:@"separator"]) {
1402 [availableItems addObject:temp];
1404 [myItems removeObjectAtIndex:dragRow];
1406 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1407 dragData = [pb stringForType:@"AllTableViewPboardType"];
1408 dragRow = [dragData intValue];
1409 temp = [availableItems objectAtIndex:dragRow];
1411 [myItems insertObject:temp atIndex:row];
1413 if (![temp isEqualToString:@"separator"]) {
1414 [availableItems removeObjectAtIndex:dragRow];
1418 [menuTableView reloadData];
1419 [allTableView reloadData];
1420 [self changeMenus:self];
1424 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1426 if (tableView == allTableView) {
1427 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1428 return NSDragOperationNone;
1431 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1432 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1433 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1434 return NSDragOperationNone;
1438 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1439 return NSDragOperationGeneric;
1442 if (operation == NSTableViewDropOn || row == -1)
1444 return NSDragOperationNone;
1446 return NSDragOperationGeneric;
1450 /*************************************************************************/
1452 #pragma mark DEALLOCATION METHODS
1453 /*************************************************************************/
1457 [[NSNotificationCenter defaultCenter] removeObserver:self];
1458 [hotKeysArray release];
1459 [hotKeysDictionary release];
1460 [effectClasses release];
1461 [menuTableView setDataSource:nil];
1462 [allTableView setDataSource:nil];
1463 [controller release];
1464 [availableItems release];
1465 [submenuItems release];