Last active
March 11, 2016 19:26
-
-
Save boredzo/6b0cdc17eaeb0e5fe363 to your computer and use it in GitHub Desktop.
Dragging source code for promising files from my never-released movie player app.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//PRHFrameGrabber grabs frame images from the movie. | |
//It wraps a couple of AVAssetImageGenerators, one of which is allowed to round to a more convenient time (e.g., a keyframe). | |
//Thus, the temporally lax generator will return an image first, while we wait for the temporally strict generator, which may take a significant fraction of a second. | |
//PRHFrameGrabber is also an NSPasteboardWriter. It's what the movie view feeds to the dragging session when the user tries to drag a frame; the frame grabber fulfills its NSPasteboardWriter duties by returning the grabbed frame. | |
@interface PRHFrameGrabber () | |
@property(readwrite, copy) NSImage *image; | |
@end | |
@implementation PRHFrameGrabber | |
{ | |
AVAssetImageGenerator *_temporallyStrictImageGenerator; | |
AVAssetImageGenerator *_temporallyLaxImageGenerator; | |
} | |
[boring constructor code] | |
- (NSArray *) writableTypesForPasteboard:(NSPasteboard *)pasteboard { | |
NSImage *image = self.image ?: self.roughImage; | |
NSArray *writableTypes = [image writableTypesForPasteboard:pasteboard]; | |
writableTypes = [writableTypes arrayByAddingObjectsFromArray:@[ /* (__bridge NSString *)kPasteboardTypeFileURLPromise,*/ | |
(__bridge NSString *)kPasteboardTypeFilePromiseContent ]]; | |
return writableTypes; | |
} | |
- (NSPasteboardWritingOptions) writingOptionsForType:(NSString *)type pasteboard:(NSPasteboard *)pasteboard { | |
NSPasteboardWritingOptions options = UTTypeEqual(kPasteboardTypeFilePromiseContent, (__bridge CFStringRef)type) ? 0 : NSPasteboardWritingPromised; | |
return options; | |
} | |
- (id) pasteboardPropertyListForType:(NSString *)type { | |
if (UTTypeEqual(kPasteboardTypeFilePromiseContent, (__bridge CFStringRef)type)) { | |
return (NSString *)kUTTypeJPEG; | |
} | |
NSImage *image = self.image ?: self.imageFromTemporallyLaxImageGenerator; | |
NSData *data = [image pasteboardPropertyListForType:type]; | |
if (data == nil) { | |
//Really, NSImage? | |
NSRect rect = { NSZeroPoint, image.size }; | |
CGImageRef imageCG = [image CGImageForProposedRect:&rect | |
context:nil | |
hints:nil]; | |
CFMutableDataRef mutableData = CFDataCreateMutable(kCFAllocatorDefault, /*capacity*/ 0); | |
if (mutableData != NULL) { | |
CGImageDestinationRef dest = CGImageDestinationCreateWithData(mutableData, (__bridge CFStringRef)type, /*count*/ 1, /*options*/ NULL); | |
if (dest != NULL) { | |
CGImageDestinationAddImage(dest, imageCG, /*properties*/ NULL); | |
if ( ! CGImageDestinationFinalize(dest) ) { | |
CFRelease(mutableData); | |
mutableData = NULL; | |
} | |
CFRelease(dest); | |
} | |
data = (__bridge_transfer NSData *)mutableData; | |
} | |
} | |
return data; | |
} | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This is one of the main classes in a movie player app I wrote and nearly finished before I joined Apple. | |
//It's a direct subclass of NSView—not QTMovieView, and AVKit hadn't been released yet when I wrote this. | |
//It's pretty huge, but here's the drag-and-drop code. | |
@interface PRHMovieView () <NSDraggingSource> | |
@property(nonatomic) bool drawDropTargetHighlight; | |
@end | |
@implementation PRHMovieView | |
{ | |
bool _clickBeganInThisView; | |
bool _dragBeganInThisView; | |
NSEvent *_mouseDownEvent; | |
NSDraggingSession *_draggingSession; | |
CMTime _draggingFrameTime; | |
NSURL *_promisedFrameImageFileURL; | |
PRHFrameGrabber *_draggingFrameGrabber; | |
NSTimer *_cursorHidingTimer; | |
bool _mouseIsWithin; | |
//PRHTimeFormatter formats movie timestamps. In one configuration, it does so appropriately for filenames. | |
PRHTimeFormatter *_timeFormatterForDraggedFrameImageFilenames; | |
} | |
[lots of boring and/or irrelevant and/or ugly code—seriously, this is a *huge* class] | |
//This view supports both clicks and drags. The click logic isn't relevant here, but suffice to say that we start drag(ging session)s from mouseDragged:. | |
- (void) mouseDown:(NSEvent *)event { | |
_mouseDownEvent = event; | |
_clickBeganInThisView = true; | |
} | |
- (void) mouseDragged:(NSEvent *)event { | |
//Ignore mouseDragged:s that come from the user beginning a drag in the controller and taking it through here. | |
if (!_clickBeganInThisView) | |
return; | |
if (!_dragBeganInThisView) { | |
// https://github.com/boredzo/PRHVector | |
bool isDrag = [PRHVector isStartOfDragFromMouseDownEvent:_mouseDownEvent | |
toMouseDraggedEvent:event]; | |
if (isDrag) { | |
//Our immediate first drag image is simply a screenshot of the view. We can dish that up even before the temporally lax frame grabber comes through. | |
NSRect frameInWindow = [self.superview convertRect:self.frame toView:nil]; | |
NSRect frameInScreen = [self.window convertRectToScreen:frameInWindow]; | |
NSScreen *mainMenuScreen = [[NSScreen screens] objectAtIndex:0UL]; | |
NSRect screenFrame = mainMenuScreen.frame; | |
NSAffineTransform *transform = [NSAffineTransform transform]; | |
[transform translateXBy:0.0 yBy:screenFrame.size.height]; | |
[transform scaleXBy:1.0 yBy:-1.0]; | |
[transform translateXBy:0.0 yBy:frameInWindow.size.height]; | |
frameInScreen.origin = [transform transformPoint:frameInScreen.origin]; | |
CGImageRef screenshot = CGWindowListCreateImage(frameInScreen, kCGWindowListOptionIncludingWindow, (CGWindowID)self.window.windowNumber, kCGWindowImageBoundsIgnoreFraming); | |
NSImage *image = [[NSImage alloc] initWithCGImage:screenshot size:PRHNaturalSizeOfAsset(self.player.currentItem.asset)]; | |
CGImageRelease(screenshot); | |
_draggingFrameTime = self.player.currentTime; | |
PRHFrameGrabber *frameGrabber = [PRHFrameGrabber frameGrabberForAsset:self.player.currentItem.asset]; | |
frameGrabber.roughImage = image; | |
frameGrabber.desiredFrameTime = _draggingFrameTime; | |
frameGrabber.desiredFrameSize = self.bounds.size; | |
[frameGrabber startGrab]; | |
_draggingFrameGrabber = frameGrabber; | |
PRHTimeFormatter *timeFormatter = [PRHTimeFormatter new]; | |
[timeFormatter configureForTimestamp]; | |
NSDraggingItem *item = [[NSDraggingItem alloc] initWithPasteboardWriter:frameGrabber]; | |
item.draggingFrame = self.bounds; | |
//This entire block simply sets the drag image, which is the thumbnail with a timestamp beneath it. | |
item.imageComponentsProvider = ^NSArray *{ | |
NSRect imageRect = { NSZeroPoint, image.size }; | |
NSDraggingImageComponent *imageComponent = [NSDraggingImageComponent draggingImageComponentWithKey:NSDraggingImageComponentIconKey]; | |
imageComponent.contents = image; | |
imageComponent.frame = imageRect; | |
frameGrabber.imageForExactTimeArrived = ^(NSImage *imageAtExactTime) { | |
imageComponent.contents = imageAtExactTime; | |
}; | |
NSString *string = [timeFormatter stringForTime:self->_draggingFrameTime]; | |
NSMutableParagraphStyle *centered = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; | |
centered.alignment = NSCenterTextAlignment; | |
NSAttributedString *attrStr = [[NSAttributedString alloc] | |
initWithString:[@[@" ", string, @" "] componentsJoinedByString:@""] | |
attributes:@{ | |
NSFontAttributeName: [NSFont fontWithName:@"Helvetica" size:12.0], | |
NSParagraphStyleAttributeName: centered, | |
NSForegroundColorAttributeName: [NSColor whiteColor], | |
NSBackgroundColorAttributeName: [[NSColor blackColor] colorWithAlphaComponent:0.75], | |
} | |
]; | |
NSSize textSize = [attrStr size]; | |
NSRect textRect = { { NSMidX(imageRect) - textSize.width / 2.0, NSMinY(imageRect) }, textSize }; | |
textRect.origin.y = -textRect.size.height; | |
textRect.size.width += (20.0 - textRect.size.height) * 2.0; | |
textRect.size.height = 20.0; | |
NSImage *stringImage = [[NSImage alloc] initWithSize:textRect.size]; | |
[stringImage lockFocus]; | |
[attrStr drawAtPoint:NSZeroPoint]; | |
[stringImage unlockFocus]; | |
NSDraggingImageComponent *labelComponent = [NSDraggingImageComponent draggingImageComponentWithKey:NSDraggingImageComponentLabelKey]; | |
labelComponent.contents = stringImage; | |
labelComponent.frame = textRect; | |
return @[ imageComponent, labelComponent ]; | |
}; | |
NSArray *items = @[ item ]; | |
_draggingSession = [self beginDraggingSessionWithItems:items | |
event:_mouseDownEvent | |
source:self]; | |
_dragBeganInThisView = true; | |
} | |
} | |
} | |
- (void) mouseUp:(NSEvent *)theEvent { | |
//Ignore mouseUp:s that come from the user beginning a drag in the controller and ending it here. | |
if (!_clickBeganInThisView) | |
return; | |
_clickBeganInThisView = false; | |
if (!_dragBeganInThisView) | |
[NSApp sendAction:self.action to:self.target from:self]; | |
else | |
_dragBeganInThisView = false; | |
} | |
[other event methods that aren't relevant] | |
- (NSDragOperation) draggingSession:(NSDraggingSession *)session | |
sourceOperationMaskForDraggingContext:(NSDraggingContext)context | |
{ | |
switch (context) { | |
case NSDraggingContextWithinApplication: | |
return NSDragOperationNone; | |
case NSDraggingContextOutsideApplication: | |
return NSDragOperationCopy; | |
} | |
return NSDragOperationNone; | |
} | |
- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)destURL { | |
NSDocument *document = self.document; | |
if (_timeFormatterForDraggedFrameImageFilenames == nil) { | |
_timeFormatterForDraggedFrameImageFilenames = [PRHTimeFormatter new]; | |
[_timeFormatterForDraggedFrameImageFilenames configureForFilename]; | |
_timeFormatterForDraggedFrameImageFilenames.minimumNumberOfComponents = [PRHTimeFormatter numberOfComponentsInTime:self.player.currentItem.duration]; | |
} | |
NSString *timeString = [_timeFormatterForDraggedFrameImageFilenames stringForTime:_draggingFrameTime]; | |
PRHFrameGrabber *frameGrabber = _draggingFrameGrabber; | |
NSString *type = [frameGrabber pasteboardPropertyListForType:(__bridge NSString *)kPasteboardTypeFilePromiseContent]; | |
NSString *extension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)type, kUTTagClassFilenameExtension); | |
if ([extension isEqualToString:@"jpeg"]) { | |
//ಠ_ಠ | |
extension = @"jpg"; | |
} | |
NSString *imageFilename = [NSString stringWithFormat:@"%@@%@.%@", document.displayName, timeString, extension]; | |
_promisedFrameImageFileURL = [destURL URLByAppendingPathComponent:imageFilename isDirectory:NO]; | |
return @[ imageFilename ]; | |
} | |
- (void) draggingSession:(NSDraggingSession *)session | |
endedAtPoint:(NSPoint)screenPoint | |
operation:(NSDragOperation)operation | |
{ | |
if (operation == NSDragOperationCopy) { | |
PRHFrameGrabber *frameGrabber = _draggingFrameGrabber; | |
NSString *type = [frameGrabber pasteboardPropertyListForType:(__bridge NSString *)kPasteboardTypeFilePromiseContent]; | |
NSData *imageData = [frameGrabber pasteboardPropertyListForType:type]; | |
[imageData writeToURL:_promisedFrameImageFileURL options:NSDataWritingAtomic error:NULL]; | |
} | |
_draggingFrameGrabber = nil; | |
_clickBeganInThisView = _dragBeganInThisView = false; | |
_draggingSession = nil; | |
} | |
[other stuff, including dragging *destination* logic] | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment