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