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

Last change on this file since 355 was 355, checked in by Nicholas Riley, 12 years ago

English.lproj/MainMenu.nib: Modernize menu and alarm set dialog
layout. Use keyed archiving (10.2+) nib format.

Info-Pester.plist: Moved from old PBX project.

NJRFSObjectSelector.m: Bug fixes from code sent to Joey: remove
incorrect usage of tryToPerform:with:; fix logic error in menu
construction. Work around Cocoa's deciding that the menu font size
needs adjustment when it doesn't - so the menu font size now matches
the button font size, though the position is still off. Don't pop up
a menu if we're disabled. Use IconRefs? for menu icons, though not
(yet) for the button icon.

NJRHistoryTrackingComboBox.m: Remove item height adjustment
workaround; it now makes the items too tall.

NJRHotKey.m: Add a missing [super dealloc] caught by current GCC.

NJRHotKeyField.m: Add a missing [super dealloc] caught by current GCC.

NJRHotKeyManager.m: Add a missing [super dealloc] caught by current
GCC.

NJRIntervalField.m: Fix some type errors.

NJRQTMediaPopUpButton.m: Replace SoundFileManager? SPI usage, which
doesn't work in Leopard anyway, with manual enumeration of system
sounds. Start migration to QTKit. Use IconRefs? for menu icons.

NJRReadMeController.m: Change source encoding to UTF-8.

NJRSoundManager.m: Fix a type error.

NJRVoicePopUpButton.m: Change source encoding to UTF-8.

NSMenuItem-NJRExtensions.[hm]: Code from ICeCoffEE to use IconRefs? for
menu item icons.

PSAlarm.m: Change source encoding to UTF-8.

PSAlarms.m: Fix a signedness mismatch.

PSAlarmsController.m: Change source encoding to UTF-8.

PSAlarmSetController.m: Set keyboard focus after unchecking "Do
script:" and "Play" checkboxes.

PSAlerts.m: Add a missing [super dealloc] caught by current GCC. Fix
a memory leak in property list serialization.

PSPowerManager.[hm]: There's now API for scheduling wakeups; use it
(the old code asserted on startup). To fix: removing scheduled
wakeup. Fix a small type-checking error.

PSPreferencesController.m: Add a missing [super dealloc] caught by
current GCC.

PSScriptAlert.m: Change source encoding to UTF-8.

PSTimeDateEditor.m: Fix a tiny, and one-time, memory leak.

PSTimer.m: Update for new PSPowerManager API.

Pester.pbproj: Deleted; now supporting OS X 10.4+ (up from 10.1,
aiee.)

Pester.xcodeproj: Xcode 2.4+ project, upgraded targets, etc.

SoundFileManager?.h: Deleted; this SPI no longer exists in Leopard and
possibly earlier.

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