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

Last change on this file since 578 was 576, checked in by Nicholas Riley, 15 years ago

NJRQTMediaPopUpButton.m: Hide extension on system sounds.

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