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
RevLine 
[34]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"
[133]10#import "NJRSoundManager.h"
[541]11#import "QTMovie-NJRExtensions.h"
[355]12#import "NSMenuItem-NJRExtensions.h"
[34]13
[355]14#include <limits.h>
15
[39]16static const int NJRQTMediaPopUpButtonMaxRecentItems = 10;
17
[41]18NSString * const NJRQTMediaPopUpButtonMovieChangedNotification = @"NJRQTMediaPopUpButtonMovieChangedNotification";
19
[34]20@interface NJRQTMediaPopUpButton (Private)
[39]21- (void)_setPath:(NSString *)path;
[40]22- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
[41]23- (BOOL)_validateWithPreview:(BOOL)doPreview;
[133]24- (void)_startSoundPreview;
[533]25- (void)_resetPreview;
26- (void)_resetOutputVolume;
[34]27@end
28
29@implementation NJRQTMediaPopUpButton
30
31// XXX handle refreshing sound list on resume
[39]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]34
[40]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
[41]39#pragma mark recently selected media tracking
40
[39]41- (NSString *)_defaultKey;
42{
[103]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"));
[39]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];
[47]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];
[39]61 [item setTarget: self];
62 [item setRepresentedObject: alias];
[355]63 [item setImageFromPath: path];
[39]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{
[40]87 NSEnumerator *e = [recentMediaAliasData reverseObjectEnumerator];
[39]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);
[40]97 while ( (aliasData = [e nextObject]) != nil) { // go BACKWARD through array while going DOWN menu
[39]98 recentItemIndex++;
99 item = [self itemAtIndex: recentItemIndex];
100 itemAlias = [item representedObject];
101 }
102}
103
[41]104#pragma mark initialize-release
105
[53]106- (void)_setUp;
[34]107{
[355]108 NSMenu *menu = [self menu];
109 [self removeAllItems];
110 [menu setAutoenablesItems: NO];
[39]111
[355]112 NSMenuItem *item = [menu addItemWithTitle: @"Alert sound" action: @selector(_beepSelected:) keyEquivalent: @""];
[34]113 [item setTarget: self];
114 [menu addItem: [NSMenuItem separatorItem]];
[355]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;
[576]141 NSString *displayName;
[355]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
[576]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
[355]156 action: @selector(_systemSoundSelected:)
157 keyEquivalent: @""];
[34]158 [item setTarget: self];
[355]159 [item setImageFromPath: path];
160 path = [folderPath stringByAppendingPathComponent: path];
161 [item setRepresentedObject: path];
162 [item setToolTip: path];
[34]163 }
164 }
[355]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
[34]172 [menu addItem: [NSMenuItem separatorItem]];
[103]173 item = [menu addItemWithTitle: NSLocalizedString(@"Other...", "Media popup item to select another sound/movie/image") action: @selector(select:) keyEquivalent: @""];
[34]174 [item setTarget: self];
[39]175 otherItem = [item retain];
176
[41]177 [self _validateWithPreview: NO];
178
[39]179 recentMediaAliasData = [[NSMutableArray alloc] initWithCapacity: NJRQTMediaPopUpButtonMaxRecentItems + 1];
180 [self _addRecentMediaFromAliasesData: [[NSUserDefaults standardUserDefaults] arrayForKey: [self _defaultKey]]];
[41]181 // [self _validateRecentMedia];
[39]182
183 [self registerForDraggedTypes:
184 [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
[34]185}
186
[53]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
[39]203- (void)dealloc;
[34]204{
[39]205 [recentMediaAliasData release]; recentMediaAliasData = nil;
206 [otherItem release];
207 [selectedAlias release]; [previousAlias release];
208 [super dealloc];
[34]209}
210
[41]211#pragma mark accessing
212
[39]213- (BDAlias *)selectedAlias;
[34]214{
[39]215 return selectedAlias;
216}
217
218- (void)_setAlias:(BDAlias *)alias;
219{
220 BDAlias *oldAlias = [selectedAlias retain];
221 [previousAlias release];
222 previousAlias = oldAlias;
[34]223 if (selectedAlias != alias) {
224 [selectedAlias release];
225 selectedAlias = [alias retain];
226 }
227}
228
[53]229- (void)setAlias:(BDAlias *)alias;
230{
231 [self _setAlias: alias];
232 if ([self _validateWithPreview: NO]) {
233 [self selectItem: [self _itemForAlias: selectedAlias]];
234 }
235}
236
[39]237- (void)_setPath:(NSString *)path;
[34]238{
[39]239 [self _setAlias: [BDAlias aliasWithPath: path]];
[34]240}
241
[39]242- (NSMenuItem *)_itemForAlias:(BDAlias *)alias;
[34]243{
[355]244 if (alias == nil) return [self itemAtIndex: 0];
[34]245
[41]246 // [self _validateRecentMedia];
[355]247 NSString *path = [alias fullPath];
[34]248
[39]249 // selected a system sound?
[355]250 int itemIndex = [[self menu] indexOfItemWithRepresentedObject: path];
251 if (itemIndex != -1) {
[41]252 // NSLog(@"_itemForAlias: selected system sound");
[355]253 return [self itemAtIndex: itemIndex];
[39]254 } else {
[40]255 NSEnumerator *e = [recentMediaAliasData reverseObjectEnumerator];
[39]256 NSData *aliasData;
257 NSMenuItem *item;
[40]258 int recentIndex = 1;
[34]259
[39]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];
[40]264 int menuIndex = recentIndex + otherIndex;
[39]265 if (menuIndex == otherIndex + 1) return [self itemAtIndex: menuIndex]; // already at top
266 // remove item, add (at top) later
[41]267 // NSLog(@"_itemForAlias removing item: idx %d + otherItemIdx %d + 1 = %d [%@]", recentIndex, otherIndex, menuIndex, [self itemAtIndex: menuIndex]);
[39]268 [self removeItemAtIndex: menuIndex];
[40]269 [recentMediaAliasData removeObjectAtIndex: [recentMediaAliasData count] - recentIndex];
[39]270 break;
[34]271 }
[39]272 recentIndex++;
[34]273 }
[39]274
275 // create the item
276 item = [self _addRecentMediaAtPath: path withAlias: alias];
277 [self _writeRecentMedia];
278 return item;
[34]279 }
280}
281
[41]282- (BOOL)canRepeat;
283{
284 return movieCanRepeat;
285}
286
[133]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];
[533]305 } else {
306 [self _startSoundPreview];
[133]307 }
308}
309
[41]310#pragma mark selected media validation
311
[39]312- (void)_invalidateSelection;
[34]313{
[39]314 [self _setAlias: previousAlias];
315 [self selectItem: [self _itemForAlias: [self selectedAlias]]];
[41]316 [[NSNotificationCenter defaultCenter] postNotificationName: NJRQTMediaPopUpButtonMovieChangedNotification object: self];
[34]317}
318
[533]319- (void)_startSoundPreview;
[133]320{
[533]321 if ([preview movie] == nil || outputVolume == kNoVolume)
322 return;
323
324 if (savedVolume || [NJRSoundManager saveDefaultOutputVolume]) {
[133]325 savedVolume = YES;
326 [NJRSoundManager setDefaultOutputVolume: outputVolume];
327 }
[533]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];
[133]337}
338
[533]339- (void)_soundPreviewDidEnd:(NSNotification *)notification;
[133]340{
[533]341 [self _resetPreview];
[133]342}
343
344- (void)_resetPreview;
345{
[533]346 [preview setMovie: nil];
347 [self _resetOutputVolume];
[133]348}
349
[533]350- (void)_resetOutputVolume;
[60]351{
[533]352 [NJRSoundManager restoreSavedDefaultOutputVolumeIfCurrently: outputVolume];
353 savedVolume = NO;
[60]354}
355
[41]356- (BOOL)_validateWithPreview:(BOOL)doPreview;
[34]357{
[535]358 // prevent _resetPreview from triggering afterward (crashes)
359 [[NSNotificationCenter defaultCenter] removeObserver: self
360 name: QTMovieDidEndNotification
361 object: [preview movie]];
[533]362 [preview pause: self];
[34]363 if (selectedAlias == nil) {
364 [preview setMovie: nil];
[41]365 movieCanRepeat = YES;
[133]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 }
[34]372 } else {
[541]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];
[60]378 } else {
[133]379 [self _resetPreview];
380 doPreview = NO;
[34]381 if (movie == nil) {
[541]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]]);
[39]383 [self _invalidateSelection];
[34]384 return NO;
385 }
[541]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]]);
[39]388 [self _invalidateSelection];
[34]389 [movie release];
390 return NO;
391 }
392 }
[60]393 if (doPreview) {
[133]394 [self _startSoundPreview];
[60]395 }
[34]396 [movie release];
397 }
[41]398 [[NSNotificationCenter defaultCenter] postNotificationName: NJRQTMediaPopUpButtonMovieChangedNotification object: self];
[34]399 return YES;
400}
401
[41]402#pragma mark actions
403
[39]404- (IBAction)stopSoundPreview:(id)sender;
405{
[533]406 [preview pause: self];
[133]407 [self _resetPreview];
[39]408}
409
410- (void)_beepSelected:(NSMenuItem *)sender;
411{
412 [self _setAlias: nil];
[41]413 [self _validateWithPreview: YES];
[39]414}
415
[355]416- (void)_systemSoundSelected:(NSMenuItem *)sender;
[39]417{
[355]418 [self _setPath: [sender representedObject]];
[41]419 if (![self _validateWithPreview: YES]) {
[39]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];
[41]429 if (![self _validateWithPreview: YES]) {
[39]430 [[self menu] removeItem: sender];
431 } else if (index > otherIndex + 1) { // move "other" item to top of list
[40]432 int recentIndex = [recentMediaAliasData count] - index + otherIndex;
[39]433 NSMenuItem *item = [[self itemAtIndex: index] retain];
434 NSData *data = [[recentMediaAliasData objectAtIndex: recentIndex] retain];
[45]435 // [self _validateRecentMedia];
[39]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");
[41]441 // NSLog(@"_aliasSelected removing item %d - %d + %d = %d of recentMediaAliasData", [recentMediaAliasData count], index, otherIndex, recentIndex);
[39]442 [recentMediaAliasData removeObjectAtIndex: recentIndex];
443 [recentMediaAliasData addObject: data];
[40]444 [self _validateRecentMedia];
[39]445 [data release];
[40]446 } // else NSLog(@"_aliasSelected ...already at top");
[39]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];
[537]456 [openPanel setDelegate: self];
[39]457 [openPanel beginSheetForDirectory: [path stringByDeletingLastPathComponent]
458 file: [path lastPathComponent]
[537]459 types: nil
[39]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]];
[41]474 if ([self _validateWithPreview: YES]) {
[39]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
[43]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
[41]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
[34]511@end
[39]512
[537]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
[39]528@implementation NJRQTMediaPopUpButton (NSDraggingDestination)
529
530- (BOOL)acceptsDragFrom:(id <NSDraggingInfo>)sender;
531{
532 NSURL *url = [NSURL URLFromPasteboard: [sender draggingPasteboard]];
[40]533 NSFileManager *fm = [NSFileManager defaultManager];
534 BOOL isDir;
[39]535
536 if (url == nil || ![url isFileURL]) return NO;
[40]537
538 if (![fm fileExistsAtPath: [url path] isDirectory: &isDir]) return NO;
539
540 if (isDir) return NO;
541
[39]542 return YES;
543}
544
[40]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 {
[103]575 [s appendFormat: @" '%@'", type];
[40]576 }
577 }
578 return s;
579}
580
[39]581- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
582{
583 if ([self acceptsDragFrom: sender] && [sender draggingSourceOperationMask] &
[40]584 (NSDragOperationCopy | NSDragOperationLink)) {
[39]585 dragAccepted = YES;
586 [self setNeedsDisplay: YES];
[40]587 // NSLog(@"draggingEntered accept:\n%@", [self _descriptionForDraggingInfo: sender]);
588 return NSDragOperationLink;
[39]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]];
[41]612 if ([self _validateWithPreview: YES]) {
[40]613 [self selectItem: [self _itemForAlias: selectedAlias]];
614 }
[39]615 }
616 return YES;
617}
618
619@end
Note: See TracBrowser for help on using the repository browser.