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"
10 #import <Security/Security.h>
12 #import <netinet/in.h>
14 #import <openssl/sha.h>
18 #import <ITKit/ITLoginItem.h>
20 #import <ITKit/ITHotKeyCenter.h>
21 #import <ITKit/ITKeyCombo.h>
22 #import <ITKit/ITKeyComboPanel.h>
23 #import <ITKit/ITWindowPositioning.h>
24 #import <ITKit/ITKeyBroadcaster.h>
26 #import <ITKit/ITTSWBackgroundView.h>
27 #import <ITKit/ITWindowEffect.h>
28 #import <ITKit/ITCutWindowEffect.h>
29 #import <ITKit/ITDissolveWindowEffect.h>
30 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
31 #import <ITKit/ITSlideVerticallyWindowEffect.h>
32 #import <ITKit/ITPivotWindowEffect.h>
35 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
36 #define AUDIOSCROBBLER_KEYCHAIN_SERVICE "MenuTunes: Audioscrobbler"
37 #define AUDIOSCROBBLER_KEYCHAIN_KIND "application password"
39 /*************************************************************************/
41 #pragma mark PRIVATE INTERFACE
42 /*************************************************************************/
44 @interface PreferencesController (Private)
46 - (void)setupCustomizationTables;
47 - (void)setupMenuItems;
49 - (void)setupScreenPopup;
50 - (void)setStatusWindowEntryEffect:(Class)effectClass;
51 - (void)setStatusWindowExitEffect:(Class)effectClass;
52 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
53 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
54 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
55 - (IBAction)changeMenus:(id)sender;
57 - (SecKeychainItemRef)keychainItemForUser:(NSString *)user;
58 - (BOOL)keychainItemExistsForUser:(NSString *)user;
59 - (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password;
60 - (BOOL)deleteKeychainItemForUser:(NSString *)user;
61 - (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user;
65 @implementation PreferencesController
68 /*************************************************************************/
70 #pragma mark STATIC VARIABLES
71 /*************************************************************************/
73 static PreferencesController *prefs = nil;
76 /*************************************************************************/
78 #pragma mark INITIALIZATION METHODS
79 /*************************************************************************/
81 + (PreferencesController *)sharedPrefs;
84 prefs = [[self alloc] init];
91 if ( (self = [super init]) ) {
92 ITDebugLog(@"Preferences initialized.");
93 df = [[NSUserDefaults standardUserDefaults] retain];
95 effectClasses = [[ITWindowEffect effectClasses] retain];
97 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
111 @"ToggleShufflability",
121 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
135 @"Toggle Song Included In Shuffle",
136 @"Pop-up status menu",
137 [NSString stringWithUTF8String:"Set Rating: ☆☆☆☆☆"],
138 [NSString stringWithUTF8String:"Set Rating: ★☆☆☆☆"],
139 [NSString stringWithUTF8String:"Set Rating: ★★☆☆☆"],
140 [NSString stringWithUTF8String:"Set Rating: ★★★☆☆"],
141 [NSString stringWithUTF8String:"Set Rating: ★★★★☆"],
142 [NSString stringWithUTF8String:"Set Rating: ★★★★★"],
144 hotKeysDictionary = [[NSMutableDictionary alloc] init];
147 [self setupWindow]; // Load in the nib, and perform any initial setup.
148 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
154 /*************************************************************************/
156 #pragma mark ACCESSOR METHODS
157 /*************************************************************************/
164 - (void)setController:(id)object
166 [controller autorelease];
167 controller = [object retain];
171 /*************************************************************************/
173 #pragma mark INSTANCE METHODS
174 /*************************************************************************/
176 - (BOOL)showPasswordPanel
178 [passwordPanel setLevel:NSStatusWindowLevel];
179 [passwordPanelOKButton setTitle:@"Connect"];
180 [passwordPanelTitle setStringValue:@"Password Required"];
181 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
182 [passwordPanel setLevel:NSStatusWindowLevel];
183 [NSApp activateIgnoringOtherApps:YES];
184 [passwordPanel center];
185 [passwordPanel orderFrontRegardless];
186 [passwordPanel makeKeyWindow];
187 if ([NSApp runModalForWindow:passwordPanel]) {
194 - (BOOL)showInvalidPasswordPanel
196 [passwordPanel setLevel:NSStatusWindowLevel];
197 [passwordPanelOKButton setTitle:@"Retry"];
198 [passwordPanelTitle setStringValue:@"Invalid Password"];
199 [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]]];
200 [passwordPanel setLevel:NSStatusWindowLevel];
201 [NSApp activateIgnoringOtherApps:YES];
202 [passwordPanel center];
203 [passwordPanel orderFrontRegardless];
204 [passwordPanel makeKeyWindow];
205 if ([NSApp runModalForWindow:passwordPanel]) {
212 - (IBAction)showPrefsWindow:(id)sender
214 ITDebugLog(@"Showing preferences window.");
215 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
216 ITDebugLog(@"Window doesn't exist, initial setup.");
217 [self setupCustomizationTables]; // Setup the DnD manu config tables.
218 [self setupMenuItems]; // Setup the arrays of menu items
219 [self setupUI]; // Sets up additional UI
220 [window setDelegate:self];
221 [menuTableView reloadData];
222 [hotKeysTableView reloadData];
223 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
225 //Change the launch player checkbox to the proper name
227 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
229 [controller networkError:localException];
233 [self resetRemotePlayerTextFields];
234 [launchAtLoginCheckbox becomeFirstResponder];
235 [NSApp activateIgnoringOtherApps:YES];
236 if (![window isVisible]) {
239 [window orderFrontRegardless];
240 [window makeKeyWindow];
243 - (IBAction)changeGeneralSetting:(id)sender
245 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
246 if ( [sender tag] == 1010) {
247 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
248 } else if ( [sender tag] == 1020) {
249 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
250 } else if ( [sender tag] == 1030) {
251 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
252 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
253 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0];
255 } else if ( [sender tag] == 1040) {
256 // This will not be executed. Song info always shows the title of the song.
257 // [df setBool:SENDER_STATE forKey:@"showName"];
258 } else if ( [sender tag] == 1050) {
259 [df setBool:SENDER_STATE forKey:@"showArtist"];
260 } else if ( [sender tag] == 1055) {
261 [df setBool:SENDER_STATE forKey:@"showComposer"];
262 } else if ( [sender tag] == 1060) {
263 [df setBool:SENDER_STATE forKey:@"showAlbum"];
264 } else if ( [sender tag] == 1070) {
265 [df setBool:SENDER_STATE forKey:@"showTime"];
266 } else if ( [sender tag] == 1080) {
267 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
268 } else if ( [sender tag] == 1085) {
269 [df setBool:SENDER_STATE forKey:@"showPlayCount"];
270 } else if ( [sender tag] == 1090) {
271 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
272 } else if ( [sender tag] == 1100) {
273 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
274 } else if ( [sender tag] == 1110) {
275 [df setBool:SENDER_STATE forKey:@"runScripts"];
277 [runScriptsCheckbox setState:NSOnState];
278 [showScriptsButton setEnabled:YES];
280 [showScriptsButton setEnabled:NO];
282 } else if ( [sender tag] == 1120) {
283 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
284 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
285 } else if ( [sender tag] == 6010) {
286 //Toggle the other Audioscrobbler options
287 [df setBool:SENDER_STATE forKey:@"audioscrobblerEnabled"];
288 [audioscrobblerUseCacheCheckbox setEnabled:SENDER_STATE];
289 [audioscrobblerUserTextField setEnabled:SENDER_STATE];
290 [audioscrobblerPasswordTextField setEnabled:SENDER_STATE];
291 } else if ( [sender tag ] == 6015) {
292 //Here we create a new keychain item if needed and deletes the keychain item if the field is cleared.
293 NSString *currentAccount = [df stringForKey:@"audioscrobblerUser"], *newAccount = [sender stringValue];
294 if ([newAccount length] == 0) {
295 [self deleteKeychainItemForUser:currentAccount];
296 } else if (![currentAccount isEqualToString:newAccount] && [[audioscrobblerPasswordTextField stringValue] length] > 0) {
297 [df setObject:newAccount forKey:@"audioscrobblerUser"];
298 if ([self keychainItemExistsForUser:currentAccount]) {
299 //Delete the current keychain item if there is one
300 [self deleteKeychainItemForUser:currentAccount];
302 [self createKeychainItemForUser:newAccount andPassword:[audioscrobblerPasswordTextField stringValue]];
304 } else if ( [sender tag ] == 6030) {
305 //Here we set the password for an existing keychain item or we create a new keychain item.
306 if ([[audioscrobblerUserTextField stringValue] length] > 0) {
307 NSString *account = [df stringForKey:@"audioscrobblerUser"];
308 if ([self keychainItemExistsForUser:account]) {
309 //Update the current keychain item
310 [self setKeychainItemPassword:[sender stringValue] forUser:account];
311 } else if ([[sender stringValue] length] > 0 && [[audioscrobblerUserTextField stringValue] length]) {
312 //Create a new keychain item
313 [self createKeychainItemForUser:account andPassword:[sender stringValue]];
316 } else if ( [sender tag] == 6045) {
317 [df setBool:SENDER_STATE forKey:@"audioscrobblerCacheSubmissions"];
322 - (IBAction)changeSharingSetting:(id)sender
324 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
325 if ( [sender tag] == 5010 ) {
326 BOOL state = SENDER_STATE;
327 [df setBool:state forKey:@"enableSharing"];
328 //Disable/enable the use of shared player options
329 [useSharedMenuTunesCheckbox setEnabled:!state];
330 [passwordTextField setEnabled:state];
331 [nameTextField setEnabled:state];
332 [selectSharedPlayerButton setEnabled:NO];
333 [controller setServerStatus:state]; //Set server status
334 } else if ( [sender tag] == 5015 ) {
335 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
336 [[NetworkController sharedController] resetServerName];
337 } else if ( [sender tag] == 5030 ) {
338 //Set the server password
339 const char *instring = [[sender stringValue] UTF8String];
340 const char *password = "p4s5w0rdMT1.2";
342 NSData *hashedPass, *passwordStringHash;
343 if ([[sender stringValue] length] == 0) {
344 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
347 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
348 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
349 result = (char *)SHA1((unsigned char *)password, strlen(password), NULL);
350 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
351 if (![hashedPass isEqualToData:passwordStringHash]) {
352 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
353 [sender setStringValue:@"p4s5w0rdMT1.2"];
355 } else if ( [sender tag] == 5040 ) {
356 BOOL state = SENDER_STATE;
357 [df setBool:state forKey:@"useSharedPlayer"];
358 //Disable/enable the use of sharing options
359 [shareMenuTunesCheckbox setEnabled:!state];
360 [passwordTextField setEnabled:NO];
361 [nameTextField setEnabled:NO];
362 [selectSharedPlayerButton setEnabled:state];
364 if (state && ([controller connectToServer] == 1)) {
365 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
366 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
368 [selectedPlayerTextField setStringValue:@"No shared player selected."];
369 [locationTextField setStringValue:@"-"];
370 if ([[NetworkController sharedController] isConnectedToServer]) {
371 [controller disconnectFromServer];
375 } else if ( [sender tag] == 5050 ) {
376 //If no player is selected in the table view, turn off OK button.
377 if ([sender clickedRow] == -1 ) {
378 [sharingPanelOKButton setEnabled:NO];
380 [sharingPanelOKButton setEnabled:YES];
382 } else if ( [sender tag] == 5051 ) {
383 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
384 } else if ( [sender tag] == 5060 ) {
385 //Set OK button state
386 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
387 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
388 [sharingPanelOKButton setEnabled:NO];
390 [sharingPanelOKButton setEnabled:YES];
392 //Show selection sheet
393 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
394 } else if ( [sender tag] == 5100 ) {
396 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
397 NSRect frame = [selectPlayerSheet frame];
398 frame.origin.y -= 58;
399 frame.size.height = 273;
400 if ([sharingTableView selectedRow] == -1) {
401 [sharingPanelOKButton setEnabled:NO];
403 [selectPlayerBox setContentView:zeroConfView];
404 [selectPlayerSheet setFrame:frame display:YES animate:YES];
405 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
406 NSRect frame = [selectPlayerSheet frame];
407 frame.origin.y += 58;
408 frame.size.height = 215;
409 if ([[hostTextField stringValue] length] == 0) {
410 [sharingPanelOKButton setEnabled:NO];
412 [sharingPanelOKButton setEnabled:YES];
414 [selectPlayerBox setContentView:manualView];
415 [selectPlayerSheet setFrame:frame display:YES animate:YES];
416 [hostTextField selectText:nil];
418 } else if ( [sender tag] == 5150 ) {
419 const char *instring = [[sender stringValue] UTF8String];
421 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
422 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
423 } else if ( [sender tag] == 5110 ) {
425 [NSApp endSheet:selectPlayerSheet];
426 [selectPlayerSheet orderOut:nil];
427 if ([selectPlayerBox contentView] == manualView) {
428 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
431 } else if ( [sender tag] == 5120 ) {
433 [NSApp endSheet:selectPlayerSheet];
434 [selectPlayerSheet orderOut:nil];
436 [self changeSharingSetting:clientPasswordTextField];
438 if ([selectPlayerBox contentView] == manualView) {
439 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
441 if ([sharingTableView selectedRow] > -1) {
442 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
446 if ([controller connectToServer] == 1) {
447 [useSharedMenuTunesCheckbox setState:NSOnState];
448 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
449 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
451 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);
453 } else if ( [sender tag] == 6010 ) {
454 //Cancel password entry
455 [passwordPanel orderOut:nil];
456 [NSApp stopModalWithCode:0];
457 } else if ( [sender tag] == 6020 ) {
458 //OK password entry, retry connect
459 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
461 result = (char *)SHA1((unsigned char *)instring, strlen(instring), NULL);
462 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
463 [passwordPanel orderOut:nil];
464 [NSApp stopModalWithCode:1];
469 - (IBAction)changeStatusWindowSetting:(id)sender
471 StatusWindow *sw = [StatusWindow sharedWindow];
472 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
474 if ( [sender tag] == 2010) {
476 BOOL entryEffectValid = YES;
477 BOOL exitEffectValid = YES;
479 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
480 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
481 [sw setVerticalPosition:[sender selectedRow]];
482 [sw setHorizontalPosition:[sender selectedColumn]];
484 // Enable/disable the items in the popups.
485 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
486 horizontalPosition:[sw horizontalPosition]];
488 // Make sure the effects support the new position.
489 entryEffectValid = ( [self effect:[[sw entryEffect] class]
490 supportsVerticalPosition:[sw verticalPosition]
491 withHorizontalPosition:[sw horizontalPosition]] );
492 exitEffectValid = ( [self effect:[[sw exitEffect] class]
493 supportsVerticalPosition:[sw verticalPosition]
494 withHorizontalPosition:[sw horizontalPosition]] );
496 if ( ! entryEffectValid ) {
497 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
498 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
500 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
503 if ( ! exitEffectValid ) {
504 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
505 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
507 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
510 [(MainController *)controller showCurrentTrackInfo];
512 } else if ( [sender tag] == 2020) {
514 // Update screen selection.
515 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:[sender indexOfSelectedItem]]];
516 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowScreenIndex"];
517 [(MainController *)controller showCurrentTrackInfo];
519 } else if ( [sender tag] == 2030) {
521 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
522 [(MainController *)controller showCurrentTrackInfo];
524 } else if ( [sender tag] == 2040) {
526 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
527 [(MainController *)controller showCurrentTrackInfo];
529 } else if ( [sender tag] == 2050) {
530 float newTime = ( -([sender floatValue]) );
531 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
532 [[sw entryEffect] setEffectTime:newTime];
533 } else if ( [sender tag] == 2060) {
534 float newTime = ( -([sender floatValue]) );
535 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
536 [[sw exitEffect] setEffectTime:newTime];
537 } else if ( [sender tag] == 2070) {
538 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
539 [sw setExitDelay:[sender floatValue]];
540 } else if ( [sender tag] == 2080) {
541 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
542 } else if ( [sender tag] == 2090) {
544 int setting = [sender indexOfSelectedItem];
546 if ( setting == 0 ) {
547 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
548 [backgroundColorWell setEnabled:NO];
549 [backgroundColorPopup setEnabled:NO];
550 } else if ( setting == 1 ) {
551 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
552 [backgroundColorWell setEnabled:NO];
553 [backgroundColorPopup setEnabled:NO];
554 } else if ( setting == 2 ) {
555 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
556 [backgroundColorWell setEnabled:YES];
557 [backgroundColorPopup setEnabled:YES];
560 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
561 [(MainController *)controller showCurrentTrackInfo];
563 } else if ( [sender tag] == 2091) {
564 [self setCustomColor:[sender color] updateWell:NO];
565 [(MainController *)controller showCurrentTrackInfo];
566 } else if ( [sender tag] == 2092) {
568 int selectedItem = [sender indexOfSelectedItem];
570 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
571 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
572 } else if ( selectedItem == 2 ) {
573 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
574 } else if ( selectedItem == 3 ) {
575 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
576 } else if ( selectedItem == 4 ) {
577 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
578 } else if ( selectedItem == 5 ) {
579 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
581 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
583 [(MainController *)controller showCurrentTrackInfo];
585 } else if ( [sender tag] == 2095) {
586 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
587 [(MainController *)controller showCurrentTrackInfo];
593 - (void)registerDefaults
595 ITDebugLog(@"Registering defaults.");
596 [df setObject:[NSArray arrayWithObjects:
609 nil] forKey:@"menu"];
611 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
612 [df setInteger:5 forKey:@"SongsInAdvance"];
613 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
614 [df setBool:YES forKey:@"showArtist"];
615 [df setBool:YES forKey:@"showAlbumArtwork"];
616 [df setBool:NO forKey:@"showAlbum"];
617 [df setBool:NO forKey:@"showComposer"];
618 [df setBool:NO forKey:@"showTime"];
619 [df setBool:NO forKey:@"showToolTip"];
621 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
622 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
623 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
624 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
625 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
626 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
627 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
628 [df setInteger:0 forKey:@"statusWindowScreenIndex"];
629 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
630 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
631 [df setBool:YES forKey:@"showSongInfoOnChange"];
633 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
637 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
638 [[StatusWindowController sharedController] showSetupQueryWindow];
644 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
645 [[StatusWindow sharedWindow] vanish:self];
646 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
648 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
651 - (void)autoLaunchCancel
653 [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
654 [[StatusWindow sharedWindow] vanish:self];
655 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
658 - (void)deletePressedInTableView:(NSTableView *)tableView
660 if (tableView == menuTableView) {
661 int selRow = [tableView selectedRow];
662 ITDebugLog(@"Delete pressed in menu table view.");
664 NSString *object = [myItems objectAtIndex:selRow];
666 if ([object isEqualToString:@"preferences"]) {
671 if (![object isEqualToString:@"separator"])
672 [availableItems addObject:object];
673 ITDebugLog(@"Removing object named %@", object);
674 [myItems removeObjectAtIndex:selRow];
675 [menuTableView reloadData];
676 [allTableView reloadData];
678 [self changeMenus:self];
682 - (void)resetRemotePlayerTextFields
684 if ([[NetworkController sharedController] isConnectedToServer]) {
685 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
686 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
688 [selectedPlayerTextField setStringValue:@"No shared player selected."];
689 [locationTextField setStringValue:@"-"];
693 /*************************************************************************/
695 #pragma mark KEYCHAIN SUPPORT METHODS
696 /*************************************************************************/
698 - (SecKeychainItemRef)keychainItemForUser:(NSString *)user
700 SecKeychainSearchRef search;
701 SecKeychainItemRef item;
703 SecKeychainAttribute attributes[3];
704 SecKeychainAttributeList list;
706 ITDebugLog(@"Audioscrobbler: Searching for keychain item for %@.", user);
707 attributes[0].tag = kSecAccountItemAttr;
708 attributes[0].data = (char *)[user UTF8String];
709 attributes[0].length = [user length];
710 attributes[1].tag = kSecDescriptionItemAttr;
711 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
712 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
713 attributes[2].tag = kSecLabelItemAttr;
714 attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
715 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
717 list.attr = attributes;
719 status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
721 if (status != noErr) {
722 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
725 status = SecKeychainSearchCopyNext(search, &item);
727 if (status != noErr) {
728 ITDebugLog(@"Audioscrobbler: Error searching for existing keychain item: %i", status);
736 - (BOOL)keychainItemExistsForUser:(NSString *)user
738 SecKeychainItemRef item = [self keychainItemForUser:user];
739 BOOL exists = (item != nil);
746 - (BOOL)createKeychainItemForUser:(NSString *)user andPassword:(NSString *)password
748 SecKeychainItemRef item;
750 SecKeychainAttribute attributes[3];
751 SecKeychainAttributeList list;
753 ITDebugLog(@"Audioscrobbler: Creating new keychain item for %@.", user);
754 attributes[0].tag = kSecAccountItemAttr;
755 attributes[0].data = (char *)[user UTF8String];
756 attributes[0].length = [user length];
757 attributes[1].tag = kSecDescriptionItemAttr;
758 attributes[1].data = AUDIOSCROBBLER_KEYCHAIN_KIND;
759 attributes[1].length = strlen(AUDIOSCROBBLER_KEYCHAIN_KIND);
760 attributes[2].tag = kSecLabelItemAttr;
761 attributes[2].data = AUDIOSCROBBLER_KEYCHAIN_SERVICE;
762 attributes[2].length = strlen(AUDIOSCROBBLER_KEYCHAIN_SERVICE);
764 list.attr = attributes;
766 status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL, NULL, &item);
767 if (status != noErr) {
768 ITDebugLog(@"Audioscrobbler: Error creating keychain item: %i", status);
770 return (status == noErr);
773 - (BOOL)deleteKeychainItemForUser:(NSString *)user
775 OSStatus status = errSecNotAvailable;
776 SecKeychainItemRef item = [self keychainItemForUser:user];
778 status = SecKeychainItemDelete(item);
779 if (status != noErr) {
780 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
784 return (status == noErr);
787 - (BOOL)setKeychainItemPassword:(NSString *)password forUser:(NSString *)user
789 OSStatus status = errSecNotAvailable;
790 SecKeychainItemRef item = [self keychainItemForUser:user];
792 status = SecKeychainItemModifyContent(item, NULL, [password length], [password cString]);
793 if (status != noErr) {
794 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
798 return (status == noErr);
801 - (NSString *)getKeychainItemPasswordForUser:(NSString *)user
803 OSStatus status = errSecNotAvailable;
804 SecKeychainItemRef item = [self keychainItemForUser:user];
805 NSString *pass = nil;
809 status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&buffer);
810 if (status != noErr) {
811 ITDebugLog(@"Audioscrobbler: Error getting keychain item password: %i", status);
813 if ([NSString respondsToSelector:@selector(stringWithCString:encoding:)]) {
814 pass = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
816 pass = [NSString stringWithCString:buffer];
819 if (status != noErr) {
820 ITDebugLog(@"Audioscrobbler: Error deleting keychain item: %i", status);
827 /*************************************************************************/
829 #pragma mark HOTKEY SUPPORT METHODS
830 /*************************************************************************/
832 - (IBAction)clearHotKey:(id)sender
834 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
835 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
836 [controller setupHotKeys];
837 [hotKeysTableView reloadData];
840 - (IBAction)editHotKey:(id)sender
842 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
843 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
844 ITKeyCombo *keyCombo;
846 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
847 [controller clearHotKeys];
848 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
849 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
850 if ([panel runModal] == NSOKButton) {
851 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
853 keyCombo = [panel keyCombo];
855 //Check for duplicate key combo
856 while ( (nextKey = [keyEnumerator nextObject]) ) {
857 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
858 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
859 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
861 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
866 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
867 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
868 [controller setupHotKeys];
869 [hotKeysTableView reloadData];
870 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
872 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
876 - (void)hotKeysTableViewDoubleClicked:(id)sender
878 if ([sender clickedRow] > -1) {
879 [self editHotKey:sender];
883 /*************************************************************************/
885 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
886 /*************************************************************************/
890 ITDebugLog(@"Loading Preferences.nib.");
891 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
892 ITDebugLog(@"Failed to load Preferences.nib.");
898 - (void)setupCustomizationTables
900 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
901 ITDebugLog(@"Setting up table views.");
902 // Set the table view cells up
903 [imgCell setImageScaling:NSScaleNone];
904 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
905 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
907 // Register for drag and drop
908 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
909 @"MenuTableViewPboardType",
910 @"AllTableViewPboardType",
912 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
913 @"MenuTableViewPboardType",
914 @"AllTableViewPboardType",
918 - (void)setupMenuItems
920 NSEnumerator *itemEnum;
922 ITDebugLog(@"Setting up table view arrays.");
923 // Set the list of items you can have.
924 availableItems = [[NSMutableArray alloc] initWithObjects:
943 // Get our preferred menu
944 myItems = [[df arrayForKey:@"menu"] mutableCopy];
946 // Delete items in the availableItems array that are already part of the menu
947 itemEnum = [myItems objectEnumerator];
948 while ( (anItem = [itemEnum nextObject]) ) {
949 if (![anItem isEqualToString:@"separator"]) {
950 [availableItems removeObject:anItem];
954 // Items that show should a submenu image
955 submenuItems = [[NSArray alloc] initWithObjects:
967 NSEnumerator *keyArrayEnum;
968 NSString *serverName;
973 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
975 ITDebugLog(@"Setting up preferences UI.");
976 // Fill in the number of songs in advance to show field
977 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
979 // Fill hot key array
980 keyArrayEnum = [hotKeysArray objectEnumerator];
982 while ( (anItem = [keyArrayEnum nextObject]) ) {
983 if ([df objectForKey:anItem]) {
984 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
985 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
987 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
991 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupScreenPopup) name:NSApplicationDidChangeScreenParametersNotification object:nil];
992 [self setupScreenPopup];
994 ITDebugLog(@"Setting up track info checkboxes.");
995 // Check current track info buttons
996 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
997 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
998 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
999 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
1000 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
1001 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
1002 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
1003 [playCountCheckbox setState:[df boolForKey:@"showPlayCount"] ? NSOnState : NSOffState];
1004 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
1005 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
1007 if ([df boolForKey:@"runScripts"]) {
1008 [runScriptsCheckbox setState:NSOnState];
1009 [showScriptsButton setEnabled:YES];
1011 [showScriptsButton setEnabled:NO];
1014 // Set the launch at login checkbox state
1015 ITDebugLog(@"Setting launch at login state.");
1016 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
1017 [launchAtLoginCheckbox setState:NSOnState];
1020 // Set the launch player checkbox state
1021 ITDebugLog(@"Setting launch player with MenuTunes state.");
1022 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
1024 // Setup the positioning controls
1025 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
1026 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
1028 // Setup effects controls
1029 // Populate the effects popups
1030 [appearanceEffectPopup setAutoenablesItems:NO];
1031 [vanishEffectPopup setAutoenablesItems:NO];
1032 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
1033 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
1035 // Attempt to find the pref'd effect in the list.
1036 // If it's not there, use cut/dissolve.
1037 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
1038 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
1040 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1043 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
1044 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
1046 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
1049 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
1050 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
1051 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
1053 // Setup General Controls
1054 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
1055 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
1057 if ( selectedBGStyle == ITTSWBackgroundColored ) {
1058 [backgroundColorWell setEnabled:YES];
1059 [backgroundColorPopup setEnabled:YES];
1061 [backgroundColorWell setEnabled:NO];
1062 [backgroundColorPopup setEnabled:NO];
1065 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
1068 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
1070 [backgroundColorWell setColor:[NSColor blueColor]];
1073 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
1075 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
1077 // Setup the sharing controls
1078 if ([df boolForKey:@"enableSharing"]) {
1079 [shareMenuTunesCheckbox setState:NSOnState];
1080 [useSharedMenuTunesCheckbox setEnabled:NO];
1081 [selectSharedPlayerButton setEnabled:NO];
1082 [passwordTextField setEnabled:YES];
1083 [nameTextField setEnabled:YES];
1084 } else if ([df boolForKey:@"useSharedPlayer"]) {
1085 [useSharedMenuTunesCheckbox setState:NSOnState];
1086 [shareMenuTunesCheckbox setEnabled:NO];
1087 [selectSharedPlayerButton setEnabled:YES];
1090 //Setup the Audioscrobbler controls
1091 if ([df boolForKey:@"audioscrobblerEnabled"]) {
1092 [audioscrobblerEnabledCheckbox setState:NSOnState];
1093 [audioscrobblerUserTextField setEnabled:YES];
1094 [audioscrobblerPasswordTextField setEnabled:YES];
1095 [audioscrobblerUseCacheCheckbox setEnabled:YES];
1097 [audioscrobblerUserTextField setEnabled:NO];
1098 [audioscrobblerPasswordTextField setEnabled:NO];
1099 [audioscrobblerUseCacheCheckbox setEnabled:NO];
1101 NSString *audioscrobblerUser = [df stringForKey:@"audioscrobblerUser"];
1102 if (audioscrobblerUser != nil && [audioscrobblerUser length] > 0 && [self keychainItemExistsForUser:audioscrobblerUser]) {
1103 NSString *password = [self getKeychainItemPasswordForUser:audioscrobblerUser];
1104 [audioscrobblerUserTextField setStringValue:audioscrobblerUser];
1105 if (password != nil) {
1106 [audioscrobblerPasswordTextField setStringValue:password];
1110 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
1112 serverName = [df stringForKey:@"sharedPlayerName"];
1113 if (!serverName || [serverName length] == 0) {
1114 serverName = @"MenuTunes Shared Player";
1116 [nameTextField setStringValue:serverName];
1118 [selectPlayerBox setContentView:zeroConfView];
1119 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
1120 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
1122 [passwordTextField setStringValue:@""];
1124 if ([df stringForKey:@"sharedPlayerHost"]) {
1125 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
1128 if ([[NetworkController sharedController] isConnectedToServer]) {
1129 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
1130 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
1132 [selectedPlayerTextField setStringValue:@"No shared player selected."];
1133 [locationTextField setStringValue:@"-"];
1137 - (void)setupScreenPopup
1139 ITDebugLog(@"Setting up screen popup");
1140 NSArray *screens = [NSScreen screens];
1141 if ([screens count] > 1) {
1142 int i, index = [df integerForKey:@"statusWindowScreenIndex"];
1143 [screenPopup setEnabled:YES];
1144 for (i = 0; i < [screens count]; i++) {
1145 NSScreen *screen = [screens objectAtIndex:i];
1146 if (![screen isEqual:[NSScreen mainScreen]]) {
1147 [screenPopup addItemWithTitle:[NSString stringWithFormat:@"Screen %i", i + 1]];
1150 [screenPopup selectItemAtIndex:index];
1151 [[StatusWindow sharedWindow] setScreen:[[NSScreen screens] objectAtIndex:index]];
1153 while ([screenPopup numberOfItems] > 1) {
1154 [screenPopup removeItemAtIndex:1];
1156 [screenPopup setEnabled:NO];
1157 [[StatusWindow sharedWindow] setScreen:[NSScreen mainScreen]];
1161 - (void)setStatusWindowEntryEffect:(Class)effectClass
1163 StatusWindow *sw = [StatusWindow sharedWindow];
1165 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
1166 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
1168 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1169 [[sw entryEffect] setEffectTime:time];
1172 - (void)setStatusWindowExitEffect:(Class)effectClass
1174 StatusWindow *sw = [StatusWindow sharedWindow];
1176 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
1177 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
1179 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
1180 [[sw exitEffect] setEffectTime:time];
1183 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
1185 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
1186 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
1189 [backgroundColorWell setColor:color];
1193 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
1195 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
1198 [appearanceEffectPopup removeAllItems];
1199 [vanishEffectPopup removeAllItems];
1201 while ( (anItem = [effectEnum nextObject]) ) {
1202 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
1203 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
1205 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
1206 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
1208 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
1209 [[appearanceEffectPopup lastItem] setEnabled:YES];
1210 [[vanishEffectPopup lastItem] setEnabled:YES];
1212 [[appearanceEffectPopup lastItem] setEnabled:NO];
1213 [[vanishEffectPopup lastItem] setEnabled:NO];
1219 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
1223 if ( vPos == ITWindowPositionTop ) {
1224 if ( hPos == ITWindowPositionLeft ) {
1225 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
1226 } else if ( hPos == ITWindowPositionCenter ) {
1227 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
1228 } else if ( hPos == ITWindowPositionRight ) {
1229 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
1231 } else if ( vPos == ITWindowPositionMiddle ) {
1232 if ( hPos == ITWindowPositionLeft ) {
1233 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
1234 } else if ( hPos == ITWindowPositionCenter ) {
1235 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1236 } else if ( hPos == ITWindowPositionRight ) {
1237 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1239 } else if ( vPos == ITWindowPositionBottom ) {
1240 if ( hPos == ITWindowPositionLeft ) {
1241 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1242 } else if ( hPos == ITWindowPositionCenter ) {
1243 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1244 } else if ( hPos == ITWindowPositionRight ) {
1245 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1252 - (IBAction)changeMenus:(id)sender
1254 ITDebugLog(@"Synchronizing menus");
1255 [df setObject:myItems forKey:@"menu"];
1258 [[controller menuController] performSelector:@selector(rebuildSubmenus) withObject:nil afterDelay:0.0];
1260 //If we're connected over a network, refresh the menu immediately
1261 if ([[NetworkController sharedController] isConnectedToServer]) {
1262 [controller timerUpdate];
1267 /*************************************************************************/
1269 #pragma mark NSWindow DELEGATE METHODS
1270 /*************************************************************************/
1272 - (void)windowWillClose:(NSNotification *)note
1274 [(MainController *)controller closePreferences];
1277 /*************************************************************************/
1279 #pragma mark NSTextField DELEGATE METHODS
1280 /*************************************************************************/
1282 - (void)controlTextDidChange:(NSNotification*)note
1284 if ([note object] == hostTextField) {
1285 if ([[hostTextField stringValue] length] == 0) {
1286 [sharingPanelOKButton setEnabled:NO];
1288 [sharingPanelOKButton setEnabled:YES];
1293 /*************************************************************************/
1295 #pragma mark NSTableView DATASOURCE METHODS
1296 /*************************************************************************/
1298 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1300 if (aTableView == menuTableView) {
1301 return [myItems count];
1302 } else if (aTableView == allTableView) {
1303 return [availableItems count];
1304 } else if (aTableView == hotKeysTableView) {
1305 return [hotKeysArray count];
1307 return [[[NetworkController sharedController] remoteServices] count];
1311 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1313 if (aTableView == menuTableView) {
1314 NSString *object = [myItems objectAtIndex:rowIndex];
1315 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1316 if ([object isEqualToString:@"showPlayer"]) {
1317 NSString *string = nil;
1319 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1321 [controller networkError:localException];
1325 return NSLocalizedString(object, @"ERROR");
1327 if ([submenuItems containsObject:object])
1329 return [NSImage imageNamed:@"submenu"];
1334 } else if (aTableView == allTableView) {
1335 NSString *object = [availableItems objectAtIndex:rowIndex];
1336 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1337 if ([object isEqualToString:@"showPlayer"]) {
1338 NSString *string = nil;
1340 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1342 [controller networkError:localException];
1346 return NSLocalizedString(object, @"ERROR");
1348 if ([submenuItems containsObject:object]) {
1349 return [NSImage imageNamed:@"submenu"];
1354 } else if (aTableView == hotKeysTableView) {
1355 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1356 return [hotKeyNamesArray objectAtIndex:rowIndex];
1358 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1361 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1365 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1367 if (tableView == menuTableView) {
1368 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1369 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1373 if (tableView == allTableView) {
1374 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1375 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1381 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1385 NSString *dragData, *temp;
1387 pb = [info draggingPasteboard];
1389 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1390 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1391 dragRow = [dragData intValue];
1392 temp = [myItems objectAtIndex:dragRow];
1394 if (tableView == menuTableView) {
1395 [myItems insertObject:temp atIndex:row];
1396 if (row > dragRow) {
1397 [myItems removeObjectAtIndex:dragRow];
1399 [myItems removeObjectAtIndex:dragRow + 1];
1401 } else if (tableView == allTableView) {
1402 if (![temp isEqualToString:@"separator"]) {
1403 [availableItems addObject:temp];
1405 [myItems removeObjectAtIndex:dragRow];
1407 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1408 dragData = [pb stringForType:@"AllTableViewPboardType"];
1409 dragRow = [dragData intValue];
1410 temp = [availableItems objectAtIndex:dragRow];
1412 [myItems insertObject:temp atIndex:row];
1414 if (![temp isEqualToString:@"separator"]) {
1415 [availableItems removeObjectAtIndex:dragRow];
1419 [menuTableView reloadData];
1420 [allTableView reloadData];
1421 [self changeMenus:self];
1425 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1427 if (tableView == allTableView) {
1428 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1429 return NSDragOperationNone;
1432 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1433 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1434 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1435 return NSDragOperationNone;
1439 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1440 return NSDragOperationGeneric;
1443 if (operation == NSTableViewDropOn || row == -1)
1445 return NSDragOperationNone;
1447 return NSDragOperationGeneric;
1451 /*************************************************************************/
1453 #pragma mark DEALLOCATION METHODS
1454 /*************************************************************************/
1458 [[NSNotificationCenter defaultCenter] removeObserver:self];
1459 [hotKeysArray release];
1460 [hotKeysDictionary release];
1461 [effectClasses release];
1462 [menuTableView setDataSource:nil];
1463 [allTableView setDataSource:nil];
1464 [controller release];
1465 [availableItems release];
1466 [submenuItems release];