Hotkeys do not show in the hotkey chooser if they don't have a modifier key.
[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         int i;
11         NSImageCell *imgCell = [[[NSImageCell alloc] init] autorelease];
12         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
13         
14         mt = [tunes retain];
15         [mt registerDefaultsIfNeeded];
16         
17         //Load the nib
18         [NSBundle loadNibNamed:@"Preferences" owner:self];
19         
20         //Show our window
21         [window setLevel:NSStatusWindowLevel];
22         [window center];
23         [window makeKeyAndOrderFront:nil];
24         
25         //Set the table view cells up
26         [imgCell setImageScaling:NSScaleNone];
27         [[menuTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
28         [[allTableView tableColumnWithIdentifier:@"submenu"] setDataCell:imgCell];
29         
30         //Register for drag and drop
31         [menuTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", @"AllTableViewPboardType", nil]];
32         [allTableView registerForDraggedTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", @"AllTableViewPboardType", nil]];
33         
34         //Set the list of items you can have.
35         availableItems = [[NSMutableArray alloc] initWithObjects:@"Current Track Info",  @"Upcoming Songs", @"Playlists", @"EQ Presets", @"Play/Pause", @"Next Track", @"Previous Track", @"Fast Forward", @"Rewind", @"<separator>", nil];
36         
37         //Get our preferred menu
38         myItems = [[[NSUserDefaults standardUserDefaults] arrayForKey:@"menu"] mutableCopy];
39         
40         //Delete items in the availableItems array that are already part of the menu
41         for (i = 0; i < [myItems count]; i++) {
42             NSString *item = [myItems objectAtIndex:i];
43             if (![item isEqualToString:@"<separator>"])
44             {
45                 [availableItems removeObject:item];
46             }
47         }
48         
49         //Items that show should a submenu image
50         submenuItems = [[NSArray alloc] initWithObjects:@"Upcoming Songs", @"Playlists", @"EQ Presets", nil];
51         
52         //Fill in the number of songs in advance to show field
53         [songsInAdvance setIntValue:[defaults integerForKey:@"SongsInAdvance"]];
54         
55         //Fill in hot key buttons
56         if ([defaults objectForKey:@"PlayPause"]){
57             playPauseCombo = [defaults keyComboForKey:@"PlayPause"];
58             [playPauseButton setTitle:[playPauseCombo userDisplayRep]];
59         } else {
60             playPauseCombo = [[KeyCombo alloc] init];
61         }
62         
63         if ([defaults objectForKey:@"NextTrack"]) {
64             nextTrackCombo = [defaults keyComboForKey:@"NextTrack"];
65             [nextTrackButton setTitle:[nextTrackCombo userDisplayRep]];
66         } else {
67             nextTrackCombo = [[KeyCombo alloc] init];
68         }
69         
70         if ([defaults objectForKey:@"PrevTrack"]) {
71             prevTrackCombo = [defaults keyComboForKey:@"PrevTrack"];
72             [previousTrackButton setTitle:[prevTrackCombo userDisplayRep]];
73         } else {
74             prevTrackCombo = [[KeyCombo alloc] init];
75         }
76         
77         if ([defaults objectForKey:@"TrackInfo"]) {
78             trackInfoCombo = [defaults keyComboForKey:@"TrackInfo"];
79             [trackInfoButton setTitle:[trackInfoCombo userDisplayRep]];
80         } else {
81             trackInfoCombo = [[KeyCombo alloc] init];
82         }
83         
84         if ([defaults objectForKey:@"UpcomingSongs"]) {
85             upcomingSongsCombo = [defaults keyComboForKey:@"UpcomingSongs"];
86             [upcomingSongsButton setTitle:[upcomingSongsCombo userDisplayRep]];
87         } else {
88             upcomingSongsCombo = [[KeyCombo alloc] init];
89         }
90         
91         //Check current track info buttons
92         [albumCheckbox setState:[defaults boolForKey:@"showAlbum"] ? NSOnState : NSOffState];
93         [nameCheckbox setState:[defaults boolForKey:@"showName"] ? NSOnState : NSOffState];
94         [artistCheckbox setState:[defaults boolForKey:@"showArtist"] ? NSOnState : NSOffState];
95         [trackTimeCheckbox setState:[defaults boolForKey:@"showTime"] ? NSOnState : NSOffState];
96         
97         //Set the launch at login checkbox state
98         {
99             NSMutableDictionary *loginwindow;
100             NSMutableArray *loginarray;
101             int i;
102             
103             [defaults synchronize];
104             loginwindow = [[defaults persistentDomainForName:@"loginwindow"] mutableCopy];
105             loginarray = [loginwindow objectForKey:@"AutoLaunchedApplicationDictionary"];
106             
107             for (i = 0; i < [loginarray count]; i++) {
108                 NSDictionary *tempDict = [loginarray objectAtIndex:i];
109                 if ([[[tempDict objectForKey:@"Path"] lastPathComponent] isEqualToString:[[[NSBundle mainBundle] bundlePath] lastPathComponent]]) {
110                     [launchAtLoginCheckbox setState:NSOnState];
111                 }
112             }
113         }
114     }
115     return self;
116 }
117
118 - (void)dealloc
119 {
120     [self setKeyCombo:nil];
121     [playPauseCombo release];
122     [nextTrackCombo release];
123     [prevTrackCombo release];
124     [trackInfoCombo release];
125     [upcomingSongsCombo release];
126     [keyComboPanel release];
127     [menuTableView setDataSource:nil];
128     [allTableView setDataSource:nil];
129     [mt release];
130     [availableItems release];
131     [submenuItems release];
132     [myItems release];
133 }
134
135 - (IBAction)apply:(id)sender
136 {
137     ProcessSerialNumber psn;
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     psn = [mt iTunesPSN];
214     if (!((psn.highLongOfPSN == kNoProcess) && (psn.lowLongOfPSN == 0))) {
215         [mt rebuildMenu];
216     }
217     [mt clearHotKeys];
218 }
219
220 - (IBAction)cancel:(id)sender
221 {
222     [window close];
223     [mt closePreferences];
224 }
225
226 - (IBAction)cancelHotKey:(id)sender
227 {
228     [[NSNotificationCenter defaultCenter] removeObserver:self];
229     [NSApp endSheet:keyComboPanel];
230     [keyComboPanel orderOut:nil];
231 }
232
233 - (IBAction)clearHotKey:(id)sender
234 {
235     [self setKeyCombo:[KeyCombo clearKeyCombo]];
236 }
237
238 - (IBAction)okHotKey:(id)sender
239 {
240     NSString *string = [combo userDisplayRep];
241     
242     if (string == nil) {
243         string = @"None";
244     }
245     if ([setHotKey isEqualToString:@"PlayPause"]) {
246         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
247             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) &&
248             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
249             
250             [window setLevel:NSNormalWindowLevel];
251             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
252             [window setLevel:NSStatusWindowLevel];
253             return;
254         }
255         playPauseCombo = [combo copy];
256         [playPauseButton setTitle:string];
257     }
258     else if ([setHotKey isEqualToString:@"NextTrack"])
259     {
260         if (([combo isEqual:playPauseCombo] || [combo isEqual:prevTrackCombo] ||
261             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
262             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
263             
264             [window setLevel:NSNormalWindowLevel];
265             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
266             [window setLevel:NSStatusWindowLevel];
267             return;
268         }
269         nextTrackCombo = [combo copy];
270         [nextTrackButton setTitle:string];
271     }
272     else if ([setHotKey isEqualToString:@"PrevTrack"])
273     {
274         if (([combo isEqual:nextTrackCombo] || [combo isEqual:playPauseCombo] ||
275             [combo isEqual:trackInfoCombo] || [combo isEqual:upcomingSongsCombo]) && 
276             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
277             
278             [window setLevel:NSNormalWindowLevel];
279             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
280             [window setLevel:NSStatusWindowLevel];
281             return;
282         }
283         prevTrackCombo = [combo copy];
284         [previousTrackButton setTitle:string];
285     }
286     else if ([setHotKey isEqualToString:@"TrackInfo"])
287     {
288         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
289             [combo isEqual:playPauseCombo] || [combo isEqual:upcomingSongsCombo]) && 
290             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
291             
292             [window setLevel:NSNormalWindowLevel];
293             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
294             [window setLevel:NSStatusWindowLevel];
295             return;
296         }
297         trackInfoCombo = [combo copy];
298         [trackInfoButton setTitle:string];
299     }
300     else if ([setHotKey isEqualToString:@"UpcomingSongs"])
301     {
302         if (([combo isEqual:nextTrackCombo] || [combo isEqual:prevTrackCombo] ||
303             [combo isEqual:trackInfoCombo] || [combo isEqual:playPauseCombo]) && 
304             !(([combo modifiers] == -1) && ([combo keyCode] == -1))) {
305             
306             [window setLevel:NSNormalWindowLevel];
307             NSRunAlertPanel(@"Duplicate Key Combo", @"Please choose a unique key combo.", @"OK", nil, nil, nil);
308             [window setLevel:NSStatusWindowLevel];
309             return;
310         }
311         upcomingSongsCombo = [combo copy];
312         [upcomingSongsButton setTitle:string];
313     }
314     [self cancelHotKey:sender];
315 }
316
317 - (IBAction)save:(id)sender
318 {
319     [self apply:nil];
320     [window close];
321     [mt closePreferences];
322 }
323
324 - (IBAction)setCurrentTrackInfo:(id)sender
325 {
326     [self setKeyCombo:trackInfoCombo];
327     [self setHotKey:@"TrackInfo"];
328 }
329
330 - (IBAction)setNextTrack:(id)sender
331 {
332     [self setKeyCombo:nextTrackCombo];
333     [self setHotKey:@"NextTrack"];
334 }
335
336 - (IBAction)setPlayPause:(id)sender
337 {
338     [self setKeyCombo:playPauseCombo];
339     [self setHotKey:@"PlayPause"];
340 }
341
342 - (IBAction)setPreviousTrack:(id)sender
343 {
344     [self setKeyCombo:prevTrackCombo];
345     [self setHotKey:@"PrevTrack"];
346 }
347
348 - (IBAction)setUpcomingSongs:(id)sender
349 {
350     [self setKeyCombo:upcomingSongsCombo];
351     [self setHotKey:@"UpcomingSongs"];
352 }
353
354 - (void)setHotKey:(NSString *)key
355 {
356     setHotKey = key;
357     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyEvent:) name:@"KeyBroadcasterEvent" object:nil];
358     [NSApp beginSheet:keyComboPanel modalForWindow:window modalDelegate:self didEndSelector:nil contextInfo:nil];
359 }
360
361 - (void)keyEvent:(NSNotification *)note
362 {
363     NSDictionary *info = [note userInfo];
364     short keyCode;
365     long modifiers;
366     KeyCombo *newCombo;
367     
368     keyCode = [[info objectForKey:@"KeyCode"] shortValue];
369     modifiers = [[info objectForKey:@"Modifiers"] longValue];
370     
371     newCombo = [[KeyCombo alloc] initWithKeyCode:keyCode andModifiers:modifiers];
372     [self setKeyCombo:newCombo];
373 }
374
375 - (void)setKeyCombo:(KeyCombo *)newCombo
376 {
377     NSString *string;
378     [combo release];
379     combo = [newCombo copy];
380     
381     string = [combo userDisplayRep];
382     if (string == nil) {
383         string = @"";
384     }
385     [keyComboField setStringValue:string];
386 }
387
388 //
389 //
390 // Table View Datasource Methods
391 //
392 //
393
394 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
395 {
396     if (aTableView == menuTableView) {
397         return [myItems count];
398     } else {
399         return [availableItems count];
400     }
401 }
402
403 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
404 {
405     if (aTableView == menuTableView) {
406         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
407             return [myItems objectAtIndex:rowIndex];
408         } else {
409             if ([submenuItems containsObject:[myItems objectAtIndex:rowIndex]])
410             {
411                 return [NSImage imageNamed:@"submenu"];
412             } else {
413                 return nil;
414             }
415         }
416     } else {
417         if ([[aTableColumn identifier] isEqualToString:@"name"]) {
418             return [availableItems objectAtIndex:rowIndex];
419         } else {
420             if ([submenuItems containsObject:[availableItems objectAtIndex:rowIndex]]) {
421                 return [NSImage imageNamed:@"submenu"];
422             } else {
423                 return nil;
424             }
425         }
426     }
427 }
428
429 - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
430 {
431     if (tableView == menuTableView) {
432         [pboard declareTypes:[NSArray arrayWithObjects:@"MenuTableViewPboardType", nil] owner:self];
433         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"MenuTableViewPboardType"];
434         return YES;
435     }
436     
437     if (tableView == allTableView) {
438         [pboard declareTypes:[NSArray arrayWithObjects:@"AllTableViewPboardType", nil] owner:self];
439         [pboard setString:[[rows objectAtIndex:0] stringValue] forType:@"AllTableViewPboardType"];
440         return YES;
441     }
442     return NO;
443 }
444
445 - (BOOL)tableView:(NSTableView*)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
446 {
447     NSPasteboard *pb;
448     int dragRow;
449     NSString *dragData, *temp;
450     
451     pb = [info draggingPasteboard];
452     
453     if ([[pb types] containsObject:@"MenuTableViewPboardType"]) {
454         dragData = [pb stringForType:@"MenuTableViewPboardType"];
455         dragRow = [dragData intValue];
456         temp = [myItems objectAtIndex:dragRow];
457         [myItems removeObjectAtIndex:dragRow];
458         
459         if (tableView == menuTableView) {
460             if (row > dragRow) {
461                 [myItems insertObject:temp atIndex:row - 1];
462             } else {
463                 [myItems insertObject:temp atIndex:row];
464             }
465         } else {
466             if (![temp isEqualToString:@"<separator>"]) {
467                 [availableItems addObject:temp];
468             }
469         }
470     } else if ([[pb types] containsObject:@"AllTableViewPboardType"]) {
471         dragData = [pb stringForType:@"AllTableViewPboardType"];
472         dragRow = [dragData intValue];
473         temp = [availableItems objectAtIndex:dragRow];
474         
475         if (![temp isEqualToString:@"<separator>"]) {
476             [availableItems removeObjectAtIndex:dragRow];
477         }
478         [myItems insertObject:temp atIndex:row];
479     }
480     
481     [menuTableView reloadData];
482     [allTableView reloadData];
483     return YES;
484 }
485
486 - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
487 {
488     if (tableView == allTableView) {
489         if ([[[info draggingPasteboard] types] containsObject:@"AllTableViewPboardType"]) {
490             return NSDragOperationNone;
491         }
492         
493         if ([[[info draggingPasteboard] types] containsObject:@"MenuTableViewPboardType"]) {
494             NSString *item = [myItems objectAtIndex:[[[info draggingPasteboard] stringForType:@"MenuTableViewPboardType"] intValue]];
495             if ([item isEqualToString:@"PreferencesÉ"] || [item isEqualToString:@"Quit"]) {
496                 return NSDragOperationNone;
497             }
498         }
499         
500         [tableView setDropRow:-1 dropOperation:NSTableViewDropOn];
501         return NSDragOperationGeneric;
502     }
503     
504     if (operation == NSTableViewDropOn || row == -1)
505     {
506         return NSDragOperationNone;
507     }
508     
509     return NSDragOperationGeneric;
510 }
511
512 @end