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);
214 /*************************************************************************/
216 #pragma mark INITIALIZATION METHODS
217 /*************************************************************************/
219 + (PreferencesController *)sharedPrefs;
222 prefs = [[self alloc] init];
229 if ( (self = [super init]) ) {
230 ITDebugLog(@"Preferences initialized.");
231 df = [[NSUserDefaults standardUserDefaults] retain];
233 effectClasses = [[ITWindowEffect effectClasses] retain];
235 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
250 @"ToggleShufflability",
260 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
275 @"Toggle Song Included In Shuffle",
276 @"Pop-up status menu",
277 [NSString stringWithUTF8String:"Set Rating: ‚òÜ‚òÜ‚òÜ‚òÜ‚òÜ"],
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: ‚òÖ‚òÖ‚òÖ‚òÖ‚òÖ"],
284 hotKeysDictionary = [[NSMutableDictionary alloc] init];
287 [self setupWindow]; // Load in the nib, and perform any initial setup.
288 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
294 /*************************************************************************/
296 #pragma mark ACCESSOR METHODS
297 /*************************************************************************/
304 - (void)setController:(id)object
306 [controller autorelease];
307 controller = [object retain];
311 /*************************************************************************/
313 #pragma mark INSTANCE METHODS
314 /*************************************************************************/
316 - (BOOL)showPasswordPanel
318 [passwordPanel setLevel:NSStatusWindowLevel];
319 [passwordPanelOKButton setTitle:@"Connect"];
320 [passwordPanelTitle setStringValue:@"Password Required"];
321 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
322 [passwordPanel setLevel:NSStatusWindowLevel];
323 [NSApp activateIgnoringOtherApps:YES];
324 [passwordPanel center];
325 [passwordPanel orderFrontRegardless];
326 [passwordPanel makeKeyWindow];
327 if ([NSApp runModalForWindow:passwordPanel]) {
334 - (BOOL)showInvalidPasswordPanel
336 [passwordPanel setLevel:NSStatusWindowLevel];
337 [passwordPanelOKButton setTitle:@"Retry"];
338 [passwordPanelTitle setStringValue:@"Invalid Password"];
339 [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]]];
340 [passwordPanel setLevel:NSStatusWindowLevel];
341 [NSApp activateIgnoringOtherApps:YES];
342 [passwordPanel center];
343 [passwordPanel orderFrontRegardless];
344 [passwordPanel makeKeyWindow];
345 if ([NSApp runModalForWindow:passwordPanel]) {
352 - (IBAction)showPrefsWindow:(id)sender
354 ITDebugLog(@"Showing preferences window.");
355 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
356 ITDebugLog(@"Window doesn't exist, initial setup.");
357 [self setupCustomizationTables]; // Setup the DnD manu config tables.
358 [self setupMenuItems]; // Setup the arrays of menu items
359 [self setupUI]; // Sets up additional UI
360 [window setDelegate:self];
361 [menuTableView reloadData];
362 [hotKeysTableView reloadData];
363 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
365 //Change the launch player checkbox to the proper name
367 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
369 [controller networkError:localException];
373 [self resetRemotePlayerTextFields];
374 [launchAtLoginCheckbox becomeFirstResponder];
375 [NSApp activateIgnoringOtherApps:YES];
376 if (![window isVisible]) {
379 [window orderFrontRegardless];
380 [window makeKeyWindow];
383 - (IBAction)changeGeneralSetting:(id)sender
385 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
386 if ( [sender tag] == 1010) {
387 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
388 } else if ( [sender tag] == 1020) {
389 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
390 } else if ( [sender tag] == 1030) {
391 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
392 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
393 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
395 } else if ( [sender tag] == 1040) {
396 // This will not be executed. Song info always shows the title of the song.
397 // [df setBool:SENDER_STATE forKey:@"showName"];
398 } else if ( [sender tag] == 1050) {
399 [df setBool:SENDER_STATE forKey:@"showArtist"];
400 } else if ( [sender tag] == 1055) {
401 [df setBool:SENDER_STATE forKey:@"showComposer"];
402 } else if ( [sender tag] == 1060) {
403 [df setBool:SENDER_STATE forKey:@"showAlbum"];
404 } else if ( [sender tag] == 1070) {
405 [df setBool:SENDER_STATE forKey:@"showTime"];
406 } else if ( [sender tag] == 1080) {
407 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
408 } else if ( [sender tag] == 1085) {
409 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
410 } else if ( [sender tag] == 1090) {
411 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
412 } else if ( [sender tag] == 1100) {
413 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
414 } else if ( [sender tag] == 1110) {
415 [df setBool:SENDER_STATE forKey:@"runScripts"];
417 [runScriptsCheckbox setState:NSOnState];
418 [showScriptsButton setEnabled:YES];
420 [showScriptsButton setEnabled:NO];
422 } else if ( [sender tag] == 1120) {
423 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
424 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
425 } else if ( [sender tag] == 6010) {
426 //Toggle the other Audioscrobbler options
427 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
428 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
429 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
430 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
432 [[AudioscrobblerController sharedController] attemptHandshake:NO];
434 } else if ( [sender tag ] == 6015) {
435 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
436 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
437 if ([newAccount length] == 0) {
438 [PreferencesController deleteKeychainItemForUser:currentAccount];
439 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
440 [df setObject:newAccount forKey:@"audioscrobblerUser"];
441 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
442 //Delete the current keychain item if there is one
443 [PreferencesController deleteKeychainItemForUser:currentAccount];
445 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
446 [[AudioscrobblerController sharedController] attemptHandshake:YES];
448 } else if ( [sender tag ] == 6030) {
449 //Here we set the password for an existing keychain item or we create a new keychain item.
450 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
451 NSString *account = [df stringForKey:@"audioscrobblerUser"];
452 if ([PreferencesController keychainItemExistsForUser:account]) {
453 //Update the current keychain item
454 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
455 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
456 //Create a new keychain item
457 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
460 } else if ( [sender tag] == 6045) {
461 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
466 - (IBAction)changeSharingSetting:(id)sender
468 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
469 if ( [sender tag] == 5010 ) {
470 BOOL state = SENDER_STATE;
471 [df setBool:state forKey:@"enableSharing"];
472 //Disable/enable the use of shared player options
473 [useSharedMenuTunesCheckbox setEnabled:!state];
474 [passwordTextField setEnabled:state];
475 [nameTextField setEnabled:state];
476 [selectSharedPlayerButton setEnabled:NO];
477 [controller setServerStatus:state]; //Set server status
478 } else if ( [sender tag] == 5015 ) {
479 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
480 [[NetworkController sharedController] resetServerName];
481 } else if ( [sender tag] == 5030 ) {
482 //Set the server password
483 const char *instring = [[sender stringValue] UTF8String];
484 const char *password = "p4s5w0rdMT1.2";
486 NSData *hashedPass, *passwordStringHash;
487 if ([[sender stringValue] length] == 0) {
488 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
491 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
492 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
493 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
494 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
495 if (![hashedPass isEqualToData:passwordStringHash]) {
496 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
497 [sender setStringValue:@"p4s5w0rdMT1.2"];
499 } else if ( [sender tag] == 5040 ) {
500 BOOL state = SENDER_STATE;
501 [df setBool:state forKey:@"useSharedPlayer"];
502 //Disable/enable the use of sharing options
503 [shareMenuTunesCheckbox setEnabled:!state];
504 [passwordTextField setEnabled:NO];
505 [nameTextField setEnabled:NO];
506 [selectSharedPlayerButton setEnabled:state];
508 if (state && ([controller connectToServer] == 1)) {
509 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
510 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
512 [selectedPlayerTextField setStringValue:@"No shared player selected."];
513 [locationTextField setStringValue:@"-"];
514 if ([[NetworkController sharedController] isConnectedToServer]) {
515 [controller disconnectFromServer];
519 } else if ( [sender tag] == 5050 ) {
520 //If no player is selected in the table view, turn off OK button.
521 if ([sender clickedRow] == -1 ) {
522 [sharingPanelOKButton setEnabled:NO];
524 [sharingPanelOKButton setEnabled:YES];
526 } else if ( [sender tag] == 5051 ) {
527 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
528 } else if ( [sender tag] == 5060 ) {
529 //Set OK button state
530 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
531 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
532 [sharingPanelOKButton setEnabled:NO];
534 [sharingPanelOKButton setEnabled:YES];
536 //Show selection sheet
537 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
538 } else if ( [sender tag] == 5100 ) {
540 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
541 NSRect frame = [selectPlayerSheet frame];
542 frame.origin.y -= 58;
543 frame.size.height = 273;
544 if ([sharingTableView selectedRow] == -1) {
545 [sharingPanelOKButton setEnabled:NO];
547 [selectPlayerBox setContentView:zeroConfView];
548 [selectPlayerSheet setFrame:frame display:YES animate:YES];
549 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
550 NSRect frame = [selectPlayerSheet frame];
551 frame.origin.y += 58;
552 frame.size.height = 215;
553 if ([[hostTextField stringValue] length] == 0) {
554 [sharingPanelOKButton setEnabled:NO];
556 [sharingPanelOKButton setEnabled:YES];
558 [selectPlayerBox setContentView:manualView];
559 [selectPlayerSheet setFrame:frame display:YES animate:YES];
560 [hostTextField selectText:nil];
562 } else if ( [sender tag] == 5150 ) {
563 const char *instring = [[sender stringValue] UTF8String];
565 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
566 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
567 } else if ( [sender tag] == 5110 ) {
569 [NSApp endSheet:selectPlayerSheet];
570 [selectPlayerSheet orderOut:nil];
571 if ([selectPlayerBox contentView] == manualView) {
572 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
575 } else if ( [sender tag] == 5120 ) {
577 [NSApp endSheet:selectPlayerSheet];
578 [selectPlayerSheet orderOut:nil];
580 [self changeSharingSetting:clientPasswordTextField];
582 if ([selectPlayerBox contentView] == manualView) {
583 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
585 if ([sharingTableView selectedRow] > -1) {
586 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
590 if ([controller connectToServer] == 1) {
591 [useSharedMenuTunesCheckbox setState:NSOnState];
592 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
593 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
595 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);
597 } else if ( [sender tag] == 6010 ) {
598 //Cancel password entry
599 [passwordPanel orderOut:nil];
600 [NSApp stopModalWithCode:0];
601 } else if ( [sender tag] == 6020 ) {
602 //OK password entry, retry connect
603 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
605 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
606 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
607 [passwordPanel orderOut:nil];
608 [NSApp stopModalWithCode:1];
613 - (IBAction)changeStatusWindowSetting:(id)sender
615 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
616 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
618 if ( [sender tag] == 2010) {
620 BOOL entryEffectValid = YES;
621 BOOL exitEffectValid = YES;
623 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
624 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
625 [sw setVerticalPosition:[sender selectedRow]];
626 [sw setHorizontalPosition:[sender selectedColumn]];
628 // Enable/disable the items in the popups.
629 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
630 horizontalPosition:[sw horizontalPosition]];
632 // Make sure the effects support the new position.
633 entryEffectValid = ( [self effect:[[sw entryEffect] class]
634 supportsVerticalPosition:[sw verticalPosition]
635 withHorizontalPosition:[sw horizontalPosition]] );
636 exitEffectValid = ( [self effect:[[sw exitEffect] class]
637 supportsVerticalPosition:[sw verticalPosition]
638 withHorizontalPosition:[sw horizontalPosition]] );
640 if ( ! entryEffectValid ) {
641 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
642 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
644 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
647 if ( ! exitEffectValid ) {
648 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
649 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
651 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
654 [(MainController *)controller showCurrentTrackInfo];
656 } else if ( [sender tag] == 2020) {
658 // Update screen selection.
659 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
660 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
661 [(MainController *)controller showCurrentTrackInfo];
663 } else if ( [sender tag] == 2030) {
665 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
666 [(MainController *)controller showCurrentTrackInfo];
668 } else if ( [sender tag] == 2040) {
670 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
671 [(MainController *)controller showCurrentTrackInfo];
673 } else if ( [sender tag] == 2050) {
674 float newTime = ( -([sender floatValue]) );
675 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
676 [[sw entryEffect] setEffectTime:newTime];
677 } else if ( [sender tag] == 2060) {
678 float newTime = ( -([sender floatValue]) );
679 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
680 [[sw exitEffect] setEffectTime:newTime];
681 } else if ( [sender tag] == 2070) {
682 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
683 [sw setExitDelay:[sender floatValue]];
684 } else if ( [sender tag] == 2080) {
685 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
686 } else if ( [sender tag] == 2090) {
688 int setting = [sender indexOfSelectedItem];
690 if ( setting == 0 ) {
691 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
692 [backgroundColorWell setEnabled:NO];
693 [backgroundColorPopup setEnabled:NO];
694 } else if ( setting == 1 ) {
695 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
696 [backgroundColorWell setEnabled:NO];
697 [backgroundColorPopup setEnabled:NO];
698 } else if ( setting == 2 ) {
699 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
700 [backgroundColorWell setEnabled:YES];
701 [backgroundColorPopup setEnabled:YES];
704 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
705 [(MainController *)controller showCurrentTrackInfo];
707 } else if ( [sender tag] == 2091) {
708 [self setCustomColor:[sender color] updateWell:NO];
709 [(MainController *)controller showCurrentTrackInfo];
710 } else if ( [sender tag] == 2092) {
712 int selectedItem = [sender indexOfSelectedItem];
714 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
715 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
716 } else if ( selectedItem == 2 ) {
717 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
718 } else if ( selectedItem == 3 ) {
719 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
720 } else if ( selectedItem == 4 ) {
721 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
722 } else if ( selectedItem == 5 ) {
723 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
725 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
727 [(MainController *)controller showCurrentTrackInfo];
729 } else if ( [sender tag] == 2095) {
730 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
731 [(MainController *)controller showCurrentTrackInfo];
737 - (void)registerDefaults
739 ITDebugLog(@"Registering defaults.");
740 [df setObject:[NSArray arrayWithObjects:
753 nil] forKey:@"menu"];
755 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
756 [df setInteger:5 forKey:@"SongsInAdvance"];
757 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
758 [df setBool:YES forKey:@"showArtist"];
759 [df setBool:YES forKey:@"showAlbumArtwork"];
760 [df setBool:NO forKey:@"showAlbum"];
761 [df setBool:NO forKey:@"showComposer"];
762 [df setBool:NO forKey:@"showTime"];
763 [df setBool:NO forKey:@"showToolTip"];
765 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
766 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
767 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
768 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
769 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
770 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
771 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
772 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
773 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
774 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
775 [df setBool:YES forKey:@"showSongInfoOnChange"];
777 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
781 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
782 [[StatusWindowController sharedController] showSetupQueryWindow];
788 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
789 [[StatusWindow sharedWindow] vanish:self];
790 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
792 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
795 - (void)autoLaunchCancel
797 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
798 [[StatusWindow sharedWindow] vanish:self];
799 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
802 - (void)deletePressedInTableView:(NSTableView *)tableView
804 if (tableView == menuTableView) {
805 int selRow = [tableView selectedRow];
806 ITDebugLog(@"Delete pressed in menu table view.");
808 NSString *object = [myItems objectAtIndex:selRow];
810 if ([object isEqualToString:@"preferences"]) {
815 if (![object isEqualToString:@"separator"])
816 [availableItems addObject:object];
817 ITDebugLog(@"Removing object named %@", object);
818 [myItems removeObjectAtIndex:selRow];
819 [menuTableView reloadData];
820 [allTableView reloadData];
822 [self changeMenus:self];
826 - (void)resetRemotePlayerTextFields
828 if ([[NetworkController sharedController] isConnectedToServer]) {
829 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
830 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
832 [selectedPlayerTextField setStringValue:@"No shared player selected."];
833 [locationTextField setStringValue:@"-"];
837 /*************************************************************************/
839 #pragma mark HOTKEY SUPPORT METHODS
840 /*************************************************************************/
842 - (IBAction)clearHotKey:(id)sender
844 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
845 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
846 [controller setupHotKeys];
847 [hotKeysTableView reloadData];
850 - (IBAction)editHotKey:(id)sender
852 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
853 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
854 ITKeyCombo *keyCombo;
856 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
857 [controller clearHotKeys];
858 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
859 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
860 if ([panel runModal] == NSOKButton) {
861 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
863 keyCombo = [panel keyCombo];
865 //Check for duplicate key combo
866 while ( (nextKey = [keyEnumerator nextObject]) ) {
867 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
868 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
869 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
871 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
876 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
877 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
878 [controller setupHotKeys];
879 [hotKeysTableView reloadData];
880 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
882 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
886 - (void)hotKeysTableViewDoubleClicked:(id)sender
888 if ([sender clickedRow] > -1) {
889 [self editHotKey:sender];
893 /*************************************************************************/
895 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
896 /*************************************************************************/
898 - (void)audioscrobblerStatusChanged:(NSNotification *)note
900 [audioscrobblerStatusTextField setStringValue:[[note userInfo] objectForKey:@"StatusString"]];
905 ITDebugLog(@"Loading Preferences.nib.");
906 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
907 ITDebugLog(@"Failed to load Preferences.nib.");
913 - (void)setupCustomizationTables
915 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
916 ITDebugLog(@"Setting up table views.");
917 // Set the table view cells up
918 [imgCell setImageScaling:NSScaleNone];
919 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
920 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
922 // Register for drag and drop
923 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
924 @"MenuTableViewPboardType",
925 @"AllTableViewPboardType",
927 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
928 @"MenuTableViewPboardType",
929 @"AllTableViewPboardType",
933 - (void)setupMenuItems
935 NSEnumerator *itemEnum;
937 ITDebugLog(@"Setting up table view arrays.");
938 // Set the list of items you can have.
939 availableItems = [[NSMutableArray alloc] initWithObjects:
958 // Get our preferred menu
959 myItems = [[df arrayForKey:@"menu"] mutableCopy];
961 // Delete items in the availableItems array that are already part of the menu
962 itemEnum = [myItems objectEnumerator];
963 while ( (anItem = [itemEnum nextObject]) ) {
964 if (![anItem isEqualToString:@"separator"]) {
965 [availableItems removeObject:anItem];
969 // Items that show should a submenu image
970 submenuItems = [[NSArray alloc] initWithObjects:
982 NSEnumerator *keyArrayEnum;
983 NSString *serverName;
988 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioscrobblerStatusChanged:) name:@"AudioscrobblerStatusChanged" object:nil];
989 if ([df boolForKey:@"audioscrobblerEnabled"]) {
990 NSString *status = [[AudioscrobblerController sharedController] lastStatus];
991 [audioscrobblerStatusTextField setStringValue:(status == nil) ? @"Idle" : status];
994 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
996 ITDebugLog(@"Setting up preferences UI.");
997 // Fill in the number of songs in advance to show field
998 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
1000 // Fill hot key array
1001 keyArrayEnum = [hotKeysArray objectEnumerator];
1003 while ( (anItem = [keyArrayEnum nextObject]) ) {
1004 if ([df objectForKey:anItem]) {
1005 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
1006 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
1008 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
1012 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
1013 [self setupScreenPopup];
1015 ITDebugLog(@"Setting up track info checkboxes.");
1016 // Check current track info buttons
1017 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
1018 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
1019 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
1020 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1021 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1022 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1023 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1024 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1025 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1026 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1028 if ([df boolForKey:@"runScripts"]) {
1029 [runScriptsCheckbox setState:NSOnState];
1030 [showScriptsButton setEnabled:YES];
1032 [showScriptsButton setEnabled:NO];
1035 // Set the launch at login checkbox state
1036 ITDebugLog(@"Setting launch at login state.");
1037 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1038 [launchAtLoginCheckbox setState:NSOnState];
1041 // Set the launch player checkbox state
1042 ITDebugLog(@"Setting launch player with MenuTunes state.");
1043 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1045 // Setup the positioning controls
1046 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1047 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1049 // Setup effects controls
1050 // Populate the effects popups
1051 [appearanceEffectPopup setAutoenablesItems:NO];
1052 [vanishEffectPopup setAutoenablesItems:NO];
1053 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1054 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1056 // Attempt to find the pref'd effect in the list.
1057 // If it's not there, use cut/dissolve.
1058 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1059 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1061 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1064 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1065 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1067 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1070 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1071 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1072 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1074 // Setup General Controls
1075 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1076 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1078 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1079 [backgroundColorWell setEnabled:YES];
1080 [backgroundColorPopup setEnabled:YES];
1082 [backgroundColorWell setEnabled:NO];
1083 [backgroundColorPopup setEnabled:NO];
1086 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1089 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1091 [backgroundColorWell setColor:[NSColor blueColor]];
1094 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1096 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1098 // Setup the sharing controls
1099 if ([df boolForKey:@"enableSharing"]) {
1100 [shareMenuTunesCheckbox setState:NSOnState];
1101 [useSharedMenuTunesCheckbox setEnabled:NO];
1102 [selectSharedPlayerButton setEnabled:NO];
1103 [passwordTextField setEnabled:YES];
1104 [nameTextField setEnabled:YES];
1105 } else if ([df boolForKey:@"useSharedPlayer"]) {
1106 [useSharedMenuTunesCheckbox setState:NSOnState];
1107 [shareMenuTunesCheckbox setEnabled:NO];
1108 [selectSharedPlayerButton setEnabled:YES];
1111 //Setup the Audioscrobbler controls
1112 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1113 [audioscrobblerEnabledCheckbox setState:NSOnState];
1114 [audioscrobblerUserTextField setEnabled:YES];
1115 [audioscrobblerPasswordTextField setEnabled:YES];
1116 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1118 [audioscrobblerUserTextField setEnabled:NO];
1119 [audioscrobblerPasswordTextField setEnabled:NO];
1120 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1122 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1123 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1124 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1125 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1126 if (password != nil) {
1127 [audioscrobblerPasswordTextField setStringValue:password];
1130 [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1132 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1134 serverName = [df stringForKey:@"sharedPlayerName"];
1135 if (!serverName || [serverName length] == 0) {
1136 serverName = @"MenuTunes Shared Player";
1138 [nameTextField setStringValue:serverName];
1140 [selectPlayerBox setContentView:zeroConfView];
1141 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1142 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1144 [passwordTextField setStringValue:@""];
1146 if ([df stringForKey:@"sharedPlayerHost"]) {
1147 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1150 if ([[NetworkController sharedController] isConnectedToServer]) {
1151 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1152 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1154 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1155 [locationTextField setStringValue:@"-"];
1159 - (void)setupScreenPopup
1161 ITDebugLog(@"Setting up screen popup");
1162 NSArray *screens = [NSScreen screens];
1163 if ([screens count] > 1) {
1164 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1165 [screenPopup setEnabled:YES];
1166 for (i = 0; i < [screens count]; i++) {
1167 NSScreen *screen = [screens objectAtIndex:i];
1168 if (![screen isEqual:[NSScreen mainScreen]]) {
1169 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1172 [screenPopup selectItemAtIndex:index];
1173 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1175 while ([screenPopup numberOfItems] > 1) {
1176 [screenPopup removeItemAtIndex:1];
1178 [screenPopup setEnabled:NO];
1179 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1183 - (void)setStatusWindowEntryEffect:(Class)effectClass
1185 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1187 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1188 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1190 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1191 [[sw entryEffect] setEffectTime:time];
1194 - (void)setStatusWindowExitEffect:(Class)effectClass
1196 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1198 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1199 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1201 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1202 [[sw exitEffect] setEffectTime:time];
1205 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1207 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1208 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1211 [backgroundColorWell setColor:color];
1215 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1217 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1220 [appearanceEffectPopup removeAllItems];
1221 [vanishEffectPopup removeAllItems];
1223 while ( (anItem = [effectEnum nextObject]) ) {
1224 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1225 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1227 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1228 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1230 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1231 [[appearanceEffectPopup lastItem] setEnabled:YES];
1232 [[vanishEffectPopup lastItem] setEnabled:YES];
1234 [[appearanceEffectPopup lastItem] setEnabled:NO];
1235 [[vanishEffectPopup lastItem] setEnabled:NO];
1241 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1245 if ( vPos == ITWindowPositionTop ) {
1246 if ( hPos == ITWindowPositionLeft ) {
1247 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1248 } else if ( hPos == ITWindowPositionCenter ) {
1249 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1250 } else if ( hPos == ITWindowPositionRight ) {
1251 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1253 } else if ( vPos == ITWindowPositionMiddle ) {
1254 if ( hPos == ITWindowPositionLeft ) {
1255 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1256 } else if ( hPos == ITWindowPositionCenter ) {
1257 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1258 } else if ( hPos == ITWindowPositionRight ) {
1259 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1261 } else if ( vPos == ITWindowPositionBottom ) {
1262 if ( hPos == ITWindowPositionLeft ) {
1263 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1264 } else if ( hPos == ITWindowPositionCenter ) {
1265 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1266 } else if ( hPos == ITWindowPositionRight ) {
1267 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1274 - (IBAction)changeMenus:(id)sender
1276 ITDebugLog(@"Synchronizing menus");
1277 [df setObject:myItems forKey:@"menu"];
1280 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1282 //If we're connected over a network, refresh the menu immediately
1283 if ([[NetworkController sharedController] isConnectedToServer]) {
1284 [controller timerUpdate];
1289 /*************************************************************************/
1291 #pragma mark NSWindow DELEGATE METHODS
1292 /*************************************************************************/
1294 - (void)windowWillClose:(NSNotification *)note
1296 [(MainController *)controller closePreferences];
1299 /*************************************************************************/
1301 #pragma mark NSTextField DELEGATE METHODS
1302 /*************************************************************************/
1304 - (void)controlTextDidChange:(NSNotification*)note
1306 if ([note object] == hostTextField) {
1307 if ([[hostTextField stringValue] length] == 0) {
1308 [sharingPanelOKButton setEnabled:NO];
1310 [sharingPanelOKButton setEnabled:YES];
1315 /*************************************************************************/
1317 #pragma mark NSTableView DATASOURCE METHODS
1318 /*************************************************************************/
1320 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1322 if (aTableView == menuTableView) {
1323 return [myItems count];
1324 } else if (aTableView == allTableView) {
1325 return [availableItems count];
1326 } else if (aTableView == hotKeysTableView) {
1327 return [hotKeysArray count];
1329 return [[[NetworkController sharedController] remoteServices] count];
1333 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1335 if (aTableView == menuTableView) {
1336 NSString *object = [myItems objectAtIndex:rowIndex];
1337 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1338 if ([object isEqualToString:@"showPlayer"]) {
1339 NSString *string = nil;
1341 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1343 [controller networkError:localException];
1347 return NSLocalizedString(object, @"ERROR");
1349 if ([submenuItems containsObject:object])
1351 return [NSImage imageNamed:@"submenu"];
1356 } else if (aTableView == allTableView) {
1357 NSString *object = [availableItems objectAtIndex:rowIndex];
1358 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1359 if ([object isEqualToString:@"showPlayer"]) {
1360 NSString *string = nil;
1362 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1364 [controller networkError:localException];
1368 return NSLocalizedString(object, @"ERROR");
1370 if ([submenuItems containsObject:object]) {
1371 return [NSImage imageNamed:@"submenu"];
1376 } else if (aTableView == hotKeysTableView) {
1377 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1378 return [hotKeyNamesArray objectAtIndex:rowIndex];
1380 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1383 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1387 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1389 if (tableView == menuTableView) {
1390 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1391 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1395 if (tableView == allTableView) {
1396 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1397 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1403 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1407 NSString *dragData, *temp;
1409 pb = [info draggingPasteboard];
1411 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1412 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1413 dragRow = [dragData intValue];
1414 temp = [myItems objectAtIndex:dragRow];
1416 if (tableView == menuTableView) {
1417 [myItems insertObject:temp atIndex:row];
1418 if (row > dragRow) {
1419 [myItems removeObjectAtIndex:dragRow];
1421 [myItems removeObjectAtIndex:dragRow + 1];
1423 } else if (tableView == allTableView) {
1424 if (![temp isEqualToString:@"separator"]) {
1425 [availableItems addObject:temp];
1427 [myItems removeObjectAtIndex:dragRow];
1429 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1430 dragData = [pb stringForType:@"AllTableViewPboardType"];
1431 dragRow = [dragData intValue];
1432 temp = [availableItems objectAtIndex:dragRow];
1434 [myItems insertObject:temp atIndex:row];
1436 if (![temp isEqualToString:@"separator"]) {
1437 [availableItems removeObjectAtIndex:dragRow];
1441 [menuTableView reloadData];
1442 [allTableView reloadData];
1443 [self changeMenus:self];
1447 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1449 if (tableView == allTableView) {
1450 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1451 return NSDragOperationNone;
1454 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1455 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1456 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1457 return NSDragOperationNone;
1461 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1462 return NSDragOperationGeneric;
1465 if (operation == NSTableViewDropOn || row == -1)
1467 return NSDragOperationNone;
1469 return NSDragOperationGeneric;
1473 /*************************************************************************/
1475 #pragma mark DEALLOCATION METHODS
1476 /*************************************************************************/
1480 [[NSNotificationCenter defaultCenter] removeObserver:self];
1481 [hotKeysArray release];
1482 [hotKeysDictionary release];
1483 [effectClasses release];
1484 [menuTableView setDataSource:nil];
1485 [allTableView setDataSource:nil];
1486 [controller release];
1487 [availableItems release];
1488 [submenuItems release];