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

Last change on this file since 591 was 591, checked in by Nicholas Riley, 10 years ago

Remove (very) obsolete comment.

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