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