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

Last change on this file since 305 was 133, checked in by Nicholas Riley, 22 years ago

VERSION: Updated for 1.1b4.

PSMovieAlertController.[hm]: Set output volume from alert; save and
restore output volume (bug 27).

PSBeepAlert.[hm]: Save and set volume, disabled pending a
synchronous equivalent to NSBeep().

NJRQTMediaPopUpButton.[hm]: Save movieHasAudio (does not include
beeps, pending the above fix, since they don't permit volume
adjustment). savedVolume indicates whether the volume has been saved
but not restored yet; outputVolume stores the set volume (eventually
incorporated into a PSMediaAlert).

PSMediaAlert.[hm]: Stores repetitions and volume information for audio
alerts. New superclass of PSBeepAlert and PSMovieAlert.

PSPreferencesController.m: Fixed bug where hot keys still appeared
even after they couldn't be set - last of bug 29. Add Command-comma
to the list of disallowed equivalents, as it's reserved for
Preferences now (still a bug - it'll show the entire set key
equivalent at the left side of the window when you press
command-comma; ah well.)

Volume [0123].png: Volume-indicating small icons from QuickTime 6.

NJRSoundManager.[hm]: Interface to volume saving, restoring, setting -
necessary because the QuickTime call to SetDefaultOutputVolume sets
the right channel volume only (bug 27).

PSVolumeController.h: Controller for volume popup window (bug 27).
Not your average NSWindowController, but it works. Keyboard control
of volume is still necessary; filed as bug 31.

PSAlarmSetController.[hm]: Added references to sound volume button and
showVolume: action. Added volume setting support (bug 27); mostly
similar interface to calendar, though we need direct calls to
NJRSoundManager to restore sound volume at times. Only enable sound
volume button if selected media file has audio component (and isn't the
system alert sound, which I discussed above). Take advantage of
PSMediaAlert to factor some code in _readAlerts:. Save and restore
volume as part of alerts.

Read Me.rtfd: Updated release notes; fixed some bizarre text
formatting problems; search/replace "* " bullet-space with "*\t"
bullet-tab to improve alignment in release notes.

PSMovieAlert.[hm]: Factored code into PSMediaAlert. Describe output
volume as percentage of maximum.

NJRHotKey.m: Fixed some odd spacing left over from Ecky's code.

PSApplication.m: Restore saved output volume on quit.

English.lproj/MainMenu.nib: Added volume button.

English.lproj/Volume.nib: Volume nib (bug 27).

PSCalendarController.m: Removed casts from a copy/paste error. Fixed
variable names in some code inherited from my TextExtras incremental
search modifications - it's not always a text field now.

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