1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "MenuController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import "StatusWindow.h"
7 #import "StatusWindowController.h"
8 #import "CustomMenuTableView.h"
9 #import "AudioscrobblerController.h"
11 #import <Security/Security.h>
13 #import <netinet/in.h>
15 #import <openssl/sha.h>
19 #import <ITKit/ITKit.h>
20 #import <ITKit/ITTSWBackgroundView.h>
22 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
23 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE(user) [[NSString stringWithFormat:@"Audioscrobbler: %@", user] UTF8String]
24 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
26 /*************************************************************************/
28 #pragma mark PRIVATE INTERFACE
29 /*************************************************************************/
31 @interface PreferencesController (Private)
32 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
33 + (BOOL)keychainItemExistsForUser:(NSString *)user;
34 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
35 + (BOOL)deleteKeychainItemForUser:(NSString *)user;
36 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
39 - (void)setupCustomizationTables;
40 - (void)setupMenuItems;
42 - (void)setupScreenPopup;
43 - (void)setStatusWindowEntryEffect:(Class)effectClass;
44 - (void)setStatusWindowExitEffect:(Class)effectClass;
45 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
46 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
47 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
48 - (IBAction)changeMenus:(id)sender;
52 @implementation PreferencesController
55 /*************************************************************************/
57 #pragma mark STATIC VARIABLES
58 /*************************************************************************/
60 static PreferencesController *prefs = nil;
62 /*************************************************************************/
64 #pragma mark STATIC KEYCHAIN SUPPORT METHODS
65 /*************************************************************************/
67 + (SecKeychainItemRef)keychainItemForUser:(NSString *)user
69 SecKeychainSearchRef search;
70 SecKeychainItemRef item;
72 SecKeychainAttribute attributes[3];
73 SecKeychainAttributeList list;
75 if ((user == nil) || ([user length] == 0)) {
79 ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
80 attributes[0].tag = kSecAccountItemAttr;
81 attributes[0].data = (char *)[user UTF8String];
82 attributes[0].length = [user length];
83 attributes[1].tag = kSecDescriptionItemAttr;
84 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
85 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
86 attributes[2].tag = kSecLabelItemAttr;
87 attributes[2].data = (char *)AUDIOSCROBBLER_KEYCHAIN_SERVICE(user);
88 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE(user));
90 list.attr = attributes;
92 status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
94 if (status != noErr) {
95 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
98 status = SecKeychainSearchCopyNext(search, &item);
100 if (status != noErr) {
101 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
109 + (BOOL)keychainItemExistsForUser:(NSString *)user
111 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
112 BOOL exists = (item != nil);
119 + (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
121 SecKeychainItemRef item;
123 SecKeychainAttribute attributes[3];
124 SecKeychainAttributeList list;
126 ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
127 attributes[0].tag = kSecAccountItemAttr;
128 attributes[0].data = (char *)[user UTF8String];
129 attributes[0].length = [user length];
130 attributes[1].tag = kSecDescriptionItemAttr;
131 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
132 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
133 attributes[2].tag = kSecLabelItemAttr;
134 attributes[2].data = (char *)AUDIOSCROBBLER_KEYCHAIN_SERVICE(user);
135 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE(user));
137 list.attr = attributes;
139 status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
140 if (status != noErr) {
141 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
143 return (status == noErr);
146 + (BOOL)deleteKeychainItemForUser:(NSString *)user
148 OSStatus status = errSecNotAvailable;
149 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
151 status = SecKeychainItemDelete(item);
152 if (status != noErr) {
153 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
157 return (status == noErr);
160 + (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
162 OSStatus status = errSecNotAvailable;
163 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
165 status = SecKeychainItemModifyContent(item, NULL, [password length], [password UTF8String]);
166 if (status != noErr) {
167 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
171 return (status == noErr);
174 + (NSString *)getKeychainItemPasswordForUser:(NSString *)user
176 OSStatus status = errSecNotAvailable;
177 SecKeychainItemRef item = [PreferencesController keychainItemForUser:user];
178 NSString *pass = nil;
182 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
183 if (status != noErr) {
184 ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
186 NSLog(@"Audioscrobbler: password buffer: \"%s\" \"Length: %i\"", buffer, length);
187 pass = [[NSString alloc] initWithBytes:buffer length:length encoding:NSUTF8StringEncoding];
189 if (status != noErr) {
190 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
194 NSLog(@"Audioscrobbler: Retrieved password: \"%@\"", pass);
195 return [pass autorelease];
198 /*************************************************************************/
200 #pragma mark INITIALIZATION METHODS
201 /*************************************************************************/
203 + (PreferencesController *)sharedPrefs;
206 prefs = [[self alloc] init];
213 if ( (self = [super init]) ) {
214 ITDebugLog(@"Preferences initialized.");
215 df = [[NSUserDefaults standardUserDefaults] retain];
217 effectClasses = [[ITWindowEffect effectClasses] retain];
219 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
234 @"ToggleShufflability",
244 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
259 @"Toggle Song Included In Shuffle",
260 @"Pop-up status menu",
261 [NSString stringWithUTF8String:"Set Rating: ☆☆☆☆☆"],
262 [NSString stringWithUTF8String:"Set Rating: ★☆☆☆☆"],
263 [NSString stringWithUTF8String:"Set Rating: ★★☆☆☆"],
264 [NSString stringWithUTF8String:"Set Rating: ★★★☆☆"],
265 [NSString stringWithUTF8String:"Set Rating: ★★★★☆"],
266 [NSString stringWithUTF8String:"Set Rating: ★★★★"],
268 hotKeysDictionary = [[NSMutableDictionary alloc] init];
271 [self setupWindow]; // Load in the nib, and perform any initial setup.
272 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
278 /*************************************************************************/
280 #pragma mark ACCESSOR METHODS
281 /*************************************************************************/
288 - (void)setController:(id)object
290 [controller autorelease];
291 controller = [object retain];
295 /*************************************************************************/
297 #pragma mark INSTANCE METHODS
298 /*************************************************************************/
300 - (BOOL)showPasswordPanel
302 [passwordPanel setLevel:NSStatusWindowLevel];
303 [passwordPanelOKButton setTitle:@"Connect"];
304 [passwordPanelTitle setStringValue:@"Password Required"];
305 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
306 [passwordPanel setLevel:NSStatusWindowLevel];
307 [NSApp activateIgnoringOtherApps:YES];
308 [passwordPanel center];
309 [passwordPanel orderFrontRegardless];
310 [passwordPanel makeKeyWindow];
311 if ([NSApp runModalForWindow:passwordPanel]) {
318 - (BOOL)showInvalidPasswordPanel
320 [passwordPanel setLevel:NSStatusWindowLevel];
321 [passwordPanelOKButton setTitle:@"Retry"];
322 [passwordPanelTitle setStringValue:@"Invalid Password"];
323 [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]]];
324 [passwordPanel setLevel:NSStatusWindowLevel];
325 [NSApp activateIgnoringOtherApps:YES];
326 [passwordPanel center];
327 [passwordPanel orderFrontRegardless];
328 [passwordPanel makeKeyWindow];
329 if ([NSApp runModalForWindow:passwordPanel]) {
336 - (IBAction)showPrefsWindow:(id)sender
338 ITDebugLog(@"Showing preferences window.");
339 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
340 ITDebugLog(@"Window doesn't exist, initial setup.");
341 [self setupCustomizationTables]; // Setup the DnD manu config tables.
342 [self setupMenuItems]; // Setup the arrays of menu items
343 [self setupUI]; // Sets up additional UI
344 [window setDelegate:self];
345 [menuTableView reloadData];
346 [hotKeysTableView reloadData];
347 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
349 //Change the launch player checkbox to the proper name
351 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
353 [controller networkError:localException];
357 [self resetRemotePlayerTextFields];
358 [launchAtLoginCheckbox becomeFirstResponder];
359 [NSApp activateIgnoringOtherApps:YES];
360 if (![window isVisible]) {
363 [window orderFrontRegardless];
364 [window makeKeyWindow];
367 - (IBAction)changeGeneralSetting:(id)sender
369 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
370 if ( [sender tag] == 1010) {
371 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
372 } else if ( [sender tag] == 1020) {
373 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
374 } else if ( [sender tag] == 1030) {
375 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
376 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
377 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
379 } else if ( [sender tag] == 1040) {
380 // This will not be executed. Song info always shows the title of the song.
381 // [df setBool:SENDER_STATE forKey:@"showName"];
382 } else if ( [sender tag] == 1050) {
383 [df setBool:SENDER_STATE forKey:@"showArtist"];
384 } else if ( [sender tag] == 1055) {
385 [df setBool:SENDER_STATE forKey:@"showComposer"];
386 } else if ( [sender tag] == 1060) {
387 [df setBool:SENDER_STATE forKey:@"showAlbum"];
388 } else if ( [sender tag] == 1070) {
389 [df setBool:SENDER_STATE forKey:@"showTime"];
390 } else if ( [sender tag] == 1080) {
391 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
392 } else if ( [sender tag] == 1085) {
393 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
394 } else if ( [sender tag] == 1090) {
395 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
396 } else if ( [sender tag] == 1100) {
397 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
398 } else if ( [sender tag] == 1110) {
399 [df setBool:SENDER_STATE forKey:@"runScripts"];
401 [runScriptsCheckbox setState:NSOnState];
402 [showScriptsButton setEnabled:YES];
404 [showScriptsButton setEnabled:NO];
406 } else if ( [sender tag] == 1120) {
407 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] UTF8String], 0744);
408 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
409 } else if ( [sender tag] == 6010) {
410 //Toggle the other Audioscrobbler options
411 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
412 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
413 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
414 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
416 [[AudioscrobblerController sharedController] attemptHandshake:NO];
418 } else if ( [sender tag ] == 6015) {
419 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
420 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
421 if ([newAccount length] == 0) {
422 [PreferencesController deleteKeychainItemForUser:currentAccount];
423 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
424 [df setObject:newAccount forKey:@"audioscrobblerUser"];
425 if ([PreferencesController keychainItemExistsForUser:currentAccount]) {
426 //Delete the current keychain item if there is one
427 [PreferencesController deleteKeychainItemForUser:currentAccount];
429 [PreferencesController createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
430 [[AudioscrobblerController sharedController] attemptHandshake:YES];
432 } else if ( [sender tag ] == 6030) {
433 //Here we set the password for an existing keychain item or we create a new keychain item.
434 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
435 NSString *account = [df stringForKey:@"audioscrobblerUser"];
436 if ([PreferencesController keychainItemExistsForUser:account]) {
437 //Update the current keychain item
438 [PreferencesController setKeychainItemPassword:[sender stringValue] forUser:account];
439 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
440 //Create a new keychain item
441 [PreferencesController createKeychainItemForUser:account andPassword:[sender stringValue]];
444 } else if ( [sender tag] == 6045) {
445 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
450 - (IBAction)changeSharingSetting:(id)sender
452 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
453 if ( [sender tag] == 5010 ) {
454 BOOL state = SENDER_STATE;
455 [df setBool:state forKey:@"enableSharing"];
456 //Disable/enable the use of shared player options
457 [useSharedMenuTunesCheckbox setEnabled:!state];
458 [passwordTextField setEnabled:state];
459 [nameTextField setEnabled:state];
460 [selectSharedPlayerButton setEnabled:NO];
461 [controller setServerStatus:state]; //Set server status
462 } else if ( [sender tag] == 5015 ) {
463 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
464 [[NetworkController sharedController] resetServerName];
465 } else if ( [sender tag] == 5030 ) {
466 //Set the server password
467 const char *instring = [[sender stringValue] UTF8String];
468 const char *password = "p4s5w0rdMT1.2";
470 NSData *hashedPass, *passwordStringHash;
471 if ([[sender stringValue] length] == 0) {
472 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
475 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
476 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
477 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
478 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
479 if (![hashedPass isEqualToData:passwordStringHash]) {
480 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
481 [sender setStringValue:@"p4s5w0rdMT1.2"];
483 } else if ( [sender tag] == 5040 ) {
484 BOOL state = SENDER_STATE;
485 [df setBool:state forKey:@"useSharedPlayer"];
486 //Disable/enable the use of sharing options
487 [shareMenuTunesCheckbox setEnabled:!state];
488 [passwordTextField setEnabled:NO];
489 [nameTextField setEnabled:NO];
490 [selectSharedPlayerButton setEnabled:state];
492 if (state && ([controller connectToServer] == 1)) {
493 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
494 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
496 [selectedPlayerTextField setStringValue:@"No shared player selected."];
497 [locationTextField setStringValue:@"-"];
498 if ([[NetworkController sharedController] isConnectedToServer]) {
499 [controller disconnectFromServer];
503 } else if ( [sender tag] == 5050 ) {
504 //If no player is selected in the table view, turn off OK button.
505 if ([sender clickedRow] == -1 ) {
506 [sharingPanelOKButton setEnabled:NO];
508 [sharingPanelOKButton setEnabled:YES];
510 } else if ( [sender tag] == 5051 ) {
511 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
512 } else if ( [sender tag] == 5060 ) {
513 //Set OK button state
514 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
515 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
516 [sharingPanelOKButton setEnabled:NO];
518 [sharingPanelOKButton setEnabled:YES];
520 //Show selection sheet
521 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
522 } else if ( [sender tag] == 5100 ) {
524 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
525 NSRect frame = [selectPlayerSheet frame];
526 frame.origin.y -= 58;
527 frame.size.height = 273;
528 if ([sharingTableView selectedRow] == -1) {
529 [sharingPanelOKButton setEnabled:NO];
531 [selectPlayerBox setContentView:zeroConfView];
532 [selectPlayerSheet setFrame:frame display:YES animate:YES];
533 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
534 NSRect frame = [selectPlayerSheet frame];
535 frame.origin.y += 58;
536 frame.size.height = 215;
537 if ([[hostTextField stringValue] length] == 0) {
538 [sharingPanelOKButton setEnabled:NO];
540 [sharingPanelOKButton setEnabled:YES];
542 [selectPlayerBox setContentView:manualView];
543 [selectPlayerSheet setFrame:frame display:YES animate:YES];
544 [hostTextField selectText:nil];
546 } else if ( [sender tag] == 5150 ) {
547 const char *instring = [[sender stringValue] UTF8String];
549 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
550 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
551 } else if ( [sender tag] == 5110 ) {
553 [NSApp endSheet:selectPlayerSheet];
554 [selectPlayerSheet orderOut:nil];
555 if ([selectPlayerBox contentView] == manualView) {
556 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
559 } else if ( [sender tag] == 5120 ) {
561 [NSApp endSheet:selectPlayerSheet];
562 [selectPlayerSheet orderOut:nil];
564 [self changeSharingSetting:clientPasswordTextField];
566 if ([selectPlayerBox contentView] == manualView) {
567 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
569 if ([sharingTableView selectedRow] > -1) {
570 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
574 if ([controller connectToServer] == 1) {
575 [useSharedMenuTunesCheckbox setState:NSOnState];
576 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
577 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
579 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);
581 } else if ( [sender tag] == 6010 ) {
582 //Cancel password entry
583 [passwordPanel orderOut:nil];
584 [NSApp stopModalWithCode:0];
585 } else if ( [sender tag] == 6020 ) {
586 //OK password entry, retry connect
587 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
589 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
590 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
591 [passwordPanel orderOut:nil];
592 [NSApp stopModalWithCode:1];
597 - (IBAction)changeStatusWindowSetting:(id)sender
599 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
600 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
602 if ( [sender tag] == 2010) {
604 BOOL entryEffectValid = YES;
605 BOOL exitEffectValid = YES;
607 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
608 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
609 [sw setVerticalPosition:[sender selectedRow]];
610 [sw setHorizontalPosition:[sender selectedColumn]];
612 // Enable/disable the items in the popups.
613 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
614 horizontalPosition:[sw horizontalPosition]];
616 // Make sure the effects support the new position.
617 entryEffectValid = ( [self effect:[[sw entryEffect] class]
618 supportsVerticalPosition:[sw verticalPosition]
619 withHorizontalPosition:[sw horizontalPosition]] );
620 exitEffectValid = ( [self effect:[[sw exitEffect] class]
621 supportsVerticalPosition:[sw verticalPosition]
622 withHorizontalPosition:[sw horizontalPosition]] );
624 if ( ! entryEffectValid ) {
625 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
626 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
628 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
631 if ( ! exitEffectValid ) {
632 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
633 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
635 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
638 [(MainController *)controller showCurrentTrackInfo];
640 } else if ( [sender tag] == 2020) {
642 // Update screen selection.
643 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
644 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
645 [(MainController *)controller showCurrentTrackInfo];
647 } else if ( [sender tag] == 2030) {
649 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
650 [(MainController *)controller showCurrentTrackInfo];
652 } else if ( [sender tag] == 2040) {
654 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
655 [(MainController *)controller showCurrentTrackInfo];
657 } else if ( [sender tag] == 2050) {
658 float newTime = ( -([sender floatValue]) );
659 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
660 [[sw entryEffect] setEffectTime:newTime];
661 } else if ( [sender tag] == 2060) {
662 float newTime = ( -([sender floatValue]) );
663 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
664 [[sw exitEffect] setEffectTime:newTime];
665 } else if ( [sender tag] == 2070) {
666 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
667 [sw setExitDelay:[sender floatValue]];
668 } else if ( [sender tag] == 2080) {
669 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
670 } else if ( [sender tag] == 2090) {
672 int setting = [sender indexOfSelectedItem];
674 if ( setting == 0 ) {
675 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
676 [backgroundColorWell setEnabled:NO];
677 [backgroundColorPopup setEnabled:NO];
678 } else if ( setting == 1 ) {
679 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
680 [backgroundColorWell setEnabled:NO];
681 [backgroundColorPopup setEnabled:NO];
682 } else if ( setting == 2 ) {
683 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
684 [backgroundColorWell setEnabled:YES];
685 [backgroundColorPopup setEnabled:YES];
688 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
689 [(MainController *)controller showCurrentTrackInfo];
691 } else if ( [sender tag] == 2091) {
692 [self setCustomColor:[sender color] updateWell:NO];
693 [(MainController *)controller showCurrentTrackInfo];
694 } else if ( [sender tag] == 2092) {
696 int selectedItem = [sender indexOfSelectedItem];
698 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
699 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
700 } else if ( selectedItem == 2 ) {
701 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
702 } else if ( selectedItem == 3 ) {
703 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
704 } else if ( selectedItem == 4 ) {
705 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
706 } else if ( selectedItem == 5 ) {
707 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
709 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
711 [(MainController *)controller showCurrentTrackInfo];
713 } else if ( [sender tag] == 2095) {
714 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
715 [(MainController *)controller showCurrentTrackInfo];
721 - (void)registerDefaults
723 ITDebugLog(@"Registering defaults.");
724 [df setObject:[NSArray arrayWithObjects:
737 nil] forKey:@"menu"];
739 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
740 [df setInteger:5 forKey:@"SongsInAdvance"];
741 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
742 [df setBool:YES forKey:@"showArtist"];
743 [df setBool:YES forKey:@"showAlbumArtwork"];
744 [df setBool:NO forKey:@"showAlbum"];
745 [df setBool:NO forKey:@"showComposer"];
746 [df setBool:NO forKey:@"showTime"];
747 [df setBool:NO forKey:@"showToolTip"];
749 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
750 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
751 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
752 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
753 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
754 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
755 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
756 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
757 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
758 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
759 [df setBool:YES forKey:@"showSongInfoOnChange"];
761 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
765 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
766 [[StatusWindowController sharedController] showSetupQueryWindow];
772 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
773 [[StatusWindow sharedWindow] vanish:self];
774 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
776 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
779 - (void)autoLaunchCancel
781 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
782 [[StatusWindow sharedWindow] vanish:self];
783 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
786 - (void)deletePressedInTableView:(NSTableView *)tableView
788 if (tableView == menuTableView) {
789 int selRow = [tableView selectedRow];
790 ITDebugLog(@"Delete pressed in menu table view.");
792 NSString *object = [myItems objectAtIndex:selRow];
794 if ([object isEqualToString:@"preferences"]) {
799 if (![object isEqualToString:@"separator"])
800 [availableItems addObject:object];
801 ITDebugLog(@"Removing object named %@", object);
802 [myItems removeObjectAtIndex:selRow];
803 [menuTableView reloadData];
804 [allTableView reloadData];
806 [self changeMenus:self];
810 - (void)resetRemotePlayerTextFields
812 if ([[NetworkController sharedController] isConnectedToServer]) {
813 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
814 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
816 [selectedPlayerTextField setStringValue:@"No shared player selected."];
817 [locationTextField setStringValue:@"-"];
821 /*************************************************************************/
823 #pragma mark HOTKEY SUPPORT METHODS
824 /*************************************************************************/
826 - (IBAction)clearHotKey:(id)sender
828 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
829 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
830 [controller setupHotKeys];
831 [hotKeysTableView reloadData];
834 - (IBAction)editHotKey:(id)sender
836 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
837 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
838 ITKeyCombo *keyCombo;
840 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
841 [controller clearHotKeys];
842 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
843 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
844 if ([panel runModal] == NSOKButton) {
845 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
847 keyCombo = [panel keyCombo];
849 //Check for duplicate key combo
850 while ( (nextKey = [keyEnumerator nextObject]) ) {
851 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
852 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
853 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
855 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
860 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
861 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
862 [controller setupHotKeys];
863 [hotKeysTableView reloadData];
864 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
866 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
870 - (void)hotKeysTableViewDoubleClicked:(id)sender
872 if ([sender clickedRow] > -1) {
873 [self editHotKey:sender];
877 /*************************************************************************/
879 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
880 /*************************************************************************/
882 - (void)audioscrobblerStatusChanged:(NSNotification *)note
884 [audioscrobblerStatusTextField setStringValue:[[note userInfo] objectForKey:@"StatusString"]];
889 ITDebugLog(@"Loading Preferences.nib.");
890 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
891 ITDebugLog(@"Failed to load Preferences.nib.");
897 - (void)setupCustomizationTables
899 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
900 ITDebugLog(@"Setting up table views.");
901 // Set the table view cells up
902 [imgCell setImageScaling:NSScaleNone];
903 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
904 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
906 // Register for drag and drop
907 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
908 @"MenuTableViewPboardType",
909 @"AllTableViewPboardType",
911 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
912 @"MenuTableViewPboardType",
913 @"AllTableViewPboardType",
917 - (void)setupMenuItems
919 NSEnumerator *itemEnum;
921 ITDebugLog(@"Setting up table view arrays.");
922 // Set the list of items you can have.
923 availableItems = [[NSMutableArray alloc] initWithObjects:
942 // Get our preferred menu
943 myItems = [[df arrayForKey:@"menu"] mutableCopy];
945 // Delete items in the availableItems array that are already part of the menu
946 itemEnum = [myItems objectEnumerator];
947 while ( (anItem = [itemEnum nextObject]) ) {
948 if (![anItem isEqualToString:@"separator"]) {
949 [availableItems removeObject:anItem];
953 // Items that show should a submenu image
954 submenuItems = [[NSArray alloc] initWithObjects:
966 NSEnumerator *keyArrayEnum;
967 NSString *serverName;
972 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioscrobblerStatusChanged:) name:@"AudioscrobblerStatusChanged" object:nil];
973 if ([df boolForKey:@"audioscrobblerEnabled"]) {
974 NSString *status = [[AudioscrobblerController sharedController] lastStatus];
975 [audioscrobblerStatusTextField setStringValue:(status == nil) ? @"Idle" : status];
978 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
980 ITDebugLog(@"Setting up preferences UI.");
981 // Fill in the number of songs in advance to show field
982 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
984 // Fill hot key array
985 keyArrayEnum = [hotKeysArray objectEnumerator];
987 while ( (anItem = [keyArrayEnum nextObject]) ) {
988 if ([df objectForKey:anItem]) {
989 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
990 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
992 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
996 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
997 [self setupScreenPopup];
999 ITDebugLog(@"Setting up track info checkboxes.");
1000 // Check current track info buttons
1001 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
1002 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
1003 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
1004 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1005 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1006 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1007 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1008 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1009 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1010 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1012 if ([df boolForKey:@"runScripts"]) {
1013 [runScriptsCheckbox setState:NSOnState];
1014 [showScriptsButton setEnabled:YES];
1016 [showScriptsButton setEnabled:NO];
1019 // Set the launch at login checkbox state
1020 ITDebugLog(@"Setting launch at login state.");
1021 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1022 [launchAtLoginCheckbox setState:NSOnState];
1025 // Set the launch player checkbox state
1026 ITDebugLog(@"Setting launch player with MenuTunes state.");
1027 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1029 // Setup the positioning controls
1030 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1031 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1033 // Setup effects controls
1034 // Populate the effects popups
1035 [appearanceEffectPopup setAutoenablesItems:NO];
1036 [vanishEffectPopup setAutoenablesItems:NO];
1037 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1038 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1040 // Attempt to find the pref'd effect in the list.
1041 // If it's not there, use cut/dissolve.
1042 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1043 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1045 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1048 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1049 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1051 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1054 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1055 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1056 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1058 // Setup General Controls
1059 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1060 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1062 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1063 [backgroundColorWell setEnabled:YES];
1064 [backgroundColorPopup setEnabled:YES];
1066 [backgroundColorWell setEnabled:NO];
1067 [backgroundColorPopup setEnabled:NO];
1070 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1073 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1075 [backgroundColorWell setColor:[NSColor blueColor]];
1078 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1080 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1082 // Setup the sharing controls
1083 if ([df boolForKey:@"enableSharing"]) {
1084 [shareMenuTunesCheckbox setState:NSOnState];
1085 [useSharedMenuTunesCheckbox setEnabled:NO];
1086 [selectSharedPlayerButton setEnabled:NO];
1087 [passwordTextField setEnabled:YES];
1088 [nameTextField setEnabled:YES];
1089 } else if ([df boolForKey:@"useSharedPlayer"]) {
1090 [useSharedMenuTunesCheckbox setState:NSOnState];
1091 [shareMenuTunesCheckbox setEnabled:NO];
1092 [selectSharedPlayerButton setEnabled:YES];
1095 //Setup the Audioscrobbler controls
1096 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1097 [audioscrobblerEnabledCheckbox setState:NSOnState];
1098 [audioscrobblerUserTextField setEnabled:YES];
1099 [audioscrobblerPasswordTextField setEnabled:YES];
1100 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1102 [audioscrobblerUserTextField setEnabled:NO];
1103 [audioscrobblerPasswordTextField setEnabled:NO];
1104 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1106 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1107 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [PreferencesController keychainItemExistsForUser:audioscrobblerUser]) {
1108 NSString *password = [PreferencesController getKeychainItemPasswordForUser:audioscrobblerUser];
1109 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1110 if (password != nil) {
1111 [audioscrobblerPasswordTextField setStringValue:password];
1114 [audioscrobblerUseCacheCheckbox setState:[df boolForKey:@"audioscrobblerCacheSubmissions"]];
1116 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1118 serverName = [df stringForKey:@"sharedPlayerName"];
1119 if (!serverName || [serverName length] == 0) {
1120 serverName = @"MenuTunes Shared Player";
1122 [nameTextField setStringValue:serverName];
1124 [selectPlayerBox setContentView:zeroConfView];
1125 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1126 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1128 [passwordTextField setStringValue:@""];
1130 if ([df stringForKey:@"sharedPlayerHost"]) {
1131 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1134 if ([[NetworkController sharedController] isConnectedToServer]) {
1135 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1136 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1138 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1139 [locationTextField setStringValue:@"-"];
1143 - (void)setupScreenPopup
1145 ITDebugLog(@"Setting up screen popup");
1146 NSArray *screens = [NSScreen screens];
1147 if ([screens count] > 1) {
1148 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1149 [screenPopup setEnabled:YES];
1150 for (i = 0; i < [screens count]; i++) {
1151 NSScreen *screen = [screens objectAtIndex:i];
1152 if (![screen isEqual:[NSScreen mainScreen]]) {
1153 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1156 [screenPopup selectItemAtIndex:index];
1157 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1159 while ([screenPopup numberOfItems] > 1) {
1160 [screenPopup removeItemAtIndex:1];
1162 [screenPopup setEnabled:NO];
1163 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1167 - (void)setStatusWindowEntryEffect:(Class)effectClass
1169 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1171 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1172 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1174 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1175 [[sw entryEffect] setEffectTime:time];
1178 - (void)setStatusWindowExitEffect:(Class)effectClass
1180 StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1182 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1183 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1185 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1186 [[sw exitEffect] setEffectTime:time];
1189 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1191 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1192 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1195 [backgroundColorWell setColor:color];
1199 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1201 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1204 [appearanceEffectPopup removeAllItems];
1205 [vanishEffectPopup removeAllItems];
1207 while ( (anItem = [effectEnum nextObject]) ) {
1208 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1209 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1211 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1212 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1214 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1215 [[appearanceEffectPopup lastItem] setEnabled:YES];
1216 [[vanishEffectPopup lastItem] setEnabled:YES];
1218 [[appearanceEffectPopup lastItem] setEnabled:NO];
1219 [[vanishEffectPopup lastItem] setEnabled:NO];
1225 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1229 if ( vPos == ITWindowPositionTop ) {
1230 if ( hPos == ITWindowPositionLeft ) {
1231 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1232 } else if ( hPos == ITWindowPositionCenter ) {
1233 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1234 } else if ( hPos == ITWindowPositionRight ) {
1235 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1237 } else if ( vPos == ITWindowPositionMiddle ) {
1238 if ( hPos == ITWindowPositionLeft ) {
1239 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1240 } else if ( hPos == ITWindowPositionCenter ) {
1241 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1242 } else if ( hPos == ITWindowPositionRight ) {
1243 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1245 } else if ( vPos == ITWindowPositionBottom ) {
1246 if ( hPos == ITWindowPositionLeft ) {
1247 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1248 } else if ( hPos == ITWindowPositionCenter ) {
1249 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1250 } else if ( hPos == ITWindowPositionRight ) {
1251 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1258 - (IBAction)changeMenus:(id)sender
1260 ITDebugLog(@"Synchronizing menus");
1261 [df setObject:myItems forKey:@"menu"];
1264 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1266 //If we're connected over a network, refresh the menu immediately
1267 if ([[NetworkController sharedController] isConnectedToServer]) {
1268 [controller timerUpdate];
1273 /*************************************************************************/
1275 #pragma mark NSWindow DELEGATE METHODS
1276 /*************************************************************************/
1278 - (void)windowWillClose:(NSNotification *)note
1280 [(MainController *)controller closePreferences];
1283 /*************************************************************************/
1285 #pragma mark NSTextField DELEGATE METHODS
1286 /*************************************************************************/
1288 - (void)controlTextDidChange:(NSNotification*)note
1290 if ([note object] == hostTextField) {
1291 if ([[hostTextField stringValue] length] == 0) {
1292 [sharingPanelOKButton setEnabled:NO];
1294 [sharingPanelOKButton setEnabled:YES];
1299 /*************************************************************************/
1301 #pragma mark NSTableView DATASOURCE METHODS
1302 /*************************************************************************/
1304 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1306 if (aTableView == menuTableView) {
1307 return [myItems count];
1308 } else if (aTableView == allTableView) {
1309 return [availableItems count];
1310 } else if (aTableView == hotKeysTableView) {
1311 return [hotKeysArray count];
1313 return [[[NetworkController sharedController] remoteServices] count];
1317 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1319 if (aTableView == menuTableView) {
1320 NSString *object = [myItems objectAtIndex:rowIndex];
1321 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1322 if ([object isEqualToString:@"showPlayer"]) {
1323 NSString *string = nil;
1325 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1327 [controller networkError:localException];
1331 return NSLocalizedString(object, @"ERROR");
1333 if ([submenuItems containsObject:object])
1335 return [NSImage imageNamed:@"submenu"];
1340 } else if (aTableView == allTableView) {
1341 NSString *object = [availableItems objectAtIndex:rowIndex];
1342 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1343 if ([object isEqualToString:@"showPlayer"]) {
1344 NSString *string = nil;
1346 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1348 [controller networkError:localException];
1352 return NSLocalizedString(object, @"ERROR");
1354 if ([submenuItems containsObject:object]) {
1355 return [NSImage imageNamed:@"submenu"];
1360 } else if (aTableView == hotKeysTableView) {
1361 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1362 return [hotKeyNamesArray objectAtIndex:rowIndex];
1364 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1367 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1371 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1373 if (tableView == menuTableView) {
1374 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1375 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1379 if (tableView == allTableView) {
1380 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1381 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1387 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1391 NSString *dragData, *temp;
1393 pb = [info draggingPasteboard];
1395 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1396 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1397 dragRow = [dragData intValue];
1398 temp = [myItems objectAtIndex:dragRow];
1400 if (tableView == menuTableView) {
1401 [myItems insertObject:temp atIndex:row];
1402 if (row > dragRow) {
1403 [myItems removeObjectAtIndex:dragRow];
1405 [myItems removeObjectAtIndex:dragRow + 1];
1407 } else if (tableView == allTableView) {
1408 if (![temp isEqualToString:@"separator"]) {
1409 [availableItems addObject:temp];
1411 [myItems removeObjectAtIndex:dragRow];
1413 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1414 dragData = [pb stringForType:@"AllTableViewPboardType"];
1415 dragRow = [dragData intValue];
1416 temp = [availableItems objectAtIndex:dragRow];
1418 [myItems insertObject:temp atIndex:row];
1420 if (![temp isEqualToString:@"separator"]) {
1421 [availableItems removeObjectAtIndex:dragRow];
1425 [menuTableView reloadData];
1426 [allTableView reloadData];
1427 [self changeMenus:self];
1431 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1433 if (tableView == allTableView) {
1434 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1435 return NSDragOperationNone;
1438 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1439 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1440 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1441 return NSDragOperationNone;
1445 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1446 return NSDragOperationGeneric;
1449 if (operation == NSTableViewDropOn || row == -1)
1451 return NSDragOperationNone;
1453 return NSDragOperationGeneric;
1457 /*************************************************************************/
1459 #pragma mark DEALLOCATION METHODS
1460 /*************************************************************************/
1464 [[NSNotificationCenter defaultCenter] removeObserver:self];
1465 [hotKeysArray release];
1466 [hotKeysDictionary release];
1467 [effectClasses release];
1468 [menuTableView setDataSource:nil];
1469 [allTableView setDataSource:nil];
1470 [controller release];
1471 [availableItems release];
1472 [submenuItems release];