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 <netinet/in.h>
12 #import <openssl/sha.h>
16 #import <ITFoundation/ITLoginItem.h>
18 #import <ITKit/ITHotKeyCenter.h>
19 #import <ITKit/ITKeyCombo.h>
20 #import <ITKit/ITKeyComboPanel.h>
21 #import <ITKit/ITWindowPositioning.h>
22 #import <ITKit/ITKeyBroadcaster.h>
24 #import <ITKit/ITTSWBackgroundView.h>
25 #import <ITKit/ITWindowEffect.h>
26 #import <ITKit/ITCutWindowEffect.h>
27 #import <ITKit/ITDissolveWindowEffect.h>
28 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
29 #import <ITKit/ITSlideVerticallyWindowEffect.h>
30 #import <ITKit/ITPivotWindowEffect.h>
33 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
35 /*************************************************************************/
37 #pragma mark PRIVATE INTERFACE
38 /*************************************************************************/
40 @interface PreferencesController (Private)
42 - (void)setupCustomizationTables;
43 - (void)setupMenuItems;
45 - (void)setStatusWindowEntryEffect:(Class)effectClass;
46 - (void)setStatusWindowExitEffect:(Class)effectClass;
47 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
48 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
49 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
50 - (IBAction)changeMenus:(id)sender;
54 @implementation PreferencesController
57 /*************************************************************************/
59 #pragma mark STATIC VARIABLES
60 /*************************************************************************/
62 static PreferencesController *prefs = nil;
65 /*************************************************************************/
67 #pragma mark INITIALIZATION METHODS
68 /*************************************************************************/
70 + (PreferencesController *)sharedPrefs;
73 prefs = [[self alloc] init];
80 if ( (self = [super init]) ) {
81 ITDebugLog(@"Preferences initialized.");
82 df = [[NSUserDefaults standardUserDefaults] retain];
84 effectClasses = [[ITWindowEffect effectClasses] retain];
86 hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
100 hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
113 hotKeysDictionary = [[NSMutableDictionary alloc] init];
116 [self setupWindow]; // Load in the nib, and perform any initial setup.
117 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
123 /*************************************************************************/
125 #pragma mark ACCESSOR METHODS
126 /*************************************************************************/
133 - (void)setController:(id)object
135 [controller autorelease];
136 controller = [object retain];
140 /*************************************************************************/
142 #pragma mark INSTANCE METHODS
143 /*************************************************************************/
145 - (BOOL)showPasswordPanel
147 [passwordPanel setLevel:NSStatusWindowLevel];
148 [passwordPanelOKButton setTitle:@"Connect"];
149 [passwordPanelTitle setStringValue:@"Password Required"];
150 [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
151 [passwordPanel setLevel:NSStatusWindowLevel];
152 [NSApp activateIgnoringOtherApps:YES];
153 [passwordPanel center];
154 [passwordPanel orderFrontRegardless];
155 [passwordPanel makeKeyWindow];
156 if ([NSApp runModalForWindow:passwordPanel]) {
163 - (BOOL)showInvalidPasswordPanel
165 [passwordPanel setLevel:NSStatusWindowLevel];
166 [passwordPanelOKButton setTitle:@"Retry"];
167 [passwordPanelTitle setStringValue:@"Invalid Password"];
168 [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]]];
169 [passwordPanel setLevel:NSStatusWindowLevel];
170 [NSApp activateIgnoringOtherApps:YES];
171 [passwordPanel center];
172 [passwordPanel orderFrontRegardless];
173 [passwordPanel makeKeyWindow];
174 if ([NSApp runModalForWindow:passwordPanel]) {
181 - (IBAction)showPrefsWindow:(id)sender
183 ITDebugLog(@"Showing preferences window.");
184 if (!myItems) { // If menu array does not exist yet, then the window hasn't been setup.
185 ITDebugLog(@"Window doesn't exist, initial setup.");
186 [self setupCustomizationTables]; // Setup the DnD manu config tables.
187 [self setupMenuItems]; // Setup the arrays of menu items
188 [self setupUI]; // Sets up additional UI
189 [window setDelegate:self];
190 [menuTableView reloadData];
191 [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
193 //Change the launch player checkbox to the proper name
195 [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
197 [controller networkError:localException];
201 [self resetRemotePlayerTextFields];
202 [launchAtLoginCheckbox becomeFirstResponder];
203 [NSApp activateIgnoringOtherApps:YES];
205 [window orderFrontRegardless];
206 [window makeKeyWindow];
209 - (IBAction)changeGeneralSetting:(id)sender
211 ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
212 if ( [sender tag] == 1010) {
213 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], SENDER_STATE);
214 } else if ( [sender tag] == 1020) {
215 [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
216 } else if ( [sender tag] == 1030) {
217 [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
218 if ([[controller currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
219 [[controller menuController] rebuildSubmenus];
221 } else if ( [sender tag] == 1040) {
222 // This will not be executed. Song info always shows the title of the song.
223 // [df setBool:SENDER_STATE forKey:@"showName"];
224 } else if ( [sender tag] == 1050) {
225 [df setBool:SENDER_STATE forKey:@"showArtist"];
226 } else if ( [sender tag] == 1055) {
227 [df setBool:SENDER_STATE forKey:@"showComposer"];
228 } else if ( [sender tag] == 1060) {
229 [df setBool:SENDER_STATE forKey:@"showAlbum"];
230 } else if ( [sender tag] == 1070) {
231 [df setBool:SENDER_STATE forKey:@"showTime"];
232 } else if ( [sender tag] == 1080) {
233 [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
234 } else if ( [sender tag] == 1090) {
235 [df setBool:SENDER_STATE forKey:@"showTrackRating"];
236 } else if ( [sender tag] == 1100) {
237 [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
238 } else if ( [sender tag] == 1110) {
239 [df setBool:SENDER_STATE forKey:@"runScripts"];
241 [runScriptsCheckbox setState:NSOnState];
242 [showScriptsButton setEnabled:YES];
244 [showScriptsButton setEnabled:NO];
246 } else if ( [sender tag] == 1120) {
247 mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
248 [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
253 - (IBAction)changeSharingSetting:(id)sender
255 ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
256 if ( [sender tag] == 5010 ) {
257 BOOL state = SENDER_STATE;
258 [df setBool:state forKey:@"enableSharing"];
259 //Disable/enable the use of shared player options
260 [useSharedMenuTunesCheckbox setEnabled:!state];
261 [passwordTextField setEnabled:state];
262 [nameTextField setEnabled:state];
263 [selectSharedPlayerButton setEnabled:NO];
264 [controller setServerStatus:state]; //Set server status
265 } else if ( [sender tag] == 5015 ) {
266 [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
267 [[NetworkController sharedController] resetServerName];
268 } else if ( [sender tag] == 5030 ) {
269 //Set the server password
270 const char *instring = [[sender stringValue] UTF8String];
271 const char *password = "p4s5w0rdMT1.2";
272 unsigned char *result;
273 NSData *hashedPass, *passwordStringHash;
274 if ([[sender stringValue] length] == 0) {
275 [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
278 result = SHA1(instring, strlen(instring), NULL);
279 hashedPass = [NSData dataWithBytes:result length:strlen(result)];
280 result = SHA1(password, strlen(password), NULL);
281 passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
282 if (![hashedPass isEqualToData:passwordStringHash]) {
283 [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
284 [sender setStringValue:@"p4s5w0rdMT1.2"];
286 } else if ( [sender tag] == 5040 ) {
287 BOOL state = SENDER_STATE;
288 [df setBool:state forKey:@"useSharedPlayer"];
289 //Disable/enable the use of sharing options
290 [shareMenuTunesCheckbox setEnabled:!state];
291 [passwordTextField setEnabled:NO];
292 [nameTextField setEnabled:NO];
293 [selectSharedPlayerButton setEnabled:state];
295 if (state && ([controller connectToServer] == 1)) {
296 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
297 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
299 [selectedPlayerTextField setStringValue:@"No shared player selected."];
300 [locationTextField setStringValue:@"-"];
301 if ([[NetworkController sharedController] isConnectedToServer]) {
302 [controller disconnectFromServer];
306 } else if ( [sender tag] == 5050 ) {
307 //If no player is selected in the table view, turn off OK button.
308 if ([sender clickedRow] == -1 ) {
309 [sharingPanelOKButton setEnabled:NO];
311 [sharingPanelOKButton setEnabled:YES];
313 } else if ( [sender tag] == 5051 ) {
314 [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
315 } else if ( [sender tag] == 5060 ) {
316 //Set OK button state
317 if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
318 ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
319 [sharingPanelOKButton setEnabled:NO];
321 [sharingPanelOKButton setEnabled:YES];
323 //Show selection sheet
324 [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
325 } else if ( [sender tag] == 5100 ) {
327 if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
328 NSRect frame = [selectPlayerSheet frame];
329 frame.origin.y -= 58;
330 frame.size.height = 273;
331 if ([sharingTableView selectedRow] == -1) {
332 [sharingPanelOKButton setEnabled:NO];
334 [selectPlayerBox setContentView:zeroConfView];
335 [selectPlayerSheet setFrame:frame display:YES animate:YES];
336 } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
337 NSRect frame = [selectPlayerSheet frame];
338 frame.origin.y += 58;
339 frame.size.height = 215;
340 if ([[hostTextField stringValue] length] == 0) {
341 [sharingPanelOKButton setEnabled:NO];
343 [sharingPanelOKButton setEnabled:YES];
345 [selectPlayerBox setContentView:manualView];
346 [selectPlayerSheet setFrame:frame display:YES animate:YES];
347 [hostTextField selectText:nil];
349 } else if ( [sender tag] == 5150 ) {
350 const char *instring = [[sender stringValue] UTF8String];
351 unsigned char *result;
352 result = SHA1(instring, strlen(instring), NULL);
353 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
354 } else if ( [sender tag] == 5110 ) {
356 [NSApp endSheet:selectPlayerSheet];
357 [selectPlayerSheet orderOut:nil];
358 if ([selectPlayerBox contentView] == manualView) {
359 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
362 } else if ( [sender tag] == 5120 ) {
364 [NSApp endSheet:selectPlayerSheet];
365 [selectPlayerSheet orderOut:nil];
367 [self changeSharingSetting:clientPasswordTextField];
369 if ([selectPlayerBox contentView] == manualView) {
370 [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
372 if ([sharingTableView selectedRow] > -1) {
373 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
377 if ([controller connectToServer] == 1) {
378 [useSharedMenuTunesCheckbox setState:NSOnState];
379 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
380 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
382 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);
384 } else if ( [sender tag] == 6010 ) {
385 //Cancel password entry
386 [passwordPanel orderOut:nil];
387 [NSApp stopModalWithCode:0];
388 } else if ( [sender tag] == 6020 ) {
389 //OK password entry, retry connect
390 const char *instring = [[passwordPanelTextField stringValue] UTF8String];
391 unsigned char *result;
392 result = SHA1(instring, strlen(instring), NULL);
393 [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
394 [passwordPanel orderOut:nil];
395 [NSApp stopModalWithCode:1];
400 - (IBAction)changeStatusWindowSetting:(id)sender
402 StatusWindow *sw = [StatusWindow sharedWindow];
403 ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
405 if ( [sender tag] == 2010) {
407 BOOL entryEffectValid = YES;
408 BOOL exitEffectValid = YES;
410 [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
411 [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
412 [sw setVerticalPosition:[sender selectedRow]];
413 [sw setHorizontalPosition:[sender selectedColumn]];
415 // Enable/disable the items in the popups.
416 [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
417 horizontalPosition:[sw horizontalPosition]];
419 // Make sure the effects support the new position.
420 entryEffectValid = ( [self effect:[[sw entryEffect] class]
421 supportsVerticalPosition:[sw verticalPosition]
422 withHorizontalPosition:[sw horizontalPosition]] );
423 exitEffectValid = ( [self effect:[[sw exitEffect] class]
424 supportsVerticalPosition:[sw verticalPosition]
425 withHorizontalPosition:[sw horizontalPosition]] );
427 if ( ! entryEffectValid ) {
428 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
429 [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
431 [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
434 if ( ! exitEffectValid ) {
435 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
436 [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
438 [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
441 [(MainController *)controller showCurrentTrackInfo];
443 } else if ( [sender tag] == 2020) {
445 // Update screen selection.
447 } else if ( [sender tag] == 2030) {
449 [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
450 [(MainController *)controller showCurrentTrackInfo];
452 } else if ( [sender tag] == 2040) {
454 [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
455 [(MainController *)controller showCurrentTrackInfo];
457 } else if ( [sender tag] == 2050) {
458 float newTime = ( -([sender floatValue]) );
459 [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
460 [[sw entryEffect] setEffectTime:newTime];
461 } else if ( [sender tag] == 2060) {
462 float newTime = ( -([sender floatValue]) );
463 [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
464 [[sw exitEffect] setEffectTime:newTime];
465 } else if ( [sender tag] == 2070) {
466 [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
467 [sw setExitDelay:[sender floatValue]];
468 } else if ( [sender tag] == 2080) {
469 [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
470 } else if ( [sender tag] == 2090) {
472 int setting = [sender indexOfSelectedItem];
474 if ( setting == 0 ) {
475 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
476 [backgroundColorWell setEnabled:NO];
477 [backgroundColorPopup setEnabled:NO];
478 } else if ( setting == 1 ) {
479 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
480 [backgroundColorWell setEnabled:NO];
481 [backgroundColorPopup setEnabled:NO];
482 } else if ( setting == 2 ) {
483 [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
484 [backgroundColorWell setEnabled:YES];
485 [backgroundColorPopup setEnabled:YES];
488 [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
489 [(MainController *)controller showCurrentTrackInfo];
491 } else if ( [sender tag] == 2091) {
492 [self setCustomColor:[sender color] updateWell:NO];
493 [(MainController *)controller showCurrentTrackInfo];
494 } else if ( [sender tag] == 2092) {
496 int selectedItem = [sender indexOfSelectedItem];
498 if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title. Its first selectable item is 1.
499 [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
500 } else if ( selectedItem == 2 ) {
501 [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
502 } else if ( selectedItem == 3 ) {
503 [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
504 } else if ( selectedItem == 4 ) {
505 [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
506 } else if ( selectedItem == 5 ) {
507 [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
509 [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
511 [(MainController *)controller showCurrentTrackInfo];
513 } else if ( [sender tag] == 2095) {
514 [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
515 [(MainController *)controller showCurrentTrackInfo];
521 - (void)registerDefaults
523 ITDebugLog(@"Registering defaults.");
524 [df setObject:[NSArray arrayWithObjects:
536 nil] forKey:@"menu"];
538 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
539 [df setInteger:5 forKey:@"SongsInAdvance"];
540 // [df setBool:YES forKey:@"showName"]; // Song info will always show song title.
541 [df setBool:YES forKey:@"showArtist"];
542 [df setBool:YES forKey:@"showAlbumArtwork"];
543 [df setBool:NO forKey:@"showAlbum"];
544 [df setBool:NO forKey:@"showComposer"];
545 [df setBool:NO forKey:@"showTime"];
546 [df setBool:NO forKey:@"showToolTip"];
548 [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
549 [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
550 [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
551 [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
552 [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
553 [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
554 [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
555 [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
556 [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
557 [df setBool:YES forKey:@"showSongInfoOnChange"];
559 [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
563 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
564 [[StatusWindowController sharedController] showSetupQueryWindow];
570 [[StatusWindow sharedWindow] setLocked:NO];
571 [[StatusWindow sharedWindow] vanish:self];
572 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
574 ITSetApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath], YES);
577 - (void)autoLaunchCancel
579 [[StatusWindow sharedWindow] setLocked:NO];
580 [[StatusWindow sharedWindow] vanish:self];
581 [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
584 - (void)deletePressedInTableView:(NSTableView *)tableView
586 if (tableView == menuTableView) {
587 int selRow = [tableView selectedRow];
588 ITDebugLog(@"Delete pressed in menu table view.");
590 NSString *object = [myItems objectAtIndex:selRow];
592 if ([object isEqualToString:@"preferences"]) {
597 if (![object isEqualToString:@"separator"])
598 [availableItems addObject:object];
599 ITDebugLog(@"Removing object named %@", object);
600 [myItems removeObjectAtIndex:selRow];
601 [menuTableView reloadData];
602 [allTableView reloadData];
604 [self changeMenus:self];
608 - (void)resetRemotePlayerTextFields
610 if ([[NetworkController sharedController] isConnectedToServer]) {
611 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
612 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
614 [selectedPlayerTextField setStringValue:@"No shared player selected."];
615 [locationTextField setStringValue:@"-"];
619 /*************************************************************************/
621 #pragma mark HOTKEY SUPPORT METHODS
622 /*************************************************************************/
624 - (IBAction)clearHotKey:(id)sender
626 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
627 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
628 [controller setupHotKeys];
629 [hotKeysTableView reloadData];
632 - (IBAction)editHotKey:(id)sender
634 ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
635 NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
636 ITKeyCombo *keyCombo;
638 ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
639 [controller clearHotKeys];
640 [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
641 [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
642 if ([panel runModal] == NSOKButton) {
643 NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
645 keyCombo = [panel keyCombo];
647 //Check for duplicate key combo
648 while ( (nextKey = [keyEnumerator nextObject]) ) {
649 if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
650 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
651 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
653 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
658 [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
659 [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
660 [controller setupHotKeys];
661 [hotKeysTableView reloadData];
662 ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
664 ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
668 - (void)hotKeysTableViewDoubleClicked:(id)sender
670 if ([sender clickedRow] > -1) {
671 [self editHotKey:sender];
675 /*************************************************************************/
677 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
678 /*************************************************************************/
682 ITDebugLog(@"Loading Preferences.nib.");
683 if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
684 ITDebugLog(@"Failed to load Preferences.nib.");
690 - (void)setupCustomizationTables
692 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
693 ITDebugLog(@"Setting up table views.");
694 // Set the table view cells up
695 [imgCell setImageScaling:NSScaleNone];
696 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
697 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
699 // Register for drag and drop
700 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
701 @"MenuTableViewPboardType",
702 @"AllTableViewPboardType",
704 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
705 @"MenuTableViewPboardType",
706 @"AllTableViewPboardType",
710 - (void)setupMenuItems
712 NSEnumerator *itemEnum;
714 ITDebugLog(@"Setting up table view arrays.");
715 // Set the list of items you can have.
716 availableItems = [[NSMutableArray alloc] initWithObjects:
732 // Get our preferred menu
733 myItems = [[df arrayForKey:@"menu"] mutableCopy];
735 // Delete items in the availableItems array that are already part of the menu
736 itemEnum = [myItems objectEnumerator];
737 while ( (anItem = [itemEnum nextObject]) ) {
738 if (![anItem isEqualToString:@"separator"]) {
739 [availableItems removeObject:anItem];
743 // Items that show should a submenu image
744 submenuItems = [[NSArray alloc] initWithObjects:
754 NSEnumerator *keyArrayEnum;
755 NSString *serverName;
760 [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
762 ITDebugLog(@"Setting up preferences UI.");
763 // Fill in the number of songs in advance to show field
764 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
766 // Fill hot key array
767 keyArrayEnum = [hotKeysArray objectEnumerator];
769 while ( (anItem = [keyArrayEnum nextObject]) ) {
770 if ([df objectForKey:anItem]) {
771 ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
772 [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
774 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
778 ITDebugLog(@"Setting up track info checkboxes.");
779 // Check current track info buttons
780 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
781 [nameCheckbox setState:NSOnState]; // Song info will ALWAYS show song title.
782 [nameCheckbox setEnabled:NO]; // Song info will ALWAYS show song title.
783 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
784 [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
785 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
786 [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
787 [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
788 [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
790 if ([df boolForKey:@"runScripts"]) {
791 [runScriptsCheckbox setState:NSOnState];
792 [showScriptsButton setEnabled:YES];
794 [showScriptsButton setEnabled:NO];
797 // Set the launch at login checkbox state
798 ITDebugLog(@"Setting launch at login state.");
799 if (ITDoesApplicationLaunchOnLogin([[NSBundle mainBundle] bundlePath])) {
800 [launchAtLoginCheckbox setState:NSOnState];
803 // Set the launch player checkbox state
804 ITDebugLog(@"Setting launch player with MenuTunes state.");
805 [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
807 // Setup the positioning controls
808 [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
809 column:[df integerForKey:@"statusWindowHorizontalPosition"]];
811 // Setup effects controls
812 // Populate the effects popups
813 [appearanceEffectPopup setAutoenablesItems:NO];
814 [vanishEffectPopup setAutoenablesItems:NO];
815 [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"]
816 horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
818 // Attempt to find the pref'd effect in the list.
819 // If it's not there, use cut/dissolve.
820 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
821 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
823 [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
826 if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
827 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
829 [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
832 [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
833 [vanishSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
834 [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
836 // Setup General Controls
837 selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
838 [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
840 if ( selectedBGStyle == ITTSWBackgroundColored ) {
841 [backgroundColorWell setEnabled:YES];
842 [backgroundColorPopup setEnabled:YES];
844 [backgroundColorWell setEnabled:NO];
845 [backgroundColorPopup setEnabled:NO];
848 colorData = [df dataForKey:@"statusWindowBackgroundColor"];
851 [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
853 [backgroundColorWell setColor:[NSColor blueColor]];
856 [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
858 [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
860 // Setup the sharing controls
861 if ([df boolForKey:@"enableSharing"]) {
862 [shareMenuTunesCheckbox setState:NSOnState];
863 [useSharedMenuTunesCheckbox setEnabled:NO];
864 [selectSharedPlayerButton setEnabled:NO];
865 [passwordTextField setEnabled:YES];
866 [nameTextField setEnabled:YES];
867 } else if ([df boolForKey:@"useSharedPlayer"]) {
868 [useSharedMenuTunesCheckbox setState:NSOnState];
869 [shareMenuTunesCheckbox setEnabled:NO];
870 [selectSharedPlayerButton setEnabled:YES];
873 [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
875 serverName = [df stringForKey:@"sharedPlayerName"];
876 if (!serverName || [serverName length] == 0) {
877 serverName = @"MenuTunes Shared Player";
879 [nameTextField setStringValue:serverName];
881 [selectPlayerBox setContentView:zeroConfView];
882 if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
883 [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
885 [passwordTextField setStringValue:@""];
887 if ([df stringForKey:@"sharedPlayerHost"]) {
888 [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
891 if ([[NetworkController sharedController] isConnectedToServer]) {
892 [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
893 [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
895 [selectedPlayerTextField setStringValue:@"No shared player selected."];
896 [locationTextField setStringValue:@"-"];
900 - (void)setStatusWindowEntryEffect:(Class)effectClass
902 StatusWindow *sw = [StatusWindow sharedWindow];
904 float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
905 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
907 [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
908 [[sw entryEffect] setEffectTime:time];
911 - (void)setStatusWindowExitEffect:(Class)effectClass
913 StatusWindow *sw = [StatusWindow sharedWindow];
915 float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
916 [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
918 [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
919 [[sw exitEffect] setEffectTime:time];
922 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
924 [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
925 [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
928 [backgroundColorWell setColor:color];
932 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
934 NSEnumerator *effectEnum = [effectClasses objectEnumerator];
937 [appearanceEffectPopup removeAllItems];
938 [vanishEffectPopup removeAllItems];
940 while ( (anItem = [effectEnum nextObject]) ) {
941 [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
942 [vanishEffectPopup addItemWithTitle:[anItem effectName]];
944 [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
945 [[vanishEffectPopup lastItem] setRepresentedObject:anItem];
947 if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
948 [[appearanceEffectPopup lastItem] setEnabled:YES];
949 [[vanishEffectPopup lastItem] setEnabled:YES];
951 [[appearanceEffectPopup lastItem] setEnabled:NO];
952 [[vanishEffectPopup lastItem] setEnabled:NO];
958 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
962 if ( vPos == ITWindowPositionTop ) {
963 if ( hPos == ITWindowPositionLeft ) {
964 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
965 } else if ( hPos == ITWindowPositionCenter ) {
966 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
967 } else if ( hPos == ITWindowPositionRight ) {
968 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
970 } else if ( vPos == ITWindowPositionMiddle ) {
971 if ( hPos == ITWindowPositionLeft ) {
972 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
973 } else if ( hPos == ITWindowPositionCenter ) {
974 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
975 } else if ( hPos == ITWindowPositionRight ) {
976 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
978 } else if ( vPos == ITWindowPositionBottom ) {
979 if ( hPos == ITWindowPositionLeft ) {
980 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
981 } else if ( hPos == ITWindowPositionCenter ) {
982 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
983 } else if ( hPos == ITWindowPositionRight ) {
984 valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
991 - (IBAction)changeMenus:(id)sender
993 ITDebugLog(@"Synchronizing menus");
994 [df setObject:myItems forKey:@"menu"];
997 //If we're connected over a network, refresh the menu immediately
998 if ([[NetworkController sharedController] isConnectedToServer]) {
999 [controller timerUpdate];
1004 /*************************************************************************/
1006 #pragma mark NSWindow DELEGATE METHODS
1007 /*************************************************************************/
1009 - (void)windowWillClose:(NSNotification *)note
1011 [(MainController *)controller closePreferences];
1014 /*************************************************************************/
1016 #pragma mark NSTextField DELEGATE METHODS
1017 /*************************************************************************/
1019 - (void)controlTextDidChange:(NSNotification*)note
1021 if ([note object] == hostTextField) {
1022 if ([[hostTextField stringValue] length] == 0) {
1023 [sharingPanelOKButton setEnabled:NO];
1025 [sharingPanelOKButton setEnabled:YES];
1030 /*************************************************************************/
1032 #pragma mark NSTableView DATASOURCE METHODS
1033 /*************************************************************************/
1035 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1037 if (aTableView == menuTableView) {
1038 return [myItems count];
1039 } else if (aTableView == allTableView) {
1040 return [availableItems count];
1041 } else if (aTableView == hotKeysTableView) {
1042 return [hotKeysArray count];
1044 return [[[NetworkController sharedController] remoteServices] count];
1048 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1050 if (aTableView == menuTableView) {
1051 NSString *object = [myItems objectAtIndex:rowIndex];
1052 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1053 if ([object isEqualToString:@"showPlayer"]) {
1054 NSString *string = nil;
1056 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1058 [controller networkError:localException];
1062 return NSLocalizedString(object, @"ERROR");
1064 if ([submenuItems containsObject:object])
1066 return [NSImage imageNamed:@"submenu"];
1071 } else if (aTableView == allTableView) {
1072 NSString *object = [availableItems objectAtIndex:rowIndex];
1073 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1074 if ([object isEqualToString:@"showPlayer"]) {
1075 NSString *string = nil;
1077 string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1079 [controller networkError:localException];
1083 return NSLocalizedString(object, @"ERROR");
1085 if ([submenuItems containsObject:object]) {
1086 return [NSImage imageNamed:@"submenu"];
1091 } else if (aTableView == hotKeysTableView) {
1092 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1093 return [hotKeyNamesArray objectAtIndex:rowIndex];
1095 return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1098 return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1102 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1104 if (tableView == menuTableView) {
1105 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1106 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1110 if (tableView == allTableView) {
1111 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1112 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1118 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1122 NSString *dragData, *temp;
1124 pb = [info draggingPasteboard];
1126 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1127 dragData = [pb stringForType:@"MenuTableViewPboardType"];
1128 dragRow = [dragData intValue];
1129 temp = [myItems objectAtIndex:dragRow];
1131 if (tableView == menuTableView) {
1132 [myItems insertObject:temp atIndex:row];
1133 if (row > dragRow) {
1134 [myItems removeObjectAtIndex:dragRow];
1136 [myItems removeObjectAtIndex:dragRow + 1];
1138 } else if (tableView == allTableView) {
1139 if (![temp isEqualToString:@"separator"]) {
1140 [availableItems addObject:temp];
1142 [myItems removeObjectAtIndex:dragRow];
1144 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1145 dragData = [pb stringForType:@"AllTableViewPboardType"];
1146 dragRow = [dragData intValue];
1147 temp = [availableItems objectAtIndex:dragRow];
1149 [myItems insertObject:temp atIndex:row];
1151 if (![temp isEqualToString:@"separator"]) {
1152 [availableItems removeObjectAtIndex:dragRow];
1156 [menuTableView reloadData];
1157 [allTableView reloadData];
1158 [self changeMenus:self];
1162 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1164 if (tableView == allTableView) {
1165 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1166 return NSDragOperationNone;
1169 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1170 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1171 if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1172 return NSDragOperationNone;
1176 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1177 return NSDragOperationGeneric;
1180 if (operation == NSTableViewDropOn || row == -1)
1182 return NSDragOperationNone;
1184 return NSDragOperationGeneric;
1188 /*************************************************************************/
1190 #pragma mark DEALLOCATION METHODS
1191 /*************************************************************************/
1195 [hotKeysArray release];
1196 [hotKeysDictionary release];
1197 [effectClasses release];
1198 [menuTableView setDataSource:nil];
1199 [allTableView setDataSource:nil];
1200 [controller release];
1201 [availableItems release];
1202 [submenuItems release];