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

Last change on this file since 39 was 39, checked in by Nicholas Riley, 20 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.