Oops, I suck at making things compile
[MenuTunes.git] / PreferencesController.m
1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "MenuController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import "StatusWindow.h"
7 #import "StatusWindowController.h"
8 #import "CustomMenuTableView.h"
9
10 #import <netinet/in.h>
11 #import <arpa/inet.h>
12 #import <openssl/sha.h>
13 #import <sys/types.h>
14 #import <sys/stat.h>
15
16 #import <ITFoundation/ITFoundation.h>
17
18 #import <ITKit/ITHotKeyCenter.h>
19 #import <ITKit/ITKeyCombo.h>
20 #import <ITKit/ITKeyComboPanel.h>
21 #import <ITKit/ITWindowPositioning.h>
22 #import <ITKit/ITKeyBroadcaster.h>
23
24 #import <ITKit/ITTSWBackgroundView.h>
25 #import <ITKit/ITWindowEffect.h>
26 #import <ITKit/ITCutWindowEffect.h>
27 #import <ITKit/ITDissolveWindowEffect.h>
28 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
29 #import <ITKit/ITSlideVerticallyWindowEffect.h>
30 #import <ITKit/ITPivotWindowEffect.h>
31
32
33 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
34
35 /*************************************************************************/
36 #pragma mark -
37 #pragma mark PRIVATE INTERFACE
38 /*************************************************************************/
39
40 @interface PreferencesController (Private)
41 - (void)setupWindow;
42 - (void)setupCustomizationTables;
43 - (void)setupMenuItems;
44 - (void)setupUI;
45 - (void)setStatusWindowEntryEffect:(Class)effectClass;
46 - (void)setStatusWindowExitEffect:(Class)effectClass;
47 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update;
48 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos;
49 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos;
50 - (IBAction)changeMenus:(id)sender;
51 - (void)setLaunchesAtLogin:(BOOL)flag;
52 @end
53
54
55 @implementation PreferencesController
56
57
58 /*************************************************************************/
59 #pragma mark -
60 #pragma mark STATIC VARIABLES
61 /*************************************************************************/
62
63 static PreferencesController *prefs = nil;
64
65
66 /*************************************************************************/
67 #pragma mark -
68 #pragma mark INITIALIZATION METHODS
69 /*************************************************************************/
70
71 + (PreferencesController *)sharedPrefs;
72 {
73     if (! prefs) {
74         prefs = [[self alloc] init];
75     }
76     return prefs;
77 }
78
79 - (id)init
80 {
81     if ( (self = [super init]) ) {
82         ITDebugLog(@"Preferences initialized.");
83         df = [[NSUserDefaults standardUserDefaults] retain];
84         
85         effectClasses = [[ITWindowEffect effectClasses] retain];
86         
87         hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
88                                                        @"NextTrack",
89                                                        @"PrevTrack",
90                                                        @"ShowPlayer",
91                                                        @"TrackInfo",
92                                                        @"UpcomingSongs",
93                                                        @"IncrementVolume",
94                                                        @"DecrementVolume",
95                                                        @"IncrementRating",
96                                                        @"DecrementRating",
97                                                        @"ToggleShuffle",
98                                                        @"ToggleLoop",
99                                                        nil];
100         
101         hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
102                                                        @"Next Track",
103                                                        @"Previous Track",
104                                                        @"Show Player",
105                                                        @"Track Info",
106                                                        @"Upcoming Songs",
107                                                        @"Increment Volume",
108                                                        @"Decrement Volume",
109                                                        @"Increment Rating",
110                                                        @"Decrement Rating",
111                                                        @"Toggle Shuffle",
112                                                        @"Toggle Loop",
113                                                        nil];
114         hotKeysDictionary = [[NSMutableDictionary alloc] init];
115         controller = nil;
116         
117         [self setupWindow];  // Load in the nib, and perform any initial setup.
118         [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
119     }
120     return self;
121 }
122
123
124 /*************************************************************************/
125 #pragma mark -
126 #pragma mark ACCESSOR METHODS
127 /*************************************************************************/
128
129 - (id)controller
130 {
131     return controller;
132 }
133
134 - (void)setController:(id)object
135 {
136     [controller autorelease];
137     controller = [object retain];
138 }
139
140
141 /*************************************************************************/
142 #pragma mark -
143 #pragma mark INSTANCE METHODS
144 /*************************************************************************/
145
146 - (BOOL)showPasswordPanel
147 {
148     [passwordPanel setLevel:NSStatusWindowLevel];
149     [passwordPanelOKButton setTitle:@"Connect"];
150     [passwordPanelTitle setStringValue:@"Password Required"];
151     [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
152     [passwordPanel setLevel:NSStatusWindowLevel];
153     [NSApp activateIgnoringOtherApps:YES];
154     [passwordPanel center];
155     [passwordPanel orderFrontRegardless];
156     [passwordPanel makeKeyWindow];
157     if ([NSApp runModalForWindow:passwordPanel]) {
158         return YES;
159     } else {
160         return NO;
161     }
162 }
163
164 - (BOOL)showInvalidPasswordPanel
165 {
166     [passwordPanel setLevel:NSStatusWindowLevel];
167     [passwordPanelOKButton setTitle:@"Retry"];
168     [passwordPanelTitle setStringValue:@"Invalid Password"];
169     [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"The password entered for access to the MenuTunes player named %@ at %@ is invalid.  Please provide a new password.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
170     [passwordPanel setLevel:NSStatusWindowLevel];
171     [NSApp activateIgnoringOtherApps:YES];
172     [passwordPanel center];
173     [passwordPanel orderFrontRegardless];
174     [passwordPanel makeKeyWindow];
175     if ([NSApp runModalForWindow:passwordPanel]) {
176         return YES;
177     } else {
178         return NO;
179     }
180 }
181
182 - (IBAction)showPrefsWindow:(id)sender
183 {
184     ITDebugLog(@"Showing preferences window.");
185     if (!myItems) {  // If menu array does not exist yet, then the window hasn't been setup.
186         ITDebugLog(@"Window doesn't exist, initial setup.");
187         [self setupCustomizationTables];  // Setup the DnD manu config tables.
188         [self setupMenuItems];  // Setup the arrays of menu items
189         [self setupUI]; // Sets up additional UI
190         [window setDelegate:self];
191         [menuTableView reloadData];
192         [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
193         
194         //Change the launch player checkbox to the proper name
195         NS_DURING
196             [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
197         NS_HANDLER
198             [controller networkError:localException];
199         NS_ENDHANDLER
200     }
201
202     [self resetRemotePlayerTextFields];
203     [launchAtLoginCheckbox becomeFirstResponder];
204     [NSApp activateIgnoringOtherApps:YES];
205     [window center];
206     [window orderFrontRegardless];
207     [window makeKeyWindow];
208 }
209
210 - (IBAction)changeGeneralSetting:(id)sender
211 {
212     ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
213     if ( [sender tag] == 1010) {
214         [self setLaunchesAtLogin:SENDER_STATE];
215     } else if ( [sender tag] == 1020) {
216         [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
217     } else if ( [sender tag] == 1030) {
218         [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
219         [[controller menuController] rebuildSubmenus];
220     } else if ( [sender tag] == 1040) {
221         // This will not be executed.  Song info always shows the title of the song.
222         // [df setBool:SENDER_STATE forKey:@"showName"];
223     } else if ( [sender tag] == 1050) {
224         [df setBool:SENDER_STATE forKey:@"showArtist"];
225     } else if ( [sender tag] == 1055) {
226         [df setBool:SENDER_STATE forKey:@"showComposer"];
227     } else if ( [sender tag] == 1060) {
228         [df setBool:SENDER_STATE forKey:@"showAlbum"];
229     } else if ( [sender tag] == 1070) {
230         [df setBool:SENDER_STATE forKey:@"showTime"];
231     } else if ( [sender tag] == 1080) {
232         [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
233     } else if ( [sender tag] == 1090) {
234         [df setBool:SENDER_STATE forKey:@"showTrackRating"];
235     } else if ( [sender tag] == 1100) {
236         [df setBool:SENDER_STATE forKey:@"showAlbumArtwork"];
237     } else if ( [sender tag] == 1110) {
238         [df setBool:SENDER_STATE forKey:@"runScripts"];
239         if (SENDER_STATE) {
240             [runScriptsCheckbox setState:NSOnState];
241             [showScriptsButton setEnabled:YES];
242         } else {
243             [showScriptsButton setEnabled:NO];
244         }
245     } else if ( [sender tag] == 1120) {
246         mkdir([[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] cString], 0744);
247         [[NSWorkspace sharedWorkspace] openFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
248     }
249     [df synchronize];
250 }
251
252 - (IBAction)changeSharingSetting:(id)sender
253 {
254     ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
255     if ( [sender tag] == 5010 ) {
256         BOOL state = SENDER_STATE;
257         [df setBool:state forKey:@"enableSharing"];
258         //Disable/enable the use of shared player options
259         [useSharedMenuTunesCheckbox setEnabled:!state];
260         [passwordTextField setEnabled:state];
261         [nameTextField setEnabled:state];
262         [selectSharedPlayerButton setEnabled:NO];
263         [controller setServerStatus:state]; //Set server status
264     } else if ( [sender tag] == 5015 ) {
265         [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
266         [[NetworkController sharedController] resetServerName];
267     } else if ( [sender tag] == 5030 ) {
268         //Set the server password
269         const char *instring = [[sender stringValue] UTF8String];
270         const char *password = "p4s5w0rdMT1.2";
271         unsigned char *result;
272         NSData *hashedPass, *passwordStringHash;
273         if ([[sender stringValue] length] == 0) {
274             [df setObject:[NSData data] forKey:@"sharedPlayerPassword"];
275             return;
276         }
277         result = SHA1(instring, strlen(instring), NULL);
278         hashedPass = [NSData dataWithBytes:result length:strlen(result)];
279         result = SHA1(password, strlen(password), NULL);
280         passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
281         if (![hashedPass isEqualToData:passwordStringHash]) {
282             [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
283             [sender setStringValue:@"p4s5w0rdMT1.2"];
284         }
285     } else if ( [sender tag] == 5040 ) {
286         BOOL state = SENDER_STATE;
287         [df setBool:state forKey:@"useSharedPlayer"];
288         //Disable/enable the use of sharing options
289         [shareMenuTunesCheckbox setEnabled:!state];
290         [passwordTextField setEnabled:NO];
291         [nameTextField setEnabled:NO];
292         [selectSharedPlayerButton setEnabled:state];
293         
294         if (state && ([controller connectToServer] == 1)) {
295             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
296             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
297         } else {
298             [selectedPlayerTextField setStringValue:@"No shared player selected."];
299             [locationTextField setStringValue:@"-"];
300             if ([[NetworkController sharedController] isConnectedToServer]) {
301                 [controller disconnectFromServer];
302             }
303             
304         }
305     } else if ( [sender tag] == 5050 ) {
306         //If no player is selected in the table view, turn off OK button.
307         if ([sender clickedRow] == -1 ) {
308             [sharingPanelOKButton setEnabled:NO];
309         } else {
310             [sharingPanelOKButton setEnabled:YES];
311         }
312     } else if ( [sender tag] == 5051 ) {
313         [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
314     } else if ( [sender tag] == 5060 ) {
315         //Set OK button state
316         if (([selectPlayerBox contentView] == zeroConfView && [sharingTableView selectedRow] == -1) ||
317             ([selectPlayerBox contentView] == manualView && [[hostTextField stringValue] length] == 0)) {
318             [sharingPanelOKButton setEnabled:NO];
319         } else {
320             [sharingPanelOKButton setEnabled:YES];
321         }
322         //Show selection sheet
323         [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
324     } else if ( [sender tag] == 5100 ) {
325         //Change view
326         if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
327             NSRect frame = [selectPlayerSheet frame];
328             frame.origin.y -= 58;
329             frame.size.height = 273;
330             if ([sharingTableView selectedRow] == -1) {
331                 [sharingPanelOKButton setEnabled:NO];
332             }
333             [selectPlayerBox setContentView:zeroConfView];
334             [selectPlayerSheet setFrame:frame display:YES animate:YES];
335         } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
336             NSRect frame = [selectPlayerSheet frame];
337             frame.origin.y += 58;
338             frame.size.height = 215;
339             if ([[hostTextField stringValue] length] == 0) {
340                 [sharingPanelOKButton setEnabled:NO];
341             } else {
342                 [sharingPanelOKButton setEnabled:YES];
343             }
344             [selectPlayerBox setContentView:manualView];
345             [selectPlayerSheet setFrame:frame display:YES animate:YES];
346             [hostTextField selectText:nil];
347         }
348     } else if ( [sender tag] == 5150 ) {
349         const char *instring = [[sender stringValue] UTF8String];
350         unsigned char *result;
351         result = SHA1(instring, strlen(instring), NULL);
352         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
353     } else if ( [sender tag] == 5110 ) {
354         //Cancel
355         [NSApp endSheet:selectPlayerSheet];
356         [selectPlayerSheet orderOut:nil];
357         if ([selectPlayerBox contentView] == manualView) {
358             [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
359         } else {
360         }
361     } else if ( [sender tag] == 5120 ) {
362         //OK, try to connect
363         [NSApp endSheet:selectPlayerSheet];
364         [selectPlayerSheet orderOut:nil];
365         
366         [self changeSharingSetting:clientPasswordTextField];
367         
368         if ([selectPlayerBox contentView] == manualView) {
369             [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
370         } else {
371             if ([sharingTableView selectedRow] > -1) {
372                 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
373             }
374         }
375         
376         if ([controller connectToServer] == 1) {
377             [useSharedMenuTunesCheckbox setState:NSOnState];
378             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
379             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
380         } else {
381             NSRunAlertPanel(@"Connection error.", @"The MenuTunes server you attempted to connect to was not responding. MenuTunes will revert back to the local player.", @"OK", nil, nil);
382         }
383     } else if ( [sender tag] == 6010 ) {
384         //Cancel password entry
385         [passwordPanel orderOut:nil];
386         [NSApp stopModalWithCode:0];
387     } else if ( [sender tag] == 6020 ) {
388         //OK password entry, retry connect
389         const char *instring = [[passwordPanelTextField stringValue] UTF8String];
390         unsigned char *result;
391         result = SHA1(instring, strlen(instring), NULL);
392         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
393         [passwordPanel orderOut:nil];
394         [NSApp stopModalWithCode:1];
395     }
396     [df synchronize];
397 }
398
399 - (IBAction)changeStatusWindowSetting:(id)sender
400 {
401     StatusWindow *sw = [StatusWindow sharedWindow];
402     ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
403     
404     if ( [sender tag] == 2010) {
405     
406         BOOL entryEffectValid = YES;
407         BOOL exitEffectValid  = YES;
408                 
409         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
410         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
411         [sw setVerticalPosition:[sender selectedRow]];
412         [sw setHorizontalPosition:[sender selectedColumn]];
413         
414         // Enable/disable the items in the popups.
415         [self repopulateEffectPopupsForVerticalPosition:[sw verticalPosition]
416                                      horizontalPosition:[sw horizontalPosition]];
417
418         // Make sure the effects support the new position.  
419         entryEffectValid = ( [self effect:[[sw entryEffect] class] 
420                  supportsVerticalPosition:[sw verticalPosition]
421                    withHorizontalPosition:[sw horizontalPosition]] );
422         exitEffectValid  = ( [self effect:[[sw exitEffect] class] 
423                  supportsVerticalPosition:[sw verticalPosition]
424                    withHorizontalPosition:[sw horizontalPosition]] );
425         
426         if ( ! entryEffectValid ) {
427             [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITCutWindowEffect")]];
428             [self setStatusWindowEntryEffect:NSClassFromString(@"ITCutWindowEffect")];
429         } else {
430             [appearanceEffectPopup selectItemAtIndex:[[appearanceEffectPopup menu] indexOfItemWithRepresentedObject:[[sw entryEffect] class]]];
431         }
432         
433         if ( ! exitEffectValid ) {
434             [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:NSClassFromString(@"ITDissolveWindowEffect")]];
435             [self setStatusWindowExitEffect:NSClassFromString(@"ITDissolveWindowEffect")];
436         } else {
437             [vanishEffectPopup selectItemAtIndex:[[vanishEffectPopup menu] indexOfItemWithRepresentedObject:[[sw exitEffect] class]]];
438         }
439         
440         [(MainController *)controller showCurrentTrackInfo];
441         
442     } else if ( [sender tag] == 2020) {
443     
444         // Update screen selection.
445         
446     } else if ( [sender tag] == 2030) {
447     
448         [self setStatusWindowEntryEffect:[[sender selectedItem] representedObject]];
449         [(MainController *)controller showCurrentTrackInfo];
450         
451     } else if ( [sender tag] == 2040) {
452     
453         [self setStatusWindowExitEffect:[[sender selectedItem] representedObject]];
454         [(MainController *)controller showCurrentTrackInfo];
455         
456     } else if ( [sender tag] == 2050) {
457         float newTime = ( -([sender floatValue]) );
458         [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
459         [[sw entryEffect] setEffectTime:newTime];
460     } else if ( [sender tag] == 2060) {
461         float newTime = ( -([sender floatValue]) );
462         [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
463         [[sw exitEffect] setEffectTime:newTime];
464     } else if ( [sender tag] == 2070) {
465         [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
466         [sw setExitDelay:[sender floatValue]];
467     } else if ( [sender tag] == 2080) {
468         [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
469     } else if ( [sender tag] == 2090) {
470         
471         int setting = [sender indexOfSelectedItem];
472         
473         if ( setting == 0 ) {
474             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundApple];
475             [backgroundColorWell  setEnabled:NO];
476             [backgroundColorPopup setEnabled:NO];
477         } else if ( setting == 1 ) {
478             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundReadable];
479             [backgroundColorWell  setEnabled:NO];
480             [backgroundColorPopup setEnabled:NO];
481         } else if ( setting == 2 ) {
482             [(ITTSWBackgroundView *)[sw contentView] setBackgroundMode:ITTSWBackgroundColored];
483             [backgroundColorWell  setEnabled:YES];
484             [backgroundColorPopup setEnabled:YES];
485         }
486
487         [df setInteger:setting forKey:@"statusWindowBackgroundMode"];
488         [(MainController *)controller showCurrentTrackInfo];
489         
490     } else if ( [sender tag] == 2091) {
491         [self setCustomColor:[sender color] updateWell:NO];
492         [(MainController *)controller showCurrentTrackInfo];
493     } else if ( [sender tag] == 2092) {
494         
495         int selectedItem = [sender indexOfSelectedItem];
496         
497         if ( selectedItem == 1 ) { // An NSPopUpButton in PullDown mode uses item 0 as its title.  Its first selectable item is 1.
498             [self setCustomColor:[NSColor colorWithCalibratedRed:0.92549 green:0.686275 blue:0.0 alpha:1.0] updateWell:YES];
499         } else if ( selectedItem == 2 ) {
500             [self setCustomColor:[NSColor colorWithCalibratedRed:0.380392 green:0.670588 blue:0.0 alpha:1.0] updateWell:YES];
501         } else if ( selectedItem == 3 ) {
502             [self setCustomColor:[NSColor colorWithCalibratedRed:0.443137 green:0.231373 blue:0.619608 alpha:1.0] updateWell:YES];
503         } else if ( selectedItem == 4 ) {
504             [self setCustomColor:[NSColor colorWithCalibratedRed:0.831373 green:0.12549 blue:0.509804 alpha:1.0] updateWell:YES];
505         } else if ( selectedItem == 5 ) {
506             [self setCustomColor:[NSColor colorWithCalibratedRed:0.00784314 green:0.611765 blue:0.662745 alpha:1.0] updateWell:YES];
507         } else {
508             [self setCustomColor:[NSColor colorWithCalibratedWhite:0.15 alpha:0.70] updateWell:YES];
509         }
510         [(MainController *)controller showCurrentTrackInfo];
511
512     } else if ( [sender tag] == 2095) {
513         [df setInteger:[sender indexOfSelectedItem] forKey:@"statusWindowSizing"];
514         [(MainController *)controller showCurrentTrackInfo];
515     }
516     
517     [df synchronize];
518 }
519
520 - (void)registerDefaults
521 {
522     BOOL found = NO;
523     NSMutableDictionary *loginWindow;
524     NSMutableArray *loginArray;
525     NSEnumerator *loginEnum;
526     id anItem;
527     ITDebugLog(@"Registering defaults.");
528     [df setObject:[NSArray arrayWithObjects:
529         @"trackInfo",
530         @"separator",
531         @"playPause",
532         @"prevTrack",
533         @"nextTrack",
534         @"separator",
535         @"playlists",
536         @"upcomingSongs",
537         @"separator",
538         @"preferences",
539         @"quit",
540         nil] forKey:@"menu"];
541
542     [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
543     [df setInteger:5 forKey:@"SongsInAdvance"];
544 //  [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
545     [df setBool:YES forKey:@"showArtist"];
546     [df setBool:YES forKey:@"showAlbumArtwork"];
547     [df setBool:NO forKey:@"showAlbum"];
548     [df setBool:NO forKey:@"showComposer"];
549     [df setBool:NO forKey:@"showTime"];
550     [df setBool:NO forKey:@"showToolTip"];
551
552     [df setObject:@"ITCutWindowEffect" forKey:@"statusWindowAppearanceEffect"];
553     [df setObject:@"ITDissolveWindowEffect" forKey:@"statusWindowVanishEffect"];
554     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
555     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
556     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
557     [df setInteger:(int)ITWindowPositionBottom forKey:@"statusWindowVerticalPosition"];
558     [df setInteger:(int)ITWindowPositionLeft forKey:@"statusWindowHorizontalPosition"];
559     [[StatusWindow sharedWindow] setVerticalPosition:(int)ITWindowPositionBottom];
560     [[StatusWindow sharedWindow] setHorizontalPosition:(int)ITWindowPositionLeft];
561     [df setBool:YES forKey:@"showSongInfoOnChange"];
562     
563     [df setObject:[NSArchiver archivedDataWithRootObject:[NSColor blueColor]] forKey:@"statusWindowBackgroundColor"];
564     
565     [df synchronize];
566     
567     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
568     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
569     loginEnum = [loginArray objectEnumerator];
570
571     while ( (anItem = [loginEnum nextObject]) ) {
572         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
573             found = YES;
574         }
575     }
576     [loginWindow release];
577     
578     if (!found) {
579         [[StatusWindowController sharedController] showSetupQueryWindow];
580     }
581 }
582
583 - (void)autoLaunchOK
584 {
585     [[StatusWindow sharedWindow] setLocked:NO];
586     [[StatusWindow sharedWindow] vanish:self];
587     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
588     
589     [self setLaunchesAtLogin:YES];
590 }
591
592 - (void)autoLaunchCancel
593 {
594     [[StatusWindow sharedWindow] setLocked:NO];
595     [[StatusWindow sharedWindow] vanish:self];
596     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
597 }
598
599 - (void)deletePressedInTableView:(NSTableView *)tableView
600 {
601     if (tableView == menuTableView) {
602         int selRow = [tableView selectedRow];
603         ITDebugLog(@"Delete pressed in menu table view.");
604         if (selRow != - 1) {
605             NSString *object = [myItems objectAtIndex:selRow];
606             
607             if ([object isEqualToString:@"preferences"]) {
608                 NSBeep();
609                 return;
610             }
611             
612             if (![object isEqualToString:@"separator"])
613                 [availableItems addObject:object];
614             ITDebugLog(@"Removing object named %@", object);
615             [myItems removeObjectAtIndex:selRow];
616             [menuTableView reloadData];
617             [allTableView reloadData];
618         }
619         [self changeMenus:self];
620     }
621 }
622
623 - (void)resetRemotePlayerTextFields
624 {
625     if ([[NetworkController sharedController] isConnectedToServer]) {
626         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
627         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
628     } else {
629         [selectedPlayerTextField setStringValue:@"No shared player selected."];
630         [locationTextField setStringValue:@"-"];
631     }
632 }
633
634 /*************************************************************************/
635 #pragma mark -
636 #pragma mark HOTKEY SUPPORT METHODS
637 /*************************************************************************/
638
639 - (IBAction)clearHotKey:(id)sender
640 {
641     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
642     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
643     [controller setupHotKeys];
644     [hotKeysTableView reloadData];
645 }
646
647 - (IBAction)editHotKey:(id)sender
648 {
649     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
650     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
651     ITKeyCombo *keyCombo;
652     
653     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
654     [controller clearHotKeys];
655     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
656     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
657     if ([panel runModal] == NSOKButton) {
658         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
659         NSString *nextKey;
660         keyCombo = [panel keyCombo];
661         
662         //Check for duplicate key combo
663         while ( (nextKey = [keyEnumerator nextObject]) ) {
664             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
665                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
666                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
667                                    forKey:nextKey];
668                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
669                     forKey:nextKey];
670             }
671         }
672         
673         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
674         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
675         [controller setupHotKeys];
676         [hotKeysTableView reloadData];
677         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
678     } else {
679         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
680     }
681 }
682
683 - (void)hotKeysTableViewDoubleClicked:(id)sender
684 {
685     if ([sender clickedRow] > -1) {
686         [self editHotKey:sender];
687     }
688 }
689
690 /*************************************************************************/
691 #pragma mark -
692 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
693 /*************************************************************************/
694
695 - (void)setupWindow
696 {
697     ITDebugLog(@"Loading Preferences.nib.");
698     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
699         ITDebugLog(@"Failed to load Preferences.nib.");
700         NSBeep();
701         return;
702     }
703 }
704
705 - (void)setupCustomizationTables
706 {
707     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
708     ITDebugLog(@"Setting up table views.");
709     // Set the table view cells up
710     [imgCell setImageScaling:NSScaleNone];
711     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
712     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
713
714     // Register for drag and drop
715     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
716         @"MenuTableViewPboardType",
717         @"AllTableViewPboardType",
718         nil]];
719     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
720         @"MenuTableViewPboardType",
721         @"AllTableViewPboardType",
722         nil]];
723 }
724
725 - (void)setupMenuItems
726 {
727     NSEnumerator *itemEnum;
728     id            anItem;
729     ITDebugLog(@"Setting up table view arrays.");
730     // Set the list of items you can have.
731     availableItems = [[NSMutableArray alloc] initWithObjects:
732         @"separator",
733         @"trackInfo",
734         @"upcomingSongs",
735         @"playlists",
736         @"eqPresets",
737         @"songRating",
738         @"playPause",
739         @"nextTrack",
740         @"prevTrack",
741         @"fastForward",
742         @"rewind",
743         @"showPlayer",
744         @"quit",
745         nil];
746     
747     // Get our preferred menu
748     myItems = [[df arrayForKey:@"menu"] mutableCopy];
749     
750     // Delete items in the availableItems array that are already part of the menu
751     itemEnum = [myItems objectEnumerator];
752     while ( (anItem = [itemEnum nextObject]) ) {
753         if (![anItem isEqualToString:@"separator"]) {
754             [availableItems removeObject:anItem];
755         }
756     }
757     
758     // Items that show should a submenu image
759     submenuItems = [[NSArray alloc] initWithObjects:
760         @"upcomingSongs",
761         @"playlists",
762         @"eqPresets",
763         @"songRating",
764         nil];
765 }
766
767 - (void)setupUI
768 {
769     NSMutableDictionary *loginwindow;
770     NSMutableArray      *loginarray;
771     NSEnumerator   *loginEnum;
772     NSEnumerator   *keyArrayEnum;
773     NSString       *serverName;
774     NSData         *colorData;
775     int selectedBGStyle;
776     id anItem;
777     
778     [df setInteger:MT_CURRENT_VERSION forKey:@"appVersion"];
779     
780     ITDebugLog(@"Setting up preferences UI.");
781     // Fill in the number of songs in advance to show field
782     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
783     
784     // Fill hot key array
785     keyArrayEnum = [hotKeysArray objectEnumerator];
786     
787     while ( (anItem = [keyArrayEnum nextObject]) ) {
788         if ([df objectForKey:anItem]) {
789             ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
790             [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
791         } else {
792             [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
793         }
794     }
795     
796     ITDebugLog(@"Setting up track info checkboxes.");
797     // Check current track info buttons
798     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
799     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
800     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
801     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
802     [composerCheckbox setState:[df boolForKey:@"showComposer"] ? NSOnState : NSOffState];
803     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
804     [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
805     [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
806     [albumArtworkCheckbox setState:[df boolForKey:@"showAlbumArtwork"] ? NSOnState : NSOffState];
807     
808     if ([df boolForKey:@"runScripts"]) {
809         [runScriptsCheckbox setState:NSOnState];
810         [showScriptsButton setEnabled:YES];
811     } else {
812         [showScriptsButton setEnabled:NO];
813     }
814     
815     // Set the launch at login checkbox state
816     ITDebugLog(@"Setting launch at login state.");
817     [df synchronize];
818     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
819     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
820     
821     loginEnum = [loginarray objectEnumerator];
822     while ( (anItem = [loginEnum nextObject]) ) {
823         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
824             [launchAtLoginCheckbox setState:NSOnState];
825         }
826     }
827     
828     // Set the launch player checkbox state
829     ITDebugLog(@"Setting launch player with MenuTunes state.");
830     [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
831     
832     // Setup the positioning controls
833     [positionMatrix selectCellAtRow:[df integerForKey:@"statusWindowVerticalPosition"]
834                              column:[df integerForKey:@"statusWindowHorizontalPosition"]];
835     
836     // Setup effects controls
837     // Populate the effects popups
838     [appearanceEffectPopup setAutoenablesItems:NO];
839     [vanishEffectPopup     setAutoenablesItems:NO];
840     [self repopulateEffectPopupsForVerticalPosition:[df integerForKey:@"statusWindowVerticalPosition"] 
841                                  horizontalPosition:[df integerForKey:@"statusWindowHorizontalPosition"]];
842     
843     // Attempt to find the pref'd effect in the list.
844     // If it's not there, use cut/dissolve.
845     if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])] ) {
846         [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowAppearanceEffect"])]];
847     } else {
848         [appearanceEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
849     }
850     
851     if ( [effectClasses containsObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])] ) {
852         [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString([df stringForKey:@"statusWindowVanishEffect"])]];
853     } else {
854         [vanishEffectPopup selectItemAtIndex:[effectClasses indexOfObject:NSClassFromString(@"ITCutWindowEffect")]];
855     }
856     
857     [appearanceSpeedSlider setFloatValue:( -([df floatForKey:@"statusWindowAppearanceSpeed"]) )];
858     [vanishSpeedSlider     setFloatValue:( -([df floatForKey:@"statusWindowVanishSpeed"]) )];
859     [vanishDelaySlider     setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
860
861     // Setup General Controls
862     selectedBGStyle = [df integerForKey:@"statusWindowBackgroundMode"];
863     [backgroundStylePopup selectItem:[backgroundStylePopup itemAtIndex:[backgroundStylePopup indexOfItemWithTag:selectedBGStyle]]];
864
865     if ( selectedBGStyle == ITTSWBackgroundColored ) {
866         [backgroundColorWell  setEnabled:YES];
867         [backgroundColorPopup setEnabled:YES];
868     } else {
869         [backgroundColorWell  setEnabled:NO];
870         [backgroundColorPopup setEnabled:NO];
871     }
872
873     colorData = [df dataForKey:@"statusWindowBackgroundColor"];
874
875     if ( colorData ) {
876         [backgroundColorWell setColor:(NSColor *)[NSUnarchiver unarchiveObjectWithData:colorData]];
877     } else {
878         [backgroundColorWell setColor:[NSColor blueColor]];
879     }
880     
881     [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
882     
883     [windowSizingPopup selectItem:[windowSizingPopup itemAtIndex:[windowSizingPopup indexOfItemWithTag:[df integerForKey:@"statusWindowSizing"]]]];
884
885     // Setup the sharing controls
886     if ([df boolForKey:@"enableSharing"]) {
887         [shareMenuTunesCheckbox setState:NSOnState];
888         [useSharedMenuTunesCheckbox setEnabled:NO];
889         [selectSharedPlayerButton setEnabled:NO];
890         [passwordTextField setEnabled:YES];
891         [nameTextField setEnabled:YES];
892     } else if ([df boolForKey:@"useSharedPlayer"]) {
893         [useSharedMenuTunesCheckbox setState:NSOnState];
894         [shareMenuTunesCheckbox setEnabled:NO];
895         [selectSharedPlayerButton setEnabled:YES];
896     }
897     
898     [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
899     
900     serverName = [df stringForKey:@"sharedPlayerName"];
901     if (!serverName || [serverName length] == 0) {
902         serverName = @"MenuTunes Shared Player";
903     }
904     [nameTextField setStringValue:serverName];
905     
906     [selectPlayerBox setContentView:zeroConfView];
907     if ([[df dataForKey:@"sharedPlayerPassword"] length] > 0) {
908         [passwordTextField setStringValue:@"p4s5w0rdMT1.2"];
909     } else {
910         [passwordTextField setStringValue:@""];
911     }
912     if ([df stringForKey:@"sharedPlayerHost"]) {
913         [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
914     }
915     
916     if ([[NetworkController sharedController] isConnectedToServer]) {
917         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
918         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
919     } else {
920         [selectedPlayerTextField setStringValue:@"No shared player selected."];
921         [locationTextField setStringValue:@"-"];
922     }
923 }
924
925 - (void)setStatusWindowEntryEffect:(Class)effectClass
926 {
927     StatusWindow *sw = [StatusWindow sharedWindow];
928     
929     float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
930     [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowAppearanceEffect"];
931     
932     [sw setEntryEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
933     [[sw entryEffect] setEffectTime:time];
934 }
935
936 - (void)setStatusWindowExitEffect:(Class)effectClass
937 {
938     StatusWindow *sw = [StatusWindow sharedWindow];
939     
940     float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
941     [df setObject:NSStringFromClass(effectClass) forKey:@"statusWindowVanishEffect"];
942     
943     [sw setExitEffect:[[[effectClass alloc] initWithWindow:sw] autorelease]];
944     [[sw exitEffect] setEffectTime:time];
945 }
946
947 - (void)setCustomColor:(NSColor *)color updateWell:(BOOL)update
948 {
949     [(ITTSWBackgroundView *)[[StatusWindow sharedWindow] contentView] setBackgroundColor:color];
950     [df setObject:[NSArchiver archivedDataWithRootObject:color] forKey:@"statusWindowBackgroundColor"];
951     
952     if ( update ) {
953         [backgroundColorWell setColor:color];
954     }
955 }
956
957 - (void)repopulateEffectPopupsForVerticalPosition:(ITVerticalWindowPosition)vPos horizontalPosition:(ITHorizontalWindowPosition)hPos
958 {
959     NSEnumerator *effectEnum = [effectClasses objectEnumerator];
960     id anItem;
961     
962     [appearanceEffectPopup removeAllItems];
963     [vanishEffectPopup     removeAllItems];
964     
965     while ( (anItem = [effectEnum nextObject]) ) {
966         [appearanceEffectPopup addItemWithTitle:[anItem effectName]];
967         [vanishEffectPopup     addItemWithTitle:[anItem effectName]];
968         
969         [[appearanceEffectPopup lastItem] setRepresentedObject:anItem];
970         [[vanishEffectPopup     lastItem] setRepresentedObject:anItem];
971         
972         if ( [self effect:anItem supportsVerticalPosition:vPos withHorizontalPosition:hPos] ) {
973             [[appearanceEffectPopup lastItem] setEnabled:YES];
974             [[vanishEffectPopup     lastItem] setEnabled:YES];
975         } else {
976             [[appearanceEffectPopup lastItem] setEnabled:NO];
977             [[vanishEffectPopup     lastItem] setEnabled:NO];
978         }
979     }
980     
981 }
982
983 - (BOOL)effect:(Class)effectClass supportsVerticalPosition:(ITVerticalWindowPosition)vPos withHorizontalPosition:(ITHorizontalWindowPosition)hPos
984 {
985     BOOL valid = NO;
986     
987     if ( vPos == ITWindowPositionTop ) {
988         if ( hPos == ITWindowPositionLeft ) {
989             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Left"] boolValue] ) ;
990         } else if ( hPos == ITWindowPositionCenter ) {
991             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Center"] boolValue] );
992         } else if ( hPos == ITWindowPositionRight ) {
993             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Top"] objectForKey:@"Right"] boolValue] );
994         }
995     } else if ( vPos == ITWindowPositionMiddle ) {
996         if ( hPos == ITWindowPositionLeft ) {
997             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Left"] boolValue] );
998         } else if ( hPos == ITWindowPositionCenter ) {
999             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Center"] boolValue] );
1000         } else if ( hPos == ITWindowPositionRight ) {
1001             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Middle"] objectForKey:@"Right"] boolValue] );
1002         }
1003     } else if ( vPos == ITWindowPositionBottom ) {
1004         if ( hPos == ITWindowPositionLeft ) {
1005             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Left"] boolValue] );
1006         } else if ( hPos == ITWindowPositionCenter ) {
1007             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Center"] boolValue] );
1008         } else if ( hPos == ITWindowPositionRight ) {
1009             valid = ( [[[[effectClass supportedPositions] objectForKey:@"Bottom"] objectForKey:@"Right"] boolValue] );
1010         }
1011     }
1012     
1013     return valid;
1014 }
1015
1016 - (IBAction)changeMenus:(id)sender
1017 {
1018     ITDebugLog(@"Synchronizing menus");
1019     [df setObject:myItems forKey:@"menu"];
1020     [df synchronize];
1021     
1022     //If we're connected over a network, refresh the menu immediately
1023     if ([[NetworkController sharedController] isConnectedToServer]) {
1024         [controller timerUpdate];
1025     }
1026 }
1027
1028 - (void)setLaunchesAtLogin:(BOOL)flag
1029 {
1030     NSMutableDictionary *loginwindow;
1031     NSMutableArray *loginarray;
1032     ITDebugLog(@"Setting launches at login: %i", flag);
1033     [df synchronize];
1034     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
1035     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
1036     
1037     if (flag) {
1038         NSDictionary *itemDict = [NSDictionary dictionaryWithObjectsAndKeys:
1039         [[NSBundle mainBundle] bundlePath], @"Path",
1040         [NSNumber numberWithInt:0], @"Hide", nil];
1041         [loginarray addObject:itemDict];
1042     } else {
1043         int i;
1044         for (i = 0; i < [loginarray count]; i++) {
1045             NSDictionary *tempDict = [loginarray objectAtIndex:i];
1046             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
1047                 [loginarray removeObjectAtIndex:i];
1048                 break;
1049             }
1050         }
1051     }
1052     [df setPersistentDomain:loginwindow forName:@"loginwindow"];
1053     [df synchronize];
1054     [loginwindow release];
1055     ITDebugLog(@"Finished setting launches at login.");
1056 }
1057
1058
1059 /*************************************************************************/
1060 #pragma mark -
1061 #pragma mark NSWindow DELEGATE METHODS
1062 /*************************************************************************/
1063
1064 - (void)windowWillClose:(NSNotification *)note
1065 {
1066     [(MainController *)controller closePreferences]; 
1067 }
1068
1069 /*************************************************************************/
1070 #pragma mark -
1071 #pragma mark NSTextField DELEGATE METHODS
1072 /*************************************************************************/
1073
1074 - (void)controlTextDidChange:(NSNotification*)note
1075 {
1076     if ([note object] == hostTextField) {
1077         if ([[hostTextField stringValue] length] == 0) {
1078             [sharingPanelOKButton setEnabled:NO];
1079         } else {
1080             [sharingPanelOKButton setEnabled:YES];
1081         }
1082     }
1083 }
1084
1085 /*************************************************************************/
1086 #pragma mark -
1087 #pragma mark NSTableView DATASOURCE METHODS
1088 /*************************************************************************/
1089
1090 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
1091 {
1092     if (aTableView == menuTableView) {
1093         return [myItems count];
1094     } else if (aTableView == allTableView) {
1095         return [availableItems count];
1096     } else if (aTableView == hotKeysTableView) {
1097         return [hotKeysArray count];
1098     } else {
1099         return [[[NetworkController sharedController] remoteServices] count];
1100     }
1101 }
1102
1103 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
1104 {
1105     if (aTableView == menuTableView) {
1106         NSString *object = [myItems objectAtIndex:rowIndex];
1107         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1108             if ([object isEqualToString:@"showPlayer"]) {
1109                 NSString *string = nil;
1110                 NS_DURING
1111                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1112                 NS_HANDLER
1113                     [controller networkError:localException];
1114                 NS_ENDHANDLER
1115                 return string;
1116             }
1117             return NSLocalizedString(object, @"ERROR");
1118         } else {
1119             if ([submenuItems containsObject:object])
1120             {
1121                 return [NSImage imageNamed:@"submenu"];
1122             } else {
1123                 return nil;
1124             }
1125         }
1126     } else if (aTableView == allTableView) {
1127         NSString *object = [availableItems objectAtIndex:rowIndex];
1128         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1129             if ([object isEqualToString:@"showPlayer"]) {
1130                 NSString *string = nil;
1131                 NS_DURING
1132                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
1133                 NS_HANDLER
1134                     [controller networkError:localException];
1135                 NS_ENDHANDLER
1136                 return string;
1137             }
1138             return NSLocalizedString(object, @"ERROR");
1139         } else {
1140             if ([submenuItems containsObject:object]) {
1141                 return [NSImage imageNamed:@"submenu"];
1142             } else {
1143                 return nil;
1144             }
1145         }
1146     } else if (aTableView == hotKeysTableView) {
1147         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
1148             return [hotKeyNamesArray objectAtIndex:rowIndex];
1149         } else {
1150             return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
1151         }
1152     } else {
1153         return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
1154     }
1155 }
1156
1157 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
1158 {
1159     if (tableView == menuTableView) {
1160         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
1161         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
1162         return YES;
1163     }
1164     
1165     if (tableView == allTableView) {
1166         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
1167         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
1168         return YES;
1169     }
1170     return NO;
1171 }
1172
1173 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
1174 {
1175     NSPasteboard *pb;
1176     int dragRow;
1177     NSString *dragData, *temp;
1178     
1179     pb = [info draggingPasteboard];
1180     
1181     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
1182         dragData = [pb stringForType:@"MenuTableViewPboardType"];
1183         dragRow = [dragData intValue];
1184         temp = [myItems objectAtIndex:dragRow];
1185         
1186         if (tableView == menuTableView) {
1187             [myItems insertObject:temp atIndex:row];
1188             if (row > dragRow) {
1189                 [myItems removeObjectAtIndex:dragRow];
1190             } else {
1191                 [myItems removeObjectAtIndex:dragRow + 1];
1192             }
1193         } else if (tableView == allTableView) {
1194             if (![temp isEqualToString:@"separator"]) {
1195                 [availableItems addObject:temp];
1196             }
1197             [myItems removeObjectAtIndex:dragRow];
1198         }
1199     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
1200         dragData = [pb stringForType:@"AllTableViewPboardType"];
1201         dragRow = [dragData intValue];
1202         temp = [availableItems objectAtIndex:dragRow];
1203         
1204         [myItems insertObject:temp atIndex:row];
1205         
1206         if (![temp isEqualToString:@"separator"]) {
1207             [availableItems removeObjectAtIndex:dragRow];
1208         }
1209     }
1210     
1211     [menuTableView reloadData];
1212     [allTableView reloadData];
1213     [self changeMenus:self];
1214     return YES;
1215 }
1216
1217 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
1218 {
1219     if (tableView == allTableView) {
1220         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
1221             return NSDragOperationNone;
1222         }
1223         
1224         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
1225             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
1226             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
1227                 return NSDragOperationNone;
1228             }
1229         }
1230         
1231         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
1232         return NSDragOperationGeneric;
1233     }
1234     
1235     if (operation == NSTableViewDropOn || row == -1)
1236     {
1237         return NSDragOperationNone;
1238     }
1239     return NSDragOperationGeneric;
1240 }
1241
1242
1243 /*************************************************************************/
1244 #pragma mark -
1245 #pragma mark DEALLOCATION METHODS
1246 /*************************************************************************/
1247
1248 - (void)dealloc
1249 {
1250     [hotKeysArray release];
1251     [hotKeysDictionary release];
1252     [effectClasses release];
1253     [menuTableView setDataSource:nil];
1254     [allTableView setDataSource:nil];
1255     [controller release];
1256     [availableItems release];
1257     [submenuItems release];
1258     [myItems release];
1259     [df release];
1260 }
1261
1262 @end