1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "MenuController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import "StatusWindow.h"
7 #import "StatusWindowController.h"
8 #import "CustomMenuTableView.h"
9 #import "AudioscrobblerController.h"
11 #import <Security/Security.h>
13 #import <netinet/in.h>
15 #import <openssl/sha.h>
19 #import <ITKit/ITKit.h>
20 #import <ITKit/ITTSWBackgroundView.h>
22 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
23 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE(user) [[NSString stringWithFormat:@"Audioscrobbler: %@", user] UTF8String]
24 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
26 /*************************************************************************/
28 #pragma mark PRIVATE INTERFACE
29 /*************************************************************************/
31 @interface PreferencesController (Private)
32 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
33 + (BOOL)keychainItemExistsForUser:(NSString *)user;
34 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
35 + (BOOL)deleteKeychainItemForUser:(NSString *)user;
36 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
39 - (void)setupCustomizationTables;
40 - (void)setupMenuItems;
42 - (void)setupScreenPopup;
43 - (void)setStatusWindowEntryEffect:(Class)effectClass;
44 - (void)setStatusWindowExitEffect:(Class)effectClass;
45 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
46 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
47 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
48 - (IBAction)changeMenus:(id)sender;
52 @implementation PreferencesController
55 /*************************************************************************/
57 #pragma mark STATIC VARIABLES
58 /*************************************************************************/
60 static PreferencesController *prefs = nil;
62 /*************************************************************************/
64 #pragma mark STATIC KEYCHAIN SUPPORT METHODS
65 /*************************************************************************/
67 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user
69 SecKeychainSearchRef search;
70 SecKeychainItemRef item;
72 SecKeychainAttribute attributes[3];
73 SecKeychainAttributeList list;
75 if ((user == nil) || ([user length] == 0)) {
79 ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
80 attributes[0].tag = kSecAccountItemAttr;
81 attributes[0].data = (char *)[user UTF8String];
82 attributes[0].length = [user length];
83 attributes[1].tag = kSecDescriptionItemAttr;
84 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
85 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
86 attributes[2].tag = kSecLabelItemAttr;
87 attributes[2].data = (char *)AUDIOSCROBBLER_KEYCHAIN_SERVICE(user);
88 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE(user));
90 list.attr = attributes;
92 status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
94 if (status != noErr) {
95 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
98 status = SecKeychainSearchCopyNext(search, &item);
100 if (status != noErr) {
101 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
109 + (BOOL)keychainItemExistsForUser:(NSString *)user
111 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
112 BOOL exists = (item != nil);
119 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
121 SecKeychainItemRef item;
123 SecKeychainAttribute attributes[3];
124 SecKeychainAttributeList list;
126 ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
127 attributes[0].tag = kSecAccountItemAttr;
128 attributes[0].data = (char *)[user UTF8String];
129 attributes[0].length = [user length];
130 attributes[1].tag = kSecDescriptionItemAttr;
131 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
132 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
133 attributes[2].tag = kSecLabelItemAttr;
134 attributes[2].data = (char *)AUDIOSCROBBLER_KEYCHAIN_SERVICE(user);
135 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE(user));
137 list.attr = attributes;
139 status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
140 if (status != noErr) {
141 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
143 return (status == noErr);
146 + (BOOL)deleteKeychainItemForUser:(NSString *)user
148 OSStatus status = errSecNotAvailable;
149 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
151 status = SecKeychainItemDelete(item);
152 if (status != noErr) {
153 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
157 return (status == noErr);
160 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
162 OSStatus status = errSecNotAvailable;
163 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
165 status = SecKeychainItemModifyContent(item, NULL, [password length], [password UTF8String]);
166 if (status != noErr) {
167 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
171 return (status == noErr);
174 + (NSString *)getKeychainItemPasswordForUser:(NSString *)user
176 OSStatus status = errSecNotAvailable;
177 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
178 NSString *pass = nil;
182 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
183 if (status != noErr) {
184 ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
186 pass = [[NSString alloc] initWithBytes:buffer length:length encoding:NSUTF8StringEncoding];
188 if (status != noErr) {
189 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
191 SecKeychainItemFreeContent(NULL, buffer);
194 return [pass autorelease];
197 /*************************************************************************/
199 #pragma mark INITIALIZATION METHODS
200 /*************************************************************************/
202 + (PreferencesController *)sharedPrefs;
205 prefs = [[self alloc] init];
212 if ( (self = [super init]) ) {
213 ITDebugLog(@"Preferences initialized.");
214 df = [[NSUserDefaults standardUserDefaults] retain];
216 effectClasses = [[ITWindowEffect effectClasses] retain];
218 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
233 @"ToggleShufflability",
243 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
258 @"Toggle Song Included In Shuffle",
259 @"Pop-up status menu",
260 [NSString stringWithUTF8String:"Set Rating: ☆☆☆☆☆"],
261 [NSString stringWithUTF8String:"Set Rating: ★☆☆☆☆"],
262 [NSString stringWithUTF8String:"Set Rating: ★★☆☆☆"],
263 [NSString stringWithUTF8String:"Set Rating: ★★★☆☆"],
264 [NSString stringWithUTF8String:"Set Rating: ★★★★☆"],
265 [NSString stringWithUTF8String:"Set Rating: ★★★★"],
267 hotKeysDictionary = [[NSMutableDictionary alloc] init];
270 [self setupWindow]; // Load in the nib, and perform any initial setup.
271 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
277 /*************************************************************************/
279 #pragma mark ACCESSOR METHODS
280 /*************************************************************************/
287 - (void)setController:(id)object
289 [controller autorelease];
290 controller = [object retain];
294 /*************************************************************************/
296 #pragma mark INSTANCE METHODS
297 /*************************************************************************/
299 - (BOOL)showPasswordPanel
301 [passwordPanel setLevel:NSStatusWindowLevel];
302 [passwordPanelOKButton setTitle:@"Connect"];
303 [passwordPanelTitle setStringValue:@"Password Required"];
304 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
305 [passwordPanel setLevel:NSStatusWindowLevel];
306 [NSApp activateIgnoringOtherApps:YES];
307 [passwordPanel center];
308 [passwordPanel orderFrontRegardless];
309 [passwordPanel makeKeyWindow];
310 if ([NSApp runModalForWindow:passwordPanel]) {
317 - (BOOL)showInvalidPasswordPanel
319 [passwordPanel setLevel:NSStatusWindowLevel];
320 [passwordPanelOKButton setTitle:@"Retry"];
321 [passwordPanelTitle setStringValue:@"Invalid Password"];
322 [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]]];
323 [passwordPanel setLevel:NSStatusWindowLevel];
324 [NSApp activateIgnoringOtherApps:YES];
325 [passwordPanel center];
326 [passwordPanel orderFrontRegardless];
327 [passwordPanel makeKeyWindow];
328 if ([NSApp runModalForWindow:passwordPanel]) {
335 - (IBAction)showPrefsWindow:(id)sender
337 ITDebugLog(@"Showing preferences window.");
338 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
339 ITDebugLog(@"Window doesn't exist, initial setup.");
340 [self setupCustomizationTables]; // Setup the DnD manu config tables.
341 [self setupMenuItems]; // Setup the arrays of menu items
342 [self setupUI]; // Sets up additional UI
343 [window setDelegate:self];
344 [menuTableView reloadData];
345 [hotKeysTableView reloadData];
346 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
348 //Change the launch player checkbox to the proper name
350 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
352 [controller networkError:localException];
356 [self resetRemotePlayerTextFields];
357 [launchAtLoginCheckbox becomeFirstResponder];
358 [NSApp activateIgnoringOtherApps:YES];
359 if (![window isVisible]) {
362 [window orderFrontRegardless];
363 [window makeKeyWindow];
366 - (IBAction)changeGeneralSetting:(id)sender
368 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
369 if ( [sender tag] == 1010) {
370 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
371 } else if ( [sender tag] == 1020) {
372 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
373 } else if ( [sender tag] == 1030) {
374 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
375 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
376 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
378 } else if ( [sender tag] == 1040) {
379 // This will not be executed. Song info always shows the title of the song.
380 // [df setBool:SENDER_STATE forKey:@"showName"];
381 } else if ( [sender tag] == 1050) {
382 [df setBool:SENDER_STATE forKey:@"showArtist"];
383 } else if ( [sender tag] == 1055) {
384 [df setBool:SENDER_STATE forKey:@"showComposer"];
385 } else if ( [sender tag] == 1060) {
386 [df setBool:SENDER_STATE forKey:@"showAlbum"];
387 } else if ( [sender tag] == 1070) {
388 [df setBool:SENDER_STATE forKey:@"showTime"];
389 } else if ( [sender tag] == 1080) {
390 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
391 } else if ( [sender tag] == 1085) {
392 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
393 } else if ( [sender tag] == 1090) {
394 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
395 } else if ( [sender tag] == 1100) {
396 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
397 } else if ( [sender tag] == 1110) {
398 [df setBool:SENDER_STATE forKey:@"runScripts"];
400 [runScriptsCheckbox setState:NSOnState];
401 [showScriptsButton setEnabled:YES];
403 [showScriptsButton setEnabled:NO];
405 } else if ( [sender tag] == 1120) {
406 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] UTF8String], 0744);
407 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
408 } else if ( [sender tag] == 6010) {
409 //Toggle the other Audioscrobbler options
410 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
411 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
412 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
413 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
415 [[AudioscrobblerController sharedController] attemptHandshake:NO];
417 } else if ( [sender tag ] == 6015) {
418 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
419 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
420 if ([newAccount length] == 0) {
421 [PreferencesController deleteKeychainItemForUser:currentAccount];
422 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
423 [df setObject:newAccount forKey:@"audioscrobblerUser"];
424 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
425 //Delete the current keychain item if there is one
426 [PreferencesController deleteKeychainItemForUser:currentAccount];
428 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
429 [[AudioscrobblerController sharedController] attemptHandshake:YES];
431 } else if ( [sender tag ] == 6030) {
432 //Here we set the password for an existing keychain item or we create a new keychain item.
433 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
434 NSString *account = [df stringForKey:@"audioscrobblerUser"];
435 if ([PreferencesController keychainItemExistsForUser:account]) {
436 //Update the current keychain item
437 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
438 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
439 //Create a new keychain item
440 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
443 } else if ( [sender tag] == 6045) {
444 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
449 - (IBAction)changeSharingSetting:(id)sender
451 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
452 if ( [sender tag] == 5010 ) {
453 BOOL state = SENDER_STATE;
454 [df setBool:state forKey:@"enableSharing"];
455 //Disable/enable the use of shared player options
456 [useSharedMenuTunesCheckbox setEnabled:!state];
457 [passwordTextField setEnabled:state];
458 [nameTextField setEnabled:state];
459 [selectSharedPlayerButton setEnabled:NO];
460 [controller setServerStatus:state]; //Set server status
461 } else if ( [sender tag] == 5015 ) {
462 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
463 [[NetworkController sharedController] resetServerName];
464 } else if ( [sender tag] == 5030 ) {
465 //Set the server password
466 const char *instring = [[sender stringValue] UTF8String];
467 const char *password = "p4s5w0rdMT1.2";
469 NSData *hashedPass, *passwordStringHash;
470 if ([[sender stringValue] length] == 0) {
471 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
474 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
475 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
476 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
477 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
478 if (![hashedPass isEqualToData:passwordStringHash]) {
479 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
480 [sender setStringValue:@"p4s5w0rdMT1.2"];
482 } else if ( [sender tag] == 5040 ) {
483 BOOL state = SENDER_STATE;
484 [df setBool:state forKey:@"useSharedPlayer"];
485 //Disable/enable the use of sharing options
486 [shareMenuTunesCheckbox setEnabled:!state];
487 [passwordTextField setEnabled:NO];
488 [nameTextField setEnabled:NO];
489 [selectSharedPlayerButton setEnabled:state];
491 if (state && ([controller connectToServer] == 1)) {
492 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
493 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
495 [selectedPlayerTextField setStringValue:@"No shared player selected."];
496 [locationTextField setStringValue:@"-"];
497 if ([[NetworkController sharedController] isConnectedToServer]) {
498 [controller disconnectFromServer];
502 } else if ( [sender tag] == 5050 ) {
503 //If no player is selected in the table view, turn off OK button.
504 if ([sender clickedRow] == -1 ) {
505 [sharingPanelOKButton setEnabled:NO];
507 [sharingPanelOKButton setEnabled:YES];
509 } else if ( [sender tag] == 5051 ) {
510 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
511 } else if ( [sender tag] == 5060 ) {
512 //Set OK button state
513 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
514 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
515 [sharingPanelOKButton setEnabled:NO];
517 [sharingPanelOKButton setEnabled:YES];
519 //Show selection sheet
520 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
521 } else if ( [sender tag] == 5100 ) {
523 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
524 NSRect frame = [selectPlayerSheet frame];
525 frame.origin.y -= 58;
526 frame.size.height = 273;
527 if ([sharingTableView selectedRow] == -1) {
528 [sharingPanelOKButton setEnabled:NO];
530 [selectPlayerBox setContentView:zeroConfView];
531 [selectPlayerSheet setFrame:frame display:YES animate:YES];
532 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
533 NSRect frame = [selectPlayerSheet frame];
534 frame.origin.y += 58;
535 frame.size.height = 215;
536 if ([[hostTextField stringValue] length] == 0) {
537 [sharingPanelOKButton setEnabled:NO];
539 [sharingPanelOKButton setEnabled:YES];
541 [selectPlayerBox setContentView:manualView];
542 [selectPlayerSheet setFrame:frame display:YES animate:YES];
543 [hostTextField selectText:nil];
545 } else if ( [sender tag] == 5150 ) {
546 const char *instring = [[sender stringValue] UTF8String];
548 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
549 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
550 } else if ( [sender tag] == 5110 ) {
552 [NSApp endSheet:selectPlayerSheet];
553 [selectPlayerSheet orderOut:nil];
554 if ([selectPlayerBox contentView] == manualView) {
555 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
558 } else if ( [sender tag] == 5120 ) {
560 [NSApp endSheet:selectPlayerSheet];
561 [selectPlayerSheet orderOut:nil];
563 [self changeSharingSetting:clientPasswordTextField];
565 if ([selectPlayerBox contentView] == manualView) {
566 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
568 if ([sharingTableView selectedRow] > -1) {
569 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
573 if ([controller connectToServer] == 1) {
574 [useSharedMenuTunesCheckbox setState:NSOnState];
575 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
576 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
578 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);
580 } else if ( [sender tag] == 6010 ) {
581 //Cancel password entry
582 [passwordPanel orderOut:nil];
583 [NSApp stopModalWithCode:0];
584 } else if ( [sender tag] == 6020 ) {
585 //OK password entry, retry connect
586 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
588 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
589 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
590 [passwordPanel orderOut:nil];
591 [NSApp stopModalWithCode:1];
596 - (IBAction)changeStatusWindowSetting:(id)sender
598 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
599 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
601 if ( [sender tag] == 2010) {
603 BOOL entryEffectValid = YES;
604 BOOL exitEffectValid = YES;
606 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
607 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
608 [sw setVerticalPosition:[sender selectedRow]];
609 [sw setHorizontalPosition:[sender selectedColumn]];
611 // Enable/disable the items in the popups.
612 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
613 horizontalPosition:[sw horizontalPosition]];
615 // Make sure the effects support the new position.
616 entryEffectValid = ( [self effect:[[sw entryEffect] class]
617 supportsVerticalPosition:[sw verticalPosition]
618 withHorizontalPosition:[sw horizontalPosition]] );
619 exitEffectValid = ( [self effect:[[sw exitEffect] class]
620 supportsVerticalPosition:[sw verticalPosition]
621 withHorizontalPosition:[sw horizontalPosition]] );
623 if ( ! entryEffectValid ) {
624 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
625 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
627 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
630 if ( ! exitEffectValid ) {
631 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
632 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
634 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
637 [(MainController *)controller showCurrentTrackInfo];
639 } else if ( [sender tag] == 2020) {
641 // Update screen selection.
642 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
643 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
644 [(MainController *)controller showCurrentTrackInfo];
646 } else if ( [sender tag] == 2030) {
648 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
649 [(MainController *)controller showCurrentTrackInfo];
651 } else if ( [sender tag] == 2040) {
653 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
654 [(MainController *)controller showCurrentTrackInfo];
656 } else if ( [sender tag] == 2050) {
657 float newTime = ( -([sender floatValue]) );
658 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
659 [[sw entryEffect] setEffectTime:newTime];
660 } else if ( [sender tag] == 2060) {
661 float newTime = ( -([sender floatValue]) );
662 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
663 [[sw exitEffect] setEffectTime:newTime];
664 } else if ( [sender tag] == 2070) {
665 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
666 [sw setExitDelay:[sender floatValue]];
667 } else if ( [sender tag] == 2080) {
668 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
669 } else if ( [sender tag] == 2090) {
671 int setting = [sender indexOfSelectedItem];
673 if ( setting == 0 ) {
674 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
675 [backgroundColorWell setEnabled:NO];
676 [backgroundColorPopup setEnabled:NO];
677 } else if ( setting == 1 ) {
678 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
679 [backgroundColorWell setEnabled:NO];
680 [backgroundColorPopup setEnabled:NO];
681 } else if ( setting == 2 ) {
682 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
683 [backgroundColorWell setEnabled:YES];
684 [backgroundColorPopup setEnabled:YES];
687 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
688 [(MainController *)controller showCurrentTrackInfo];
690 } else if ( [sender tag] == 2091) {
691 [self setCustomColor:[sender color] updateWell:NO];
692 [(MainController *)controller showCurrentTrackInfo];
693 } else if ( [sender tag] == 2092) {
695 int selectedItem = [sender indexOfSelectedItem];
697 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
698 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
699 } else if ( selectedItem == 2 ) {
700 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
701 } else if ( selectedItem == 3 ) {
702 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
703 } else if ( selectedItem == 4 ) {
704 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
705 } else if ( selectedItem == 5 ) {
706 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
708 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
710 [(MainController *)controller showCurrentTrackInfo];
712 } else if ( [sender tag] == 2095) {
713 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
714 [(MainController *)controller showCurrentTrackInfo];
720 - (void)registerDefaults
722 ITDebugLog(@"Registering defaults.");
723 [df setObject:[NSArray arrayWithObjects:
736 nil] forKey:@"menu"];
738 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
739 [df setInteger:5 forKey:@"SongsInAdvance"];
740 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
741 [df setBool:YES forKey:@"showArtist"];
742 [df setBool:YES forKey:@"showAlbumArtwork"];
743 [df setBool:NO forKey:@"showAlbum"];
744 [df setBool:NO forKey:@"showComposer"];
745 [df setBool:NO forKey:@"showTime"];
746 [df setBool:NO forKey:@"showToolTip"];
748 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
749 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
750 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
751 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
752 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
753 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
754 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
755 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
756 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
757 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
758 [df setBool:YES forKey:@"showSongInfoOnChange"];
760 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
764 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
765 [[StatusWindowController sharedController] showSetupQueryWindow];
771 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
772 [[StatusWindow sharedWindow] vanish:self];
773 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
775 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
778 - (void)autoLaunchCancel
780 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
781 [[StatusWindow sharedWindow] vanish:self];
782 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
785 - (void)deletePressedInTableView:(NSTableView *)tableView
787 if (tableView == menuTableView) {
788 int selRow = [tableView selectedRow];
789 ITDebugLog(@"Delete pressed in menu table view.");
791 NSString *object = [myItems objectAtIndex:selRow];
793 if ([object isEqualToString:@"preferences"]) {
798 if (![object isEqualToString:@"separator"])
799 [availableItems addObject:object];
800 ITDebugLog(@"Removing object named %@", object);
801 [myItems removeObjectAtIndex:selRow];
802 [menuTableView reloadData];
803 [allTableView reloadData];
805 [self changeMenus:self];
809 - (void)resetRemotePlayerTextFields
811 if ([[NetworkController sharedController] isConnectedToServer]) {
812 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
813 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
815 [selectedPlayerTextField setStringValue:@"No shared player selected."];
816 [locationTextField setStringValue:@"-"];
820 /*************************************************************************/
822 #pragma mark HOTKEY SUPPORT METHODS
823 /*************************************************************************/
825 - (IBAction)clearHotKey:(id)sender
827 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
828 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
829 [controller setupHotKeys];
830 [hotKeysTableView reloadData];
833 - (IBAction)editHotKey:(id)sender
835 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
836 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
837 ITKeyCombo *keyCombo;
839 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
840 [controller clearHotKeys];
841 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
842 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
843 if ([panel runModal] == NSOKButton) {
844 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
846 keyCombo = [panel keyCombo];
848 //Check for duplicate key combo
849 while ( (nextKey = [keyEnumerator nextObject]) ) {
850 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
851 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
852 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
854 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
859 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
860 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
861 [controller setupHotKeys];
862 [hotKeysTableView reloadData];
863 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
865 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
869 - (void)hotKeysTableViewDoubleClicked:(id)sender
871 if ([sender clickedRow] > -1) {
872 [self editHotKey:sender];
876 /*************************************************************************/
878 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
879 /*************************************************************************/
881 - (void)audioscrobblerStatusChanged:(NSNotification *)note
883 [audioscrobblerStatusTextField setStringValue:[[note userInfo] objectForKey:@"StatusString"]];
888 ITDebugLog(@"Loading Preferences.nib.");
889 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
890 ITDebugLog(@"Failed to load Preferences.nib.");
896 - (void)setupCustomizationTables
898 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
899 ITDebugLog(@"Setting up table views.");
900 // Set the table view cells up
901 [imgCell setImageScaling:NSScaleNone];
902 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
903 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
905 // Register for drag and drop
906 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
907 @"MenuTableViewPboardType",
908 @"AllTableViewPboardType",
910 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
911 @"MenuTableViewPboardType",
912 @"AllTableViewPboardType",
916 - (void)setupMenuItems
918 NSEnumerator *itemEnum;
920 ITDebugLog(@"Setting up table view arrays.");
921 // Set the list of items you can have.
922 availableItems = [[NSMutableArray alloc] initWithObjects:
941 // Get our preferred menu
942 myItems = [[df arrayForKey:@"menu"] mutableCopy];
944 // Delete items in the availableItems array that are already part of the menu
945 itemEnum = [myItems objectEnumerator];
946 while ( (anItem = [itemEnum nextObject]) ) {
947 if (![anItem isEqualToString:@"separator"]) {
948 [availableItems removeObject:anItem];
952 // Items that show should a submenu image
953 submenuItems = [[NSArray alloc] initWithObjects:
965 NSEnumerator *keyArrayEnum;
966 NSString *serverName;
971 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioscrobblerStatusChanged:) name:@"AudioscrobblerStatusChanged" object:nil];
972 if ([df boolForKey:@"audioscrobblerEnabled"]) {
973 NSString *status = [[AudioscrobblerController sharedController] lastStatus];
974 [audioscrobblerStatusTextField setStringValue:(status == nil) ? @"Idle" : status];
977 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
979 ITDebugLog(@"Setting up preferences UI.");
980 // Fill in the number of songs in advance to show field
981 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
983 // Fill hot key array
984 keyArrayEnum = [hotKeysArray objectEnumerator];
986 while ( (anItem = [keyArrayEnum nextObject]) ) {
987 if ([df objectForKey:anItem]) {
988 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
989 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
991 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
995 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
996 [self setupScreenPopup];
998 ITDebugLog(@"Setting up track info checkboxes.");
999 // Check current track info buttons
1000 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
1001 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
1002 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
1003 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1004 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1005 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1006 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1007 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1008 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1009 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1011 if ([df boolForKey:@"runScripts"]) {
1012 [runScriptsCheckbox setState:NSOnState];
1013 [showScriptsButton setEnabled:YES];
1015 [showScriptsButton setEnabled:NO];
1018 // Set the launch at login checkbox state
1019 ITDebugLog(@"Setting launch at login state.");
1020 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1021 [launchAtLoginCheckbox setState:NSOnState];
1024 // Set the launch player checkbox state
1025 ITDebugLog(@"Setting launch player with MenuTunes state.");
1026 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1028 // Setup the positioning controls
1029 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1030 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1032 // Setup effects controls
1033 // Populate the effects popups
1034 [appearanceEffectPopup setAutoenablesItems:NO];
1035 [vanishEffectPopup setAutoenablesItems:NO];
1036 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1037 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1039 // Attempt to find the pref'd effect in the list.
1040 // If it's not there, use cut/dissolve.
1041 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1042 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1044 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1047 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1048 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1050 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1053 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1054 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1055 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1057 // Setup General Controls
1058 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1059 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1061 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1062 [backgroundColorWell setEnabled:YES];
1063 [backgroundColorPopup setEnabled:YES];
1065 [backgroundColorWell setEnabled:NO];
1066 [backgroundColorPopup setEnabled:NO];
1069 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1072 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1074 [backgroundColorWell setColor:[NSColor blueColor]];
1077 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1079 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1081 // Setup the sharing controls
1082 if ([df boolForKey:@"enableSharing"]) {
1083 [shareMenuTunesCheckbox setState:NSOnState];
1084 [useSharedMenuTunesCheckbox setEnabled:NO];
1085 [selectSharedPlayerButton setEnabled:NO];
1086 [passwordTextField setEnabled:YES];
1087 [nameTextField setEnabled:YES];
1088 } else if ([df boolForKey:@"useSharedPlayer"]) {
1089 [useSharedMenuTunesCheckbox setState:NSOnState];
1090 [shareMenuTunesCheckbox setEnabled:NO];
1091 [selectSharedPlayerButton setEnabled:YES];
1094 //Setup the Audioscrobbler controls
1095 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1096 [audioscrobblerEnabledCheckbox setState:NSOnState];
1097 [audioscrobblerUserTextField setEnabled:YES];
1098 [audioscrobblerPasswordTextField setEnabled:YES];
1099 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1101 [audioscrobblerUserTextField setEnabled:NO];
1102 [audioscrobblerPasswordTextField setEnabled:NO];
1103 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1105 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1106 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1107 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1108 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1109 if (password != nil) {
1110 [audioscrobblerPasswordTextField setStringValue:password];
1113 [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1115 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1117 serverName = [df stringForKey:@"sharedPlayerName"];
1118 if (!serverName || [serverName length] == 0) {
1119 serverName = @"MenuTunes Shared Player";
1121 [nameTextField setStringValue:serverName];
1123 [selectPlayerBox setContentView:zeroConfView];
1124 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1125 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1127 [passwordTextField setStringValue:@""];
1129 if ([df stringForKey:@"sharedPlayerHost"]) {
1130 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1133 if ([[NetworkController sharedController] isConnectedToServer]) {
1134 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1135 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1137 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1138 [locationTextField setStringValue:@"-"];
1142 - (void)setupScreenPopup
1144 ITDebugLog(@"Setting up screen popup");
1145 NSArray *screens = [NSScreen screens];
1146 if ([screens count] > 1) {
1147 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1148 [screenPopup setEnabled:YES];
1149 for (i = 0; i < [screens count]; i++) {
1150 NSScreen *screen = [screens objectAtIndex:i];
1151 if (![screen isEqual:[NSScreen mainScreen]]) {
1152 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1155 [screenPopup selectItemAtIndex:index];
1156 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1158 while ([screenPopup numberOfItems] > 1) {
1159 [screenPopup removeItemAtIndex:1];
1161 [screenPopup setEnabled:NO];
1162 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1166 - (void)setStatusWindowEntryEffect:(Class)effectClass
1168 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1170 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1171 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1173 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1174 [[sw entryEffect] setEffectTime:time];
1177 - (void)setStatusWindowExitEffect:(Class)effectClass
1179 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1181 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1182 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1184 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1185 [[sw exitEffect] setEffectTime:time];
1188 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1190 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1191 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1194 [backgroundColorWell setColor:color];
1198 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1200 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1203 [appearanceEffectPopup removeAllItems];
1204 [vanishEffectPopup removeAllItems];
1206 while ( (anItem = [effectEnum nextObject]) ) {
1207 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1208 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1210 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1211 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1213 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1214 [[appearanceEffectPopup lastItem] setEnabled:YES];
1215 [[vanishEffectPopup lastItem] setEnabled:YES];
1217 [[appearanceEffectPopup lastItem] setEnabled:NO];
1218 [[vanishEffectPopup lastItem] setEnabled:NO];
1224 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1228 if ( vPos == ITWindowPositionTop ) {
1229 if ( hPos == ITWindowPositionLeft ) {
1230 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1231 } else if ( hPos == ITWindowPositionCenter ) {
1232 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1233 } else if ( hPos == ITWindowPositionRight ) {
1234 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1236 } else if ( vPos == ITWindowPositionMiddle ) {
1237 if ( hPos == ITWindowPositionLeft ) {
1238 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1239 } else if ( hPos == ITWindowPositionCenter ) {
1240 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1241 } else if ( hPos == ITWindowPositionRight ) {
1242 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1244 } else if ( vPos == ITWindowPositionBottom ) {
1245 if ( hPos == ITWindowPositionLeft ) {
1246 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1247 } else if ( hPos == ITWindowPositionCenter ) {
1248 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1249 } else if ( hPos == ITWindowPositionRight ) {
1250 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1257 - (IBAction)changeMenus:(id)sender
1259 ITDebugLog(@"Synchronizing menus");
1260 [df setObject:myItems forKey:@"menu"];
1263 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1265 //If we're connected over a network, refresh the menu immediately
1266 if ([[NetworkController sharedController] isConnectedToServer]) {
1267 [controller timerUpdate];
1272 /*************************************************************************/
1274 #pragma mark NSWindow DELEGATE METHODS
1275 /*************************************************************************/
1277 - (void)windowWillClose:(NSNotification *)note
1279 [(MainController *)controller closePreferences];
1282 /*************************************************************************/
1284 #pragma mark NSTextField DELEGATE METHODS
1285 /*************************************************************************/
1287 - (void)controlTextDidChange:(NSNotification*)note
1289 if ([note object] == hostTextField) {
1290 if ([[hostTextField stringValue] length] == 0) {
1291 [sharingPanelOKButton setEnabled:NO];
1293 [sharingPanelOKButton setEnabled:YES];
1298 /*************************************************************************/
1300 #pragma mark NSTableView DATASOURCE METHODS
1301 /*************************************************************************/
1303 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1305 if (aTableView == menuTableView) {
1306 return [myItems count];
1307 } else if (aTableView == allTableView) {
1308 return [availableItems count];
1309 } else if (aTableView == hotKeysTableView) {
1310 return [hotKeysArray count];
1312 return [[[NetworkController sharedController] remoteServices] count];
1316 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1318 if (aTableView == menuTableView) {
1319 NSString *object = [myItems objectAtIndex:rowIndex];
1320 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1321 if ([object isEqualToString:@"showPlayer"]) {
1322 NSString *string = nil;
1324 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1326 [controller networkError:localException];
1330 return NSLocalizedString(object, @"ERROR");
1332 if ([submenuItems containsObject:object])
1334 return [NSImage imageNamed:@"submenu"];
1339 } else if (aTableView == allTableView) {
1340 NSString *object = [availableItems objectAtIndex:rowIndex];
1341 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1342 if ([object isEqualToString:@"showPlayer"]) {
1343 NSString *string = nil;
1345 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1347 [controller networkError:localException];
1351 return NSLocalizedString(object, @"ERROR");
1353 if ([submenuItems containsObject:object]) {
1354 return [NSImage imageNamed:@"submenu"];
1359 } else if (aTableView == hotKeysTableView) {
1360 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1361 return [hotKeyNamesArray objectAtIndex:rowIndex];
1363 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1366 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1370 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1372 if (tableView == menuTableView) {
1373 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1374 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1378 if (tableView == allTableView) {
1379 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1380 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1386 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1390 NSString *dragData, *temp;
1392 pb = [info draggingPasteboard];
1394 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1395 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1396 dragRow = [dragData intValue];
1397 temp = [myItems objectAtIndex:dragRow];
1399 if (tableView == menuTableView) {
1400 [myItems insertObject:temp atIndex:row];
1401 if (row > dragRow) {
1402 [myItems removeObjectAtIndex:dragRow];
1404 [myItems removeObjectAtIndex:dragRow + 1];
1406 } else if (tableView == allTableView) {
1407 if (![temp isEqualToString:@"separator"]) {
1408 [availableItems addObject:temp];
1410 [myItems removeObjectAtIndex:dragRow];
1412 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1413 dragData = [pb stringForType:@"AllTableViewPboardType"];
1414 dragRow = [dragData intValue];
1415 temp = [availableItems objectAtIndex:dragRow];
1417 [myItems insertObject:temp atIndex:row];
1419 if (![temp isEqualToString:@"separator"]) {
1420 [availableItems removeObjectAtIndex:dragRow];
1424 [menuTableView reloadData];
1425 [allTableView reloadData];
1426 [self changeMenus:self];
1430 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1432 if (tableView == allTableView) {
1433 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1434 return NSDragOperationNone;
1437 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1438 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1439 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1440 return NSDragOperationNone;
1444 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1445 return NSDragOperationGeneric;
1448 if (operation == NSTableViewDropOn || row == -1)
1450 return NSDragOperationNone;
1452 return NSDragOperationGeneric;
1456 /*************************************************************************/
1458 #pragma mark DEALLOCATION METHODS
1459 /*************************************************************************/
1463 [[NSNotificationCenter defaultCenter] removeObserver:self];
1464 [hotKeysArray release];
1465 [hotKeysDictionary release];
1466 [effectClasses release];
1467 [menuTableView setDataSource:nil];
1468 [allTableView setDataSource:nil];
1469 [controller release];
1470 [availableItems release];
1471 [submenuItems release];