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",
249 @"ToggleShufflability",
259 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
273 @"Toggle Song Included In Shuffle",
274 @"Pop-up status menu",
275 [NSString stringWithUTF8String:"Set Rating: ‚òÜ‚òÜ‚òÜ‚òÜ‚òÜ"],
276 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÜ‚òÜ‚òÜ‚òÜ"],
277 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÜ‚òÜ‚òÜ"],
278 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÖ‚òÜ‚òÜ"],
279 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÖ‚òÖ‚òÜ"],
280 [NSString stringWithUTF8String:"Set Rating: ‚òÖ‚òÖ‚òÖ‚òÖ‚òÖ"],
282 hotKeysDictionary = [[NSMutableDictionary alloc] init];
285 [self setupWindow]; // Load in the nib, and perform any initial setup.
286 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
292 /*************************************************************************/
294 #pragma mark ACCESSOR METHODS
295 /*************************************************************************/
302 - (void)setController:(id)object
304 [controller autorelease];
305 controller = [object retain];
309 /*************************************************************************/
311 #pragma mark INSTANCE METHODS
312 /*************************************************************************/
314 - (BOOL)showPasswordPanel
316 [passwordPanel setLevel:NSStatusWindowLevel];
317 [passwordPanelOKButton setTitle:@"Connect"];
318 [passwordPanelTitle setStringValue:@"Password Required"];
319 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
320 [passwordPanel setLevel:NSStatusWindowLevel];
321 [NSApp activateIgnoringOtherApps:YES];
322 [passwordPanel center];
323 [passwordPanel orderFrontRegardless];
324 [passwordPanel makeKeyWindow];
325 if ([NSApp runModalForWindow:passwordPanel]) {
332 - (BOOL)showInvalidPasswordPanel
334 [passwordPanel setLevel:NSStatusWindowLevel];
335 [passwordPanelOKButton setTitle:@"Retry"];
336 [passwordPanelTitle setStringValue:@"Invalid Password"];
337 [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]]];
338 [passwordPanel setLevel:NSStatusWindowLevel];
339 [NSApp activateIgnoringOtherApps:YES];
340 [passwordPanel center];
341 [passwordPanel orderFrontRegardless];
342 [passwordPanel makeKeyWindow];
343 if ([NSApp runModalForWindow:passwordPanel]) {
350 - (IBAction)showPrefsWindow:(id)sender
352 ITDebugLog(@"Showing preferences window.");
353 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
354 ITDebugLog(@"Window doesn't exist, initial setup.");
355 [self setupCustomizationTables]; // Setup the DnD manu config tables.
356 [self setupMenuItems]; // Setup the arrays of menu items
357 [self setupUI]; // Sets up additional UI
358 [window setDelegate:self];
359 [menuTableView reloadData];
360 [hotKeysTableView reloadData];
361 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
363 //Change the launch player checkbox to the proper name
365 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
367 [controller networkError:localException];
371 [self resetRemotePlayerTextFields];
372 [launchAtLoginCheckbox becomeFirstResponder];
373 [NSApp activateIgnoringOtherApps:YES];
374 if (![window isVisible]) {
377 [window orderFrontRegardless];
378 [window makeKeyWindow];
381 - (IBAction)changeGeneralSetting:(id)sender
383 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
384 if ( [sender tag] == 1010) {
385 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
386 } else if ( [sender tag] == 1020) {
387 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
388 } else if ( [sender tag] == 1030) {
389 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
390 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
391 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
393 } else if ( [sender tag] == 1040) {
394 // This will not be executed. Song info always shows the title of the song.
395 // [df setBool:SENDER_STATE forKey:@"showName"];
396 } else if ( [sender tag] == 1050) {
397 [df setBool:SENDER_STATE forKey:@"showArtist"];
398 } else if ( [sender tag] == 1055) {
399 [df setBool:SENDER_STATE forKey:@"showComposer"];
400 } else if ( [sender tag] == 1060) {
401 [df setBool:SENDER_STATE forKey:@"showAlbum"];
402 } else if ( [sender tag] == 1070) {
403 [df setBool:SENDER_STATE forKey:@"showTime"];
404 } else if ( [sender tag] == 1080) {
405 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
406 } else if ( [sender tag] == 1085) {
407 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
408 } else if ( [sender tag] == 1090) {
409 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
410 } else if ( [sender tag] == 1100) {
411 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
412 } else if ( [sender tag] == 1110) {
413 [df setBool:SENDER_STATE forKey:@"runScripts"];
415 [runScriptsCheckbox setState:NSOnState];
416 [showScriptsButton setEnabled:YES];
418 [showScriptsButton setEnabled:NO];
420 } else if ( [sender tag] == 1120) {
421 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
422 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
423 } else if ( [sender tag] == 6010) {
424 //Toggle the other Audioscrobbler options
425 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
426 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
427 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
428 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
430 [[AudioscrobblerController sharedController] attemptHandshake:NO];
432 } else if ( [sender tag ] == 6015) {
433 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
434 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
435 if ([newAccount length] == 0) {
436 [PreferencesController deleteKeychainItemForUser:currentAccount];
437 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
438 [df setObject:newAccount forKey:@"audioscrobblerUser"];
439 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
440 //Delete the current keychain item if there is one
441 [PreferencesController deleteKeychainItemForUser:currentAccount];
443 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
444 [[AudioscrobblerController sharedController] attemptHandshake:YES];
446 } else if ( [sender tag ] == 6030) {
447 //Here we set the password for an existing keychain item or we create a new keychain item.
448 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
449 NSString *account = [df stringForKey:@"audioscrobblerUser"];
450 if ([PreferencesController keychainItemExistsForUser:account]) {
451 //Update the current keychain item
452 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
453 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
454 //Create a new keychain item
455 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
458 } else if ( [sender tag] == 6045) {
459 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
464 - (IBAction)changeSharingSetting:(id)sender
466 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
467 if ( [sender tag] == 5010 ) {
468 BOOL state = SENDER_STATE;
469 [df setBool:state forKey:@"enableSharing"];
470 //Disable/enable the use of shared player options
471 [useSharedMenuTunesCheckbox setEnabled:!state];
472 [passwordTextField setEnabled:state];
473 [nameTextField setEnabled:state];
474 [selectSharedPlayerButton setEnabled:NO];
475 [controller setServerStatus:state]; //Set server status
476 } else if ( [sender tag] == 5015 ) {
477 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
478 [[NetworkController sharedController] resetServerName];
479 } else if ( [sender tag] == 5030 ) {
480 //Set the server password
481 const char *instring = [[sender stringValue] UTF8String];
482 const char *password = "p4s5w0rdMT1.2";
484 NSData *hashedPass, *passwordStringHash;
485 if ([[sender stringValue] length] == 0) {
486 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
489 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
490 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
491 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
492 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
493 if (![hashedPass isEqualToData:passwordStringHash]) {
494 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
495 [sender setStringValue:@"p4s5w0rdMT1.2"];
497 } else if ( [sender tag] == 5040 ) {
498 BOOL state = SENDER_STATE;
499 [df setBool:state forKey:@"useSharedPlayer"];
500 //Disable/enable the use of sharing options
501 [shareMenuTunesCheckbox setEnabled:!state];
502 [passwordTextField setEnabled:NO];
503 [nameTextField setEnabled:NO];
504 [selectSharedPlayerButton setEnabled:state];
506 if (state && ([controller connectToServer] == 1)) {
507 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
508 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
510 [selectedPlayerTextField setStringValue:@"No shared player selected."];
511 [locationTextField setStringValue:@"-"];
512 if ([[NetworkController sharedController] isConnectedToServer]) {
513 [controller disconnectFromServer];
517 } else if ( [sender tag] == 5050 ) {
518 //If no player is selected in the table view, turn off OK button.
519 if ([sender clickedRow] == -1 ) {
520 [sharingPanelOKButton setEnabled:NO];
522 [sharingPanelOKButton setEnabled:YES];
524 } else if ( [sender tag] == 5051 ) {
525 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
526 } else if ( [sender tag] == 5060 ) {
527 //Set OK button state
528 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
529 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
530 [sharingPanelOKButton setEnabled:NO];
532 [sharingPanelOKButton setEnabled:YES];
534 //Show selection sheet
535 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
536 } else if ( [sender tag] == 5100 ) {
538 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
539 NSRect frame = [selectPlayerSheet frame];
540 frame.origin.y -= 58;
541 frame.size.height = 273;
542 if ([sharingTableView selectedRow] == -1) {
543 [sharingPanelOKButton setEnabled:NO];
545 [selectPlayerBox setContentView:zeroConfView];
546 [selectPlayerSheet setFrame:frame display:YES animate:YES];
547 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
548 NSRect frame = [selectPlayerSheet frame];
549 frame.origin.y += 58;
550 frame.size.height = 215;
551 if ([[hostTextField stringValue] length] == 0) {
552 [sharingPanelOKButton setEnabled:NO];
554 [sharingPanelOKButton setEnabled:YES];
556 [selectPlayerBox setContentView:manualView];
557 [selectPlayerSheet setFrame:frame display:YES animate:YES];
558 [hostTextField selectText:nil];
560 } else if ( [sender tag] == 5150 ) {
561 const char *instring = [[sender stringValue] UTF8String];
563 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
564 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
565 } else if ( [sender tag] == 5110 ) {
567 [NSApp endSheet:selectPlayerSheet];
568 [selectPlayerSheet orderOut:nil];
569 if ([selectPlayerBox contentView] == manualView) {
570 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
573 } else if ( [sender tag] == 5120 ) {
575 [NSApp endSheet:selectPlayerSheet];
576 [selectPlayerSheet orderOut:nil];
578 [self changeSharingSetting:clientPasswordTextField];
580 if ([selectPlayerBox contentView] == manualView) {
581 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
583 if ([sharingTableView selectedRow] > -1) {
584 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
588 if ([controller connectToServer] == 1) {
589 [useSharedMenuTunesCheckbox setState:NSOnState];
590 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
591 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
593 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);
595 } else if ( [sender tag] == 6010 ) {
596 //Cancel password entry
597 [passwordPanel orderOut:nil];
598 [NSApp stopModalWithCode:0];
599 } else if ( [sender tag] == 6020 ) {
600 //OK password entry, retry connect
601 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
603 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
604 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
605 [passwordPanel orderOut:nil];
606 [NSApp stopModalWithCode:1];
611 - (IBAction)changeStatusWindowSetting:(id)sender
613 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
614 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
616 if ( [sender tag] == 2010) {
618 BOOL entryEffectValid = YES;
619 BOOL exitEffectValid = YES;
621 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
622 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
623 [sw setVerticalPosition:[sender selectedRow]];
624 [sw setHorizontalPosition:[sender selectedColumn]];
626 // Enable/disable the items in the popups.
627 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
628 horizontalPosition:[sw horizontalPosition]];
630 // Make sure the effects support the new position.
631 entryEffectValid = ( [self effect:[[sw entryEffect] class]
632 supportsVerticalPosition:[sw verticalPosition]
633 withHorizontalPosition:[sw horizontalPosition]] );
634 exitEffectValid = ( [self effect:[[sw exitEffect] class]
635 supportsVerticalPosition:[sw verticalPosition]
636 withHorizontalPosition:[sw horizontalPosition]] );
638 if ( ! entryEffectValid ) {
639 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
640 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
642 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
645 if ( ! exitEffectValid ) {
646 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
647 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
649 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
652 [(MainController *)controller showCurrentTrackInfo];
654 } else if ( [sender tag] == 2020) {
656 // Update screen selection.
657 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
658 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
659 [(MainController *)controller showCurrentTrackInfo];
661 } else if ( [sender tag] == 2030) {
663 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
664 [(MainController *)controller showCurrentTrackInfo];
666 } else if ( [sender tag] == 2040) {
668 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
669 [(MainController *)controller showCurrentTrackInfo];
671 } else if ( [sender tag] == 2050) {
672 float newTime = ( -([sender floatValue]) );
673 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
674 [[sw entryEffect] setEffectTime:newTime];
675 } else if ( [sender tag] == 2060) {
676 float newTime = ( -([sender floatValue]) );
677 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
678 [[sw exitEffect] setEffectTime:newTime];
679 } else if ( [sender tag] == 2070) {
680 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
681 [sw setExitDelay:[sender floatValue]];
682 } else if ( [sender tag] == 2080) {
683 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
684 } else if ( [sender tag] == 2090) {
686 int setting = [sender indexOfSelectedItem];
688 if ( setting == 0 ) {
689 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
690 [backgroundColorWell setEnabled:NO];
691 [backgroundColorPopup setEnabled:NO];
692 } else if ( setting == 1 ) {
693 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
694 [backgroundColorWell setEnabled:NO];
695 [backgroundColorPopup setEnabled:NO];
696 } else if ( setting == 2 ) {
697 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
698 [backgroundColorWell setEnabled:YES];
699 [backgroundColorPopup setEnabled:YES];
702 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
703 [(MainController *)controller showCurrentTrackInfo];
705 } else if ( [sender tag] == 2091) {
706 [self setCustomColor:[sender color] updateWell:NO];
707 [(MainController *)controller showCurrentTrackInfo];
708 } else if ( [sender tag] == 2092) {
710 int selectedItem = [sender indexOfSelectedItem];
712 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
713 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
714 } else if ( selectedItem == 2 ) {
715 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
716 } else if ( selectedItem == 3 ) {
717 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
718 } else if ( selectedItem == 4 ) {
719 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
720 } else if ( selectedItem == 5 ) {
721 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
723 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
725 [(MainController *)controller showCurrentTrackInfo];
727 } else if ( [sender tag] == 2095) {
728 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
729 [(MainController *)controller showCurrentTrackInfo];
735 - (void)registerDefaults
737 ITDebugLog(@"Registering defaults.");
738 [df setObject:[NSArray arrayWithObjects:
751 nil] forKey:@"menu"];
753 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
754 [df setInteger:5 forKey:@"SongsInAdvance"];
755 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
756 [df setBool:YES forKey:@"showArtist"];
757 [df setBool:YES forKey:@"showAlbumArtwork"];
758 [df setBool:NO forKey:@"showAlbum"];
759 [df setBool:NO forKey:@"showComposer"];
760 [df setBool:NO forKey:@"showTime"];
761 [df setBool:NO forKey:@"showToolTip"];
763 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
764 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
765 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
766 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
767 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
768 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
769 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
770 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
771 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
772 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
773 [df setBool:YES forKey:@"showSongInfoOnChange"];
775 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
779 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
780 [[StatusWindowController sharedController] showSetupQueryWindow];
786 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
787 [[StatusWindow sharedWindow] vanish:self];
788 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
790 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
793 - (void)autoLaunchCancel
795 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
796 [[StatusWindow sharedWindow] vanish:self];
797 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
800 - (void)deletePressedInTableView:(NSTableView *)tableView
802 if (tableView == menuTableView) {
803 int selRow = [tableView selectedRow];
804 ITDebugLog(@"Delete pressed in menu table view.");
806 NSString *object = [myItems objectAtIndex:selRow];
808 if ([object isEqualToString:@"preferences"]) {
813 if (![object isEqualToString:@"separator"])
814 [availableItems addObject:object];
815 ITDebugLog(@"Removing object named %@", object);
816 [myItems removeObjectAtIndex:selRow];
817 [menuTableView reloadData];
818 [allTableView reloadData];
820 [self changeMenus:self];
824 - (void)resetRemotePlayerTextFields
826 if ([[NetworkController sharedController] isConnectedToServer]) {
827 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
828 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
830 [selectedPlayerTextField setStringValue:@"No shared player selected."];
831 [locationTextField setStringValue:@"-"];
835 /*************************************************************************/
837 #pragma mark HOTKEY SUPPORT METHODS
838 /*************************************************************************/
840 - (IBAction)clearHotKey:(id)sender
842 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
843 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
844 [controller setupHotKeys];
845 [hotKeysTableView reloadData];
848 - (IBAction)editHotKey:(id)sender
850 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
851 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
852 ITKeyCombo *keyCombo;
854 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
855 [controller clearHotKeys];
856 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
857 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
858 if ([panel runModal] == NSOKButton) {
859 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
861 keyCombo = [panel keyCombo];
863 //Check for duplicate key combo
864 while ( (nextKey = [keyEnumerator nextObject]) ) {
865 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
866 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
867 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
869 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
874 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
875 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
876 [controller setupHotKeys];
877 [hotKeysTableView reloadData];
878 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
880 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
884 - (void)hotKeysTableViewDoubleClicked:(id)sender
886 if ([sender clickedRow] > -1) {
887 [self editHotKey:sender];
891 /*************************************************************************/
893 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
894 /*************************************************************************/
896 - (void)audioscrobblerStatusChanged:(NSNotification *)note
898 [audioscrobblerStatusTextField setStringValue:[[note userInfo] objectForKey:@"StatusString"]];
903 ITDebugLog(@"Loading Preferences.nib.");
904 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
905 ITDebugLog(@"Failed to load Preferences.nib.");
911 - (void)setupCustomizationTables
913 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
914 ITDebugLog(@"Setting up table views.");
915 // Set the table view cells up
916 [imgCell setImageScaling:NSScaleNone];
917 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
918 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
920 // Register for drag and drop
921 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
922 @"MenuTableViewPboardType",
923 @"AllTableViewPboardType",
925 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
926 @"MenuTableViewPboardType",
927 @"AllTableViewPboardType",
931 - (void)setupMenuItems
933 NSEnumerator *itemEnum;
935 ITDebugLog(@"Setting up table view arrays.");
936 // Set the list of items you can have.
937 availableItems = [[NSMutableArray alloc] initWithObjects:
956 // Get our preferred menu
957 myItems = [[df arrayForKey:@"menu"] mutableCopy];
959 // Delete items in the availableItems array that are already part of the menu
960 itemEnum = [myItems objectEnumerator];
961 while ( (anItem = [itemEnum nextObject]) ) {
962 if (![anItem isEqualToString:@"separator"]) {
963 [availableItems removeObject:anItem];
967 // Items that show should a submenu image
968 submenuItems = [[NSArray alloc] initWithObjects:
980 NSEnumerator *keyArrayEnum;
981 NSString *serverName;
986 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioscrobblerStatusChanged:) name:@"AudioscrobblerStatusChanged" object:nil];
987 if ([df boolForKey:@"audioscrobblerEnabled"]) {
988 NSString *status = [[AudioscrobblerController sharedController] lastStatus];
989 [audioscrobblerStatusTextField setStringValue:(status == nil) ? @"Idle" : status];
992 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
994 ITDebugLog(@"Setting up preferences UI.");
995 // Fill in the number of songs in advance to show field
996 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
998 // Fill hot key array
999 keyArrayEnum = [hotKeysArray objectEnumerator];
1001 while ( (anItem = [keyArrayEnum nextObject]) ) {
1002 if ([df objectForKey:anItem]) {
1003 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
1004 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
1006 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
1010 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
1011 [self setupScreenPopup];
1013 ITDebugLog(@"Setting up track info checkboxes.");
1014 // Check current track info buttons
1015 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
1016 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
1017 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
1018 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1019 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1020 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1021 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1022 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1023 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1024 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1026 if ([df boolForKey:@"runScripts"]) {
1027 [runScriptsCheckbox setState:NSOnState];
1028 [showScriptsButton setEnabled:YES];
1030 [showScriptsButton setEnabled:NO];
1033 // Set the launch at login checkbox state
1034 ITDebugLog(@"Setting launch at login state.");
1035 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1036 [launchAtLoginCheckbox setState:NSOnState];
1039 // Set the launch player checkbox state
1040 ITDebugLog(@"Setting launch player with MenuTunes state.");
1041 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1043 // Setup the positioning controls
1044 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1045 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1047 // Setup effects controls
1048 // Populate the effects popups
1049 [appearanceEffectPopup setAutoenablesItems:NO];
1050 [vanishEffectPopup setAutoenablesItems:NO];
1051 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1052 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1054 // Attempt to find the pref'd effect in the list.
1055 // If it's not there, use cut/dissolve.
1056 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1057 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1059 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1062 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1063 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1065 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1068 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1069 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1070 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1072 // Setup General Controls
1073 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1074 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1076 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1077 [backgroundColorWell setEnabled:YES];
1078 [backgroundColorPopup setEnabled:YES];
1080 [backgroundColorWell setEnabled:NO];
1081 [backgroundColorPopup setEnabled:NO];
1084 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1087 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1089 [backgroundColorWell setColor:[NSColor blueColor]];
1092 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1094 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1096 // Setup the sharing controls
1097 if ([df boolForKey:@"enableSharing"]) {
1098 [shareMenuTunesCheckbox setState:NSOnState];
1099 [useSharedMenuTunesCheckbox setEnabled:NO];
1100 [selectSharedPlayerButton setEnabled:NO];
1101 [passwordTextField setEnabled:YES];
1102 [nameTextField setEnabled:YES];
1103 } else if ([df boolForKey:@"useSharedPlayer"]) {
1104 [useSharedMenuTunesCheckbox setState:NSOnState];
1105 [shareMenuTunesCheckbox setEnabled:NO];
1106 [selectSharedPlayerButton setEnabled:YES];
1109 //Setup the Audioscrobbler controls
1110 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1111 [audioscrobblerEnabledCheckbox setState:NSOnState];
1112 [audioscrobblerUserTextField setEnabled:YES];
1113 [audioscrobblerPasswordTextField setEnabled:YES];
1114 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1116 [audioscrobblerUserTextField setEnabled:NO];
1117 [audioscrobblerPasswordTextField setEnabled:NO];
1118 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1120 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1121 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1122 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1123 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1124 if (password != nil) {
1125 [audioscrobblerPasswordTextField setStringValue:password];
1128 [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1130 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1132 serverName = [df stringForKey:@"sharedPlayerName"];
1133 if (!serverName || [serverName length] == 0) {
1134 serverName = @"MenuTunes Shared Player";
1136 [nameTextField setStringValue:serverName];
1138 [selectPlayerBox setContentView:zeroConfView];
1139 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1140 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1142 [passwordTextField setStringValue:@""];
1144 if ([df stringForKey:@"sharedPlayerHost"]) {
1145 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1148 if ([[NetworkController sharedController] isConnectedToServer]) {
1149 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1150 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1152 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1153 [locationTextField setStringValue:@"-"];
1157 - (void)setupScreenPopup
1159 ITDebugLog(@"Setting up screen popup");
1160 NSArray *screens = [NSScreen screens];
1161 if ([screens count] > 1) {
1162 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1163 [screenPopup setEnabled:YES];
1164 for (i = 0; i < [screens count]; i++) {
1165 NSScreen *screen = [screens objectAtIndex:i];
1166 if (![screen isEqual:[NSScreen mainScreen]]) {
1167 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1170 [screenPopup selectItemAtIndex:index];
1171 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1173 while ([screenPopup numberOfItems] > 1) {
1174 [screenPopup removeItemAtIndex:1];
1176 [screenPopup setEnabled:NO];
1177 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1181 - (void)setStatusWindowEntryEffect:(Class)effectClass
1183 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1185 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1186 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1188 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1189 [[sw entryEffect] setEffectTime:time];
1192 - (void)setStatusWindowExitEffect:(Class)effectClass
1194 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1196 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1197 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1199 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1200 [[sw exitEffect] setEffectTime:time];
1203 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1205 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1206 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1209 [backgroundColorWell setColor:color];
1213 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1215 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1218 [appearanceEffectPopup removeAllItems];
1219 [vanishEffectPopup removeAllItems];
1221 while ( (anItem = [effectEnum nextObject]) ) {
1222 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1223 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1225 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1226 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1228 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1229 [[appearanceEffectPopup lastItem] setEnabled:YES];
1230 [[vanishEffectPopup lastItem] setEnabled:YES];
1232 [[appearanceEffectPopup lastItem] setEnabled:NO];
1233 [[vanishEffectPopup lastItem] setEnabled:NO];
1239 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1243 if ( vPos == ITWindowPositionTop ) {
1244 if ( hPos == ITWindowPositionLeft ) {
1245 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1246 } else if ( hPos == ITWindowPositionCenter ) {
1247 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1248 } else if ( hPos == ITWindowPositionRight ) {
1249 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1251 } else if ( vPos == ITWindowPositionMiddle ) {
1252 if ( hPos == ITWindowPositionLeft ) {
1253 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1254 } else if ( hPos == ITWindowPositionCenter ) {
1255 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1256 } else if ( hPos == ITWindowPositionRight ) {
1257 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1259 } else if ( vPos == ITWindowPositionBottom ) {
1260 if ( hPos == ITWindowPositionLeft ) {
1261 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1262 } else if ( hPos == ITWindowPositionCenter ) {
1263 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1264 } else if ( hPos == ITWindowPositionRight ) {
1265 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1272 - (IBAction)changeMenus:(id)sender
1274 ITDebugLog(@"Synchronizing menus");
1275 [df setObject:myItems forKey:@"menu"];
1278 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1280 //If we're connected over a network, refresh the menu immediately
1281 if ([[NetworkController sharedController] isConnectedToServer]) {
1282 [controller timerUpdate];
1287 /*************************************************************************/
1289 #pragma mark NSWindow DELEGATE METHODS
1290 /*************************************************************************/
1292 - (void)windowWillClose:(NSNotification *)note
1294 [(MainController *)controller closePreferences];
1297 /*************************************************************************/
1299 #pragma mark NSTextField DELEGATE METHODS
1300 /*************************************************************************/
1302 - (void)controlTextDidChange:(NSNotification*)note
1304 if ([note object] == hostTextField) {
1305 if ([[hostTextField stringValue] length] == 0) {
1306 [sharingPanelOKButton setEnabled:NO];
1308 [sharingPanelOKButton setEnabled:YES];
1313 /*************************************************************************/
1315 #pragma mark NSTableView DATASOURCE METHODS
1316 /*************************************************************************/
1318 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1320 if (aTableView == menuTableView) {
1321 return [myItems count];
1322 } else if (aTableView == allTableView) {
1323 return [availableItems count];
1324 } else if (aTableView == hotKeysTableView) {
1325 return [hotKeysArray count];
1327 return [[[NetworkController sharedController] remoteServices] count];
1331 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1333 if (aTableView == menuTableView) {
1334 NSString *object = [myItems objectAtIndex:rowIndex];
1335 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1336 if ([object isEqualToString:@"showPlayer"]) {
1337 NSString *string = nil;
1339 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1341 [controller networkError:localException];
1345 return NSLocalizedString(object, @"ERROR");
1347 if ([submenuItems containsObject:object])
1349 return [NSImage imageNamed:@"submenu"];
1354 } else if (aTableView == allTableView) {
1355 NSString *object = [availableItems objectAtIndex:rowIndex];
1356 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1357 if ([object isEqualToString:@"showPlayer"]) {
1358 NSString *string = nil;
1360 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1362 [controller networkError:localException];
1366 return NSLocalizedString(object, @"ERROR");
1368 if ([submenuItems containsObject:object]) {
1369 return [NSImage imageNamed:@"submenu"];
1374 } else if (aTableView == hotKeysTableView) {
1375 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1376 return [hotKeyNamesArray objectAtIndex:rowIndex];
1378 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1381 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1385 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1387 if (tableView == menuTableView) {
1388 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1389 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1393 if (tableView == allTableView) {
1394 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1395 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1401 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1405 NSString *dragData, *temp;
1407 pb = [info draggingPasteboard];
1409 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1410 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1411 dragRow = [dragData intValue];
1412 temp = [myItems objectAtIndex:dragRow];
1414 if (tableView == menuTableView) {
1415 [myItems insertObject:temp atIndex:row];
1416 if (row > dragRow) {
1417 [myItems removeObjectAtIndex:dragRow];
1419 [myItems removeObjectAtIndex:dragRow + 1];
1421 } else if (tableView == allTableView) {
1422 if (![temp isEqualToString:@"separator"]) {
1423 [availableItems addObject:temp];
1425 [myItems removeObjectAtIndex:dragRow];
1427 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1428 dragData = [pb stringForType:@"AllTableViewPboardType"];
1429 dragRow = [dragData intValue];
1430 temp = [availableItems objectAtIndex:dragRow];
1432 [myItems insertObject:temp atIndex:row];
1434 if (![temp isEqualToString:@"separator"]) {
1435 [availableItems removeObjectAtIndex:dragRow];
1439 [menuTableView reloadData];
1440 [allTableView reloadData];
1441 [self changeMenus:self];
1445 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1447 if (tableView == allTableView) {
1448 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1449 return NSDragOperationNone;
1452 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1453 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1454 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1455 return NSDragOperationNone;
1459 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1460 return NSDragOperationGeneric;
1463 if (operation == NSTableViewDropOn || row == -1)
1465 return NSDragOperationNone;
1467 return NSDragOperationGeneric;
1471 /*************************************************************************/
1473 #pragma mark DEALLOCATION METHODS
1474 /*************************************************************************/
1478 [[NSNotificationCenter defaultCenter] removeObserver:self];
1479 [hotKeysArray release];
1480 [hotKeysDictionary release];
1481 [effectClasses release];
1482 [menuTableView setDataSource:nil];
1483 [allTableView setDataSource:nil];
1484 [controller release];
1485 [availableItems release];
1486 [submenuItems release];