Lots of rewritten code. Not yet working because the status item doesn't
[MenuTunes.git] / PreferencesController.m
1 #import "PreferencesController.h"
2 #import "MainController.h"
3 #import "HotKeyCenter.h"
4
5 @implementation PreferencesController
6
7 - (id)initWithMenuTunes:(MainController *)tunes;
8 {
9     if ( (self = [super init]) ) {
10         int i;
11         NSImageCell *imgCell = [[[NSImageCell alloc] init] autorelease];
12         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
13         
14         mt = [tunes retain];
15         [mt registerDefaults];
16         
17         //Load the nib
18         [NSBundle loadNibNamed:@"Preferences" owner:self];
19         
20         //Set the table view cells up
21         [imgCell setImageScaling:NSScaleNone];
22         [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
23         [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
24         
25         //Register for drag and drop
26         [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", @"AllTableViewPboardType", nil]];
27         [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", @"AllTableViewPboardType", nil]];
28         
29         //Set the list of items you can have.
30         availableItems = [[NSMutableArray alloc] initWithObjects:@"Current Track Info",  @"Upcoming Songs", @"Playlists", @"EQ Presets", @"Song Rating", @"Play/Pause", @"Next Track", @"Previous Track", @"Fast Forward", @"Rewind", @"<separator>", nil];
31         
32         //Get our preferred menu
33         myItems = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"menu"] mutableCopy];
34         
35         //Delete items in the availableItems array that are already part of the menu
36         for (i = 0; i < [myItems count]; i++) {
37             NSString *item = [myItems objectAtIndex:i];
38             if (![item isEqualToString:@"<separator>"])
39             {
40                 [availableItems removeObject:item];
41             }
42         }
43         
44         //Items that show should a submenu image
45         submenuItems = [[NSArray alloc] initWithObjects:@"Upcoming Songs", @"Playlists", @"EQ Presets", @"Song Rating", nil];
46         
47         //Fill in the number of songs in advance to show field
48         [songsInAdvance setIntValue:[defaults integerForKey:@"SongsInAdvance"]];
49         
50         //Fill in hot key buttons
51         if ([defaults objectForKey:@"PlayPause"]){
52             playPauseCombo = [defaults keyComboForKey:@"PlayPause"];
53             [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
54         } else {
55             playPauseCombo = [[KeyCombo alloc] init];
56         }
57         
58         if ([defaults objectForKey:@"NextTrack"]) {
59             nextTrackCombo = [defaults keyComboForKey:@"NextTrack"];
60             [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
61         } else {
62             nextTrackCombo = [[KeyCombo alloc] init];
63         }
64         
65         if ([defaults objectForKey:@"PrevTrack"]) {
66             prevTrackCombo = [defaults keyComboForKey:@"PrevTrack"];
67             [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
68         } else {
69             prevTrackCombo = [[KeyCombo alloc] init];
70         }
71         
72         if ([defaults objectForKey:@"TrackInfo"]) {
73             trackInfoCombo = [defaults keyComboForKey:@"TrackInfo"];
74             [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
75         } else {
76             trackInfoCombo = [[KeyCombo alloc] init];
77         }
78         
79         if ([defaults objectForKey:@"UpcomingSongs"]) {
80             upcomingSongsCombo = [defaults keyComboForKey:@"UpcomingSongs"];
81             [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
82         } else {
83             upcomingSongsCombo = [[KeyCombo alloc] init];
84         }
85         
86         //Check current track info buttons
87         [albumCheckbox setState:[defaults boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
88         [nameCheckbox setState:[defaults boolForKey:@"showName"] ? NSOnState : NSOffState];
89         [artistCheckbox setState:[defaults boolForKey:@"showArtist"] ? NSOnState : NSOffState];
90         [trackTimeCheckbox setState:[defaults boolForKey:@"showTime"] ? NSOnState : NSOffState];
91         
92         //Set the launch at login checkbox state
93         {
94             NSMutableDictionary *loginwindow;
95             NSMutableArray *loginarray;
96             int i;
97             
98             [defaults synchronize];
99             loginwindow = [[defaults persistentDomainForName:@"loginwindow"] mutableCopy];
100             loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
101             
102             for (i = 0; i < [loginarray count]; i++) {
103                 NSDictionary *tempDict = [loginarray objectAtIndex:i];
104                 if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
105                     [launchAtLoginCheckbox setState:NSOnState];
106                 }
107             }
108         }
109         
110         //Show our window
111         [window setLevel:NSStatusWindowLevel];
112         [window center];
113         [window makeKeyAndOrderFront:nil];
114         [window setDelegate:self];
115     }
116     return self;
117 }
118
119 - (void)dealloc
120 {
121     [self setKeyCombo:nil];
122     [playPauseCombo release];
123     [nextTrackCombo release];
124     [prevTrackCombo release];
125     [trackInfoCombo release];
126     [upcomingSongsCombo release];
127     [keyComboPanel release];
128     [menuTableView setDataSource:nil];
129     [allTableView setDataSource:nil];
130     [mt release];
131     [availableItems release];
132     [submenuItems release];
133     [myItems release];
134 }
135
136 - (IBAction)apply:(id)sender
137 {
138     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
139     [defaults setObject:myItems forKey:@"menu"];
140     
141     //Set key combos
142     [defaults setKeyCombo:playPauseCombo forKey:@"PlayPause"];
143     [defaults setKeyCombo:nextTrackCombo forKey:@"NextTrack"];
144     [defaults setKeyCombo:prevTrackCombo forKey:@"PrevTrack"];
145     [defaults setKeyCombo:trackInfoCombo forKey:@"TrackInfo"];
146     [defaults setKeyCombo:upcomingSongsCombo forKey:@"UpcomingSongs"];
147     
148     //Set info checkboxes
149     [defaults setBool:[albumCheckbox state] forKey:@"showAlbum"];
150     [defaults setBool:[nameCheckbox state] forKey:@"showName"];
151     [defaults setBool:[artistCheckbox state] forKey:@"showArtist"];
152     [defaults setBool:[trackTimeCheckbox state] forKey:@"showTime"];
153     
154     //Here we set whether we will launch at login by modifying loginwindow.plist
155     if ([launchAtLoginCheckbox state] == NSOnState) {
156         NSMutableDictionary *loginwindow;
157         NSMutableArray *loginarray;
158         ComponentInstance temp = OpenDefaultComponent(kOSAComponentType, kAppleScriptSubtype);;
159         int i;
160         BOOL skip = NO;
161         
162         [defaults synchronize];
163         loginwindow = [[defaults persistentDomainForName:@"loginwindow"] mutableCopy];
164         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
165         
166         for (i = 0; i < [loginarray count]; i++) {
167             NSDictionary *tempDict = [loginarray objectAtIndex:i];
168             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
169                 skip = YES;
170             }
171         }
172         
173         if (!skip) {
174             AEDesc scriptDesc, resultDesc;
175             NSString *script = [NSString stringWithFormat:@"tell application \"System Events\"\nmake new login item at end of login items with properties {path:\"%@\", kind:\"APPLICATION\"}\nend tell", [[NSBundle mainBundle] bundlePath]];
176             
177             AECreateDesc(typeChar, [script cString], [script cStringLength], 
178         &scriptDesc);
179             
180             OSADoScript(temp, &scriptDesc, kOSANullScript, typeChar, kOSAModeCanInteract, &resultDesc);
181             
182             AEDisposeDesc(&scriptDesc);
183             AEDisposeDesc(&resultDesc);
184             CloseComponent(temp);
185         }
186     } else {
187         NSMutableDictionary *loginwindow;
188         NSMutableArray *loginarray;
189         int i;
190         
191         [[NSUserDefaults standardUserDefaults] synchronize];
192         loginwindow = [[[NSUserDefaults standardUserDefaults] persistentDomainForName:@"loginwindow"] mutableCopy];
193         loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
194         
195         for (i = 0; i < [loginarray count]; i++) {
196             NSDictionary *tempDict = [loginarray objectAtIndex:i];
197             if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
198                 [loginarray removeObjectAtIndex:i];
199                 [defaults setPersistentDomain:loginwindow forName:@"loginwindow"];
200                 [defaults synchronize];
201                 break;
202             }
203         }
204     }
205     
206     //Set songs in advance
207     if ([songsInAdvance intValue]) {
208         [defaults setInteger:[songsInAdvance intValue] forKey:@"SongsInAdvance"];
209     } else {
210         [defaults setInteger:5 forKey:@"SongsInAdvance"];
211     }
212     
213     /*{
214         NSArray *apps = [[NSWorkspace sharedWorkspace] launchedApplications];
215         int i;
216         
217         for (i = 0; i < [apps count]; i++) {
218             if ([[[apps objectAtIndex:i] objectForKey:@"NSApplicationName"]
219                     isEqualToString:@"iTunes"]) {
220                 [mt rebuildMenu];
221             }
222         }
223     }*/
224     [mt clearHotKeys];
225 }
226
227 - (IBAction)cancel:(id)sender
228 {
229     [window close];
230     [mt closePreferences];
231 }
232
233 - (IBAction)cancelHotKey:(id)sender
234 {
235     [[NSNotificationCenter defaultCenter] removeObserver:self];
236     [NSApp endSheet:keyComboPanel];
237     [keyComboPanel orderOut:nil];
238 }
239
240 - (IBAction)clearHotKey:(id)sender
241 {
242     [self setKeyCombo:[KeyCombo clearKeyCombo]];
243 }
244
245 - (IBAction)okHotKey:(id)sender
246 {
247     NSString *string = [combo userDisplayRep];
248     
249     if (string == nil) {
250         string = @"None";
251     }
252     if ([setHotKey isEqualToString:@"PlayPause"]) {
253         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
254             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) &&
255             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
256             
257             [window setLevel:NSNormalWindowLevel];
258             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
259             [window setLevel:NSStatusWindowLevel];
260             return;
261         }
262         playPauseCombo = [combo copy];
263         [playPauseButton setTitle:string];
264     }
265     else if ([setHotKey isEqualToString:@"NextTrack"])
266     {
267         if (([combo isEqual:playPauseCombo] || [combo isEqual:prevTrackCombo] ||
268             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
269             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
270             
271             [window setLevel:NSNormalWindowLevel];
272             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
273             [window setLevel:NSStatusWindowLevel];
274             return;
275         }
276         nextTrackCombo = [combo copy];
277         [nextTrackButton setTitle:string];
278     }
279     else if ([setHotKey isEqualToString:@"PrevTrack"])
280     {
281         if (([combo isEqual:nextTrackCombo] || [combo isEqual:playPauseCombo] ||
282             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
283             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
284             
285             [window setLevel:NSNormalWindowLevel];
286             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
287             [window setLevel:NSStatusWindowLevel];
288             return;
289         }
290         prevTrackCombo = [combo copy];
291         [previousTrackButton setTitle:string];
292     }
293     else if ([setHotKey isEqualToString:@"TrackInfo"])
294     {
295         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
296             [combo isEqual:playPauseCombo] || [combo isEqual:upcomingSongsCombo]) && 
297             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
298             
299             [window setLevel:NSNormalWindowLevel];
300             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
301             [window setLevel:NSStatusWindowLevel];
302             return;
303         }
304         trackInfoCombo = [combo copy];
305         [trackInfoButton setTitle:string];
306     }
307     else if ([setHotKey isEqualToString:@"UpcomingSongs"])
308     {
309         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
310             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo]) && 
311             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
312             
313             [window setLevel:NSNormalWindowLevel];
314             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
315             [window setLevel:NSStatusWindowLevel];
316             return;
317         }
318         upcomingSongsCombo = [combo copy];
319         [upcomingSongsButton setTitle:string];
320     }
321     [self cancelHotKey:sender];
322 }
323
324 - (IBAction)save:(id)sender
325 {
326     [self apply:nil];
327     [window close];
328     [mt closePreferences];
329 }
330
331 - (IBAction)setCurrentTrackInfo:(id)sender
332 {
333     [self setKeyCombo:trackInfoCombo];
334     [self setHotKey:@"TrackInfo"];
335 }
336
337 - (IBAction)setNextTrack:(id)sender
338 {
339     [self setKeyCombo:nextTrackCombo];
340     [self setHotKey:@"NextTrack"];
341 }
342
343 - (IBAction)setPlayPause:(id)sender
344 {
345     [self setKeyCombo:playPauseCombo];
346     [self setHotKey:@"PlayPause"];
347 }
348
349 - (IBAction)setPreviousTrack:(id)sender
350 {
351     [self setKeyCombo:prevTrackCombo];
352     [self setHotKey:@"PrevTrack"];
353 }
354
355 - (IBAction)setUpcomingSongs:(id)sender
356 {
357     [self setKeyCombo:upcomingSongsCombo];
358     [self setHotKey:@"UpcomingSongs"];
359 }
360
361 - (void)setHotKey:(NSString *)key
362 {
363     setHotKey = key;
364     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
365     [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
366 }
367
368 - (void)keyEvent:(NSNotification *)note
369 {
370     NSDictionary *info = [note userInfo];
371     short keyCode;
372     long modifiers;
373     KeyCombo *newCombo;
374     
375     keyCode = [[info objectForKey:@"KeyCode"] shortValue];
376     modifiers = [[info objectForKey:@"Modifiers"] longValue];
377     
378     newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
379     [self setKeyCombo:newCombo];
380 }
381
382 - (void)setKeyCombo:(KeyCombo *)newCombo
383 {
384     NSString *string;
385     [combo release];
386     combo = [newCombo copy];
387     
388     string = [combo userDisplayRep];
389     if (string == nil) {
390         string = @"";
391     }
392     [keyComboField setStringValue:string];
393 }
394
395 //
396 //
397
398 - (void)windowWillClose:(NSNotification *)note
399 {
400     [mt closePreferences];
401 }
402
403 //
404 //
405 // Table View Datasource Methods
406 //
407 //
408
409 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
410 {
411     if (aTableView == menuTableView) {
412         return [myItems count];
413     } else {
414         return [availableItems count];
415     }
416 }
417
418 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
419 {
420     if (aTableView == menuTableView) {
421         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
422             return [myItems objectAtIndex:rowIndex];
423         } else {
424             if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
425             {
426                 return [NSImage imageNamed:@"submenu"];
427             } else {
428                 return nil;
429             }
430         }
431     } else {
432         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
433             return [availableItems objectAtIndex:rowIndex];
434         } else {
435             if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]]) {
436                 return [NSImage imageNamed:@"submenu"];
437             } else {
438                 return nil;
439             }
440         }
441     }
442 }
443
444 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
445 {
446     if (tableView == menuTableView) {
447         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
448         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
449         return YES;
450     }
451     
452     if (tableView == allTableView) {
453         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
454         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
455         return YES;
456     }
457     return NO;
458 }
459
460 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
461 {
462     NSPasteboard *pb;
463     int dragRow;
464     NSString *dragData, *temp;
465     
466     pb = [info draggingPasteboard];
467     
468     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
469         dragData = [pb stringForType:@"MenuTableViewPboardType"];
470         dragRow = [dragData intValue];
471         temp = [myItems objectAtIndex:dragRow];
472         [myItems removeObjectAtIndex:dragRow];
473         
474         if (tableView == menuTableView) {
475             if (row > dragRow) {
476                 [myItems insertObject:temp atIndex:row - 1];
477             } else {
478                 [myItems insertObject:temp atIndex:row];
479             }
480         } else {
481             if (![temp isEqualToString:@"<separator>"]) {
482                 [availableItems addObject:temp];
483             }
484         }
485     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
486         dragData = [pb stringForType:@"AllTableViewPboardType"];
487         dragRow = [dragData intValue];
488         temp = [availableItems objectAtIndex:dragRow];
489         
490         if (![temp isEqualToString:@"<separator>"]) {
491             [availableItems removeObjectAtIndex:dragRow];
492         }
493         [myItems insertObject:temp atIndex:row];
494     }
495     
496     [menuTableView reloadData];
497     [allTableView reloadData];
498     return YES;
499 }
500
501 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
502 {
503     if (tableView == allTableView) {
504         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
505             return NSDragOperationNone;
506         }
507         
508         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
509             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
510             if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"]) {
511                 return NSDragOperationNone;
512             }
513         }
514         
515         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
516         return NSDragOperationGeneric;
517     }
518     
519     if (operation == NSTableViewDropOn || row == -1)
520     {
521         return NSDragOperationNone;
522     }
523     
524     return NSDragOperationGeneric;
525 }
526
527 @end