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

Last change on this file since 602 was 602, checked in by Nicholas Riley, 9 years ago

Rename variables which shadow stdlib functions (and maxSize).

File size: 21.4 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 "NJRFSObjectSelector.h"
11#import "NJRSoundManager.h"
12#import "QTMovie-NJRExtensions.h"
13#import "NSMenuItem-NJRExtensions.h"
14
15#include <limits.h>
16
17static const int NJRQTMediaPopUpButtonMaxRecentItems = 10;
18
19NSString * const NJRQTMediaPopUpButtonMovieChangedNotification = @"NJRQTMediaPopUpButtonMovieChangedNotification";
20
21@interface NJRQTMediaPopUpButton (Private)
22- (void)_setPath:(NSString *)path;
23- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
24- (BOOL)_validateWithPreview:(BOOL)doPreview;
25- (void)_startSoundPreview;
26- (void)_soundPreviewDidEnd:(NSNotification *)notification;
27- (void)_resetPreview;
28- (void)_resetOutputVolume;
29- (void)_aliasSelected:(NSMenuItem *)sender;
30- (void)_beepSelected:(NSMenuItem *)sender;
31- (void)_systemSoundSelected:(NSMenuItem *)sender;
32@end
33
34@interface NJRQTMediaPopUpButton (NSOpenPanelRuntime)
35- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
36@end
37
38@implementation NJRQTMediaPopUpButton
39
40// XXX handle refreshing sound list on resume
41// XXX launch preview on a separate thread (if movies take too long to load, they inhibit the interface responsiveness)
42
43// Recent media layout:
44// Most recent media are at TOP of menu (smaller item numbers, starting at [self indexOfItem: otherItem] + 1)
45// Most recent media are at END of array (larger indices)
46
47#pragma mark recently selected media tracking
48
49- (NSString *)_defaultKey;
50{
51    NSAssert([self tag] != 0, NSLocalizedString(@"Can't track recently selected media for popup with tag 0: please set a tag", "Assertion for QuickTime media popup button if tag is 0"));
52    return [NSString stringWithFormat: @"NJRQTMediaPopUpButtonMaxRecentItems tag %d", [self tag]];
53}
54
55- (void)_writeRecentMedia;
56{
57    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
58    [defaults setObject: recentMediaAliasData forKey: [self _defaultKey]];
59    [defaults synchronize];
60}
61
62- (NSMenuItem *)_addRecentMediaAtPath:(NSString *)path withAlias:(BDAlias *)alias;
63{
64    NSString *title = [[NSFileManager defaultManager] displayNameAtPath: path];
65    NSMenu *menu = [self menu];
66    NSMenuItem *item;
67    if (title == nil || path == nil) return nil;
68    item = [menu insertItemWithTitle: title action: @selector(_aliasSelected:) keyEquivalent: @"" atIndex: [menu indexOfItem: otherItem] + 1];
69    [item setTarget: self];
70    [item setRepresentedObject: alias];
71    [item setImageFromPath: path];
72    [recentMediaAliasData addObject: [alias aliasData]];
73    if ([recentMediaAliasData count] > NJRQTMediaPopUpButtonMaxRecentItems) {
74        [menu removeItemAtIndex: [menu numberOfItems] - 1];
75        [recentMediaAliasData removeObjectAtIndex: 0];
76    }
77    return item;
78}
79
80- (void)_addRecentMediaFromAliasesData:(NSArray *)aliasesData;
81{
82    NSEnumerator *e = [aliasesData objectEnumerator];
83    NSData *aliasData;
84    BDAlias *alias;
85    while ( (aliasData = [e nextObject]) != nil) {
86        if ( (alias = [[BDAlias alloc] initWithData: aliasData]) != nil) {
87            [self _addRecentMediaAtPath: [alias fullPath] withAlias: alias];
88            [alias release];
89        }
90    }
91}
92
93- (void)_validateRecentMedia;
94{
95    NSEnumerator *e = [recentMediaAliasData reverseObjectEnumerator];
96    NSData *aliasData;
97    NSMenuItem *item;
98    BDAlias *itemAlias;
99    int otherIndex = [self indexOfItem: otherItem];
100    int aliasDataCount = [recentMediaAliasData count];
101    int lastItemIndex = [self numberOfItems] - 1;
102    int recentItemCount = lastItemIndex - otherIndex;
103    int recentItemIndex = otherIndex;
104    NSAssert2(recentItemCount == aliasDataCount, @"Counted %d recent menu items, %d of alias data", recentItemCount, aliasDataCount);
105    while ( (aliasData = [e nextObject]) != nil) { // go BACKWARD through array while going DOWN menu
106        recentItemIndex++;
107        item = [self itemAtIndex: recentItemIndex];
108        itemAlias = [item representedObject];
109    }
110}
111
112#pragma mark initialize-release
113
114- (void)_setUp;
115{
116    NSMenu *menu = [self menu];
117    [self removeAllItems];
118    [menu setAutoenablesItems: NO];
119
120    NSMenuItem *item = [menu addItemWithTitle: @"Alert sound" action: @selector(_beepSelected:) keyEquivalent: @""];
121    [item setTarget: self];
122    [menu addItem: [NSMenuItem separatorItem]];
123
124    NSMutableArray *soundFolderPaths = [[NSMutableArray alloc] initWithCapacity: kLastDomainConstant - kSystemDomain + 1];
125    for (FSVolumeRefNum domain = kSystemDomain ; domain <= kLastDomainConstant ; domain++) {
126        OSStatus err;
127        FSRef fsr;
128        err = FSFindFolder(domain, kSystemSoundsFolderType, false, &fsr);
129        if (err != noErr) continue;
130
131        UInt8 path[PATH_MAX];
132        err = FSRefMakePath(&fsr, path, PATH_MAX);
133        if (err != noErr) continue;
134
135        CFStringRef pathString = CFStringCreateWithFileSystemRepresentation(NULL, (const char *)path);
136        if (pathString == NULL) continue;
137
138        [soundFolderPaths addObject: (NSString *)pathString];
139        CFRelease(pathString);
140    }
141    NSFileManager *fm = [NSFileManager defaultManager];
142    NSEnumerator *e = [soundFolderPaths objectEnumerator];
143    NSString *folderPath;
144    while ( (folderPath = [e nextObject]) != nil) {
145        if (![fm changeCurrentDirectoryPath: folderPath]) continue;
146
147        NSDirectoryEnumerator *de = [fm enumeratorAtPath: folderPath];
148        NSString *path;
149        NSString *displayName;
150        while ( (path = [de nextObject]) != nil) {
151            BOOL isDir;
152            if (![fm fileExistsAtPath: path isDirectory: &isDir] || isDir) {
153                [de skipDescendents];
154                continue;
155            }
156
157            if (![QTMovie canInitWithFile: path]) continue;
158           
159            displayName = [fm displayNameAtPath: path];
160            if ([[NSNumber numberWithBool: NO] isEqualTo: [[de fileAttributes] objectForKey: NSFileExtensionHidden]])
161                displayName = [displayName stringByDeletingPathExtension];
162
163            item = [menu addItemWithTitle: displayName
164                                   action: @selector(_systemSoundSelected:)
165                            keyEquivalent: @""];
166            [item setTarget: self];
167            [item setImageFromPath: path];
168            path = [folderPath stringByAppendingPathComponent: path];
169            [item setRepresentedObject: path];
170            [item setToolTip: path];
171        }
172    }
173    [soundFolderPaths release];
174   
175    if ([menu numberOfItems] == 2) {
176        item = [menu addItemWithTitle: NSLocalizedString(@"Can't locate alert sounds", "QuickTime media popup menu item surrogate for alert sound list if no sounds are found") action: nil keyEquivalent: @""];
177        [item setEnabled: NO];
178    }
179         
180    [menu addItem: [NSMenuItem separatorItem]];
181    item = [menu addItemWithTitle: NSLocalizedString(@"Other...", "Media popup item to select another sound/movie/image") action: @selector(select:) keyEquivalent: @""];
182    [item setTarget: self];
183    otherItem = [item retain];
184
185    [self _validateWithPreview: NO];
186
187    recentMediaAliasData = [[NSMutableArray alloc] initWithCapacity: NJRQTMediaPopUpButtonMaxRecentItems + 1];
188    [self _addRecentMediaFromAliasesData: [[NSUserDefaults standardUserDefaults] arrayForKey: [self _defaultKey]]];
189    // [self _validateRecentMedia];
190
191    [self registerForDraggedTypes:
192        [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
193}
194
195- (id)initWithFrame:(NSRect)frame;
196{
197    if ( (self = [super initWithFrame: frame]) != nil) {
198        [self _setUp];
199    }
200    return self;
201}
202
203- (id)initWithCoder:(NSCoder *)coder;
204{
205    if ( (self = [super initWithCoder: coder]) != nil) {
206        [self _setUp];
207    }
208    return self;
209}
210
211- (void)dealloc;
212{
213    [recentMediaAliasData release]; recentMediaAliasData = nil;
214    [otherItem release];
215    [selectedAlias release]; [previousAlias release];
216    [super dealloc];
217}
218
219#pragma mark accessing
220
221- (BDAlias *)selectedAlias;
222{
223    return selectedAlias;
224}
225
226- (void)_setAlias:(BDAlias *)alias;
227{
228    BDAlias *oldAlias = [selectedAlias retain];
229    [previousAlias release];
230    previousAlias = oldAlias;
231    if (selectedAlias != alias) {
232        [selectedAlias release];
233        selectedAlias = [alias retain];
234    }
235}
236
237- (void)setAlias:(BDAlias *)alias;
238{
239    [self _setAlias: alias];
240    if ([self _validateWithPreview: NO]) {
241        [self selectItem: [self _itemForAlias: selectedAlias]];
242    }
243}
244
245- (void)_setPath:(NSString *)path;
246{
247    [self _setAlias: [BDAlias aliasWithPath: path]];
248}
249
250- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
251{
252    if (alias == nil) return [self itemAtIndex: 0];
253
254    // [self _validateRecentMedia];
255    NSString *path = [alias fullPath];
256
257    // selected a system sound?
258    int itemIndex = [[self menu] indexOfItemWithRepresentedObject: path];
259    if (itemIndex != -1) {
260        // NSLog(@"_itemForAlias: selected system sound");
261        return [self itemAtIndex: itemIndex];
262    } else {
263        NSEnumerator *e = [recentMediaAliasData reverseObjectEnumerator];
264        NSData *aliasData;
265        NSMenuItem *item;
266        int recentIndex = 1;
267
268        while ( (aliasData = [e nextObject]) != nil) {
269            // selected a recently selected, non-system sound?
270            if ([alias aliasDataIsEqual: aliasData]) {
271                int otherIndex = [self indexOfItem: otherItem];
272                int menuIndex = recentIndex + otherIndex;
273                if (menuIndex == otherIndex + 1) return [self itemAtIndex: menuIndex]; // already at top
274                // remove item, add (at top) later
275                // NSLog(@"_itemForAlias removing item: idx %d + otherItemIdx %d + 1 = %d [%@]", recentIndex, otherIndex, menuIndex, [self itemAtIndex: menuIndex]);
276                [self removeItemAtIndex: menuIndex];
277                [recentMediaAliasData removeObjectAtIndex: [recentMediaAliasData count] - recentIndex];
278                break;
279            }
280            recentIndex++;
281        }
282
283        // create the item
284        item = [self _addRecentMediaAtPath: path withAlias: alias];
285        [self _writeRecentMedia];
286        return item;
287    }
288}
289
290- (BOOL)canRepeat;
291{
292    return movieCanRepeat;
293}
294
295- (BOOL)hasAudio;
296{
297    return movieHasAudio;
298}
299
300- (float)outputVolume;
301{
302    return outputVolume;
303}
304
305- (void)setOutputVolume:(float)volume withPreview:(BOOL)doPreview;
306{
307    if (![NJRSoundManager volumeIsNotMutedOrInvalid: volume]) return;
308    outputVolume = volume;
309    if (!doPreview) return;
310    // NSLog(@"setting volume to %f, preview movie %@", volume, [preview movie]);
311    if ([preview movie] == nil) {
312        [self _validateWithPreview: YES];
313    } else {
314        [self _startSoundPreview];
315    }
316}
317
318#pragma mark selected media validation
319
320- (void)_invalidateSelection;
321{
322    [self _setAlias: previousAlias];
323    [self selectItem: [self _itemForAlias: [self selectedAlias]]];
324    [[NSNotificationCenter defaultCenter] postNotificationName: NJRQTMediaPopUpButtonMovieChangedNotification object: self];
325}
326
327- (void)_startSoundPreview;
328{
329    if ([preview movie] == nil || outputVolume == kNoVolume)
330        return;
331
332    if (savedVolume || [NJRSoundManager saveDefaultOutputVolume]) {
333        savedVolume = YES;
334        [NJRSoundManager setDefaultOutputVolume: outputVolume];
335    }
336
337    if ([[preview movie] rate] != 0)
338        return; // don't restart preview if already playing
339   
340    [[NSNotificationCenter defaultCenter] addObserver: self
341                                             selector: @selector(_soundPreviewDidEnd:)
342                                                 name: QTMovieDidEndNotification
343                                               object: [preview movie]];
344    [preview play: self];
345}
346
347- (void)_soundPreviewDidEnd:(NSNotification *)notification;
348{
349    [self _resetPreview];
350}
351
352- (void)_resetPreview;
353{
354    [preview setMovie: nil];
355    [self _resetOutputVolume];
356}
357
358- (void)_resetOutputVolume;
359{
360    [NJRSoundManager restoreSavedDefaultOutputVolumeIfCurrently: outputVolume];
361    savedVolume = NO;
362}
363
364- (BOOL)_validateWithPreview:(BOOL)doPreview;
365{
366    // prevent _resetPreview from triggering afterward (crashes)
367    [[NSNotificationCenter defaultCenter] removeObserver: self
368                                                    name: QTMovieDidEndNotification
369                                                  object: [preview movie]];
370    [preview pause: self];
371    if (selectedAlias == nil) {
372        [preview setMovie: nil];
373        movieCanRepeat = YES;
374        movieHasAudio = NO; // XXX should be YES - this is broken, NSBeep() is asynchronous
375        if (doPreview) {
376            // XXX [self _updateOutputVolume];
377            NSBeep();
378            // XXX [self _resetOutputVolume];
379        }
380    } else {
381        NSError *error;
382        QTMovie *movie = [[QTMovie alloc] initWithFile: [selectedAlias fullPath] error: &error];
383        movieCanRepeat = ![movie NJR_isStatic];
384        if (movieHasAudio = [movie NJR_hasAudio]) {
385            [preview setMovie: doPreview ? movie : nil];
386        } else {
387            [self _resetPreview];
388            doPreview = NO;
389            if (movie == nil) {
390                NSBeginAlertSheet(@"Format not recognized", nil, nil, nil, [self window], nil, nil, nil, nil, [NSString stringWithFormat: NSLocalizedString(@"The item you selected isn't an image, sound or movie recognized by QuickTime. (%@)\n\nPlease select a different item.", "Message displayed in alert sheet when media document is not recognized by QuickTime"), [error localizedDescription]]);
391                [self _invalidateSelection];
392                return NO;
393            }
394            if (![movie NJR_hasAudio] && ![movie NJR_hasVideo]) {
395                NSBeginAlertSheet(@"No video or audio", nil, nil, nil, [self window], nil, nil, nil, nil, NSLocalizedString(@"'%@' contains neither audio nor video content playable by QuickTime.\n\nPlease select a different item.", "Message displayed in alert sheet when media document is readable, but has neither audio nor video tracks"), [[NSFileManager defaultManager] displayNameAtPath: [selectedAlias fullPath]]);
396                [self _invalidateSelection];
397                [movie release];
398                return NO;
399            }
400        }
401        if (doPreview) {
402            [self _startSoundPreview];
403        }
404        [movie release];
405    }
406    [[NSNotificationCenter defaultCenter] postNotificationName: NJRQTMediaPopUpButtonMovieChangedNotification object: self];
407    return YES;
408}
409
410#pragma mark actions
411
412- (IBAction)stopSoundPreview:(id)sender;
413{
414    [preview pause: self];
415    [self _resetPreview];
416}
417
418- (void)_beepSelected:(NSMenuItem *)sender;
419{
420    [self _setAlias: nil];
421    [self _validateWithPreview: YES];
422}
423
424- (void)_systemSoundSelected:(NSMenuItem *)sender;
425{
426    [self _setPath: [sender representedObject]];
427    if (![self _validateWithPreview: YES]) {
428        [[self menu] removeItem: sender];
429    }
430}
431
432- (void)_aliasSelected:(NSMenuItem *)sender;
433{
434    BDAlias *alias = [sender representedObject];
435    int itemIndex = [self indexOfItem: sender], otherIndex = [self indexOfItem: otherItem];
436    [self _setAlias: alias];
437    if (![self _validateWithPreview: YES]) {
438        [[self menu] removeItem: sender];
439    } else if (itemIndex > otherIndex + 1) { // move "other" item to top of list
440        int recentIndex = [recentMediaAliasData count] - itemIndex + otherIndex;
441        NSMenuItem *item = [[self itemAtIndex: itemIndex] retain];
442        NSData *data = [[recentMediaAliasData objectAtIndex: recentIndex] retain];
443        // [self _validateRecentMedia];
444        [self removeItemAtIndex: itemIndex];
445        [[self menu] insertItem: item atIndex: otherIndex + 1];
446        [self selectItem: item];
447        [item release];
448        NSAssert(recentIndex >= 0, @"Recent media index invalid");
449        // NSLog(@"_aliasSelected removing item %d - %d + %d = %d of recentMediaAliasData", [recentMediaAliasData count], index, otherIndex, recentIndex);
450        [recentMediaAliasData removeObjectAtIndex: recentIndex];
451        [recentMediaAliasData addObject: data];
452        [self _validateRecentMedia];
453        [data release];
454    } // else NSLog(@"_aliasSelected ...already at top");
455}
456
457- (IBAction)select:(id)sender;
458{
459    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
460    NSString *path = [selectedAlias fullPath];
461    [openPanel setAllowsMultipleSelection: NO];
462    [openPanel setCanChooseDirectories: NO];
463    [openPanel setCanChooseFiles: YES];
464    [openPanel setDelegate: self];
465    [openPanel beginSheetForDirectory: [path stringByDeletingLastPathComponent]
466                                 file: [path lastPathComponent]
467                                types: nil
468                       modalForWindow: [self window]
469                        modalDelegate: self
470                       didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
471                          contextInfo: nil];
472}
473
474- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
475{
476    [sheet close];
477
478    if (returnCode == NSOKButton) {
479        NSArray *files = [sheet filenames];
480        NSAssert1([files count] == 1, @"%d items returned, only one expected", [files count]);
481        [self _setPath: [files objectAtIndex: 0]];
482        if ([self _validateWithPreview: YES]) {
483            [self selectItem: [self _itemForAlias: selectedAlias]];
484        }
485    } else {
486        // "Other..." item is still selected, revert to previously selected item
487        // XXX issue with cancelling, top item in recent menu is sometimes duplicated!?
488        [self selectItem: [self _itemForAlias: selectedAlias]];
489    }
490    // [self _validateRecentMedia];
491}
492
493- (void)setEnabled:(BOOL)flag;
494{
495    [super setEnabled: flag];
496    if (flag) ; // XXX [self startSoundPreview: self]; // need to prohibit at startup
497    else [self stopSoundPreview: self];
498}
499
500#pragma mark drag feedback
501
502- (void)drawRect:(NSRect)rect;
503{
504    if (dragAccepted) {
505        NSWindow *window = [self window];
506        NSRect boundsRect = [self bounds];
507        BOOL isFirstResponder = ([window firstResponder] == self);
508        // focus ring and drag feedback interfere with one another
509        if (isFirstResponder) [window makeFirstResponder: window];
510        [super drawRect: rect];
511        [[NSColor selectedControlColor] set];
512        NSFrameRectWithWidthUsingOperation(NSInsetRect(boundsRect, 2, 2), 3, NSCompositeSourceIn);
513        if (isFirstResponder) [window makeFirstResponder: self];
514    } else {
515        [super drawRect: rect];
516    }
517}
518
519@end
520
521@implementation NJRQTMediaPopUpButton (NSSavePanelDelegate)
522
523- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
524{
525    BOOL isDir = NO;
526    [[NSFileManager defaultManager] fileExistsAtPath: filename isDirectory: &isDir];
527
528    if (isDir)
529        return YES;
530
531    return [QTMovie canInitWithFile: filename];
532}
533
534@end
535
536@implementation NJRQTMediaPopUpButton (NSDraggingDestination)
537
538- (BOOL)acceptsDragFrom:(id <NSDraggingInfo>)sender;
539{
540    NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
541    NSFileManager *fm = [NSFileManager defaultManager];
542    BOOL isDir;
543
544    if (url == nil || ![url isFileURL]) return NO;
545
546    if (![fm fileExistsAtPath: [url path] isDirectory: &isDir]) return NO;
547
548    if (isDir) return NO;
549   
550    return YES;
551}
552
553- (NSString *)_descriptionForDraggingInfo:(id <NSDraggingInfo>)sender;
554{
555    NSDragOperation mask = [sender draggingSourceOperationMask];
556    NSMutableString *s = [NSMutableString stringWithFormat: @"Drag seq %d source: %@",
557        [sender draggingSequenceNumber], [sender draggingSource]];
558    NSPasteboard *draggingPasteboard = [sender draggingPasteboard];
559    NSArray *types = [draggingPasteboard types];
560    NSEnumerator *e = [types objectEnumerator];
561    NSString *type;
562    [s appendString: @"\nDrag operations:"];
563    if (mask & NSDragOperationCopy) [s appendString: @" copy"];
564    if (mask & NSDragOperationLink) [s appendString: @" link"];
565    if (mask & NSDragOperationGeneric) [s appendString: @" generic"];
566    if (mask & NSDragOperationPrivate) [s appendString: @" private"];
567    if (mask & NSDragOperationMove) [s appendString: @" move"];
568    if (mask & NSDragOperationDelete) [s appendString: @" delete"];
569    if (mask & NSDragOperationEvery) [s appendString: @" every"];
570    if (mask & NSDragOperationNone) [s appendString: @" none"];
571    [s appendFormat: @"\nImage: %@ at %@", [sender draggedImage],
572        NSStringFromPoint([sender draggedImageLocation])];
573    [s appendFormat: @"\nDestination: %@ at %@", [sender draggingDestinationWindow],
574        NSStringFromPoint([sender draggingLocation])];
575    [s appendFormat: @"\nPasteboard: %@ types:", draggingPasteboard];
576    while ( (type = [e nextObject]) != nil) {
577        if ([type hasPrefix: @"CorePasteboardFlavorType 0x"]) {
578            const char *osTypeHex = [[type substringFromIndex: [type rangeOfString: @"0x" options: NSBackwardsSearch].location] UTF8String];
579            OSType osType;
580            sscanf(osTypeHex, "%lx", &osType);
581            [s appendFormat: @" '%4s'", &osType];
582        } else {
583            [s appendFormat: @" '%@'", type];
584        }
585    }
586    return s;
587}
588
589- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
590{
591    if ([self acceptsDragFrom: sender] && [sender draggingSourceOperationMask] &
592        (NSDragOperationCopy | NSDragOperationLink)) {
593        dragAccepted = YES;
594        [self setNeedsDisplay: YES];
595        // NSLog(@"draggingEntered accept:\n%@", [self _descriptionForDraggingInfo: sender]);
596        return NSDragOperationLink;
597    }
598    return NSDragOperationNone;
599}
600
601- (void)draggingExited:(id <NSDraggingInfo>)sender;
602{
603    dragAccepted = NO;
604    [self setNeedsDisplay: YES];
605}
606
607- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
608{
609    dragAccepted = NO;
610    [self setNeedsDisplay: YES];
611    return [self acceptsDragFrom: sender];
612}
613
614- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
615{
616    if ([sender draggingSource] != self) {
617        NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
618        if (url == nil) return NO;
619        [self _setPath: [url path]];
620        if ([self _validateWithPreview: YES]) {
621            [self selectItem: [self _itemForAlias: selectedAlias]];
622        }
623    }
624    return YES;
625}
626
627@end
Note: See TracBrowser for help on using the repository browser.