1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "HotKeyCenter.h"
5 /*************************************************************************/
7 #pragma mark PRIVATE INTERFACE
8 /*************************************************************************/
10 @interface PreferencesController (Private)
12 - (void)setupCustomizationTables;
13 - (void)setupMenuItems;
18 @implementation PreferencesController
21 /*************************************************************************/
23 #pragma mark STATIC VARIABLES
24 /*************************************************************************/
26 static PreferencesController *prefs = nil;
29 /*************************************************************************/
31 #pragma mark INITIALIZATION METHODS
32 /*************************************************************************/
34 + (PreferencesController *)sharedPrefs;
37 prefs = [[self alloc] init];
44 if ( (self = [super init]) ) {
45 df = [[NSUserDefaults standardUserDefaults] retain];
52 /*************************************************************************/
54 #pragma mark ACCESSOR METHODS
55 /*************************************************************************/
62 - (void)setController:(id)object
64 [controller autorelease];
65 controller = [object retain];
69 /*************************************************************************/
71 #pragma mark INSTANCE METHODS
72 /*************************************************************************/
74 - (IBAction)showPrefsWindow:(id)sender
76 if (! window) { // If window does not exist yet, then the nib hasn't been loaded.
77 [self setupWindow]; // Load in the nib, and perform any initial setup.
78 [self setupCustomizationTables]; // Setup the DnD manu config tables.
79 [self setupMenuItems]; // Setup the arrays of menu items
80 [self setupUI]; // Sets up additional UI
81 [window setDelegate:self];
84 [window setLevel:NSStatusWindowLevel];
86 [window makeKeyAndOrderFront:self];
87 [NSApp activateIgnoringOtherApps:YES];
90 - (IBAction)apply:(id)sender
92 [df setObject:myItems forKey:@"menu"];
95 [df setKeyCombo:playPauseCombo forKey:@"PlayPause"];
96 [df setKeyCombo:nextTrackCombo forKey:@"NextTrack"];
97 [df setKeyCombo:prevTrackCombo forKey:@"PrevTrack"];
98 [df setKeyCombo:trackInfoCombo forKey:@"TrackInfo"];
99 [df setKeyCombo:upcomingSongsCombo forKey:@"UpcomingSongs"];
101 //Set info checkboxes
102 [df setBool:[albumCheckbox state] forKey:@"showAlbum"];
103 [df setBool:[nameCheckbox state] forKey:@"showName"];
104 [df setBool:[artistCheckbox state] forKey:@"showArtist"];
105 [df setBool:[trackTimeCheckbox state] forKey:@"showTime"];
107 //Here we set whether we will launch at login by modifying loginwindow.plist
108 if ([launchAtLoginCheckbox state] == NSOnState) {
109 NSMutableDictionary *loginwindow;
110 NSMutableArray *loginarray;
111 ComponentInstance temp = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);;
116 loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
117 loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
119 for (i = 0; i < [loginarray count]; i++) {
120 NSDictionary *tempDict = [loginarray objectAtIndex:i];
121 if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
127 AEDesc scriptDesc, resultDesc;
128 NSString *script = [NSString stringWithFormat:@"tell application \"System Events\"\nmake new login item at end of login items with properties {path:\"%@\", kind:\"APPLICATION\"}\nend tell", [[NSBundle mainBundle] bundlePath]];
130 AECreateDesc(typeChar, [script cString], [script cStringLength],
133 OSADoScript(temp, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
135 AEDisposeDesc(&scriptDesc);
136 AEDisposeDesc(&resultDesc);
137 CloseComponent(temp);
140 NSMutableDictionary *loginwindow;
141 NSMutableArray *loginarray;
145 loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
146 loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
148 for (i = 0; i < [loginarray count]; i++) {
149 NSDictionary *tempDict = [loginarray objectAtIndex:i];
150 if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
151 [loginarray removeObjectAtIndex:i];
152 [df setPersistentDomain:loginwindow forName:@"loginwindow"];
159 //Set songs in advance
160 if ([songsInAdvance intValue]) {
161 [df setInteger:[songsInAdvance intValue] forKey:@"SongsInAdvance"];
163 [df setInteger:5 forKey:@"SongsInAdvance"];
167 NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
170 for (i = 0; i < [apps count]; i++) {
171 if ([[[apps objectAtIndex:i] objectForKey:@"NSApplicationName"]
172 isEqualToString:@"iTunes"]) {
173 [controller rebuildMenu];
177 [controller clearHotKeys];
180 - (void)registerDefaults
183 NSMutableDictionary *loginWindow;
184 NSMutableArray *loginArray;
185 NSEnumerator *loginEnum;
188 [df setObject:[NSArray arrayWithObjects:
202 @"Current Track Info",
203 nil] forKey:@"menu"];
205 [df setInteger:5 forKey:@"SongsInAdvance"];
206 [df setBool:YES forKey:@"showName"];
207 [df setBool:YES forKey:@"showArtist"];
208 [df setBool:NO forKey:@"showAlbum"];
209 [df setBool:NO forKey:@"showTime"];
213 loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
214 loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
215 loginEnum = [loginArray objectEnumerator];
217 while ( (anItem = [loginEnum nextObject]) ) {
218 if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
223 [loginWindow release];
226 // We must fix it so it is no longer suxy
228 if (NSRunInformationalAlertPanel(@"Auto-launch MenuTunes", @"Would you like MenuTunes to automatically launch at login?", @"Yes", @"No", nil) == NSOKButton) {
229 AEDesc scriptDesc, resultDesc;
230 NSString *script = [NSString stringWithFormat:@"tell application \"System Events\"\nmake new login item at end of login items with properties {path:\"%@\", kind:\"APPLICATION\"}\nend tell", [[NSBundle mainBundle] bundlePath]];
231 ComponentInstance asComponent = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);
233 AECreateDesc(typeChar, [script cString], [script cStringLength],
236 OSADoScript(asComponent, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
238 AEDisposeDesc(&scriptDesc);
239 AEDisposeDesc(&resultDesc);
241 CloseComponent(asComponent);
246 - (IBAction)cancelHotKey:(id)sender
248 [[NSNotificationCenter defaultCenter] removeObserver:self];
249 [NSApp endSheet:keyComboPanel];
250 [keyComboPanel orderOut:nil];
253 - (IBAction)clearHotKey:(id)sender
255 [self setKeyCombo:[KeyCombo clearKeyCombo]];
258 - (IBAction)okHotKey:(id)sender
260 NSString *string = [combo userDisplayRep];
265 if ([setHotKey isEqualToString:@"PlayPause"]) {
266 if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
267 [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) &&
268 !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
270 [window setLevel:NSNormalWindowLevel];
271 NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
272 [window setLevel:NSStatusWindowLevel];
275 playPauseCombo = [combo copy];
276 [playPauseButton setTitle:string];
277 } else if ([setHotKey isEqualToString:@"NextTrack"]) {
278 if (([combo isEqual:playPauseCombo] || [combo isEqual:prevTrackCombo] ||
279 [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) &&
280 !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
282 [window setLevel:NSNormalWindowLevel];
283 NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
284 [window setLevel:NSStatusWindowLevel];
287 nextTrackCombo = [combo copy];
288 [nextTrackButton setTitle:string];
289 } else if ([setHotKey isEqualToString:@"PrevTrack"]) {
290 if (([combo isEqual:nextTrackCombo] || [combo isEqual:playPauseCombo] ||
291 [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) &&
292 !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
294 [window setLevel:NSNormalWindowLevel];
295 NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
296 [window setLevel:NSStatusWindowLevel];
299 prevTrackCombo = [combo copy];
300 [previousTrackButton setTitle:string];
301 } else if ([setHotKey isEqualToString:@"TrackInfo"]) {
302 if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
303 [combo isEqual:playPauseCombo] || [combo isEqual:upcomingSongsCombo]) &&
304 !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
306 [window setLevel:NSNormalWindowLevel];
307 NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
308 [window setLevel:NSStatusWindowLevel];
311 trackInfoCombo = [combo copy];
312 [trackInfoButton setTitle:string];
313 } else if ([setHotKey isEqualToString:@"UpcomingSongs"]) {
314 if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
315 [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo]) &&
316 !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
318 [window setLevel:NSNormalWindowLevel];
319 NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
320 [window setLevel:NSStatusWindowLevel];
323 upcomingSongsCombo = [combo copy];
324 [upcomingSongsButton setTitle:string];
326 [self cancelHotKey:sender];
329 - (IBAction)setCurrentTrackInfo:(id)sender
331 [self setKeyCombo:trackInfoCombo];
332 [self setHotKey:@"TrackInfo"];
335 - (IBAction)setNextTrack:(id)sender
337 [self setKeyCombo:nextTrackCombo];
338 [self setHotKey:@"NextTrack"];
341 - (IBAction)setPlayPause:(id)sender
343 [self setKeyCombo:playPauseCombo];
344 [self setHotKey:@"PlayPause"];
347 - (IBAction)setPreviousTrack:(id)sender
349 [self setKeyCombo:prevTrackCombo];
350 [self setHotKey:@"PrevTrack"];
353 - (IBAction)setUpcomingSongs:(id)sender
355 [self setKeyCombo:upcomingSongsCombo];
356 [self setHotKey:@"UpcomingSongs"];
360 /*************************************************************************/
362 #pragma mark HOTKEY SUPPORT METHODS
363 /*************************************************************************/
365 - (void)setHotKey:(NSString *)key
368 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
369 [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
372 - (void)keyEvent:(NSNotification *)note
374 NSDictionary *info = [note userInfo];
379 keyCode = [[info objectForKey:@"KeyCode"] shortValue];
380 modifiers = [[info objectForKey:@"Modifiers"] longValue];
382 newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
383 [self setKeyCombo:newCombo];
386 - (void)setKeyCombo:(KeyCombo *)newCombo
390 combo = [newCombo copy];
392 string = [combo userDisplayRep];
396 [keyComboField setStringValue:string];
400 /*************************************************************************/
402 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
403 /*************************************************************************/
407 if ( ! [NSBundle loadNibNamed:@"Preferences" owner:self] ) {
408 NSLog( @"Failed to load Preferences.nib" );
414 - (void)setupCustomizationTables
416 NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
418 // Set the table view cells up
419 [imgCell setImageScaling:NSScaleNone];
420 [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
421 [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
423 // Register for drag and drop
424 [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
425 @"MenuTableViewPboardType",
426 @"AllTableViewPboardType",
428 [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
429 @"MenuTableViewPboardType",
430 @"AllTableViewPboardType",
434 - (void)setupMenuItems
436 NSEnumerator *itemEnum;
438 // Set the list of items you can have.
439 availableItems = [[NSMutableArray alloc] initWithObjects:
440 @"Current Track Info",
453 // Get our preferred menu
454 myItems = [[df arrayForKey:@"menu"] mutableCopy];
456 // Delete items in the availableItems array that are already part of the menu
457 itemEnum = [myItems objectEnumerator];
458 while ( (anItem = [itemEnum nextObject]) ) {
459 if ( ! [anItem isEqualToString:@"<separator>"] ) {
460 [availableItems removeObject:anItem];
464 // Items that show should a submenu image
465 submenuItems = [[NSArray alloc] initWithObjects:
475 NSMutableDictionary *loginwindow;
476 NSMutableArray *loginarray;
477 NSEnumerator *loginEnum;
480 // Fill in the number of songs in advance to show field
481 [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
483 // Fill in hot key buttons
484 if ([df objectForKey:@"PlayPause"]){
485 playPauseCombo = [df keyComboForKey:@"PlayPause"];
486 [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
488 playPauseCombo = [[KeyCombo alloc] init];
491 if ([df objectForKey:@"NextTrack"]) {
492 nextTrackCombo = [df keyComboForKey:@"NextTrack"];
493 [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
495 nextTrackCombo = [[KeyCombo alloc] init];
498 if ([df objectForKey:@"PrevTrack"]) {
499 prevTrackCombo = [df keyComboForKey:@"PrevTrack"];
500 [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
502 prevTrackCombo = [[KeyCombo alloc] init];
505 if ([df objectForKey:@"TrackInfo"]) {
506 trackInfoCombo = [df keyComboForKey:@"TrackInfo"];
507 [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
509 trackInfoCombo = [[KeyCombo alloc] init];
512 if ([df objectForKey:@"UpcomingSongs"]) {
513 upcomingSongsCombo = [df keyComboForKey:@"UpcomingSongs"];
514 [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
516 upcomingSongsCombo = [[KeyCombo alloc] init];
519 // Check current track info buttons
520 [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
521 [nameCheckbox setState:[df boolForKey:@"showName"] ? NSOnState : NSOffState];
522 [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
523 [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
525 // Set the launch at login checkbox state
527 loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
528 loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
530 loginEnum = [loginarray objectEnumerator];
531 while ( (anItem = [loginEnum nextObject]) ) {
532 if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
533 [launchAtLoginCheckbox setState:NSOnState];
539 /*************************************************************************/
541 #pragma mark NSWindow DELEGATE METHODS
542 /*************************************************************************/
544 - (void)windowWillClose:(NSNotification *)note
546 [(MainController *)controller closePreferences];
550 /*************************************************************************/
552 #pragma mark NSTableView DATASOURCE METHODS
553 /*************************************************************************/
555 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
557 if (aTableView == menuTableView) {
558 return [myItems count];
560 return [availableItems count];
564 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
566 if (aTableView == menuTableView) {
567 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
568 return [myItems objectAtIndex:rowIndex];
570 if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
572 return [NSImage imageNamed:@"submenu"];
578 if ([[aTableColumn identifier] isEqualToString:@"name"]) {
579 return [availableItems objectAtIndex:rowIndex];
581 if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]]) {
582 return [NSImage imageNamed:@"submenu"];
590 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
592 if (tableView == menuTableView) {
593 [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
594 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
598 if (tableView == allTableView) {
599 [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
600 [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
606 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
610 NSString *dragData, *temp;
612 pb = [info draggingPasteboard];
614 if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
615 dragData = [pb stringForType:@"MenuTableViewPboardType"];
616 dragRow = [dragData intValue];
617 temp = [myItems objectAtIndex:dragRow];
618 [myItems removeObjectAtIndex:dragRow];
620 if (tableView == menuTableView) {
622 [myItems insertObject:temp atIndex:row - 1];
624 [myItems insertObject:temp atIndex:row];
627 if (![temp isEqualToString:@"<separator>"]) {
628 [availableItems addObject:temp];
631 } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
632 dragData = [pb stringForType:@"AllTableViewPboardType"];
633 dragRow = [dragData intValue];
634 temp = [availableItems objectAtIndex:dragRow];
636 if (![temp isEqualToString:@"<separator>"]) {
637 [availableItems removeObjectAtIndex:dragRow];
639 [myItems insertObject:temp atIndex:row];
642 [menuTableView reloadData];
643 [allTableView reloadData];
647 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
649 if (tableView == allTableView) {
650 if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
651 return NSDragOperationNone;
654 if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
655 NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
656 if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"]) {
657 return NSDragOperationNone;
661 [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
662 return NSDragOperationGeneric;
665 if (operation == NSTableViewDropOn || row == -1)
667 return NSDragOperationNone;
670 return NSDragOperationGeneric;
674 /*************************************************************************/
676 #pragma mark DEALLOCATION METHODS
677 /*************************************************************************/
681 [self setKeyCombo:nil];
682 [playPauseCombo release];
683 [nextTrackCombo release];
684 [prevTrackCombo release];
685 [trackInfoCombo release];
686 [upcomingSongsCombo release];
687 [keyComboPanel release];
688 [menuTableView setDataSource:nil];
689 [allTableView setDataSource:nil];
690 [controller release];
691 [availableItems release];
692 [submenuItems release];