Skip to content

Instantly share code, notes, and snippets.

@boredzo
Last active March 11, 2016 19:26
Show Gist options
  • Save boredzo/6b0cdc17eaeb0e5fe363 to your computer and use it in GitHub Desktop.
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.
//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 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