Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drag out to delete item

You know that effect when you drag out an item from the dock and that cloud drag cursor appears and when you let go it disappears with a poof effect? Similarly, in Xcode when you drag a breakpoint outside the line number gutter the same happens.

I would like to implement the same effect in my application but can't find the right way.

I have an NSImageView descendant to implement the NSDraggingSource and NSDraggingDestination protocols. I have several instances of this view which allow to drag their content between the others (a copy operation takes place in this scenario, but that's only relevant to show I have drag'n drop implmented and fully working for standard tasks).

Now, when I drag out an image from its view to anywhere (except another view instance) I want to have the delete operation taking place on drop. However the drag operation is fully controlled by the target view. I could manage to make them respond the way I want (even though this would be a lot of work), but it fails completely if I'm dragging outside my application.

If I could get the delete drag operation I could handle this however easily by:

- (void)draggedImage: (NSImage *)image
             endedAt: (NSPoint)screenPoint
           operation: (NSDragOperation)operation
{
    if (operation == NSDragOperationDelete) {
        NSRect rect = [self.window convertRectToScreen: [self convertRect: self.frame fromView: nil]];
        NSShowAnimationEffect(NSAnimationEffectPoof, rect.origin, self.bounds.size, nil, nil, NULL);
    }
}

I tried already to set the delete cursor like this:

- (void)draggingSession: (NSDraggingSession *)session
           movedToPoint: (NSPoint)screenPoint
{
    if (!NSPointInRect(screenPoint, self.window.frame)) {
        [[NSCursor disappearingItemCursor] set];
    }
}

(for simplicity this is for the entire windw at the moment). This works as long as I don't hit the desktop or a finder window. In starts flickering, probably because the Finder concurrently sets its own drag cursor. It is completely without effect when I hit the dock. This also happens when I define my own pasteboard data type.

Additionally, any other drop enabled view in my application will still accept my drag data (e.g. NSTextView) which I don't want to happen (I'm writing an NSURL to the dragging pasteboard with a custom scheme).

Update:

I've come a few steps further. As Peter already indicated it is essential to handle draggingSession:sourceOperationmaskForDraggingContext: which looks so in my code:

- (NSDragOperation)       draggingSession: (NSDraggingSession *)session
    sourceOperationMaskForDraggingContext: (NSDraggingContext)context;
{
    switch(context) {
        case NSDraggingContextOutsideApplication:
            return NSDragOperationDelete;
            break;

        case NSDraggingContextWithinApplication:
        default:
            return NSDragOperationDelete | NSDragOperationMove;
            break;
    }
}

This solves 2 problems: 1) outside the application the drag operation is not accepted at all, 2) it keeps all standard views from accepting this operation too (because NSOutlineView, NSTextView etc. don't handle the given drag operations). Additionally, I created an own pasteboard datatype, but this doesn't seem to be necessary. Still it's clearer to have an own one.

Unfortunately, dropping outside of my NSImageView descendant (both within and outside the application) does not give me NSDragOperationDelete in draggedImage:endedAt:operation: (what I specified above) but NSDragOperationNone. Additionally the drag cursor when moving the mouse outside the application is the not-allowed one, not the disappearing-item. So, if someone could solve these two things I'd accept it as answer to my question.

like image 259
Mike Lischke Avatar asked May 12 '13 17:05

Mike Lischke


1 Answers

I just finished implementing something very similar to what you've described. Your code is quite similar to mine with a few exceptions:

Where you use draggedImage:endedAt:operation: I'm using draggingSession:session:endedAt:operation:. Also, in this method, I do not check the operation as all of my operations are set to generic. This is also where I perform the actual delete, so I only show the poof animation if the delete is successful.

In your draggingSession:session:movedToPoint:, you may also want to set the session's animatesToStartingPositionsOnCancelOrFail to false when the point is outside the window (this is also when you set the disappearingItemCursor) and set to true otherwise. This adds a final touch that once the deletion operation is completed, the dragged image doesn't rebound back to it's originating source location.

As for why you are not seeing the proper cursors, my guess is that you are using a Pasteboard type that other things (Finder, etc.) are willing to accept. I know you said you created your own Pastboard datatypes, but I would double-check that they are being used. When I made my own types, I gained control over the cursor. At least, I've not found anything that contends for my custom type.

like image 143
pauln Avatar answered Oct 21 '22 04:10

pauln