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