Drag out to delete item - objective-c

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.

There may be a less hacky way to do this, but I can think of one possibility: once the drag begins, create a transparent, borderless window the size of the desktop to be a dummy drag destination. You may need to call -setIgnoresMouseEvents: with NO to allow it to receive the drop even though it's transparent. You'll also have to set its window level above the menu bar (NSMainMenuWindowLevel + 1) to make sure that drags to the menu bar or Dock are still intercepted by your window.
As a drag destination, this window will have to check if one of your image views is under the cursor. You can use +[NSWindow windowNumberAtPoint:belowWindowWithWindowNumber:] to find the window below your transparent overlay window which is under the cursor. Then use -[NSApplication windowWithWindowNumber:] to determine if it's one of your app's windows and, if so, call -[NSView hitTest:] on its content view (converting the cursor coordinates as appropriate) to find the view. You can then forward NSDraggingDestination methods to that view as desired.

My guess is that NSDragOperationDelete only concerns drag/drops targeting the dock's Trash, and nothing else.
NSDragOperationGeneric should be a better fit.
Be sure not to mix methods, if you're going the 10.7 route, prefer :
-(void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation

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.

OS X 10.7 or better:
- (void) draggingEnded: (id<NSDraggingInfo>) aInfo {
/*! Delete the current leaf if it is no longer represented in the subviews. */
RETURN_IF ( , NSNotFound != [self.visibleLeafs indexOfObject: iSelectedControl.representedObject] );
iIgnoreLeafsObservation = YES;
[self.leafs removeObject: iSelectedControl.representedObject];
iIgnoreLeafsObservation = NO;
}
This assumes that you have visually removed the object already. You could also set a flag during draggingEntered: and draggingExited: to indicate if drag session was last inside or outside of self.
In this program, leafs is the actual observed collection while visibleLeafs is a simulated collection of the representedObject of each visible control. Other NSDraggingDestination overloads present what will happen visually before it actually happens.

You can use the following command to make the poof appears at the mouse location:
NSShowAnimationEffect(NSAnimationEffectPoof, [NSEvent mouseLocation], NSZeroSize, NULL, NULL, NULL);
See the Application Kit Functions Reference for further information.

Related

Does setNeedsDisplay:NO have any use at all?

In Cocoa, when we want to redraw a view, we would send the view a setNeedsDisplay: message telling the view to redraw itself with a parameter of YES. I was wondering if there are any circumstances where you would want to send a view setNeedsDisplay:NO, such as multithreading environments, and if sending a view a setNeedsDisplay:YES, then setting it again immediately after with setNeedsDisplay:NO would make the view redraw itself. If there are no reasons to call setNeedsDisplay:NO, then why create such a tedious method, where they could instead implement something like [view redrawView]
setNeedsDisplay:NO may be used in case you want to discard previously called setNeedsDisplay:YES. E.g. sometimes it is easier to mark all subviews as needing display and then run an algorithm to unmark some of them.
As you perhaps know, the display update is automatic (if necessary) at each pass through the normal event loop. You call setNeedsDisplay: in order to force a display update in between if it is necessary.
From the documentation of NSView:
Discussion
Whenever the data or state used for drawing a view object changes, the view should be sent a setNeedsDisplay: message. NSView objects marked as needing display are automatically redisplayed on each pass through the application’s event loop. (View objects that need to redisplay before the event loop comes around can of course immediately be sent the appropriate display... method.)
The boolean parameter to this function simply specifies if the entire bounds of the view in question is affected or not, not if some property "needsDisplay" is set to true or false. Thus, setNeedsDisplay: does indeed work pretty much like a "redrawView", only with the additional parameter.
Edit
The above was inspired from the same documentation:
flag
If YES, marks the receiver’s entire bounds as needing display; if NO, marks it as not needing display.

prevent windows from opening (windowless application)

I want to make a windowless application. The problem is that I'm using a framework that shows some alert boxes sometimes, and I'm not able to suppress them (at least not without a crash).
I've set LSBackgroundOnly to 1 (in the info.plist app file), but this doesn't seem to work. Any chance I'm missing something here (maybe some other settings that I need to set)?
I've also tried creating a category for NSWindow:
Overwritten alloc and allocWithZone: so that no window gets created. This works on some situations, but starting a modal session throws exception (because sending a nil parameter to beginModalSessionForWindow:).
Tried overwriting NSApplication's beginModalSessionForWindow: and returned nil everytime, but this leads to EXC_BADACCESS somewhere in the framework (runModalSession: and endModalSession: were also overwritten, but the crash is before any of them being called).
Any other ideas will be welcome.
In case it helps, the framework that I'm using is Twain.Framework, so if instead anyone knows a way to block all UI from twain (this includes error alerts shown when a device is not connected) it would be great if you share.
It's not clear what you're hoping for. Are you hoping for an app that doesn't normally display windows but does allow the third-party framework to show its alerts? Or are you hoping to find a way to prevent the framework from showing its alerts so your app absolutely never has any windows?
If it's the former, you're probably looking for LSUIElement. That's an app which can have some UI elements, but doesn't show in the Dock or the Command-Tab application switcher and doesn't have a menu bar.
I managed to make it 'windowless' (some windows are still shown, but at least they're not visible, and they're automatically closed). I only handled modal windows, since these where shown in my situation.
I made a category for NSWindow, so that all windows are invisible, and can't become key or main windows:
-(BOOL)canBecomeKeyWindow{
[self setOpaque:NO];
self.alphaValue = 0;
return NO;
}
-(BOOL)canBecomeMainWindow{
[self setOpaque:NO];
self.alphaValue = 0;
return NO;
}
Subclassed NSApplication:
-(NSInteger)runModalForWindow:(NSWindow *)aWindow{
return NSCancelButton;
}
-(NSModalSession)beginModalSessionForWindow:(NSWindow *)aWindow{
NSModalSession session = [super beginModalSessionForWindow:aWindow];
[aWindow performSelector:#selector(close) withObject:nil afterDelay:1];
return session;
}
However, this didn't help much. In case of runModalForWindow: everything was good, since the owner of the window received a response. In case of beginModalSessionForWindow: the application got stuck, most probably because the window's owner was waiting for a specific event (e.g. button click) which it never received.

How to activate a custom screensaver preview in Cocoa/Obj-C?

I have created a fairly simple screensaver that runs on Mac OS 10.6.5 without issue.
The configuration screen has accumulated quite a few different options and I'm trying to implement my own preview on the configureSheet window so the user (just me, currently) can immediately see the effect of a change without having to OK and Test each change.
I've added an NSView to the configureSheet and set the custom class in Interface Builder to my ScreenSaverView subclass. I know that drawRect: is firing, because I can remove the condition for clearing the view to black, and my custom preview no longer appears with the black background.
Here is that function (based on several fine tutorials on the Internet):
- (void)drawRect:(NSRect)rect
{
if ( shouldDrawBackground )
{
[super drawRect:rect];
shouldDrawBackground = NO;
}
if (pausing == NO)
[spiroForm drawForm];
}
The spiroForm class simply draws itself into the ScreenSaverView frame using NSBezierPath and, as mentioned, is not problematical for the actual screensaver or the built-in System Preferences preview. The custom preview (configureView) frame is passed into the init method for, um, itself (since its custom class is my ScreenSaverView subclass.) The -initWithFrame method is called in configureSheet before returning the configureSheet object to the OS:
[configureView initWithFrame:[configureView bounds] isPreview:YES];
Maybe I don't have to do that? It was just something I tried to see if it was required for drawing.
I eventually added a delegate to the configureSheet to try triggering the startAnimation and stopAnimation functions of my preview via windowWillBeginSheet and windowWillEndSheet notifications, but those don't appear to be getting called for some reason. The delegate is declared as NSObject <NSWindowDelegate> and I set the delegate in the configureSheet method before returning the configureSheet object.
I've been working on this for days, but haven't been able to find anything about how the OS manages the ScreenSaverView objects (which I think is what I'm trying to emulate by running my own copy.)
Does anybody have any suggestions on how to manage this or if Apple documents it somewhere that I haven't found? This isn't really required for the screensaver to work, I just think it would be fun (I also looked for a way to use the OS preview, but it's blocked while the configureSheet is activated.)
OK, there are a couple of 'duh' moments involved with the solution:
First of all, I was setting the delegate for the sheet notifications to the sheet itself. The window that the sheet belongs to gets the notifications.
Secondly, that very window that the sheet belongs to is owned by System Preferences, I don't see any way to set my delegate class as a delegate to that window, so the whole delegate thing doesn't appear to be a viable solution.
I ended up subclassing NSWindow for the configureSheet so that I could start and stop animation on my preview by over-riding the makeKeyWindow and close methods.
- (void) makeKeyWindow
{
if (myPreview != nil)
if ( ! [myPreview isAnimating])
{
[myPreview startAnimation];
}
[super makeKeyWindow];
}
I also had to add an IBOutlet for my preview object itself and connect it in Interface Builder.
Still working out a couple of issues, but now when I click on my screensaver Options button, my configureSheet drops down and displays its own preview while you set options. Sheesh. The hoops I jump through for these little niceties. Anyway, I like it. Onward and upward.

Problems with an unusual NSOpenGLView setup

I'm trying to set a subclassed NSOpenGLView in an unusual way and I am running into some problems. Basically, I am writing a program to perform a bioengineering simulation for my PhD and I need to be able to compile it under both MacOSX and Unix (my machine is a Mac, but the sim will eventually run on a more powerful Unix machine). Since the code will get longer and longer over the next year and a half I'd rather not have to keep track of two completely different versions of the program. So, I'm hoping to be able to compile the ObjectiveC code under Unix by avoiding ObjectiveC-2.0 and keeping the interface optional (it will mostly be there to perform setup before the long simulations and monitor things for the short ones during development).
The current version works well without the interface - the simulation is performed correctly and the program is capable of rendering OpenGL frames and exporting them into image and video files without any problems. Since I am now adding the interface (right now just a simple window with an NSOpenGLView subclass and a "start" button") on top of that (so that I can run the code with an alternate version of main() without it) I have to "wire" OpenGL together in a weird way, since the drawing code is not in the drawRect function, or even anywhere in the subclassed view, but instead in the "basic" program.
What I've done so far is this:
The main program (using an object called "Lattice") performs all the simulations and rendering, correctly outputing images and video to files. This also contains the NSOpenGLContext and calls [renderContext flushBuffer];
A subclass of NSOpenGLView called PottsView contains an instance of a lattice, which is initialized together with the view like this:
- (id)initWithFrame:(NSRect)frame {
if(![super initWithFrame:frame])
return nil;
// code
frameSize.width = WIN_WIDTH;
frameSize.height = WIN_HEIGHT;
[self setFrameSize:frameSize];
init_genrand64(time(0));
latt = [Lattice alloc];
if (SEED_TYPE) {
[latt initWithRandomSites];
} else {
[latt initWithEllipse];
}
[[latt context] makeCurrentContext];
return self;
}
drawRect() is empty.
PottsController is the object instanced in the InterfaceBuilder which connects the start button to the view. The start button simply tells the lattice to run for a number of steps.
Now, pressing start results in the simulation running correctly (i.e. output to files and terminal), but the PottsView is not working correctly. It remains white, but if I cmd+tab parts if it change to sections of a rendered frame. Same if I press Expose (F3).
I've tried several combinations of flushing, setNeedsDisplay, etc, but frankly speaking I'm lost. I haven't done any programming before this April and with this being (as far as I can tell) a completely backwards way of using NSOpenGLView I'm out of ideas. I'm hoping someone can suggest how I can make the current setup work or how to completely rewire the program (while still keeping the interface optional).
It's not clear how you think that you have 'wired' the context and the view together. You can have as many openglContexts as you like - just by drawing into one won't make it's contents show up in a random NSOpenGLView. Apologies if i have missed something.
NSOpenGLView is a fairly simple subclass of NSView that creates the context and pixel format. As you already have those you can do away with NSOpenGLView and use a custom NSView subclass.
You should look at this instruction.. http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_drawing/opengl_drawing.html
To draw to the screen you must flush the graphics context from -drawRect:
This will block the main thread while the gpu processes your instructions, this could be a problem if you have many instructions. It also can not happen more than 50fps.
If you are already rendering your frames to files woudn't you be better observing the output directory and drawing the image each time a new one is added, no opengl required?

How do I detect if a drag operation was cancelled using NSDraggingInfo or similar?

I've created a subclass of NSImageView and implemented the informal protocol for dragging images between other instances of the same class. I am keeping a reference to the image of the view prior to the dragging operation and am able to set it back to said image given certain criteria.
However, I can't seem to detect if the dragging operation was cancelled. I know that the draggingEnded method is called but it is also called when a drag was successful. Any ideas?
I encountered a similar requirement. The fact that you can examine the operation argument passed to the NSDraggingSource protocol method draggedImage:endedAt:operation: to detect drag cancellation is not very well documented.
In the draggedImage:endedAt:operation: method just add the following check:
if (operation == NSDragOperationNone)
return;
// Otherwise perform any drag completion tasks.