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