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/ITLoginItem.h>
21 #import <ITKit/ITHotKeyCenter.h>
22 #import <ITKit/ITKeyCombo.h>
23 #import <ITKit/ITKeyComboPanel.h>
24 #import <ITKit/ITWindowPositioning.h>
25 #import <ITKit/ITKeyBroadcaster.h>
27 #import <ITKit/ITTSWBackgroundView.h>
28 #import <ITKit/ITWindowEffect.h>
29 #import <ITKit/ITCutWindowEffect.h>
30 #import <ITKit/ITDissolveWindowEffect.h>
31 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
32 #import <ITKit/ITSlideVerticallyWindowEffect.h>
33 #import <ITKit/ITPivotWindowEffect.h>
36 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
37 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE "MenuTunes: Audioscrobbler"
38 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
40 /*************************************************************************/
42 #pragma mark PRIVATE INTERFACE
43 /*************************************************************************/
45 @interface PreferencesController (Private)
46 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
47 + (BOOL)keychainItemExistsForUser:(NSString *)user;
48 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
49 + (BOOL)deleteKeychainItemForUser:(NSString *)user;
50 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
53 - (void)setupCustomizationTables;
54 - (void)setupMenuItems;
56 - (void)setupScreenPopup;
57 - (void)setStatusWindowEntryEffect:(Class)effectClass;
58 - (void)setStatusWindowExitEffect:(Class)effectClass;
59 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
60 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
61 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
62 - (IBAction)changeMenus:(id)sender;
66 @implementation PreferencesController
69 /*************************************************************************/
71 #pragma mark STATIC VARIABLES
72 /*************************************************************************/
74 static PreferencesController *prefs = nil;
76 /*************************************************************************/
78 #pragma mark STATIC KEYCHAIN SUPPORT METHODS
79 /*************************************************************************/
81 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user
83 SecKeychainSearchRef search;
84 SecKeychainItemRef item;
86 SecKeychainAttribute attributes[3];
87 SecKeychainAttributeList list;
89 if ((user == nil) || ([user length] == 0)) {
93 ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
94 attributes[0].tag = kSecAccountItemAttr;
95 attributes[0].data = (char *)[user UTF8String];
96 attributes[0].length = [user length];
97 attributes[1].tag = kSecDescriptionItemAttr;
98 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
99 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
100 attributes[2].tag = kSecLabelItemAttr;
101 attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
102 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
104 list.attr = attributes;
106 status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
108 if (status != noErr) {
109 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
112 status = SecKeychainSearchCopyNext(search, &item);
114 if (status != noErr) {
115 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
123 + (BOOL)keychainItemExistsForUser:(NSString *)user
125 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
126 BOOL exists = (item != nil);
133 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
135 SecKeychainItemRef item;
137 SecKeychainAttribute attributes[3];
138 SecKeychainAttributeList list;
140 ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
141 attributes[0].tag = kSecAccountItemAttr;
142 attributes[0].data = (char *)[user UTF8String];
143 attributes[0].length = [user length];
144 attributes[1].tag = kSecDescriptionItemAttr;
145 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
146 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
147 attributes[2].tag = kSecLabelItemAttr;
148 attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
149 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
151 list.attr = attributes;
153 status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
154 if (status != noErr) {
155 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
157 return (status == noErr);
160 + (BOOL)deleteKeychainItemForUser:(NSString *)user
162 OSStatus status = errSecNotAvailable;
163 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
165 status = SecKeychainItemDelete(item);
166 if (status != noErr) {
167 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
171 return (status == noErr);
174 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
176 OSStatus status = errSecNotAvailable;
177 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
179 status = SecKeychainItemModifyContent(item, NULL, [password length], [password cString]);
180 if (status != noErr) {
181 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
185 return (status == noErr);
188 + (NSString *)getKeychainItemPasswordForUser:(NSString *)user
190 OSStatus status = errSecNotAvailable;
191 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
192 NSString *pass = nil;
196 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
197 if (status != noErr) {
198 ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
200 if ([NSString respondsToSelector:@selector(stringWithCString:encoding:)]) {
201 pass = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
203 pass = [NSString stringWithCString:buffer];
206 if (status != noErr) {
207 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
211 NSLog(@"Audioscrobbler: Retrieved password: %@", pass);
215 /*************************************************************************/
217 #pragma mark INITIALIZATION METHODS
218 /*************************************************************************/
220 + (PreferencesController *)sharedPrefs;
223 prefs = [[self alloc] init];
230 if ( (self = [super init]) ) {
231 ITDebugLog(@"Preferences initialized.");
232 df = [[NSUserDefaults standardUserDefaults] retain];
234 effectClasses = [[ITWindowEffect effectClasses] retain];
236 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
251 @"ToggleShufflability",
261 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
276 @"Toggle Song Included In Shuffle",
277 @"Pop-up status menu",
278 [NSString stringWithUTF8String:"Set Rating: ‚òÜ‚òÜ‚òÜ‚òÜ‚òÜ"],
279 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÜ‚òÜ‚òÜ‚òÜ"],
280 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÜ‚òÜ‚òÜ"],
281 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÖ‚òÜ‚òÜ"],
282 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÖ‚òÖ‚òÜ"],
283 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÖ‚òÖ‚òÖ"],
285 hotKeysDictionary = [[NSMutableDictionary alloc] init];
288 [self setupWindow]; // Load in the nib, and perform any initial setup.
289 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
295 /*************************************************************************/
297 #pragma mark ACCESSOR METHODS
298 /*************************************************************************/
305 - (void)setController:(id)object
307 [controller autorelease];
308 controller = [object retain];
312 /*************************************************************************/
314 #pragma mark INSTANCE METHODS
315 /*************************************************************************/
317 - (BOOL)showPasswordPanel
319 [passwordPanel setLevel:NSStatusWindowLevel];
320 [passwordPanelOKButton setTitle:@"Connect"];
321 [passwordPanelTitle setStringValue:@"Password Required"];
322 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[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 - (BOOL)showInvalidPasswordPanel
337 [passwordPanel setLevel:NSStatusWindowLevel];
338 [passwordPanelOKButton setTitle:@"Retry"];
339 [passwordPanelTitle setStringValue:@"Invalid Password"];
340 [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]]];
341 [passwordPanel setLevel:NSStatusWindowLevel];
342 [NSApp activateIgnoringOtherApps:YES];
343 [passwordPanel center];
344 [passwordPanel orderFrontRegardless];
345 [passwordPanel makeKeyWindow];
346 if ([NSApp runModalForWindow:passwordPanel]) {
353 - (IBAction)showPrefsWindow:(id)sender
355 ITDebugLog(@"Showing preferences window.");
356 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
357 ITDebugLog(@"Window doesn't exist, initial setup.");
358 [self setupCustomizationTables]; // Setup the DnD manu config tables.
359 [self setupMenuItems]; // Setup the arrays of menu items
360 [self setupUI]; // Sets up additional UI
361 [window setDelegate:self];
362 [menuTableView reloadData];
363 [hotKeysTableView reloadData];
364 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
366 //Change the launch player checkbox to the proper name
368 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
370 [controller networkError:localException];
374 [self resetRemotePlayerTextFields];
375 [launchAtLoginCheckbox becomeFirstResponder];
376 [NSApp activateIgnoringOtherApps:YES];
377 if (![window isVisible]) {
380 [window orderFrontRegardless];
381 [window makeKeyWindow];
384 - (IBAction)changeGeneralSetting:(id)sender
386 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
387 if ( [sender tag] == 1010) {
388 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
389 } else if ( [sender tag] == 1020) {
390 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
391 } else if ( [sender tag] == 1030) {
392 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
393 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
394 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
396 } else if ( [sender tag] == 1040) {
397 // This will not be executed. Song info always shows the title of the song.
398 // [df setBool:SENDER_STATE forKey:@"showName"];
399 } else if ( [sender tag] == 1050) {
400 [df setBool:SENDER_STATE forKey:@"showArtist"];
401 } else if ( [sender tag] == 1055) {
402 [df setBool:SENDER_STATE forKey:@"showComposer"];
403 } else if ( [sender tag] == 1060) {
404 [df setBool:SENDER_STATE forKey:@"showAlbum"];
405 } else if ( [sender tag] == 1070) {
406 [df setBool:SENDER_STATE forKey:@"showTime"];
407 } else if ( [sender tag] == 1080) {
408 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
409 } else if ( [sender tag] == 1085) {
410 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
411 } else if ( [sender tag] == 1090) {
412 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
413 } else if ( [sender tag] == 1100) {
414 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
415 } else if ( [sender tag] == 1110) {
416 [df setBool:SENDER_STATE forKey:@"runScripts"];
418 [runScriptsCheckbox setState:NSOnState];
419 [showScriptsButton setEnabled:YES];
421 [showScriptsButton setEnabled:NO];
423 } else if ( [sender tag] == 1120) {
424 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
425 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
426 } else if ( [sender tag] == 6010) {
427 //Toggle the other Audioscrobbler options
428 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
429 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
430 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
431 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
433 [[AudioscrobblerController sharedController] attemptHandshake:NO];
435 } else if ( [sender tag ] == 6015) {
436 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
437 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
438 if ([newAccount length] == 0) {
439 [PreferencesController deleteKeychainItemForUser:currentAccount];
440 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
441 [df setObject:newAccount forKey:@"audioscrobblerUser"];
442 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
443 //Delete the current keychain item if there is one
444 [PreferencesController deleteKeychainItemForUser:currentAccount];
446 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
447 [[AudioscrobblerController sharedController] attemptHandshake:YES];
449 } else if ( [sender tag ] == 6030) {
450 //Here we set the password for an existing keychain item or we create a new keychain item.
451 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
452 NSString *account = [df stringForKey:@"audioscrobblerUser"];
453 if ([PreferencesController keychainItemExistsForUser:account]) {
454 //Update the current keychain item
455 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
456 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
457 //Create a new keychain item
458 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
461 } else if ( [sender tag] == 6045) {
462 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
467 - (IBAction)changeSharingSetting:(id)sender
469 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
470 if ( [sender tag] == 5010 ) {
471 BOOL state = SENDER_STATE;
472 [df setBool:state forKey:@"enableSharing"];
473 //Disable/enable the use of shared player options
474 [useSharedMenuTunesCheckbox setEnabled:!state];
475 [passwordTextField setEnabled:state];
476 [nameTextField setEnabled:state];
477 [selectSharedPlayerButton setEnabled:NO];
478 [controller setServerStatus:state]; //Set server status
479 } else if ( [sender tag] == 5015 ) {
480 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
481 [[NetworkController sharedController] resetServerName];
482 } else if ( [sender tag] == 5030 ) {
483 //Set the server password
484 const char *instring = [[sender stringValue] UTF8String];
485 const char *password = "p4s5w0rdMT1.2";
487 NSData *hashedPass, *passwordStringHash;
488 if ([[sender stringValue] length] == 0) {
489 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
492 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
493 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
494 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
495 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
496 if (![hashedPass isEqualToData:passwordStringHash]) {
497 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
498 [sender setStringValue:@"p4s5w0rdMT1.2"];
500 } else if ( [sender tag] == 5040 ) {
501 BOOL state = SENDER_STATE;
502 [df setBool:state forKey:@"useSharedPlayer"];
503 //Disable/enable the use of sharing options
504 [shareMenuTunesCheckbox setEnabled:!state];
505 [passwordTextField setEnabled:NO];
506 [nameTextField setEnabled:NO];
507 [selectSharedPlayerButton setEnabled:state];
509 if (state && ([controller connectToServer] == 1)) {
510 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
511 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
513 [selectedPlayerTextField setStringValue:@"No shared player selected."];
514 [locationTextField setStringValue:@"-"];
515 if ([[NetworkController sharedController] isConnectedToServer]) {
516 [controller disconnectFromServer];
520 } else if ( [sender tag] == 5050 ) {
521 //If no player is selected in the table view, turn off OK button.
522 if ([sender clickedRow] == -1 ) {
523 [sharingPanelOKButton setEnabled:NO];
525 [sharingPanelOKButton setEnabled:YES];
527 } else if ( [sender tag] == 5051 ) {
528 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
529 } else if ( [sender tag] == 5060 ) {
530 //Set OK button state
531 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
532 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
533 [sharingPanelOKButton setEnabled:NO];
535 [sharingPanelOKButton setEnabled:YES];
537 //Show selection sheet
538 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
539 } else if ( [sender tag] == 5100 ) {
541 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
542 NSRect frame = [selectPlayerSheet frame];
543 frame.origin.y -= 58;
544 frame.size.height = 273;
545 if ([sharingTableView selectedRow] == -1) {
546 [sharingPanelOKButton setEnabled:NO];
548 [selectPlayerBox setContentView:zeroConfView];
549 [selectPlayerSheet setFrame:frame display:YES animate:YES];
550 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
551 NSRect frame = [selectPlayerSheet frame];
552 frame.origin.y += 58;
553 frame.size.height = 215;
554 if ([[hostTextField stringValue] length] == 0) {
555 [sharingPanelOKButton setEnabled:NO];
557 [sharingPanelOKButton setEnabled:YES];
559 [selectPlayerBox setContentView:manualView];
560 [selectPlayerSheet setFrame:frame display:YES animate:YES];
561 [hostTextField selectText:nil];
563 } else if ( [sender tag] == 5150 ) {
564 const char *instring = [[sender stringValue] UTF8String];
566 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
567 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
568 } else if ( [sender tag] == 5110 ) {
570 [NSApp endSheet:selectPlayerSheet];
571 [selectPlayerSheet orderOut:nil];
572 if ([selectPlayerBox contentView] == manualView) {
573 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
576 } else if ( [sender tag] == 5120 ) {
578 [NSApp endSheet:selectPlayerSheet];
579 [selectPlayerSheet orderOut:nil];
581 [self changeSharingSetting:clientPasswordTextField];
583 if ([selectPlayerBox contentView] == manualView) {
584 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
586 if ([sharingTableView selectedRow] > -1) {
587 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
591 if ([controller connectToServer] == 1) {
592 [useSharedMenuTunesCheckbox setState:NSOnState];
593 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
594 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
596 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);
598 } else if ( [sender tag] == 6010 ) {
599 //Cancel password entry
600 [passwordPanel orderOut:nil];
601 [NSApp stopModalWithCode:0];
602 } else if ( [sender tag] == 6020 ) {
603 //OK password entry, retry connect
604 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
606 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
607 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
608 [passwordPanel orderOut:nil];
609 [NSApp stopModalWithCode:1];
614 - (IBAction)changeStatusWindowSetting:(id)sender
616 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
617 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
619 if ( [sender tag] == 2010) {
621 BOOL entryEffectValid = YES;
622 BOOL exitEffectValid = YES;
624 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
625 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
626 [sw setVerticalPosition:[sender selectedRow]];
627 [sw setHorizontalPosition:[sender selectedColumn]];
629 // Enable/disable the items in the popups.
630 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
631 horizontalPosition:[sw horizontalPosition]];
633 // Make sure the effects support the new position.
634 entryEffectValid = ( [self effect:[[sw entryEffect] class]
635 supportsVerticalPosition:[sw verticalPosition]
636 withHorizontalPosition:[sw horizontalPosition]] );
637 exitEffectValid = ( [self effect:[[sw exitEffect] class]
638 supportsVerticalPosition:[sw verticalPosition]
639 withHorizontalPosition:[sw horizontalPosition]] );
641 if ( ! entryEffectValid ) {
642 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
643 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
645 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
648 if ( ! exitEffectValid ) {
649 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
650 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
652 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
655 [(MainController *)controller showCurrentTrackInfo];
657 } else if ( [sender tag] == 2020) {
659 // Update screen selection.
660 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
661 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
662 [(MainController *)controller showCurrentTrackInfo];
664 } else if ( [sender tag] == 2030) {
666 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
667 [(MainController *)controller showCurrentTrackInfo];
669 } else if ( [sender tag] == 2040) {
671 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
672 [(MainController *)controller showCurrentTrackInfo];
674 } else if ( [sender tag] == 2050) {
675 float newTime = ( -([sender floatValue]) );
676 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
677 [[sw entryEffect] setEffectTime:newTime];
678 } else if ( [sender tag] == 2060) {
679 float newTime = ( -([sender floatValue]) );
680 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
681 [[sw exitEffect] setEffectTime:newTime];
682 } else if ( [sender tag] == 2070) {
683 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
684 [sw setExitDelay:[sender floatValue]];
685 } else if ( [sender tag] == 2080) {
686 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
687 } else if ( [sender tag] == 2090) {
689 int setting = [sender indexOfSelectedItem];
691 if ( setting == 0 ) {
692 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
693 [backgroundColorWell setEnabled:NO];
694 [backgroundColorPopup setEnabled:NO];
695 } else if ( setting == 1 ) {
696 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
697 [backgroundColorWell setEnabled:NO];
698 [backgroundColorPopup setEnabled:NO];
699 } else if ( setting == 2 ) {
700 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
701 [backgroundColorWell setEnabled:YES];
702 [backgroundColorPopup setEnabled:YES];
705 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
706 [(MainController *)controller showCurrentTrackInfo];
708 } else if ( [sender tag] == 2091) {
709 [self setCustomColor:[sender color] updateWell:NO];
710 [(MainController *)controller showCurrentTrackInfo];
711 } else if ( [sender tag] == 2092) {
713 int selectedItem = [sender indexOfSelectedItem];
715 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
716 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
717 } else if ( selectedItem == 2 ) {
718 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
719 } else if ( selectedItem == 3 ) {
720 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
721 } else if ( selectedItem == 4 ) {
722 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
723 } else if ( selectedItem == 5 ) {
724 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
726 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
728 [(MainController *)controller showCurrentTrackInfo];
730 } else if ( [sender tag] == 2095) {
731 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
732 [(MainController *)controller showCurrentTrackInfo];
738 - (void)registerDefaults
740 ITDebugLog(@"Registering defaults.");
741 [df setObject:[NSArray arrayWithObjects:
754 nil] forKey:@"menu"];
756 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
757 [df setInteger:5 forKey:@"SongsInAdvance"];
758 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
759 [df setBool:YES forKey:@"showArtist"];
760 [df setBool:YES forKey:@"showAlbumArtwork"];
761 [df setBool:NO forKey:@"showAlbum"];
762 [df setBool:NO forKey:@"showComposer"];
763 [df setBool:NO forKey:@"showTime"];
764 [df setBool:NO forKey:@"showToolTip"];
766 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
767 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
768 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
769 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
770 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
771 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
772 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
773 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
774 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
775 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
776 [df setBool:YES forKey:@"showSongInfoOnChange"];
778 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
782 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
783 [[StatusWindowController sharedController] showSetupQueryWindow];
789 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
790 [[StatusWindow sharedWindow] vanish:self];
791 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
793 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
796 - (void)autoLaunchCancel
798 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
799 [[StatusWindow sharedWindow] vanish:self];
800 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
803 - (void)deletePressedInTableView:(NSTableView *)tableView
805 if (tableView == menuTableView) {
806 int selRow = [tableView selectedRow];
807 ITDebugLog(@"Delete pressed in menu table view.");
809 NSString *object = [myItems objectAtIndex:selRow];
811 if ([object isEqualToString:@"preferences"]) {
816 if (![object isEqualToString:@"separator"])
817 [availableItems addObject:object];
818 ITDebugLog(@"Removing object named %@", object);
819 [myItems removeObjectAtIndex:selRow];
820 [menuTableView reloadData];
821 [allTableView reloadData];
823 [self changeMenus:self];
827 - (void)resetRemotePlayerTextFields
829 if ([[NetworkController sharedController] isConnectedToServer]) {
830 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
831 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
833 [selectedPlayerTextField setStringValue:@"No shared player selected."];
834 [locationTextField setStringValue:@"-"];
838 /*************************************************************************/
840 #pragma mark HOTKEY SUPPORT METHODS
841 /*************************************************************************/
843 - (IBAction)clearHotKey:(id)sender
845 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
846 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
847 [controller setupHotKeys];
848 [hotKeysTableView reloadData];
851 - (IBAction)editHotKey:(id)sender
853 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
854 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
855 ITKeyCombo *keyCombo;
857 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
858 [controller clearHotKeys];
859 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
860 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
861 if ([panel runModal] == NSOKButton) {
862 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
864 keyCombo = [panel keyCombo];
866 //Check for duplicate key combo
867 while ( (nextKey = [keyEnumerator nextObject]) ) {
868 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
869 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
870 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
872 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
877 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
878 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
879 [controller setupHotKeys];
880 [hotKeysTableView reloadData];
881 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
883 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
887 - (void)hotKeysTableViewDoubleClicked:(id)sender
889 if ([sender clickedRow] > -1) {
890 [self editHotKey:sender];
894 /*************************************************************************/
896 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
897 /*************************************************************************/
899 - (void)audioscrobblerStatusChanged:(NSNotification *)note
901 [audioscrobblerStatusTextField setStringValue:[[note userInfo] objectForKey:@"StatusString"]];
906 ITDebugLog(@"Loading Preferences.nib.");
907 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
908 ITDebugLog(@"Failed to load Preferences.nib.");
914 - (void)setupCustomizationTables
916 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
917 ITDebugLog(@"Setting up table views.");
918 // Set the table view cells up
919 [imgCell setImageScaling:NSScaleNone];
920 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
921 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
923 // Register for drag and drop
924 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
925 @"MenuTableViewPboardType",
926 @"AllTableViewPboardType",
928 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
929 @"MenuTableViewPboardType",
930 @"AllTableViewPboardType",
934 - (void)setupMenuItems
936 NSEnumerator *itemEnum;
938 ITDebugLog(@"Setting up table view arrays.");
939 // Set the list of items you can have.
940 availableItems = [[NSMutableArray alloc] initWithObjects:
959 // Get our preferred menu
960 myItems = [[df arrayForKey:@"menu"] mutableCopy];
962 // Delete items in the availableItems array that are already part of the menu
963 itemEnum = [myItems objectEnumerator];
964 while ( (anItem = [itemEnum nextObject]) ) {
965 if (![anItem isEqualToString:@"separator"]) {
966 [availableItems removeObject:anItem];
970 // Items that show should a submenu image
971 submenuItems = [[NSArray alloc] initWithObjects:
983 NSEnumerator *keyArrayEnum;
984 NSString *serverName;
989 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioscrobblerStatusChanged:) name:@"AudioscrobblerStatusChanged" object:nil];
990 if ([df boolForKey:@"audioscrobblerEnabled"]) {
991 NSString *status = [[AudioscrobblerController sharedController] lastStatus];
992 [audioscrobblerStatusTextField setStringValue:(status == nil) ? @"Idle" : status];
995 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
997 ITDebugLog(@"Setting up preferences UI.");
998 // Fill in the number of songs in advance to show field
999 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
1001 // Fill hot key array
1002 keyArrayEnum = [hotKeysArray objectEnumerator];
1004 while ( (anItem = [keyArrayEnum nextObject]) ) {
1005 if ([df objectForKey:anItem]) {
1006 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
1007 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
1009 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
1013 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
1014 [self setupScreenPopup];
1016 ITDebugLog(@"Setting up track info checkboxes.");
1017 // Check current track info buttons
1018 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
1019 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
1020 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
1021 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1022 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1023 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1024 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1025 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1026 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1027 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1029 if ([df boolForKey:@"runScripts"]) {
1030 [runScriptsCheckbox setState:NSOnState];
1031 [showScriptsButton setEnabled:YES];
1033 [showScriptsButton setEnabled:NO];
1036 // Set the launch at login checkbox state
1037 ITDebugLog(@"Setting launch at login state.");
1038 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1039 [launchAtLoginCheckbox setState:NSOnState];
1042 // Set the launch player checkbox state
1043 ITDebugLog(@"Setting launch player with MenuTunes state.");
1044 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1046 // Setup the positioning controls
1047 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1048 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1050 // Setup effects controls
1051 // Populate the effects popups
1052 [appearanceEffectPopup setAutoenablesItems:NO];
1053 [vanishEffectPopup setAutoenablesItems:NO];
1054 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1055 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1057 // Attempt to find the pref'd effect in the list.
1058 // If it's not there, use cut/dissolve.
1059 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1060 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1062 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1065 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1066 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1068 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1071 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1072 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1073 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1075 // Setup General Controls
1076 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1077 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1079 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1080 [backgroundColorWell setEnabled:YES];
1081 [backgroundColorPopup setEnabled:YES];
1083 [backgroundColorWell setEnabled:NO];
1084 [backgroundColorPopup setEnabled:NO];
1087 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1090 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1092 [backgroundColorWell setColor:[NSColor blueColor]];
1095 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1097 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1099 // Setup the sharing controls
1100 if ([df boolForKey:@"enableSharing"]) {
1101 [shareMenuTunesCheckbox setState:NSOnState];
1102 [useSharedMenuTunesCheckbox setEnabled:NO];
1103 [selectSharedPlayerButton setEnabled:NO];
1104 [passwordTextField setEnabled:YES];
1105 [nameTextField setEnabled:YES];
1106 } else if ([df boolForKey:@"useSharedPlayer"]) {
1107 [useSharedMenuTunesCheckbox setState:NSOnState];
1108 [shareMenuTunesCheckbox setEnabled:NO];
1109 [selectSharedPlayerButton setEnabled:YES];
1112 //Setup the Audioscrobbler controls
1113 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1114 [audioscrobblerEnabledCheckbox setState:NSOnState];
1115 [audioscrobblerUserTextField setEnabled:YES];
1116 [audioscrobblerPasswordTextField setEnabled:YES];
1117 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1119 [audioscrobblerUserTextField setEnabled:NO];
1120 [audioscrobblerPasswordTextField setEnabled:NO];
1121 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1123 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1124 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1125 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1126 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1127 if (password != nil) {
1128 [audioscrobblerPasswordTextField setStringValue:password];
1131 [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1133 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1135 serverName = [df stringForKey:@"sharedPlayerName"];
1136 if (!serverName || [serverName length] == 0) {
1137 serverName = @"MenuTunes Shared Player";
1139 [nameTextField setStringValue:serverName];
1141 [selectPlayerBox setContentView:zeroConfView];
1142 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1143 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1145 [passwordTextField setStringValue:@""];
1147 if ([df stringForKey:@"sharedPlayerHost"]) {
1148 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1151 if ([[NetworkController sharedController] isConnectedToServer]) {
1152 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1153 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1155 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1156 [locationTextField setStringValue:@"-"];
1160 - (void)setupScreenPopup
1162 ITDebugLog(@"Setting up screen popup");
1163 NSArray *screens = [NSScreen screens];
1164 if ([screens count] > 1) {
1165 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1166 [screenPopup setEnabled:YES];
1167 for (i = 0; i < [screens count]; i++) {
1168 NSScreen *screen = [screens objectAtIndex:i];
1169 if (![screen isEqual:[NSScreen mainScreen]]) {
1170 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1173 [screenPopup selectItemAtIndex:index];
1174 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1176 while ([screenPopup numberOfItems] > 1) {
1177 [screenPopup removeItemAtIndex:1];
1179 [screenPopup setEnabled:NO];
1180 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1184 - (void)setStatusWindowEntryEffect:(Class)effectClass
1186 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1188 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1189 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1191 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1192 [[sw entryEffect] setEffectTime:time];
1195 - (void)setStatusWindowExitEffect:(Class)effectClass
1197 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1199 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1200 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1202 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1203 [[sw exitEffect] setEffectTime:time];
1206 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1208 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1209 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1212 [backgroundColorWell setColor:color];
1216 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1218 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1221 [appearanceEffectPopup removeAllItems];
1222 [vanishEffectPopup removeAllItems];
1224 while ( (anItem = [effectEnum nextObject]) ) {
1225 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1226 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1228 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1229 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1231 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1232 [[appearanceEffectPopup lastItem] setEnabled:YES];
1233 [[vanishEffectPopup lastItem] setEnabled:YES];
1235 [[appearanceEffectPopup lastItem] setEnabled:NO];
1236 [[vanishEffectPopup lastItem] setEnabled:NO];
1242 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1246 if ( vPos == ITWindowPositionTop ) {
1247 if ( hPos == ITWindowPositionLeft ) {
1248 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1249 } else if ( hPos == ITWindowPositionCenter ) {
1250 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1251 } else if ( hPos == ITWindowPositionRight ) {
1252 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1254 } else if ( vPos == ITWindowPositionMiddle ) {
1255 if ( hPos == ITWindowPositionLeft ) {
1256 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1257 } else if ( hPos == ITWindowPositionCenter ) {
1258 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1259 } else if ( hPos == ITWindowPositionRight ) {
1260 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1262 } else if ( vPos == ITWindowPositionBottom ) {
1263 if ( hPos == ITWindowPositionLeft ) {
1264 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1265 } else if ( hPos == ITWindowPositionCenter ) {
1266 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1267 } else if ( hPos == ITWindowPositionRight ) {
1268 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1275 - (IBAction)changeMenus:(id)sender
1277 ITDebugLog(@"Synchronizing menus");
1278 [df setObject:myItems forKey:@"menu"];
1281 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1283 //If we're connected over a network, refresh the menu immediately
1284 if ([[NetworkController sharedController] isConnectedToServer]) {
1285 [controller timerUpdate];
1290 /*************************************************************************/
1292 #pragma mark NSWindow DELEGATE METHODS
1293 /*************************************************************************/
1295 - (void)windowWillClose:(NSNotification *)note
1297 [(MainController *)controller closePreferences];
1300 /*************************************************************************/
1302 #pragma mark NSTextField DELEGATE METHODS
1303 /*************************************************************************/
1305 - (void)controlTextDidChange:(NSNotification*)note
1307 if ([note object] == hostTextField) {
1308 if ([[hostTextField stringValue] length] == 0) {
1309 [sharingPanelOKButton setEnabled:NO];
1311 [sharingPanelOKButton setEnabled:YES];
1316 /*************************************************************************/
1318 #pragma mark NSTableView DATASOURCE METHODS
1319 /*************************************************************************/
1321 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1323 if (aTableView == menuTableView) {
1324 return [myItems count];
1325 } else if (aTableView == allTableView) {
1326 return [availableItems count];
1327 } else if (aTableView == hotKeysTableView) {
1328 return [hotKeysArray count];
1330 return [[[NetworkController sharedController] remoteServices] count];
1334 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1336 if (aTableView == menuTableView) {
1337 NSString *object = [myItems objectAtIndex:rowIndex];
1338 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1339 if ([object isEqualToString:@"showPlayer"]) {
1340 NSString *string = nil;
1342 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1344 [controller networkError:localException];
1348 return NSLocalizedString(object, @"ERROR");
1350 if ([submenuItems containsObject:object])
1352 return [NSImage imageNamed:@"submenu"];
1357 } else if (aTableView == allTableView) {
1358 NSString *object = [availableItems objectAtIndex:rowIndex];
1359 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1360 if ([object isEqualToString:@"showPlayer"]) {
1361 NSString *string = nil;
1363 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1365 [controller networkError:localException];
1369 return NSLocalizedString(object, @"ERROR");
1371 if ([submenuItems containsObject:object]) {
1372 return [NSImage imageNamed:@"submenu"];
1377 } else if (aTableView == hotKeysTableView) {
1378 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1379 return [hotKeyNamesArray objectAtIndex:rowIndex];
1381 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1384 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1388 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1390 if (tableView == menuTableView) {
1391 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1392 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1396 if (tableView == allTableView) {
1397 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1398 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1404 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1408 NSString *dragData, *temp;
1410 pb = [info draggingPasteboard];
1412 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1413 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1414 dragRow = [dragData intValue];
1415 temp = [myItems objectAtIndex:dragRow];
1417 if (tableView == menuTableView) {
1418 [myItems insertObject:temp atIndex:row];
1419 if (row > dragRow) {
1420 [myItems removeObjectAtIndex:dragRow];
1422 [myItems removeObjectAtIndex:dragRow + 1];
1424 } else if (tableView == allTableView) {
1425 if (![temp isEqualToString:@"separator"]) {
1426 [availableItems addObject:temp];
1428 [myItems removeObjectAtIndex:dragRow];
1430 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1431 dragData = [pb stringForType:@"AllTableViewPboardType"];
1432 dragRow = [dragData intValue];
1433 temp = [availableItems objectAtIndex:dragRow];
1435 [myItems insertObject:temp atIndex:row];
1437 if (![temp isEqualToString:@"separator"]) {
1438 [availableItems removeObjectAtIndex:dragRow];
1442 [menuTableView reloadData];
1443 [allTableView reloadData];
1444 [self changeMenus:self];
1448 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1450 if (tableView == allTableView) {
1451 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1452 return NSDragOperationNone;
1455 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1456 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1457 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1458 return NSDragOperationNone;
1462 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1463 return NSDragOperationGeneric;
1466 if (operation == NSTableViewDropOn || row == -1)
1468 return NSDragOperationNone;
1470 return NSDragOperationGeneric;
1474 /*************************************************************************/
1476 #pragma mark DEALLOCATION METHODS
1477 /*************************************************************************/
1481 [[NSNotificationCenter defaultCenter] removeObserver:self];
1482 [hotKeysArray release];
1483 [hotKeysDictionary release];
1484 [effectClasses release];
1485 [menuTableView setDataSource:nil];
1486 [allTableView setDataSource:nil];
1487 [controller release];
1488 [availableItems release];
1489 [submenuItems release];