More bugfixes in networking. Added password panels. Fixed bug with 0:60 time.
[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 <ITKit/ITHotKeyCenter.h>
14 #import <ITKit/ITKeyCombo.h>
15 #import <ITKit/ITKeyComboPanel.h>
16 #import <ITKit/ITWindowPositioning.h>
17 #import <ITKit/ITKeyBroadcaster.h>
18
19 #import <ITKit/ITCutWindowEffect.h>
20 #import <ITKit/ITDissolveWindowEffect.h>
21 #import <ITKit/ITSlideHorizontallyWindowEffect.h>
22 #import <ITKit/ITSlideVerticallyWindowEffect.h>
23 #import <ITKit/ITPivotWindowEffect.h>
24
25
26 #define SENDER_STATE (([sender state] == NSOnState) ? YES : NO)
27
28 /*************************************************************************/
29 #pragma mark -
30 #pragma mark PRIVATE INTERFACE
31 /*************************************************************************/
32
33 @interface PreferencesController (Private)
34 - (void)setupWindow;
35 - (void)setupCustomizationTables;
36 - (void)setupMenuItems;
37 - (void)setupUI;
38 - (IBAction)changeMenus:(id)sender;
39 - (void)setLaunchesAtLogin:(BOOL)flag;
40 @end
41
42
43 @implementation PreferencesController
44
45
46 /*************************************************************************/
47 #pragma mark -
48 #pragma mark STATIC VARIABLES
49 /*************************************************************************/
50
51 static PreferencesController *prefs = nil;
52
53
54 /*************************************************************************/
55 #pragma mark -
56 #pragma mark INITIALIZATION METHODS
57 /*************************************************************************/
58
59 + (PreferencesController *)sharedPrefs;
60 {
61     if (! prefs) {
62         prefs = [[self alloc] init];
63     }
64     return prefs;
65 }
66
67 - (id)init
68 {
69     if ( (self = [super init]) ) {
70         ITDebugLog(@"Preferences initialized.");
71         df = [[NSUserDefaults standardUserDefaults] retain];
72         hotKeysArray = [[NSArray alloc] initWithObjects:@"PlayPause",
73                                                        @"NextTrack",
74                                                        @"PrevTrack",
75                                                        @"ShowPlayer",
76                                                        @"TrackInfo",
77                                                        @"UpcomingSongs",
78                                                        @"IncrementVolume",
79                                                        @"DecrementVolume",
80                                                        @"IncrementRating",
81                                                        @"DecrementRating",
82                                                        @"ToggleShuffle",
83                                                        @"ToggleLoop",
84                                                        nil];
85         
86         hotKeyNamesArray = [[NSArray alloc] initWithObjects:@"Play/Pause",
87                                                        @"Next Track",
88                                                        @"Previous Track",
89                                                        @"Show Player",
90                                                        @"Track Info",
91                                                        @"Upcoming Songs",
92                                                        @"Increment Volume",
93                                                        @"Decrement Volume",
94                                                        @"Increment Rating",
95                                                        @"Decrement Rating",
96                                                        @"Toggle Shuffle",
97                                                        @"Toggle Loop",
98                                                        nil];
99         hotKeysDictionary = [[NSMutableDictionary alloc] init];
100         controller = nil;
101         
102         [self setupWindow];  // Load in the nib, and perform any initial setup.
103     }
104     return self;
105 }
106
107
108 /*************************************************************************/
109 #pragma mark -
110 #pragma mark ACCESSOR METHODS
111 /*************************************************************************/
112
113 - (id)controller
114 {
115     return controller;
116 }
117
118 - (void)setController:(id)object
119 {
120     [controller autorelease];
121     controller = [object retain];
122 }
123
124
125 /*************************************************************************/
126 #pragma mark -
127 #pragma mark INSTANCE METHODS
128 /*************************************************************************/
129
130 - (BOOL)showPasswordPanel
131 {
132     [passwordPanel setLevel:NSStatusWindowLevel];
133     [passwordPanelOKButton setTitle:@"OK"];
134     [passwordPanelMessage setStringValue:[NSString stringWithFormat:@"Please enter a password for access to the MenuTunes player named %@ at %@.", [[[NetworkController sharedController] networkObject] serverName], [[NetworkController sharedController] remoteHost]]];
135     [passwordPanel makeKeyAndOrderFront:nil];
136     if ([NSApp runModalForWindow:passwordPanel]) {
137         return YES;
138     } else {
139         return NO;
140     }
141 }
142
143 - (BOOL)showInvalidPasswordPanel
144 {
145     [passwordPanel setLevel:NSStatusWindowLevel];
146     [passwordPanelOKButton setTitle:@"Retry"];
147     [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]]];
148     [passwordPanel makeKeyAndOrderFront:nil];
149     if ([NSApp runModalForWindow:passwordPanel]) {
150         return YES;
151     } else {
152         return NO;
153     }
154 }
155
156 - (IBAction)showPrefsWindow:(id)sender
157 {
158     ITDebugLog(@"Showing preferences window.");
159     if (! window) {  // If window does not exist yet, then the nib hasn't been loaded.
160         ITDebugLog(@"Window doesn't exist, initial setup.");
161         [self setupCustomizationTables];  // Setup the DnD manu config tables.
162         [self setupMenuItems];  // Setup the arrays of menu items
163         [self setupUI]; // Sets up additional UI
164         [window setDelegate:self];
165         [menuTableView reloadData];
166         [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)];
167         
168         //Change the launch player checkbox to the proper name
169         NS_DURING
170             [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized...
171         NS_HANDLER
172             [controller networkError:localException];
173         NS_ENDHANDLER
174     }
175
176     [window center];
177     [NSApp activateIgnoringOtherApps:YES];
178     [window performSelector:@selector(makeKeyAndOrderFront:) withObject:self afterDelay:0.0];
179 }
180
181 - (IBAction)changeGeneralSetting:(id)sender
182 {
183     ITDebugLog(@"Changing general setting of tag %i.", [sender tag]);
184     if ( [sender tag] == 1010) {
185         [self setLaunchesAtLogin:SENDER_STATE];
186     } else if ( [sender tag] == 1020) {
187         [df setBool:SENDER_STATE forKey:@"LaunchPlayerWithMT"];
188     } else if ( [sender tag] == 1030) {
189         [df setInteger:[sender intValue] forKey:@"SongsInAdvance"];
190
191     } else if ( [sender tag] == 1040) {
192         // This will not be executed.  Song info always shows the title of the song.
193         // [df setBool:SENDER_STATE forKey:@"showName"];
194     } else if ( [sender tag] == 1050) {
195         [df setBool:SENDER_STATE forKey:@"showArtist"];
196     } else if ( [sender tag] == 1060) {
197         [df setBool:SENDER_STATE forKey:@"showAlbum"];
198     } else if ( [sender tag] == 1070) {
199         [df setBool:SENDER_STATE forKey:@"showTime"];
200     } else if ( [sender tag] == 1080) {
201         [df setBool:SENDER_STATE forKey:@"showTrackNumber"];
202     } else if ( [sender tag] == 1090) {
203         [df setBool:SENDER_STATE forKey:@"showTrackRating"];
204     }
205     [df synchronize];
206 }
207
208 - (IBAction)changeSharingSetting:(id)sender
209 {
210     ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]);
211     if ( [sender tag] == 5010 ) {
212         BOOL state = SENDER_STATE;
213         [df setBool:state forKey:@"enableSharing"];
214         //Disable/enable the use of shared player options
215         [useSharedMenuTunesCheckbox setEnabled:!state];
216         [usePasswordCheckbox setEnabled:state];
217         [passwordTextField setEnabled:state];
218         [nameTextField setEnabled:state];
219         [selectSharedPlayerButton setEnabled:NO];
220         [controller setServerStatus:state]; //Set server status
221     } else if ( [sender tag] == 5015 ) {
222         [df setObject:[sender stringValue] forKey:@"sharedPlayerName"];
223     } else if ( [sender tag] == 5020 ) {
224         [df setBool:SENDER_STATE forKey:@"enableSharingPassword"];
225     } else if ( [sender tag] == 5030 ) {
226         //Set the server password
227         const char *instring = [[sender stringValue] UTF8String];
228         const char *password = "password";
229         unsigned char *result;
230         NSData *hashedPass, *passwordStringHash;
231         result = SHA1(instring, strlen(instring), NULL);
232         hashedPass = [NSData dataWithBytes:result length:strlen(result)];
233         result = SHA1(password, strlen(password), NULL);
234         passwordStringHash = [NSData dataWithBytes:result length:strlen(result)];
235         if (![hashedPass isEqualToData:passwordStringHash]) {
236             [df setObject:hashedPass forKey:@"sharedPlayerPassword"];
237             [sender setStringValue:@"password"];
238         }
239     } else if ( [sender tag] == 5040 ) {
240         BOOL state = SENDER_STATE;
241         [df setBool:state forKey:@"useSharedPlayer"];
242         //Disable/enable the use of sharing options
243         [shareMenuTunesCheckbox setEnabled:!state];
244         [usePasswordCheckbox setEnabled:NO];
245         [passwordTextField setEnabled:NO];
246         [nameTextField setEnabled:NO];
247         [selectSharedPlayerButton setEnabled:state];
248         
249         if (state && [controller connectToServer]) {
250             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
251             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
252         } else {
253             [selectedPlayerTextField setStringValue:@"No shared player selected."];
254             [locationTextField setStringValue:@"-"];
255             if ([[NetworkController sharedController] isConnectedToServer]) {
256                 [controller disconnectFromServer];
257             }
258             
259         }
260     } else if ( [sender tag] == 5050 ) {
261         //Do nothing on table view click
262     } else if ( [sender tag] == 5051 ) {
263         [df setObject:[sender stringValue] forKey:@"sharedPlayerHost"];
264     } else if ( [sender tag] == 5060 ) {
265         //Show selection sheet
266         [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil];
267     } else if ( [sender tag] == 5100 ) {
268         //Change view
269         if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) {
270             NSRect frame = [selectPlayerSheet frame];
271             frame.origin.y -= 58;
272             frame.size.height = 273;
273             [selectPlayerBox setContentView:zeroConfView];
274             [selectPlayerSheet setFrame:frame display:YES animate:YES];
275         } else if ( ([sender indexOfItem:[sender selectedItem]] == 1) && ([selectPlayerBox contentView] != manualView) ){
276             NSRect frame = [selectPlayerSheet frame];
277             frame.origin.y += 58;
278             frame.size.height = 215;
279             //[window makeFirstResponder:hostTextField];
280             [selectPlayerBox setContentView:manualView];
281             [selectPlayerSheet setFrame:frame display:YES animate:YES];
282             [hostTextField selectText:nil];
283         }
284     } else if ( [sender tag] == 5150 ) {
285         const char *instring = [[sender stringValue] UTF8String];
286         unsigned char *result;
287         result = SHA1(instring, strlen(instring), NULL);
288         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
289     } else if ( [sender tag] == 5110 ) {
290         //Cancel
291         [NSApp endSheet:selectPlayerSheet];
292         [selectPlayerSheet orderOut:nil];
293         if ([selectPlayerBox contentView] == manualView) {
294             [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
295         } else {
296         }
297     } else if ( [sender tag] == 5120 ) {
298         //OK, try to connect
299         [NSApp endSheet:selectPlayerSheet];
300         [selectPlayerSheet orderOut:nil];
301         
302         [self changeSharingSetting:clientPasswordTextField];
303         
304         if ([selectPlayerBox contentView] == manualView) {
305             [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"];
306         } else {
307             if ([sharingTableView selectedRow] > -1) {
308                 [df setObject:[NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sharingTableView selectedRow]] addresses] objectAtIndex:0] bytes]).sin_addr)] forKey:@"sharedPlayerHost"];
309             }
310         }
311         
312         if ([controller connectToServer]) {
313             [useSharedMenuTunesCheckbox setState:NSOnState];
314             [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
315             [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
316         } else {
317             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);
318         }
319     } else if ( [sender tag] == 6010 ) {
320         //Cancel password entry
321         [passwordPanel orderOut:nil];
322         [NSApp stopModalWithCode:0];
323     } else if ( [sender tag] == 6020 ) {
324         //OK password entry, retry connect
325         const char *instring = [[passwordPanelTextField stringValue] UTF8String];
326         unsigned char *result;
327         result = SHA1(instring, strlen(instring), NULL);
328         [df setObject:[NSData dataWithBytes:result length:strlen(result)] forKey:@"connectPassword"];
329         [passwordPanel orderOut:nil];
330         [NSApp stopModalWithCode:1];
331     }
332     [df synchronize];
333 }
334
335 - (IBAction)changeStatusWindowSetting:(id)sender
336 {
337     StatusWindow *sw = [StatusWindow sharedWindow];
338     ITDebugLog(@"Changing status window setting of tag %i", [sender tag]);
339     if ( [sender tag] == 2010) {
340         [df setInteger:[sender selectedRow] forKey:@"statusWindowVerticalPosition"];
341         [df setInteger:[sender selectedColumn] forKey:@"statusWindowHorizontalPosition"];
342         // update the window's position here
343     } else if ( [sender tag] == 2020) {
344         // update screen selection
345     } else if ( [sender tag] == 2030) {
346         int effectTag = [[sender selectedItem] tag];
347         float time = ([df floatForKey:@"statusWindowAppearanceSpeed"] ? [df floatForKey:@"statusWindowAppearanceSpeed"] : 0.8);
348         [df setInteger:effectTag forKey:@"statusWindowAppearanceEffect"];
349
350         if ( effectTag == 2100 ) {
351             [sw setEntryEffect:[[[ITCutWindowEffect alloc] initWithWindow:sw] autorelease]];
352         } else if ( effectTag == 2101 ) {
353             [sw setEntryEffect:[[[ITDissolveWindowEffect alloc] initWithWindow:sw] autorelease]];
354         } else if ( effectTag == 2102 ) {
355             [sw setEntryEffect:[[[ITSlideVerticallyWindowEffect alloc] initWithWindow:sw] autorelease]];
356         } else if ( effectTag == 2103 ) {
357             [sw setEntryEffect:[[[ITSlideHorizontallyWindowEffect alloc] initWithWindow:sw] autorelease]];
358         } else if ( effectTag == 2104 ) {
359             [sw setEntryEffect:[[[ITPivotWindowEffect alloc] initWithWindow:sw] autorelease]];
360         }
361
362         [[sw entryEffect] setEffectTime:time];
363         
364     } else if ( [sender tag] == 2040) {
365         int effectTag = [[sender selectedItem] tag];
366         float time = ([df floatForKey:@"statusWindowVanishSpeed"] ? [df floatForKey:@"statusWindowVanishSpeed"] : 0.8);
367         
368         [df setInteger:[[sender selectedItem] tag] forKey:@"statusWindowVanishEffect"];
369         
370         if ( effectTag == 2100 ) {
371             [sw setExitEffect:[[[ITCutWindowEffect alloc] initWithWindow:sw] autorelease]];
372         } else if ( effectTag == 2101 ) {
373             [sw setExitEffect:[[[ITDissolveWindowEffect alloc] initWithWindow:sw] autorelease]];
374         } else if ( effectTag == 2102 ) {
375             [sw setExitEffect:[[[ITSlideVerticallyWindowEffect alloc] initWithWindow:sw] autorelease]];
376         } else if ( effectTag == 2103 ) {
377             [sw setExitEffect:[[[ITSlideHorizontallyWindowEffect alloc] initWithWindow:sw] autorelease]];
378         } else if ( effectTag == 2104 ) {
379             [sw setExitEffect:[[[ITPivotWindowEffect alloc] initWithWindow:sw] autorelease]];
380         }
381
382         [[sw exitEffect] setEffectTime:time];
383
384     } else if ( [sender tag] == 2050) {
385         float newTime = (-([sender floatValue]));
386         [df setFloat:newTime forKey:@"statusWindowAppearanceSpeed"];
387         [[sw entryEffect] setEffectTime:newTime];
388     } else if ( [sender tag] == 2060) {
389         float newTime = (-([sender floatValue]));
390         [df setFloat:newTime forKey:@"statusWindowVanishSpeed"];
391         [[sw exitEffect] setEffectTime:newTime];
392     } else if ( [sender tag] == 2070) {
393         [df setFloat:[sender floatValue] forKey:@"statusWindowVanishDelay"];
394         [sw setExitDelay:[sender floatValue]];
395     } else if ( [sender tag] == 2080) {
396         [df setBool:SENDER_STATE forKey:@"showSongInfoOnChange"];
397     }
398     [df synchronize];
399 }
400
401 - (void)registerDefaults
402 {
403     BOOL found = NO;
404     NSMutableDictionary *loginWindow;
405     NSMutableArray *loginArray;
406     NSEnumerator *loginEnum;
407     id anItem;
408     ITDebugLog(@"Registering defaults.");
409     [df setObject:[NSArray arrayWithObjects:
410         @"trackInfo",
411         @"separator",
412         @"playPause",
413         @"prevTrack",
414         @"nextTrack",
415         @"separator",
416         @"playlists",
417         @"upcomingSongs",
418         @"separator",
419         @"preferences",
420         @"quit",
421         nil] forKey:@"menu"];
422
423     [df setInteger:5 forKey:@"SongsInAdvance"];
424     // [df setBool:YES forKey:@"showName"];  // Song info will always show song title.
425     [df setBool:YES forKey:@"showArtist"];
426     [df setBool:NO forKey:@"showAlbum"];
427     [df setBool:NO forKey:@"showTime"];
428
429     [df setInteger:2100 forKey:@"statusWindowAppearanceEffect"];
430     [df setInteger:2101 forKey:@"statusWindowVanishEffect"];
431     [df setFloat:0.8 forKey:@"statusWindowAppearanceSpeed"];
432     [df setFloat:0.8 forKey:@"statusWindowVanishSpeed"];
433     [df setFloat:4.0 forKey:@"statusWindowVanishDelay"];
434     [df setBool:YES forKey:@"showSongInfoOnChange"];
435
436     [df synchronize];
437     
438     loginWindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
439     loginArray = [loginWindow objectForKey:@"AutoLaunchedApplicationDictionary"];
440     loginEnum = [loginArray objectEnumerator];
441
442     while ( (anItem = [loginEnum nextObject]) ) {
443         if ( [[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]] ) {
444             found = YES;
445         }
446     }
447     [loginWindow release];
448     
449     if (!found) {
450         [[StatusWindowController sharedController] showSetupQueryWindow];
451     }
452 }
453
454 - (void)autoLaunchOK
455 {
456     [[StatusWindow sharedWindow] setLocked:NO];
457     [[StatusWindow sharedWindow] vanish:self];
458     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
459     
460     [self setLaunchesAtLogin:YES];
461 }
462
463 - (void)autoLaunchCancel
464 {
465     [[StatusWindow sharedWindow] setLocked:NO];
466     [[StatusWindow sharedWindow] vanish:self];
467     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
468 }
469
470 - (void)deletePressedInTableView:(NSTableView *)tableView
471 {
472     if (tableView == menuTableView) {
473         int selRow = [tableView selectedRow];
474         ITDebugLog(@"Delete pressed in menu table view.");
475         if (selRow != - 1) {
476             NSString *object = [myItems objectAtIndex:selRow];
477             
478             if ([object isEqualToString:@"preferences"]) {
479                 NSBeep();
480                 return;
481             }
482             
483             if (![object isEqualToString:@"separator"])
484                 [availableItems addObject:object];
485             ITDebugLog(@"Removing object named %@", object);
486             [myItems removeObjectAtIndex:selRow];
487             [menuTableView reloadData];
488             [allTableView reloadData];
489         }
490         [self changeMenus:self];
491     }
492 }
493
494 /*************************************************************************/
495 #pragma mark -
496 #pragma mark HOTKEY SUPPORT METHODS
497 /*************************************************************************/
498
499 - (IBAction)clearHotKey:(id)sender
500 {
501     [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
502     [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation] forKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]];
503     [controller setupHotKeys];
504     [hotKeysTableView reloadData];
505 }
506
507 - (IBAction)editHotKey:(id)sender
508 {
509     ITKeyComboPanel *panel = [ITKeyComboPanel sharedPanel];
510     NSString *keyComboKey = [hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]];
511     ITKeyCombo *keyCombo;
512     
513     ITDebugLog(@"Setting key combo on hot key %@.", keyComboKey);
514     [controller clearHotKeys];
515     [panel setKeyCombo:[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:[hotKeysTableView selectedRow]]]];
516     [panel setKeyBindingName:[hotKeyNamesArray objectAtIndex:[hotKeysTableView selectedRow]]];
517     if ([panel runModal] == NSOKButton) {
518         NSEnumerator *keyEnumerator = [[hotKeysDictionary allKeys] objectEnumerator];
519         NSString *nextKey;
520         keyCombo = [panel keyCombo];
521         
522         //Check for duplicate key combo
523         while ( (nextKey = [keyEnumerator nextObject]) ) {
524             if ([[hotKeysDictionary objectForKey:nextKey] isEqual:keyCombo] &&
525                 ![keyCombo isEqual:[ITKeyCombo clearKeyCombo]]) {
526                 [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo]
527                                    forKey:nextKey];
528                 [df setObject:[[ITKeyCombo clearKeyCombo] plistRepresentation]
529                     forKey:nextKey];
530             }
531         }
532         
533         [hotKeysDictionary setObject:keyCombo forKey:keyComboKey];
534         [df setObject:[keyCombo plistRepresentation] forKey:keyComboKey];
535         [controller setupHotKeys];
536         [hotKeysTableView reloadData];
537         ITDebugLog(@"Set combo %@ on hot key %@.", keyCombo, keyComboKey);
538     } else {
539         ITDebugLog(@"Hot key setting on hot key %@ cancelled.", keyComboKey);
540     }
541 }
542
543 - (void)hotKeysTableViewDoubleClicked:(id)sender
544 {
545     if ([sender clickedRow] > -1) {
546         [self editHotKey:sender];
547     }
548 }
549
550 /*************************************************************************/
551 #pragma mark -
552 #pragma mark PRIVATE METHOD IMPLEMENTATIONS
553 /*************************************************************************/
554
555 - (void)setupWindow
556 {
557     ITDebugLog(@"Loading Preferences.nib.");
558     if (![NSBundle loadNibNamed:@"Preferences" owner:self]) {
559         ITDebugLog(@"Failed to load Preferences.nib.");
560         NSBeep();
561         return;
562     }
563 }
564
565 - (void)setupCustomizationTables
566 {
567     NSImageCell *imgCell = [[[NSImageCell alloc] initImageCell:nil] autorelease];
568     ITDebugLog(@"Setting up table views.");
569     // Set the table view cells up
570     [imgCell setImageScaling:NSScaleNone];
571     [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
572     [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
573
574     // Register for drag and drop
575     [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:
576         @"MenuTableViewPboardType",
577         @"AllTableViewPboardType",
578         nil]];
579     [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:
580         @"MenuTableViewPboardType",
581         @"AllTableViewPboardType",
582         nil]];
583 }
584
585 - (void)setupMenuItems
586 {
587     NSEnumerator *itemEnum;
588     id            anItem;
589     ITDebugLog(@"Setting up table view arrays.");
590     // Set the list of items you can have.
591     availableItems = [[NSMutableArray alloc] initWithObjects:
592         @"separator",
593         @"trackInfo",
594         @"upcomingSongs",
595         @"playlists",
596         @"eqPresets",
597         @"songRating",
598         @"playPause",
599         @"nextTrack",
600         @"prevTrack",
601         @"fastForward",
602         @"rewind",
603         @"showPlayer",
604         @"quit",
605         nil];
606     
607     // Get our preferred menu
608     myItems = [[df arrayForKey:@"menu"] mutableCopy];
609     
610     // Delete items in the availableItems array that are already part of the menu
611     itemEnum = [myItems objectEnumerator];
612     while ( (anItem = [itemEnum nextObject]) ) {
613         if (![anItem isEqualToString:@"separator"]) {
614             [availableItems removeObject:anItem];
615         }
616     }
617     
618     // Items that show should a submenu image
619     submenuItems = [[NSArray alloc] initWithObjects:
620         @"upcomingSongs",
621         @"playlists",
622         @"eqPresets",
623         @"songRating",
624         nil];
625 }
626
627 - (void)setupUI
628 {
629     NSMutableDictionary *loginwindow;
630     NSMutableArray *loginarray;
631     NSEnumerator *loginEnum, *keyArrayEnum;
632     NSString *serverName;
633     id anItem;
634     
635     ITDebugLog(@"Setting up preferences UI.");
636     // Fill in the number of songs in advance to show field
637     [songsInAdvance setIntValue:[df integerForKey:@"SongsInAdvance"]];
638     
639     // Fill hot key array
640     keyArrayEnum = [hotKeysArray objectEnumerator];
641     
642     while ( (anItem = [keyArrayEnum nextObject]) ) {
643         if ([df objectForKey:anItem]) {
644             ITDebugLog(@"Setting up \"%@\" hot key.", anItem);
645             [hotKeysDictionary setObject:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:anItem]] forKey:anItem];
646         } else {
647             [hotKeysDictionary setObject:[ITKeyCombo clearKeyCombo] forKey:anItem];
648         }
649     }
650     
651     ITDebugLog(@"Setting up track info checkboxes.");
652     // Check current track info buttons
653     [albumCheckbox setState:[df boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
654     [nameCheckbox setState:NSOnState];  // Song info will ALWAYS show song title.
655     [nameCheckbox setEnabled:NO];  // Song info will ALWAYS show song title.
656     [artistCheckbox setState:[df boolForKey:@"showArtist"] ? NSOnState : NSOffState];
657     [trackTimeCheckbox setState:[df boolForKey:@"showTime"] ? NSOnState : NSOffState];
658     [trackNumberCheckbox setState:[df boolForKey:@"showTrackNumber"] ? NSOnState : NSOffState];
659     [ratingCheckbox setState:[df boolForKey:@"showTrackRating"] ? NSOnState : NSOffState];
660     
661     // Set the launch at login checkbox state
662     ITDebugLog(@"Setting launch at login state.");
663     [df synchronize];
664     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
665     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
666     
667     loginEnum = [loginarray objectEnumerator];
668     while ( (anItem = [loginEnum nextObject]) ) {
669         if ([[[anItem objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
670             [launchAtLoginCheckbox setState:NSOnState];
671         }
672     }
673     
674     // Set the launch player checkbox state
675     ITDebugLog(@"Setting launch player with MenuTunes state.");
676     [launchPlayerAtLaunchCheckbox setState:[df boolForKey:@"LaunchPlayerWithMT"] ? NSOnState : NSOffState];
677     
678     // Setup the positioning controls
679     
680     // Setup effects controls
681     [appearanceEffectPopup selectItem:[appearanceEffectPopup itemAtIndex:[appearanceEffectPopup indexOfItemWithTag:[df integerForKey:@"statusWindowAppearanceEffect"]]]];
682     [vanishEffectPopup     selectItem:[vanishEffectPopup     itemAtIndex:[vanishEffectPopup     indexOfItemWithTag:[df integerForKey:@"statusWindowVanishEffect"]]]];
683     [appearanceSpeedSlider setFloatValue:-([df floatForKey:@"statusWindowAppearanceSpeed"])];
684     [vanishSpeedSlider     setFloatValue:-([df floatForKey:@"statusWindowVanishSpeed"])];
685     [vanishDelaySlider     setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]];
686     [showOnChangeCheckbox  setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)];
687     
688     // Setup the sharing controls
689     if ([df boolForKey:@"enableSharing"]) {
690         [shareMenuTunesCheckbox setState:NSOnState];
691         [useSharedMenuTunesCheckbox setEnabled:NO];
692         [selectSharedPlayerButton setEnabled:NO];
693         [passwordTextField setEnabled:YES];
694         [usePasswordCheckbox setEnabled:YES];
695         [nameTextField setEnabled:YES];
696     } else if ([df boolForKey:@"useSharedPlayer"]) {
697         [useSharedMenuTunesCheckbox setState:NSOnState];
698         [shareMenuTunesCheckbox setEnabled:NO];
699         [selectSharedPlayerButton setEnabled:YES];
700     }
701     
702     [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil];
703     
704     serverName = [df stringForKey:@"sharedPlayerName"];
705     if (!serverName || [serverName length] == 0) {
706         serverName = @"MenuTunes Shared Player";
707     }
708     [nameTextField setStringValue:serverName];
709     
710     [selectPlayerBox setContentView:zeroConfView];
711     [usePasswordCheckbox setState:([df boolForKey:@"enableSharingPassword"] ? NSOnState : NSOffState)];
712     if ([df dataForKey:@"sharedPlayerPassword"]) {
713         [passwordTextField setStringValue:@"password"];
714     }
715     if ([df stringForKey:@"sharedPlayerHost"]) {
716         [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]];
717     }
718     
719     if ([[NetworkController sharedController] isConnectedToServer]) {
720         [selectedPlayerTextField setStringValue:[[[NetworkController sharedController] networkObject] serverName]];
721         [locationTextField setStringValue:[[NetworkController sharedController] remoteHost]];
722     } else {
723         [selectedPlayerTextField setStringValue:@"No shared player selected."];
724         [locationTextField setStringValue:@"-"];
725     }
726 }
727
728 - (IBAction)changeMenus:(id)sender
729 {
730     ITDebugLog(@"Synchronizing menus");
731     [df setObject:myItems forKey:@"menu"];
732     [df synchronize];
733 }
734
735 - (void)setLaunchesAtLogin:(BOOL)flag
736 {
737     NSMutableDictionary *loginwindow;
738     NSMutableArray *loginarray;
739     ITDebugLog(@"Setting launches at login: %i", flag);
740     [df synchronize];
741     loginwindow = [[df persistentDomainForName:@"loginwindow"] mutableCopy];
742     loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
743     
744     if (flag) {
745         NSDictionary *itemDict = [NSDictionary dictionaryWithObjectsAndKeys:
746         [[NSBundle mainBundle] bundlePath], @"Path",
747         [NSNumber numberWithInt:0], @"Hide", nil];
748         [loginarray addObject:itemDict];
749     } else {
750         int i;
751         for (i = 0; i < [loginarray count]; i++) {
752             NSDictionary *tempDict = [loginarray objectAtIndex:i];
753             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
754                 [loginarray removeObjectAtIndex:i];
755                 break;
756             }
757         }
758     }
759     [df setPersistentDomain:loginwindow forName:@"loginwindow"];
760     [df synchronize];
761     [loginwindow release];
762     ITDebugLog(@"Finished setting launches at login.");
763 }
764
765
766 /*************************************************************************/
767 #pragma mark -
768 #pragma mark NSWindow DELEGATE METHODS
769 /*************************************************************************/
770
771 - (void)windowWillClose:(NSNotification *)note
772 {
773     [(MainController *)controller closePreferences]; 
774 }
775
776
777 /*************************************************************************/
778 #pragma mark -
779 #pragma mark NSTableView DATASOURCE METHODS
780 /*************************************************************************/
781
782 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
783 {
784     if (aTableView == menuTableView) {
785         return [myItems count];
786     } else if (aTableView == allTableView) {
787         return [availableItems count];
788     } else if (aTableView == hotKeysTableView) {
789         return [hotKeysArray count];
790     } else {
791         return [[[NetworkController sharedController] remoteServices] count];
792     }
793 }
794
795 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
796 {
797     if (aTableView == menuTableView) {
798         NSString *object = [myItems objectAtIndex:rowIndex];
799         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
800             if ([object isEqualToString:@"showPlayer"]) {
801                 NSString *string;
802                 NS_DURING
803                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
804                 NS_HANDLER
805                     [controller networkError:localException];
806                 NS_ENDHANDLER
807                 return string;
808             }
809             return NSLocalizedString(object, @"ERROR");
810         } else {
811             if ([submenuItems containsObject:object])
812             {
813                 return [NSImage imageNamed:@"submenu"];
814             } else {
815                 return nil;
816             }
817         }
818     } else if (aTableView == allTableView) {
819         NSString *object = [availableItems objectAtIndex:rowIndex];
820         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
821             if ([object isEqualToString:@"showPlayer"]) {
822                 NSString *string;
823                 NS_DURING
824                     string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]];
825                 NS_HANDLER
826                     [controller networkError:localException];
827                 NS_ENDHANDLER
828                 return string;
829             }
830             return NSLocalizedString(object, @"ERROR");
831         } else {
832             if ([submenuItems containsObject:object]) {
833                 return [NSImage imageNamed:@"submenu"];
834             } else {
835                 return nil;
836             }
837         }
838     } else if (aTableView == hotKeysTableView) {
839         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
840             return [hotKeyNamesArray objectAtIndex:rowIndex];
841         } else {
842             return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description];
843         }
844     } else {
845         return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] name];
846     }
847 }
848
849 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
850 {
851     if (tableView == menuTableView) {
852         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
853         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
854         return YES;
855     }
856     
857     if (tableView == allTableView) {
858         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
859         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
860         return YES;
861     }
862     return NO;
863 }
864
865 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
866 {
867     NSPasteboard *pb;
868     int dragRow;
869     NSString *dragData, *temp;
870     
871     pb = [info draggingPasteboard];
872     
873     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
874         dragData = [pb stringForType:@"MenuTableViewPboardType"];
875         dragRow = [dragData intValue];
876         temp = [myItems objectAtIndex:dragRow];
877         
878         if (tableView == menuTableView) {
879             [myItems insertObject:temp atIndex:row];
880             if (row > dragRow) {
881                 [myItems removeObjectAtIndex:dragRow];
882             } else {
883                 [myItems removeObjectAtIndex:dragRow + 1];
884             }
885         } else if (tableView == allTableView) {
886             if (![temp isEqualToString:@"separator"]) {
887                 [availableItems addObject:temp];
888             }
889             [myItems removeObjectAtIndex:dragRow];
890         }
891     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
892         dragData = [pb stringForType:@"AllTableViewPboardType"];
893         dragRow = [dragData intValue];
894         temp = [availableItems objectAtIndex:dragRow];
895         
896         [myItems insertObject:temp atIndex:row];
897         
898         if (![temp isEqualToString:@"separator"]) {
899             [availableItems removeObjectAtIndex:dragRow];
900         }
901     }
902     
903     [menuTableView reloadData];
904     [allTableView reloadData];
905     [self changeMenus:self];
906     return YES;
907 }
908
909 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
910 {
911     if (tableView == allTableView) {
912         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
913             return NSDragOperationNone;
914         }
915         
916         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
917             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
918             if ([item isEqualToString:@"preferences"] || [item isEqualToString:@"quit"]) {
919                 return NSDragOperationNone;
920             }
921         }
922         
923         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
924         return NSDragOperationGeneric;
925     }
926     
927     if (operation == NSTableViewDropOn || row == -1)
928     {
929         return NSDragOperationNone;
930     }
931     return NSDragOperationGeneric;
932 }
933
934
935 /*************************************************************************/
936 #pragma mark -
937 #pragma mark DEALLOCATION METHODS
938 /*************************************************************************/
939
940 - (void)dealloc
941 {
942     [hotKeysArray release];
943     [hotKeysDictionary release];
944     [menuTableView setDataSource:nil];
945     [allTableView setDataSource:nil];
946     [controller release];
947     [availableItems release];
948     [submenuItems release];
949     [myItems release];
950     [df release];
951 }
952
953 @end