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 NSLog(@"Audioscrobbler: password buffer: \"%s\" \"Length: %i\"", buffer, length);
187 pass = [[NSString alloc] initWithBytes:buffer length:length encoding:NSUTF8StringEncoding];
189 if (status != noErr) {
190 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
192 SecKeychainItemFreeContent(NULL, buffer);
195 NSLog(@"Audioscrobbler: Retrieved password: \"%@\"", pass);
196 return [pass autorelease];
199 /*************************************************************************/
201 #pragma mark INITIALIZATION METHODS
202 /*************************************************************************/
204 + (PreferencesController *)sharedPrefs;
207 prefs = [[self alloc] init];
214 if ( (self = [super init]) ) {
215 ITDebugLog(@"Preferences initialized.");
216 df = [[NSUserDefaults standardUserDefaults] retain];
218 effectClasses = [[ITWindowEffect effectClasses] retain];
220 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
235 @"ToggleShufflability",
245 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
260 @"Toggle Song Included In Shuffle",
261 @"Pop-up status menu",
262 [NSString stringWithUTF8String:"Set Rating: ☆☆☆☆☆"],
263 [NSString stringWithUTF8String:"Set Rating: ★☆☆☆☆"],
264 [NSString stringWithUTF8String:"Set Rating: ★★☆☆☆"],
265 [NSString stringWithUTF8String:"Set Rating: ★★★☆☆"],
266 [NSString stringWithUTF8String:"Set Rating: ★★★★☆"],
267 [NSString stringWithUTF8String:"Set Rating: ★★★★"],
269 hotKeysDictionary = [[NSMutableDictionary alloc] init];
272 [self setupWindow]; // Load in the nib, and perform any initial setup.
273 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
279 /*************************************************************************/
281 #pragma mark ACCESSOR METHODS
282 /*************************************************************************/
289 - (void)setController:(id)object
291 [controller autorelease];
292 controller = [object retain];
296 /*************************************************************************/
298 #pragma mark INSTANCE METHODS
299 /*************************************************************************/
301 - (BOOL)showPasswordPanel
303 [passwordPanel setLevel:NSStatusWindowLevel];
304 [passwordPanelOKButton setTitle:@"Connect"];
305 [passwordPanelTitle setStringValue:@"Password Required"];
306 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
307 [passwordPanel setLevel:NSStatusWindowLevel];
308 [NSApp activateIgnoringOtherApps:YES];
309 [passwordPanel center];
310 [passwordPanel orderFrontRegardless];
311 [passwordPanel makeKeyWindow];
312 if ([NSApp runModalForWindow:passwordPanel]) {
319 - (BOOL)showInvalidPasswordPanel
321 [passwordPanel setLevel:NSStatusWindowLevel];
322 [passwordPanelOKButton setTitle:@"Retry"];
323 [passwordPanelTitle setStringValue:@"Invalid Password"];
324 [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]]];
325 [passwordPanel setLevel:NSStatusWindowLevel];
326 [NSApp activateIgnoringOtherApps:YES];
327 [passwordPanel center];
328 [passwordPanel orderFrontRegardless];
329 [passwordPanel makeKeyWindow];
330 if ([NSApp runModalForWindow:passwordPanel]) {
337 - (IBAction)showPrefsWindow:(id)sender
339 ITDebugLog(@"Showing preferences window.");
340 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
341 ITDebugLog(@"Window doesn't exist, initial setup.");
342 [self setupCustomizationTables]; // Setup the DnD manu config tables.
343 [self setupMenuItems]; // Setup the arrays of menu items
344 [self setupUI]; // Sets up additional UI
345 [window setDelegate:self];
346 [menuTableView reloadData];
347 [hotKeysTableView reloadData];
348 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
350 //Change the launch player checkbox to the proper name
352 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
354 [controller networkError:localException];
358 [self resetRemotePlayerTextFields];
359 [launchAtLoginCheckbox becomeFirstResponder];
360 [NSApp activateIgnoringOtherApps:YES];
361 if (![window isVisible]) {
364 [window orderFrontRegardless];
365 [window makeKeyWindow];
368 - (IBAction)changeGeneralSetting:(id)sender
370 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
371 if ( [sender tag] == 1010) {
372 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
373 } else if ( [sender tag] == 1020) {
374 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
375 } else if ( [sender tag] == 1030) {
376 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
377 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
378 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
380 } else if ( [sender tag] == 1040) {
381 // This will not be executed. Song info always shows the title of the song.
382 // [df setBool:SENDER_STATE forKey:@"showName"];
383 } else if ( [sender tag] == 1050) {
384 [df setBool:SENDER_STATE forKey:@"showArtist"];
385 } else if ( [sender tag] == 1055) {
386 [df setBool:SENDER_STATE forKey:@"showComposer"];
387 } else if ( [sender tag] == 1060) {
388 [df setBool:SENDER_STATE forKey:@"showAlbum"];
389 } else if ( [sender tag] == 1070) {
390 [df setBool:SENDER_STATE forKey:@"showTime"];
391 } else if ( [sender tag] == 1080) {
392 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
393 } else if ( [sender tag] == 1085) {
394 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
395 } else if ( [sender tag] == 1090) {
396 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
397 } else if ( [sender tag] == 1100) {
398 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
399 } else if ( [sender tag] == 1110) {
400 [df setBool:SENDER_STATE forKey:@"runScripts"];
402 [runScriptsCheckbox setState:NSOnState];
403 [showScriptsButton setEnabled:YES];
405 [showScriptsButton setEnabled:NO];
407 } else if ( [sender tag] == 1120) {
408 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] UTF8String], 0744);
409 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
410 } else if ( [sender tag] == 6010) {
411 //Toggle the other Audioscrobbler options
412 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
413 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
414 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
415 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
417 [[AudioscrobblerController sharedController] attemptHandshake:NO];
419 } else if ( [sender tag ] == 6015) {
420 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
421 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
422 if ([newAccount length] == 0) {
423 [PreferencesController deleteKeychainItemForUser:currentAccount];
424 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
425 [df setObject:newAccount forKey:@"audioscrobblerUser"];
426 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
427 //Delete the current keychain item if there is one
428 [PreferencesController deleteKeychainItemForUser:currentAccount];
430 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
431 [[AudioscrobblerController sharedController] attemptHandshake:YES];
433 } else if ( [sender tag ] == 6030) {
434 //Here we set the password for an existing keychain item or we create a new keychain item.
435 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
436 NSString *account = [df stringForKey:@"audioscrobblerUser"];
437 if ([PreferencesController keychainItemExistsForUser:account]) {
438 //Update the current keychain item
439 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
440 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
441 //Create a new keychain item
442 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
445 } else if ( [sender tag] == 6045) {
446 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
451 - (IBAction)changeSharingSetting:(id)sender
453 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
454 if ( [sender tag] == 5010 ) {
455 BOOL state = SENDER_STATE;
456 [df setBool:state forKey:@"enableSharing"];
457 //Disable/enable the use of shared player options
458 [useSharedMenuTunesCheckbox setEnabled:!state];
459 [passwordTextField setEnabled:state];
460 [nameTextField setEnabled:state];
461 [selectSharedPlayerButton setEnabled:NO];
462 [controller setServerStatus:state]; //Set server status
463 } else if ( [sender tag] == 5015 ) {
464 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
465 [[NetworkController sharedController] resetServerName];
466 } else if ( [sender tag] == 5030 ) {
467 //Set the server password
468 const char *instring = [[sender stringValue] UTF8String];
469 const char *password = "p4s5w0rdMT1.2";
471 NSData *hashedPass, *passwordStringHash;
472 if ([[sender stringValue] length] == 0) {
473 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
476 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
477 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
478 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
479 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
480 if (![hashedPass isEqualToData:passwordStringHash]) {
481 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
482 [sender setStringValue:@"p4s5w0rdMT1.2"];
484 } else if ( [sender tag] == 5040 ) {
485 BOOL state = SENDER_STATE;
486 [df setBool:state forKey:@"useSharedPlayer"];
487 //Disable/enable the use of sharing options
488 [shareMenuTunesCheckbox setEnabled:!state];
489 [passwordTextField setEnabled:NO];
490 [nameTextField setEnabled:NO];
491 [selectSharedPlayerButton setEnabled:state];
493 if (state && ([controller connectToServer] == 1)) {
494 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
495 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
497 [selectedPlayerTextField setStringValue:@"No shared player selected."];
498 [locationTextField setStringValue:@"-"];
499 if ([[NetworkController sharedController] isConnectedToServer]) {
500 [controller disconnectFromServer];
504 } else if ( [sender tag] == 5050 ) {
505 //If no player is selected in the table view, turn off OK button.
506 if ([sender clickedRow] == -1 ) {
507 [sharingPanelOKButton setEnabled:NO];
509 [sharingPanelOKButton setEnabled:YES];
511 } else if ( [sender tag] == 5051 ) {
512 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
513 } else if ( [sender tag] == 5060 ) {
514 //Set OK button state
515 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
516 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
517 [sharingPanelOKButton setEnabled:NO];
519 [sharingPanelOKButton setEnabled:YES];
521 //Show selection sheet
522 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
523 } else if ( [sender tag] == 5100 ) {
525 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
526 NSRect frame = [selectPlayerSheet frame];
527 frame.origin.y -= 58;
528 frame.size.height = 273;
529 if ([sharingTableView selectedRow] == -1) {
530 [sharingPanelOKButton setEnabled:NO];
532 [selectPlayerBox setContentView:zeroConfView];
533 [selectPlayerSheet setFrame:frame display:YES animate:YES];
534 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
535 NSRect frame = [selectPlayerSheet frame];
536 frame.origin.y += 58;
537 frame.size.height = 215;
538 if ([[hostTextField stringValue] length] == 0) {
539 [sharingPanelOKButton setEnabled:NO];
541 [sharingPanelOKButton setEnabled:YES];
543 [selectPlayerBox setContentView:manualView];
544 [selectPlayerSheet setFrame:frame display:YES animate:YES];
545 [hostTextField selectText:nil];
547 } else if ( [sender tag] == 5150 ) {
548 const char *instring = [[sender stringValue] UTF8String];
550 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
551 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
552 } else if ( [sender tag] == 5110 ) {
554 [NSApp endSheet:selectPlayerSheet];
555 [selectPlayerSheet orderOut:nil];
556 if ([selectPlayerBox contentView] == manualView) {
557 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
560 } else if ( [sender tag] == 5120 ) {
562 [NSApp endSheet:selectPlayerSheet];
563 [selectPlayerSheet orderOut:nil];
565 [self changeSharingSetting:clientPasswordTextField];
567 if ([selectPlayerBox contentView] == manualView) {
568 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
570 if ([sharingTableView selectedRow] > -1) {
571 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
575 if ([controller connectToServer] == 1) {
576 [useSharedMenuTunesCheckbox setState:NSOnState];
577 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
578 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
580 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);
582 } else if ( [sender tag] == 6010 ) {
583 //Cancel password entry
584 [passwordPanel orderOut:nil];
585 [NSApp stopModalWithCode:0];
586 } else if ( [sender tag] == 6020 ) {
587 //OK password entry, retry connect
588 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
590 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
591 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
592 [passwordPanel orderOut:nil];
593 [NSApp stopModalWithCode:1];
598 - (IBAction)changeStatusWindowSetting:(id)sender
600 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
601 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
603 if ( [sender tag] == 2010) {
605 BOOL entryEffectValid = YES;
606 BOOL exitEffectValid = YES;
608 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
609 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
610 [sw setVerticalPosition:[sender selectedRow]];
611 [sw setHorizontalPosition:[sender selectedColumn]];
613 // Enable/disable the items in the popups.
614 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
615 horizontalPosition:[sw horizontalPosition]];
617 // Make sure the effects support the new position.
618 entryEffectValid = ( [self effect:[[sw entryEffect] class]
619 supportsVerticalPosition:[sw verticalPosition]
620 withHorizontalPosition:[sw horizontalPosition]] );
621 exitEffectValid = ( [self effect:[[sw exitEffect] class]
622 supportsVerticalPosition:[sw verticalPosition]
623 withHorizontalPosition:[sw horizontalPosition]] );
625 if ( ! entryEffectValid ) {
626 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
627 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
629 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
632 if ( ! exitEffectValid ) {
633 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
634 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
636 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
639 [(MainController *)controller showCurrentTrackInfo];
641 } else if ( [sender tag] == 2020) {
643 // Update screen selection.
644 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
645 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
646 [(MainController *)controller showCurrentTrackInfo];
648 } else if ( [sender tag] == 2030) {
650 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
651 [(MainController *)controller showCurrentTrackInfo];
653 } else if ( [sender tag] == 2040) {
655 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
656 [(MainController *)controller showCurrentTrackInfo];
658 } else if ( [sender tag] == 2050) {
659 float newTime = ( -([sender floatValue]) );
660 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
661 [[sw entryEffect] setEffectTime:newTime];
662 } else if ( [sender tag] == 2060) {
663 float newTime = ( -([sender floatValue]) );
664 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
665 [[sw exitEffect] setEffectTime:newTime];
666 } else if ( [sender tag] == 2070) {
667 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
668 [sw setExitDelay:[sender floatValue]];
669 } else if ( [sender tag] == 2080) {
670 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
671 } else if ( [sender tag] == 2090) {
673 int setting = [sender indexOfSelectedItem];
675 if ( setting == 0 ) {
676 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
677 [backgroundColorWell setEnabled:NO];
678 [backgroundColorPopup setEnabled:NO];
679 } else if ( setting == 1 ) {
680 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
681 [backgroundColorWell setEnabled:NO];
682 [backgroundColorPopup setEnabled:NO];
683 } else if ( setting == 2 ) {
684 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
685 [backgroundColorWell setEnabled:YES];
686 [backgroundColorPopup setEnabled:YES];
689 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
690 [(MainController *)controller showCurrentTrackInfo];
692 } else if ( [sender tag] == 2091) {
693 [self setCustomColor:[sender color] updateWell:NO];
694 [(MainController *)controller showCurrentTrackInfo];
695 } else if ( [sender tag] == 2092) {
697 int selectedItem = [sender indexOfSelectedItem];
699 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
700 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
701 } else if ( selectedItem == 2 ) {
702 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
703 } else if ( selectedItem == 3 ) {
704 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
705 } else if ( selectedItem == 4 ) {
706 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
707 } else if ( selectedItem == 5 ) {
708 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
710 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
712 [(MainController *)controller showCurrentTrackInfo];
714 } else if ( [sender tag] == 2095) {
715 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
716 [(MainController *)controller showCurrentTrackInfo];
722 - (void)registerDefaults
724 ITDebugLog(@"Registering defaults.");
725 [df setObject:[NSArray arrayWithObjects:
738 nil] forKey:@"menu"];
740 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
741 [df setInteger:5 forKey:@"SongsInAdvance"];
742 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
743 [df setBool:YES forKey:@"showArtist"];
744 [df setBool:YES forKey:@"showAlbumArtwork"];
745 [df setBool:NO forKey:@"showAlbum"];
746 [df setBool:NO forKey:@"showComposer"];
747 [df setBool:NO forKey:@"showTime"];
748 [df setBool:NO forKey:@"showToolTip"];
750 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
751 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
752 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
753 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
754 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
755 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
756 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
757 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
758 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
759 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
760 [df setBool:YES forKey:@"showSongInfoOnChange"];
762 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
766 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
767 [[StatusWindowController sharedController] showSetupQueryWindow];
773 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
774 [[StatusWindow sharedWindow] vanish:self];
775 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
777 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
780 - (void)autoLaunchCancel
782 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
783 [[StatusWindow sharedWindow] vanish:self];
784 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
787 - (void)deletePressedInTableView:(NSTableView *)tableView
789 if (tableView == menuTableView) {
790 int selRow = [tableView selectedRow];
791 ITDebugLog(@"Delete pressed in menu table view.");
793 NSString *object = [myItems objectAtIndex:selRow];
795 if ([object isEqualToString:@"preferences"]) {
800 if (![object isEqualToString:@"separator"])
801 [availableItems addObject:object];
802 ITDebugLog(@"Removing object named %@", object);
803 [myItems removeObjectAtIndex:selRow];
804 [menuTableView reloadData];
805 [allTableView reloadData];
807 [self changeMenus:self];
811 - (void)resetRemotePlayerTextFields
813 if ([[NetworkController sharedController] isConnectedToServer]) {
814 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
815 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
817 [selectedPlayerTextField setStringValue:@"No shared player selected."];
818 [locationTextField setStringValue:@"-"];
822 /*************************************************************************/
824 #pragma mark HOTKEY SUPPORT METHODS
825 /*************************************************************************/
827 - (IBAction)clearHotKey:(id)sender
829 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
830 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
831 [controller setupHotKeys];
832 [hotKeysTableView reloadData];
835 - (IBAction)editHotKey:(id)sender
837 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
838 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
839 ITKeyCombo *keyCombo;
841 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
842 [controller clearHotKeys];
843 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
844 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
845 if ([panel runModal] == NSOKButton) {
846 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
848 keyCombo = [panel keyCombo];
850 //Check for duplicate key combo
851 while ( (nextKey = [keyEnumerator nextObject]) ) {
852 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
853 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
854 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
856 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
861 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
862 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
863 [controller setupHotKeys];
864 [hotKeysTableView reloadData];
865 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
867 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
871 - (void)hotKeysTableViewDoubleClicked:(id)sender
873 if ([sender clickedRow] > -1) {
874 [self editHotKey:sender];
878 /*************************************************************************/
880 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
881 /*************************************************************************/
883 - (void)audioscrobblerStatusChanged:(NSNotification *)note
885 [audioscrobblerStatusTextField setStringValue:[[note userInfo] objectForKey:@"StatusString"]];
890 ITDebugLog(@"Loading Preferences.nib.");
891 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
892 ITDebugLog(@"Failed to load Preferences.nib.");
898 - (void)setupCustomizationTables
900 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
901 ITDebugLog(@"Setting up table views.");
902 // Set the table view cells up
903 [imgCell setImageScaling:NSScaleNone];
904 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
905 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
907 // Register for drag and drop
908 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
909 @"MenuTableViewPboardType",
910 @"AllTableViewPboardType",
912 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
913 @"MenuTableViewPboardType",
914 @"AllTableViewPboardType",
918 - (void)setupMenuItems
920 NSEnumerator *itemEnum;
922 ITDebugLog(@"Setting up table view arrays.");
923 // Set the list of items you can have.
924 availableItems = [[NSMutableArray alloc] initWithObjects:
943 // Get our preferred menu
944 myItems = [[df arrayForKey:@"menu"] mutableCopy];
946 // Delete items in the availableItems array that are already part of the menu
947 itemEnum = [myItems objectEnumerator];
948 while ( (anItem = [itemEnum nextObject]) ) {
949 if (![anItem isEqualToString:@"separator"]) {
950 [availableItems removeObject:anItem];
954 // Items that show should a submenu image
955 submenuItems = [[NSArray alloc] initWithObjects:
967 NSEnumerator *keyArrayEnum;
968 NSString *serverName;
973 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioscrobblerStatusChanged:) name:@"AudioscrobblerStatusChanged" object:nil];
974 if ([df boolForKey:@"audioscrobblerEnabled"]) {
975 NSString *status = [[AudioscrobblerController sharedController] lastStatus];
976 [audioscrobblerStatusTextField setStringValue:(status == nil) ? @"Idle" : status];
979 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
981 ITDebugLog(@"Setting up preferences UI.");
982 // Fill in the number of songs in advance to show field
983 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
985 // Fill hot key array
986 keyArrayEnum = [hotKeysArray objectEnumerator];
988 while ( (anItem = [keyArrayEnum nextObject]) ) {
989 if ([df objectForKey:anItem]) {
990 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
991 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
993 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
997 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
998 [self setupScreenPopup];
1000 ITDebugLog(@"Setting up track info checkboxes.");
1001 // Check current track info buttons
1002 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
1003 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
1004 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
1005 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1006 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1007 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1008 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1009 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1010 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1011 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1013 if ([df boolForKey:@"runScripts"]) {
1014 [runScriptsCheckbox setState:NSOnState];
1015 [showScriptsButton setEnabled:YES];
1017 [showScriptsButton setEnabled:NO];
1020 // Set the launch at login checkbox state
1021 ITDebugLog(@"Setting launch at login state.");
1022 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1023 [launchAtLoginCheckbox setState:NSOnState];
1026 // Set the launch player checkbox state
1027 ITDebugLog(@"Setting launch player with MenuTunes state.");
1028 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1030 // Setup the positioning controls
1031 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1032 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1034 // Setup effects controls
1035 // Populate the effects popups
1036 [appearanceEffectPopup setAutoenablesItems:NO];
1037 [vanishEffectPopup setAutoenablesItems:NO];
1038 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1039 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1041 // Attempt to find the pref'd effect in the list.
1042 // If it's not there, use cut/dissolve.
1043 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1044 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1046 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1049 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1050 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1052 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1055 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1056 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1057 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1059 // Setup General Controls
1060 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1061 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1063 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1064 [backgroundColorWell setEnabled:YES];
1065 [backgroundColorPopup setEnabled:YES];
1067 [backgroundColorWell setEnabled:NO];
1068 [backgroundColorPopup setEnabled:NO];
1071 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1074 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1076 [backgroundColorWell setColor:[NSColor blueColor]];
1079 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1081 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1083 // Setup the sharing controls
1084 if ([df boolForKey:@"enableSharing"]) {
1085 [shareMenuTunesCheckbox setState:NSOnState];
1086 [useSharedMenuTunesCheckbox setEnabled:NO];
1087 [selectSharedPlayerButton setEnabled:NO];
1088 [passwordTextField setEnabled:YES];
1089 [nameTextField setEnabled:YES];
1090 } else if ([df boolForKey:@"useSharedPlayer"]) {
1091 [useSharedMenuTunesCheckbox setState:NSOnState];
1092 [shareMenuTunesCheckbox setEnabled:NO];
1093 [selectSharedPlayerButton setEnabled:YES];
1096 //Setup the Audioscrobbler controls
1097 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1098 [audioscrobblerEnabledCheckbox setState:NSOnState];
1099 [audioscrobblerUserTextField setEnabled:YES];
1100 [audioscrobblerPasswordTextField setEnabled:YES];
1101 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1103 [audioscrobblerUserTextField setEnabled:NO];
1104 [audioscrobblerPasswordTextField setEnabled:NO];
1105 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1107 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1108 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1109 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1110 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1111 if (password != nil) {
1112 [audioscrobblerPasswordTextField setStringValue:password];
1115 [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1117 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1119 serverName = [df stringForKey:@"sharedPlayerName"];
1120 if (!serverName || [serverName length] == 0) {
1121 serverName = @"MenuTunes Shared Player";
1123 [nameTextField setStringValue:serverName];
1125 [selectPlayerBox setContentView:zeroConfView];
1126 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1127 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1129 [passwordTextField setStringValue:@""];
1131 if ([df stringForKey:@"sharedPlayerHost"]) {
1132 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1135 if ([[NetworkController sharedController] isConnectedToServer]) {
1136 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1137 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1139 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1140 [locationTextField setStringValue:@"-"];
1144 - (void)setupScreenPopup
1146 ITDebugLog(@"Setting up screen popup");
1147 NSArray *screens = [NSScreen screens];
1148 if ([screens count] > 1) {
1149 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1150 [screenPopup setEnabled:YES];
1151 for (i = 0; i < [screens count]; i++) {
1152 NSScreen *screen = [screens objectAtIndex:i];
1153 if (![screen isEqual:[NSScreen mainScreen]]) {
1154 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1157 [screenPopup selectItemAtIndex:index];
1158 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1160 while ([screenPopup numberOfItems] > 1) {
1161 [screenPopup removeItemAtIndex:1];
1163 [screenPopup setEnabled:NO];
1164 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1168 - (void)setStatusWindowEntryEffect:(Class)effectClass
1170 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1172 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1173 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1175 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1176 [[sw entryEffect] setEffectTime:time];
1179 - (void)setStatusWindowExitEffect:(Class)effectClass
1181 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1183 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1184 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1186 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1187 [[sw exitEffect] setEffectTime:time];
1190 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1192 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1193 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1196 [backgroundColorWell setColor:color];
1200 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1202 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1205 [appearanceEffectPopup removeAllItems];
1206 [vanishEffectPopup removeAllItems];
1208 while ( (anItem = [effectEnum nextObject]) ) {
1209 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1210 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1212 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1213 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1215 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1216 [[appearanceEffectPopup lastItem] setEnabled:YES];
1217 [[vanishEffectPopup lastItem] setEnabled:YES];
1219 [[appearanceEffectPopup lastItem] setEnabled:NO];
1220 [[vanishEffectPopup lastItem] setEnabled:NO];
1226 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1230 if ( vPos == ITWindowPositionTop ) {
1231 if ( hPos == ITWindowPositionLeft ) {
1232 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1233 } else if ( hPos == ITWindowPositionCenter ) {
1234 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1235 } else if ( hPos == ITWindowPositionRight ) {
1236 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1238 } else if ( vPos == ITWindowPositionMiddle ) {
1239 if ( hPos == ITWindowPositionLeft ) {
1240 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1241 } else if ( hPos == ITWindowPositionCenter ) {
1242 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1243 } else if ( hPos == ITWindowPositionRight ) {
1244 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1246 } else if ( vPos == ITWindowPositionBottom ) {
1247 if ( hPos == ITWindowPositionLeft ) {
1248 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1249 } else if ( hPos == ITWindowPositionCenter ) {
1250 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1251 } else if ( hPos == ITWindowPositionRight ) {
1252 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1259 - (IBAction)changeMenus:(id)sender
1261 ITDebugLog(@"Synchronizing menus");
1262 [df setObject:myItems forKey:@"menu"];
1265 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1267 //If we're connected over a network, refresh the menu immediately
1268 if ([[NetworkController sharedController] isConnectedToServer]) {
1269 [controller timerUpdate];
1274 /*************************************************************************/
1276 #pragma mark NSWindow DELEGATE METHODS
1277 /*************************************************************************/
1279 - (void)windowWillClose:(NSNotification *)note
1281 [(MainController *)controller closePreferences];
1284 /*************************************************************************/
1286 #pragma mark NSTextField DELEGATE METHODS
1287 /*************************************************************************/
1289 - (void)controlTextDidChange:(NSNotification*)note
1291 if ([note object] == hostTextField) {
1292 if ([[hostTextField stringValue] length] == 0) {
1293 [sharingPanelOKButton setEnabled:NO];
1295 [sharingPanelOKButton setEnabled:YES];
1300 /*************************************************************************/
1302 #pragma mark NSTableView DATASOURCE METHODS
1303 /*************************************************************************/
1305 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1307 if (aTableView == menuTableView) {
1308 return [myItems count];
1309 } else if (aTableView == allTableView) {
1310 return [availableItems count];
1311 } else if (aTableView == hotKeysTableView) {
1312 return [hotKeysArray count];
1314 return [[[NetworkController sharedController] remoteServices] count];
1318 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1320 if (aTableView == menuTableView) {
1321 NSString *object = [myItems objectAtIndex:rowIndex];
1322 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1323 if ([object isEqualToString:@"showPlayer"]) {
1324 NSString *string = nil;
1326 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1328 [controller networkError:localException];
1332 return NSLocalizedString(object, @"ERROR");
1334 if ([submenuItems containsObject:object])
1336 return [NSImage imageNamed:@"submenu"];
1341 } else if (aTableView == allTableView) {
1342 NSString *object = [availableItems objectAtIndex:rowIndex];
1343 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1344 if ([object isEqualToString:@"showPlayer"]) {
1345 NSString *string = nil;
1347 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1349 [controller networkError:localException];
1353 return NSLocalizedString(object, @"ERROR");
1355 if ([submenuItems containsObject:object]) {
1356 return [NSImage imageNamed:@"submenu"];
1361 } else if (aTableView == hotKeysTableView) {
1362 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1363 return [hotKeyNamesArray objectAtIndex:rowIndex];
1365 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1368 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1372 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1374 if (tableView == menuTableView) {
1375 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1376 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1380 if (tableView == allTableView) {
1381 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1382 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1388 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1392 NSString *dragData, *temp;
1394 pb = [info draggingPasteboard];
1396 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1397 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1398 dragRow = [dragData intValue];
1399 temp = [myItems objectAtIndex:dragRow];
1401 if (tableView == menuTableView) {
1402 [myItems insertObject:temp atIndex:row];
1403 if (row > dragRow) {
1404 [myItems removeObjectAtIndex:dragRow];
1406 [myItems removeObjectAtIndex:dragRow + 1];
1408 } else if (tableView == allTableView) {
1409 if (![temp isEqualToString:@"separator"]) {
1410 [availableItems addObject:temp];
1412 [myItems removeObjectAtIndex:dragRow];
1414 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1415 dragData = [pb stringForType:@"AllTableViewPboardType"];
1416 dragRow = [dragData intValue];
1417 temp = [availableItems objectAtIndex:dragRow];
1419 [myItems insertObject:temp atIndex:row];
1421 if (![temp isEqualToString:@"separator"]) {
1422 [availableItems removeObjectAtIndex:dragRow];
1426 [menuTableView reloadData];
1427 [allTableView reloadData];
1428 [self changeMenus:self];
1432 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1434 if (tableView == allTableView) {
1435 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1436 return NSDragOperationNone;
1439 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1440 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1441 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1442 return NSDragOperationNone;
1446 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1447 return NSDragOperationGeneric;
1450 if (operation == NSTableViewDropOn || row == -1)
1452 return NSDragOperationNone;
1454 return NSDragOperationGeneric;
1458 /*************************************************************************/
1460 #pragma mark DEALLOCATION METHODS
1461 /*************************************************************************/
1465 [[NSNotificationCenter defaultCenter] removeObserver:self];
1466 [hotKeysArray release];
1467 [hotKeysDictionary release];
1468 [effectClasses release];
1469 [menuTableView setDataSource:nil];
1470 [allTableView setDataSource:nil];
1471 [controller release];
1472 [availableItems release];
1473 [submenuItems release];