source: trunk/Cocoa/Pester/Source/NJRQTMediaPopUpButton.m @ 39

Last change on this file since 39 was 39, checked in by Nicholas Riley, 18 years ago

Lots of wonderful changes for which I composed a detailed svn commit
message, which it proceeded to mangle yet AGAIN on failed commit.

File size: 12.7 KB
Line 
1//
2//  NJRQTMediaPopUpButton.m
3//  Pester
4//
5//  Created by Nicholas Riley on Sat Oct 26 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "NJRQTMediaPopUpButton.h"
10#import "SoundFileManager.h"
11#import "NSMovie-NJRExtensions.h"
12#import "NSImage-NJRExtensions.h"
13
14static const int NJRQTMediaPopUpButtonMaxRecentItems = 10;
15
16@interface NJRQTMediaPopUpButton (Private)
17- (void)_setPath:(NSString *)path;
18- (BOOL)_validatePreview;
19@end
20
21@implementation NJRQTMediaPopUpButton
22
23// XXX handle refreshing sound list on resume
24// XXX don't add icons on Puma, they look like ass
25// XXX launch preview on a separate thread (if movies take too long to load, they inhibit the interface responsiveness)
26
27- (NSString *)_defaultKey;
28{
29    NSAssert([self tag] != 0, @"CanÕt track recently selected media for popup with tag 0: please set a tag");
30    return [NSString stringWithFormat: @"NJRQTMediaPopUpButtonMaxRecentItems tag %d", [self tag]];
31}
32
33- (void)_writeRecentMedia;
34{
35    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
36    [defaults setObject: recentMediaAliasData forKey: [self _defaultKey]];
37    [defaults synchronize];
38}
39
40- (NSMenuItem *)_addRecentMediaAtPath:(NSString *)path withAlias:(BDAlias *)alias;
41{
42    NSString *title = [[NSFileManager defaultManager] displayNameAtPath: path];
43    NSMenu *menu = [self menu];
44    NSMenuItem *item = [menu insertItemWithTitle: title action: @selector(_aliasSelected:) keyEquivalent: @"" atIndex: [menu indexOfItem: otherItem] + 1];
45    [item setTarget: self];
46    [item setRepresentedObject: alias];
47    [item setImage: [[[NSWorkspace sharedWorkspace] iconForFile: path] bestFitImageForSize: NSMakeSize(16, 16)]];
48    [recentMediaAliasData addObject: [alias aliasData]];
49    if ([recentMediaAliasData count] > NJRQTMediaPopUpButtonMaxRecentItems) {
50        [menu removeItemAtIndex: [menu numberOfItems] - 1];
51        [recentMediaAliasData removeObjectAtIndex: 0];
52    }
53    return item;
54}
55
56- (void)_addRecentMediaFromAliasesData:(NSArray *)aliasesData;
57{
58    NSEnumerator *e = [aliasesData objectEnumerator];
59    NSData *aliasData;
60    BDAlias *alias;
61    while ( (aliasData = [e nextObject]) != nil) {
62        if ( (alias = [[BDAlias alloc] initWithData: aliasData]) != nil) {
63            [self _addRecentMediaAtPath: [alias fullPath] withAlias: alias];
64            [alias release];
65        }
66    }
67}
68
69- (void)_validateRecentMedia;
70{
71    NSEnumerator *e = [recentMediaAliasData objectEnumerator];
72    NSData *aliasData;
73    NSMenuItem *item;
74    BDAlias *itemAlias;
75    int otherIndex = [self indexOfItem: otherItem];
76    int aliasDataCount = [recentMediaAliasData count];
77    int lastItemIndex = [self numberOfItems] - 1;
78    int recentItemCount = lastItemIndex - otherIndex;
79    int recentItemIndex = otherIndex;
80    NSAssert2(recentItemCount == aliasDataCount, @"Counted %d recent menu items, %d of alias data", recentItemCount, aliasDataCount);
81    while ( (aliasData = [e nextObject]) != nil) {
82        recentItemIndex++;
83        item = [self itemAtIndex: recentItemIndex];
84        itemAlias = [item representedObject];
85        if (![itemAlias aliasDataIsEqual: aliasData])
86            NSLog(@"item %d %@: %@", recentItemIndex, [item title], [itemAlias fullPath]);
87        else
88            NSLog(@"ITEM %d %@: %@ != aliasData %@", recentItemIndex, [item title], [itemAlias fullPath], [[BDAlias aliasWithData: aliasData] fullPath]);
89    }
90}
91
92- (void)awakeFromNib;
93{
94    NSMenu *menu;
95    NSMenuItem *item;
96    SoundFileManager *sfm = [SoundFileManager sharedSoundFileManager];
97    int soundCount = [sfm count];
98
99    [self removeAllItems];
100    menu = [self menu];
101    item = [menu addItemWithTitle: @"Alert sound" action: @selector(_beepSelected:) keyEquivalent: @""];
102    [item setTarget: self];
103    [menu addItem: [NSMenuItem separatorItem]];
104    if (soundCount == 0) {
105        item = [menu addItemWithTitle: @"CanÕt locate alert sounds" action: nil keyEquivalent: @""];
106        [item setEnabled: NO];
107    } else {
108        SoundFile *sf;
109        int i;
110        [sfm sortByName];
111        for (i = 0 ; i < soundCount ; i++) {
112            sf = [sfm soundFileAtIndex: i];
113            item = [menu addItemWithTitle: [sf name] action: @selector(_soundFileSelected:) keyEquivalent: @""];
114            [item setTarget: self];
115            [item setRepresentedObject: sf];
116            [item setImage: [[[NSWorkspace sharedWorkspace] iconForFile: [sf path]] bestFitImageForSize: NSMakeSize(16, 16)]];
117        }
118    }
119    [menu addItem: [NSMenuItem separatorItem]];
120    item = [menu addItemWithTitle: @"OtherÉ" action: @selector(select:) keyEquivalent: @""];
121    [item setTarget: self];
122    otherItem = [item retain];
123
124    recentMediaAliasData = [[NSMutableArray alloc] initWithCapacity: NJRQTMediaPopUpButtonMaxRecentItems + 1];
125    [self _addRecentMediaFromAliasesData: [[NSUserDefaults standardUserDefaults] arrayForKey: [self _defaultKey]]];
126
127    [self registerForDraggedTypes:
128        [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
129}
130
131- (void)dealloc;
132{
133    [recentMediaAliasData release]; recentMediaAliasData = nil;
134    [otherItem release];
135    [selectedAlias release]; [previousAlias release];
136    [super dealloc];
137}
138
139- (BDAlias *)selectedAlias;
140{
141    return selectedAlias;
142}
143
144- (void)_setAlias:(BDAlias *)alias;
145{
146   
147    BDAlias *oldAlias = [selectedAlias retain];
148    [previousAlias release];
149    previousAlias = oldAlias;
150    if (selectedAlias != alias) {
151        [selectedAlias release];
152        selectedAlias = [alias retain];
153    }
154}
155
156- (void)_setPath:(NSString *)path;
157{
158    [self _setAlias: [BDAlias aliasWithPath: path]];
159}
160
161- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
162{
163    NSString *path;
164    SoundFile *sf;
165    if (alias == nil) {
166        return [self itemAtIndex: 0];
167    }
168
169    // [self _validateRecentMedia];
170    path = [alias fullPath];
171    sf = [[SoundFileManager sharedSoundFileManager] soundFileFromPath: path];
172    NSLog(@"_itemForAlias: %@", path);
173
174    // selected a system sound?
175    if (sf != nil) {
176        NSLog(@"_itemForAlias: selected system sound");
177        return [self itemAtIndex: [self indexOfItemWithRepresentedObject: sf]];
178    } else {
179        NSEnumerator *e = [recentMediaAliasData objectEnumerator];
180        NSData *aliasData;
181        NSMenuItem *item;
182        int recentIndex = 0;
183
184        while ( (aliasData = [e nextObject]) != nil) {
185            // selected a recently selected, non-system sound?
186            if ([alias aliasDataIsEqual: aliasData]) {
187                int otherIndex = [self indexOfItem: otherItem];
188                int menuIndex = [recentMediaAliasData count] - recentIndex + otherIndex + 1;
189                if (menuIndex == otherIndex + 1) return [self itemAtIndex: menuIndex]; // already at top
190                // remove item, add (at top) later
191                NSLog(@"_itemForAlias removing item: count %d - idx %d + otherItemIndex %d + 1 = %d [%@]", [recentMediaAliasData count], recentIndex, otherIndex, menuIndex, [self itemAtIndex: menuIndex]);
192                [self removeItemAtIndex: menuIndex];
193                [recentMediaAliasData removeObjectAtIndex: recentIndex];
194                break;
195            }
196            recentIndex++;
197        }
198
199        // create the item
200        item = [self _addRecentMediaAtPath: path withAlias: alias];
201        [self _writeRecentMedia];
202        return item;
203    }
204}
205
206- (void)_invalidateSelection;
207{
208    [self _setAlias: previousAlias];
209    [self selectItem: [self _itemForAlias: [self selectedAlias]]];
210}
211
212- (BOOL)_validatePreview;
213{
214    [preview stop: self];
215    if (selectedAlias == nil) {
216        [preview setMovie: nil];
217        NSBeep();
218    } else {
219        NSMovie *movie = [[NSMovie alloc] initWithURL: [NSURL fileURLWithPath: [selectedAlias fullPath]] byReference: YES];
220        if ([movie hasAudio])
221            [preview setMovie: movie];
222        else {
223            [preview setMovie: nil];
224            if (movie == nil) {
225                NSBeginAlertSheet(@"Format not recognized", @"OK", nil, nil, [self window], nil, nil, nil, nil, @"The item you selected isnÕt a sound or movie recognized by QuickTime.  Please select a different item.");
226                [self _invalidateSelection];
227                return NO;
228            }
229            if (![movie hasAudio] && ![movie hasVideo]) {
230                NSBeginAlertSheet(@"No video or audio", @"OK", nil, nil, [self window], nil, nil, nil, nil, @"Ò%@Ó contains neither audio nor video content playable by QuickTime.  Please select a different item.", [[NSFileManager defaultManager] displayNameAtPath: [selectedAlias fullPath]]);
231                [self _invalidateSelection];
232                [movie release];
233                return NO;
234            }
235        }
236        [movie release];
237        [preview start: self];
238    }
239    return YES;
240}
241
242- (IBAction)stopSoundPreview:(id)sender;
243{
244    [preview stop: self];
245}
246
247- (void)_beepSelected:(NSMenuItem *)sender;
248{
249    [self _setAlias: nil];
250    [self _validatePreview];
251}
252
253- (void)_soundFileSelected:(NSMenuItem *)sender;
254{
255    [self _setPath: [(SoundFile *)[sender representedObject] path]];
256    if (![self _validatePreview]) {
257        [[self menu] removeItem: sender];
258    }
259}
260
261- (void)_aliasSelected:(NSMenuItem *)sender;
262{
263    BDAlias *alias = [sender representedObject];
264    int index = [self indexOfItem: sender], otherIndex = [self indexOfItem: otherItem];
265    [self _setAlias: alias];
266    if (![self _validatePreview]) {
267        [[self menu] removeItem: sender];
268    } else if (index > otherIndex + 1) { // move "other" item to top of list
269        int recentIndex = [recentMediaAliasData count] - index + otherIndex + 1;
270        NSMenuItem *item = [[self itemAtIndex: index] retain];
271        NSData *data = [[recentMediaAliasData objectAtIndex: recentIndex] retain];
272        [self removeItemAtIndex: index];
273        [[self menu] insertItem: item atIndex: otherIndex + 1];
274        [self selectItem: item];
275        [item release];
276        NSAssert(recentIndex >= 0, @"Recent media index invalid");
277        NSLog(@"_aliasSelected removing item %d - %d + %d + 1 = %d of recentMediaAliasData", [recentMediaAliasData count], index, otherIndex, recentIndex);
278        [recentMediaAliasData removeObjectAtIndex: recentIndex];
279        [recentMediaAliasData addObject: data];
280        [data release];
281    } else NSLog(@"_aliasSelected ...already at top");
282}
283
284- (IBAction)select:(id)sender;
285{
286    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
287    NSString *path = [selectedAlias fullPath];
288    [openPanel setAllowsMultipleSelection: NO];
289    [openPanel setCanChooseDirectories: NO];
290    [openPanel setCanChooseFiles: YES];
291    [openPanel beginSheetForDirectory: [path stringByDeletingLastPathComponent]
292                                 file: [path lastPathComponent]
293                                types: nil // XXX fix for QuickTime!
294                       modalForWindow: [self window]
295                        modalDelegate: self
296                       didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
297                          contextInfo: nil];
298}
299
300- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
301{
302    [sheet close];
303
304    if (returnCode == NSOKButton) {
305        NSArray *files = [sheet filenames];
306        NSAssert1([files count] == 1, @"%d items returned, only one expected", [files count]);
307        [self _setPath: [files objectAtIndex: 0]];
308        if ([self _validatePreview]) {
309            [self selectItem: [self _itemForAlias: selectedAlias]];
310        }
311    } else {
312        // "Other..." item is still selected, revert to previously selected item
313        // XXX issue with cancelling, top item in recent menu is sometimes duplicated!?
314        [self selectItem: [self _itemForAlias: selectedAlias]];
315    }
316    // [self _validateRecentMedia];
317}
318
319@end
320
321@implementation NJRQTMediaPopUpButton (NSDraggingDestination)
322
323- (BOOL)acceptsDragFrom:(id <NSDraggingInfo>)sender;
324{
325    NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
326
327    if (url == nil || ![url isFileURL]) return NO;
328    return YES;
329}
330
331- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
332{
333    if ([self acceptsDragFrom: sender] && [sender draggingSourceOperationMask] &
334        NSDragOperationCopy) {
335        dragAccepted = YES;
336        [self setNeedsDisplay: YES];
337        return NSDragOperationGeneric;
338    }
339    return NSDragOperationNone;
340}
341
342- (void)draggingExited:(id <NSDraggingInfo>)sender;
343{
344    dragAccepted = NO;
345    [self setNeedsDisplay: YES];
346}
347
348- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
349{
350    dragAccepted = NO;
351    [self setNeedsDisplay: YES];
352    return [self acceptsDragFrom: sender];
353}
354
355- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
356{
357    if ([sender draggingSource] != self) {
358        NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
359        if (url == nil) return NO;
360        [self _setPath: [url path]];
361        [self _validatePreview];
362    }
363    return YES;
364}
365
366@end
Note: See TracBrowser for help on using the repository browser.