Adding new controller into CVS under the name NewMainController. However, building...
[MenuTunes.git] / PreferencesController.m
1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "HotKeyCenter.h"
4 #import <ITKit/ITWindowPositioning.h>
5
6 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
7
8 /*************************************************************************/
9 #pragma mark -
10 #pragma mark PRIVATE INTERFACE
11 /*************************************************************************/
12
13 @interface PreferencesController (Private)
14 - (void)setupWindow;
15 - (void)setupCustomizationTables;
16 - (void)setupMenuItems;
17 - (void)setupUI;
18 - (IBAction)changeMenus:(id)sender;
19 - (void)setLaunchesAtLogin:(BOOL)flag;
20 @end
21
22
23 @implementation PreferencesController
24
25
26 /*************************************************************************/
27 #pragma mark -
28 #pragma mark STATIC VARIABLES
29 /*************************************************************************/
30
31 static PreferencesController *prefs = nil;
32
33
34 /*************************************************************************/
35 #pragma mark -
36 #pragma mark INITIALIZATION METHODS
37 /*************************************************************************/
38
39 + (PreferencesController *)sharedPrefs;
40 {
41     if (! prefs) {
42         prefs = [[self alloc] init];
43     }
44     return prefs;
45 }
46
47 - (id)init
48 {
49     if ( (self = [super init]) ) {
50         df = [[NSUserDefaults standardUserDefaults] retain];
51         controller = nil;
52     }
53     return self;
54 }
55
56
57 /*************************************************************************/
58 #pragma mark -
59 #pragma mark ACCESSOR METHODS
60 /*************************************************************************/
61
62 - (id)controller
63 {
64     return controller;
65 }
66
67 - (void)setController:(id)object
68 {
69     [controller autorelease];
70     controller = [object retain];
71 }
72
73
74 /*************************************************************************/
75 #pragma mark -
76 #pragma mark INSTANCE METHODS
77 /*************************************************************************/
78
79 - (IBAction)showPrefsWindow:(id)sender
80 {
81     if (! window) {  // If window does not exist yet, then the nib hasn't been loaded.
82         [self setupWindow];  // Load in the nib, and perform any initial setup.
83         [self setupCustomizationTables];  // Setup the DnD manu config tables.
84         [self setupMenuItems];  // Setup the arrays of menu items
85         [self setupUI]; // Sets up additional UI
86         [window setDelegate:self];
87     }
88     
89     [window setLevel:NSStatusWindowLevel];
90     [window center];
91     [window makeKeyAndOrderFront:self];
92     [NSApp activateIgnoringOtherApps:YES];
93 }
94
95 - (IBAction)changeGeneralSetting:(id)sender
96 {
97     BOOL rebuildRequired = NO;
98
99     if ( [sender tag] == 1010) {
100         [self setLaunchesAtLogin:SENDER_STATE];
101     } else if ( [sender tag] == 1020) {
102         [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
103     } else if ( [sender tag] == 1030) {
104         [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
105         rebuildRequired = YES;
106     } else if ( [sender tag] == 1040) {
107         // This will not be executed.  Song info always shows the title of the song.
108         // [df setBool:SENDER_STATE forKey:@"showName"];
109         // rebuildRequired = YES;
110     } else if ( [sender tag] == 1050) {
111         [df setBool:SENDER_STATE forKey:@"showArtist"];
112         rebuildRequired = YES;
113     } else if ( [sender tag] == 1060) {
114         [df setBool:SENDER_STATE forKey:@"showAlbum"];
115         rebuildRequired = YES;
116     } else if ( [sender tag] == 1070) {
117         [df setBool:SENDER_STATE forKey:@"showTime"];
118         rebuildRequired = YES;
119     } else if ( [sender tag] == 1080) {
120         [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
121         rebuildRequired = YES;
122     } else if ( [sender tag] == 1090) {
123         [df setBool:SENDER_STATE forKey:@"showTrackRating"];
124         rebuildRequired = YES;
125     }
126
127     if ( rebuildRequired ) {
128         [controller rebuildMenu];
129         // redraw song info status window, or upcoming songs here
130     }
131
132     [df synchronize];
133 }
134
135 - (IBAction)changeStatusWindowSetting:(id)sender
136 {
137     if ( [sender tag] == 2010) {
138         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
139         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
140         // update the window's position here
141     } else if ( [sender tag] == 2020) {
142         // update screen selection
143     } else if ( [sender tag] == 2030) {
144         // Update appearance effect
145     } else if ( [sender tag] == 2040) {
146         // Update Vanish Effect
147     } else if ( [sender tag] == 2050) {
148         // Update appearance speed
149     } else if ( [sender tag] == 2060) {
150         // Update vanish speed
151     } else if ( [sender tag] == 2070) {
152         // Update vanish delay
153     } else if ( [sender tag] == 2080) {
154         // Update "Song Info window when song changes" setting.
155         [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
156     }
157 }
158
159 - (IBAction)changeHotKey:(id)sender
160 {
161     switch ([sender tag])
162     {
163         case 4010:
164             [self setKeyCombo:playPauseCombo];
165             [self setHotKey:@"PlayPause"];
166             break;
167         case 4020:
168             [self setKeyCombo:nextTrackCombo];
169             [self setHotKey:@"NextTrack"];
170             break;
171         case 4030:
172             [self setKeyCombo:prevTrackCombo];
173             [self setHotKey:@"PrevTrack"];
174             break;
175         case 4040:
176             [self setKeyCombo:toggleLoopCombo];
177             [self setHotKey:@"ToggleLoop"];
178             break;
179         case 4050:
180             [self setKeyCombo:toggleShuffleCombo];
181             [self setHotKey:@"ToggleShuffle"];
182             break;
183         case 4060:
184             [self setKeyCombo:trackInfoCombo];
185             [self setHotKey:@"TrackInfo"];
186             break;
187         case 4070:
188             [self setKeyCombo:upcomingSongsCombo];
189             [self setHotKey:@"UpcomingSongs"];
190             break;
191         case 4080:
192             [self setKeyCombo:volumeIncrementCombo];
193             [self setHotKey:@"IncrementVolume"];
194             break;
195         case 4090:
196             [self setKeyCombo:volumeDecrementCombo];
197             [self setHotKey:@"DecrementVolume"];
198             break;
199         case 4100:
200             [self setKeyCombo:ratingIncrementCombo];
201             [self setHotKey:@"IncrementRating"];
202             break;
203         case 4110:
204             [self setKeyCombo:ratingDecrementCombo];
205             [self setHotKey:@"DecrementRating"];
206             break;
207     }
208 }
209
210 - (void)registerDefaults
211 {
212     BOOL found = NO;
213     NSMutableDictionary *loginWindow;
214     NSMutableArray *loginArray;
215     NSEnumerator *loginEnum;
216     id anItem;
217
218     [df setObject:[NSArray arrayWithObjects:
219         @"Play/Pause",
220         @"Next Track",
221         @"Previous Track",
222         @"Fast Forward",
223         @"Rewind",
224         @"Show Player",
225         @"<separator>",
226         @"Upcoming Songs",
227         @"Playlists",
228         @"Song Rating",
229         @"<separator>",
230         @"PreferencesÉ",
231         @"Quit",
232         @"<separator>",
233         @"Current Track Info",
234         nil] forKey:@"menu"];
235
236     [df setInteger:5 forKey:@"SongsInAdvance"];
237     // [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
238     [df setBool:YES forKey:@"showArtist"];
239     [df setBool:NO forKey:@"showAlbum"];
240     [df setBool:NO forKey:@"showTime"];
241
242     [df synchronize];
243     
244     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
245     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
246     loginEnum = [loginArray objectEnumerator];
247
248     while ( (anItem = [loginEnum nextObject]) ) {
249         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
250             found = YES;
251         }
252     }
253
254     [loginWindow release];
255     
256     // This is teh sux
257     // We must fix it so it is no longer suxy
258     if (!found) {
259         if (NSRunInformationalAlertPanel(@"Auto-launch MenuTunes", @"Would you like MenuTunes to automatically launch at login?", @"Yes", @"No", nil) == NSOKButton) {
260             AEDesc scriptDesc, resultDesc;
261             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]];
262             ComponentInstance asComponent = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);
263
264             AECreateDesc(typeChar, [script cString], [script cStringLength],
265                          &scriptDesc);
266
267             OSADoScript(asComponent, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
268
269             AEDisposeDesc(&scriptDesc);
270             AEDisposeDesc(&resultDesc);
271
272             CloseComponent(asComponent);
273         }
274     }
275 }
276
277 - (IBAction)cancelHotKey:(id)sender
278 {
279     [[NSNotificationCenter defaultCenter] removeObserver:self];
280     [NSApp endSheet:keyComboPanel];
281     [keyComboPanel orderOut:nil];
282 }
283
284 - (IBAction)clearHotKey:(id)sender
285 {
286     [self setKeyCombo:[KeyCombo clearKeyCombo]];
287 }
288
289 - (IBAction)okHotKey:(id)sender
290 {
291     NSString *string = [combo userDisplayRep];
292     
293     if (string == nil) {
294         string = @"";
295     }
296     if ([setHotKey isEqualToString:@"PlayPause"]) {
297         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
298             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo] ||
299             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
300             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
301             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) &&
302             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
303             
304             [window setLevel:NSNormalWindowLevel];
305             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
306             [window setLevel:NSStatusWindowLevel];
307             return;
308         }
309         playPauseCombo = [combo copy];
310         [playPauseButton setTitle:string];
311     } else if ([setHotKey isEqualToString:@"NextTrack"]) {
312         if (([combo isEqual:playPauseCombo] || [combo isEqual:prevTrackCombo] ||
313             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo] ||
314             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
315             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
316             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
317             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
318             
319             [window setLevel:NSNormalWindowLevel];
320             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
321             [window setLevel:NSStatusWindowLevel];
322             return;
323         }
324         nextTrackCombo = [combo copy];
325         [nextTrackButton setTitle:string];
326     } else if ([setHotKey isEqualToString:@"PrevTrack"]) {
327         if (([combo isEqual:nextTrackCombo] || [combo isEqual:playPauseCombo] ||
328             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo] ||
329             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
330             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
331             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
332             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
333             
334             [window setLevel:NSNormalWindowLevel];
335             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
336             [window setLevel:NSStatusWindowLevel];
337             return;
338         }
339         prevTrackCombo = [combo copy];
340         [previousTrackButton setTitle:string];
341     } else if ([setHotKey isEqualToString:@"TrackInfo"]) {
342         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
343             [combo isEqual:playPauseCombo] || [combo isEqual:upcomingSongsCombo] ||
344             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
345             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
346             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
347             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
348             
349             [window setLevel:NSNormalWindowLevel];
350             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
351             [window setLevel:NSStatusWindowLevel];
352             return;
353         }
354         trackInfoCombo = [combo copy];
355         [trackInfoButton setTitle:string];
356     } else if ([setHotKey isEqualToString:@"UpcomingSongs"]) {
357         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
358             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
359             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
360             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
361             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
362             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
363             
364             [window setLevel:NSNormalWindowLevel];
365             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
366             [window setLevel:NSStatusWindowLevel];
367             return;
368         }
369         upcomingSongsCombo = [combo copy];
370         [upcomingSongsButton setTitle:string];
371     //THE NEW COMBOS!
372     } else if ([setHotKey isEqualToString:@"IncrementVolume"]) {
373         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
374             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
375             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
376             [combo isEqual:upcomingSongsCombo] || [combo isEqual:volumeDecrementCombo] ||
377             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
378             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
379             
380             [window setLevel:NSNormalWindowLevel];
381             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
382             [window setLevel:NSStatusWindowLevel];
383             return;
384         }
385         volumeIncrementCombo = [combo copy];
386         [volumeIncrementButton setTitle:string];
387     } else if ([setHotKey isEqualToString:@"DecrementVolume"]) {
388         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
389             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
390             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
391             [combo isEqual:volumeIncrementCombo] || [combo isEqual:upcomingSongsCombo] ||
392             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
393             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
394             
395             [window setLevel:NSNormalWindowLevel];
396             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
397             [window setLevel:NSStatusWindowLevel];
398             return;
399         }
400         volumeDecrementCombo = [combo copy];
401         [volumeDecrementButton setTitle:string];
402     } else if ([setHotKey isEqualToString:@"IncrementRating"]) {
403         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
404             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
405             [combo isEqual:upcomingSongsCombo] || [combo isEqual:ratingDecrementCombo] ||
406             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
407             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
408             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
409             
410             [window setLevel:NSNormalWindowLevel];
411             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
412             [window setLevel:NSStatusWindowLevel];
413             return;
414         }
415         ratingIncrementCombo = [combo copy];
416         [ratingIncrementButton setTitle:string];
417     } else if ([setHotKey isEqualToString:@"DecrementRating"]) {
418         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
419             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
420             [combo isEqual:ratingIncrementCombo] || [combo isEqual:upcomingSongsCombo] ||
421             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
422             [combo isEqual:toggleLoopCombo] || [combo isEqual:toggleShuffleCombo]) && 
423             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
424             
425             [window setLevel:NSNormalWindowLevel];
426             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
427             [window setLevel:NSStatusWindowLevel];
428             return;
429         }
430         ratingDecrementCombo = [combo copy];
431         [ratingDecrementButton setTitle:string];
432     } else if ([setHotKey isEqualToString:@"ToggleLoop"]) {
433         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
434             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
435             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
436             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
437             [combo isEqual:upcomingSongsCombo] || [combo isEqual:toggleShuffleCombo]) && 
438             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
439             
440             [window setLevel:NSNormalWindowLevel];
441             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
442             [window setLevel:NSStatusWindowLevel];
443             return;
444         }
445         toggleLoopCombo = [combo copy];
446         [toggleLoopButton setTitle:string];
447     } else if ([setHotKey isEqualToString:@"ToggleShuffle"]) {
448         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
449             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo] ||
450             [combo isEqual:ratingIncrementCombo] || [combo isEqual:ratingDecrementCombo] ||
451             [combo isEqual:volumeIncrementCombo] || [combo isEqual:volumeDecrementCombo] ||
452             [combo isEqual:toggleLoopCombo] || [combo isEqual:upcomingSongsCombo]) && 
453             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
454             
455             [window setLevel:NSNormalWindowLevel];
456             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
457             [window setLevel:NSStatusWindowLevel];
458             return;
459         }
460         toggleShuffleCombo = [combo copy];
461         [toggleShuffleButton setTitle:string];
462     }
463     [df setKeyCombo:combo forKey:setHotKey];
464     [controller rebuildMenu];
465     [self cancelHotKey:sender];
466 }
467
468
469
470 /*************************************************************************/
471 #pragma mark -
472 #pragma mark HOTKEY SUPPORT METHODS
473 /*************************************************************************/
474
475 - (void)setHotKey:(NSString *)key
476 {
477     setHotKey = key;
478     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
479     [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
480 }
481
482 - (void)keyEvent:(NSNotification *)note
483 {
484     NSDictionary *info = [note userInfo];
485     short keyCode;
486     long modifiers;
487     KeyCombo *newCombo;
488     
489     keyCode = [[info objectForKey:@"KeyCode"] shortValue];
490     modifiers = [[info objectForKey:@"Modifiers"] longValue];
491     
492     newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
493     [self setKeyCombo:newCombo];
494 }
495
496 - (void)setKeyCombo:(KeyCombo *)newCombo
497 {
498     NSString *string;
499     [combo release];
500     combo = [newCombo copy];
501     
502     string = [combo userDisplayRep];
503     if (string == nil) {
504         string = @"";
505     }
506     [keyComboField setStringValue:string];
507 }
508
509
510 /*************************************************************************/
511 #pragma mark -
512 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
513 /*************************************************************************/
514
515 - (void)setupWindow
516 {
517     if ( ! [NSBundle loadNibNamed:@"Preferences" owner:self] ) {
518         NSLog( @"Failed to load Preferences.nib" );
519         NSBeep();
520         return;
521     }
522 }
523
524 - (void)setupCustomizationTables
525 {
526     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
527     
528     // Set the table view cells up
529     [imgCell setImageScaling:NSScaleNone];
530     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
531     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
532
533     // Register for drag and drop
534     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
535         @"MenuTableViewPboardType",
536         @"AllTableViewPboardType",
537         nil]];
538     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
539         @"MenuTableViewPboardType",
540         @"AllTableViewPboardType",
541         nil]];
542 }
543
544 - (void)setupMenuItems
545 {
546     NSEnumerator *itemEnum;
547     id            anItem;
548     // Set the list of items you can have.
549     availableItems = [[NSMutableArray alloc] initWithObjects:
550         @"Current Track Info",
551         @"Upcoming Songs",
552         @"Playlists",
553         @"EQ Presets",
554         @"Song Rating",
555         @"Play/Pause",
556         @"Next Track",
557         @"Previous Track",
558         @"Fast Forward",
559         @"Rewind",
560         @"Show Player",
561         @"<separator>",
562         nil];
563     
564     // Get our preferred menu
565     myItems = [[df arrayForKey:@"menu"] mutableCopy];
566     
567     // Delete items in the availableItems array that are already part of the menu
568     itemEnum = [myItems objectEnumerator];
569     while ( (anItem = [itemEnum nextObject]) ) {
570         if ( ! [anItem isEqualToString:@"<separator>"] ) {
571             [availableItems removeObject:anItem];
572         }
573     }
574     
575     // Items that show should a submenu image
576     submenuItems = [[NSArray alloc] initWithObjects:
577         @"Upcoming Songs",
578         @"Playlists",
579         @"EQ Presets",
580         @"Song Rating",
581         nil];
582 }
583
584 - (void)setupUI
585 {
586     NSMutableDictionary *loginwindow;
587     NSMutableArray *loginarray;
588     NSEnumerator *loginEnum;
589     id anItem;
590
591     // Fill in the number of songs in advance to show field
592     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
593     
594     // Fill in hot key buttons
595     if ([df objectForKey:@"PlayPause"]){
596         playPauseCombo = [df keyComboForKey:@"PlayPause"];
597         [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
598     } else {
599         playPauseCombo = [[KeyCombo alloc] init];
600     }
601     
602     if ([df objectForKey:@"NextTrack"]) {
603         nextTrackCombo = [df keyComboForKey:@"NextTrack"];
604         [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
605     } else {
606         nextTrackCombo = [[KeyCombo alloc] init];
607     }
608     
609     if ([df objectForKey:@"PrevTrack"]) {
610         prevTrackCombo = [df keyComboForKey:@"PrevTrack"];
611         [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
612     } else {
613         prevTrackCombo = [[KeyCombo alloc] init];
614     }
615     
616     if ([df objectForKey:@"TrackInfo"]) {
617         trackInfoCombo = [df keyComboForKey:@"TrackInfo"];
618         [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
619     } else {
620         trackInfoCombo = [[KeyCombo alloc] init];
621     }
622     
623     if ([df objectForKey:@"UpcomingSongs"]) {
624         upcomingSongsCombo = [df keyComboForKey:@"UpcomingSongs"];
625         [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
626     } else {
627         upcomingSongsCombo = [[KeyCombo alloc] init];
628     }
629     
630     if ([df objectForKey:@"IncrementVolume"]) {
631         volumeIncrementCombo = [df keyComboForKey:@"IncrementVolume"];
632         [volumeIncrementButton setTitle:[volumeIncrementCombo userDisplayRep]];
633     } else {
634         volumeIncrementCombo = [[KeyCombo alloc] init];
635     }
636     
637     if ([df objectForKey:@"DecrementVolume"]) {
638         volumeDecrementCombo = [df keyComboForKey:@"DecrementVolume"];
639         [volumeDecrementButton setTitle:[volumeDecrementCombo userDisplayRep]];
640     } else {
641         volumeDecrementCombo = [[KeyCombo alloc] init];
642     }
643     
644     if ([df objectForKey:@"IncrementRating"]) {
645         ratingIncrementCombo = [df keyComboForKey:@"IncrementRating"];
646         [ratingIncrementButton setTitle:[ratingIncrementCombo userDisplayRep]];
647     } else {
648         ratingIncrementCombo = [[KeyCombo alloc] init];
649     }
650     
651     if ([df objectForKey:@"DecrementRating"]) {
652         ratingDecrementCombo = [df keyComboForKey:@"DecrementRating"];
653         [ratingDecrementButton setTitle:[ratingDecrementCombo userDisplayRep]];
654     } else {
655         ratingDecrementCombo = [[KeyCombo alloc] init];
656     }
657     
658     if ([df objectForKey:@"ToggleLoop"]) {
659         toggleLoopCombo = [df keyComboForKey:@"ToggleLoop"];
660         [toggleLoopButton setTitle:[toggleLoopCombo userDisplayRep]];
661     } else {
662         toggleLoopCombo = [[KeyCombo alloc] init];
663     }
664     
665     if ([df objectForKey:@"ToggleShuffle"]) {
666         toggleShuffleCombo = [df keyComboForKey:@"ToggleShuffle"];
667         [toggleShuffleButton setTitle:[toggleShuffleCombo userDisplayRep]];
668     } else {
669         toggleShuffleCombo = [[KeyCombo alloc] init];
670     }
671     
672     // Check current track info buttons
673     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
674     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
675     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
676     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
677     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
678     
679     // Set the launch at login checkbox state
680     [df synchronize];
681     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
682     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
683     
684     loginEnum = [loginarray objectEnumerator];
685     while ( (anItem = [loginEnum nextObject]) ) {
686         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
687             [launchAtLoginCheckbox setState:NSOnState];
688         }
689     }
690 }
691
692 - (IBAction)changeMenus:(id)sender
693 {
694     [df setObject:myItems forKey:@"menu"];
695     [df synchronize];
696     [controller rebuildMenu];
697 }
698
699 - (void)setLaunchesAtLogin:(BOOL)flag
700 {
701     if ( flag ) {
702         NSMutableDictionary *loginwindow;
703         NSMutableArray *loginarray;
704         ComponentInstance temp = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);;
705         int i;
706         BOOL skip = NO;
707
708         [df synchronize];
709         loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
710         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
711
712         for (i = 0; i < [loginarray count]; i++) {
713             NSDictionary *tempDict = [loginarray objectAtIndex:i];
714             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
715                 skip = YES;
716             }
717         }
718
719         if (!skip) {
720             AEDesc scriptDesc, resultDesc;
721             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]];
722
723             AECreateDesc(typeChar, [script cString], [script cStringLength],
724                          &scriptDesc);
725
726             OSADoScript(temp, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
727
728             AEDisposeDesc(&scriptDesc);
729             AEDisposeDesc(&resultDesc);
730             CloseComponent(temp);
731         }
732
733     } else {
734         NSMutableDictionary *loginwindow;
735         NSMutableArray *loginarray;
736         int i;
737
738         [df synchronize];
739         loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
740         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
741
742         for (i = 0; i < [loginarray count]; i++) {
743             NSDictionary *tempDict = [loginarray objectAtIndex:i];
744             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
745                 [loginarray removeObjectAtIndex:i];
746                 [df setPersistentDomain:loginwindow forName:@"loginwindow"];
747                 [df synchronize];
748                 break;
749             }
750         }
751     }
752 }
753
754
755 /*************************************************************************/
756 #pragma mark -
757 #pragma mark NSWindow DELEGATE METHODS
758 /*************************************************************************/
759
760 - (void)windowWillClose:(NSNotification *)note
761 {
762     [(MainController *)controller closePreferences]; 
763 }
764
765
766 /*************************************************************************/
767 #pragma mark -
768 #pragma mark NSTableView DATASOURCE METHODS
769 /*************************************************************************/
770
771 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
772 {
773     if (aTableView == menuTableView) {
774         return [myItems count];
775     } else {
776         return [availableItems count];
777     }
778 }
779
780 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
781 {
782     if (aTableView == menuTableView) {
783         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
784             NSString *object = [myItems objectAtIndex:rowIndex];
785             if ([object isEqualToString:@"Show Player"]) {
786                 return [NSString stringWithFormat:@"Show %@", [[controller currentRemote] playerSimpleName]];
787             }
788             return object;
789         } else {
790             if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
791             {
792                 return [NSImage imageNamed:@"submenu"];
793             } else {
794                 return nil;
795             }
796         }
797     } else {
798         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
799             return [availableItems objectAtIndex:rowIndex];
800         } else {
801             if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]]) {
802                 return [NSImage imageNamed:@"submenu"];
803             } else {
804                 return nil;
805             }
806         }
807     }
808 }
809
810 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
811 {
812     if (tableView == menuTableView) {
813         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
814         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
815         return YES;
816     }
817     
818     if (tableView == allTableView) {
819         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
820         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
821         return YES;
822     }
823     return NO;
824 }
825
826 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
827 {
828     NSPasteboard *pb;
829     int dragRow;
830     NSString *dragData, *temp;
831     
832     pb = [info draggingPasteboard];
833     
834     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
835         dragData = [pb stringForType:@"MenuTableViewPboardType"];
836         dragRow = [dragData intValue];
837         temp = [myItems objectAtIndex:dragRow];
838         [myItems removeObjectAtIndex:dragRow];
839         
840         if (tableView == menuTableView) {
841             if (row > dragRow) {
842                 [myItems insertObject:temp atIndex:row - 1];
843             } else {
844                 [myItems insertObject:temp atIndex:row];
845             }
846         } else {
847             if (![temp isEqualToString:@"<separator>"]) {
848                 [availableItems addObject:temp];
849             }
850         }
851     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
852         dragData = [pb stringForType:@"AllTableViewPboardType"];
853         dragRow = [dragData intValue];
854         temp = [availableItems objectAtIndex:dragRow];
855         
856         if (![temp isEqualToString:@"<separator>"]) {
857             [availableItems removeObjectAtIndex:dragRow];
858         }
859         [myItems insertObject:temp atIndex:row];
860     }
861     
862     [menuTableView reloadData];
863     [allTableView reloadData];
864     [self changeMenus:self];
865     return YES;
866 }
867
868 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
869 {
870     if (tableView == allTableView) {
871         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
872             return NSDragOperationNone;
873         }
874         
875         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
876             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
877             if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"]) {
878                 return NSDragOperationNone;
879             }
880         }
881         
882         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
883         return NSDragOperationGeneric;
884     }
885     
886     if (operation == NSTableViewDropOn || row == -1)
887     {
888         return NSDragOperationNone;
889     }
890     
891     return NSDragOperationGeneric;
892 }
893
894
895 /*************************************************************************/
896 #pragma mark -
897 #pragma mark DEALLOCATION METHODS
898 /*************************************************************************/
899
900 - (void)dealloc
901 {
902     [self setKeyCombo:nil];
903     [playPauseCombo release];
904     [nextTrackCombo release];
905     [prevTrackCombo release];
906     [trackInfoCombo release];
907     [upcomingSongsCombo release];
908     [keyComboPanel release];
909     [menuTableView setDataSource:nil];
910     [allTableView setDataSource:nil];
911     [controller release];
912     [availableItems release];
913     [submenuItems release];
914     [myItems release];
915     [df release];
916 }
917
918
919 @end