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