Search code examples
objective-cmacoscocoadrag-and-dropnsdragginginfo

How to use a NSStatusItem as a drag destination?


I'm trying to build an application that allows users to drag files from Finder to the menubar icon for processing. I've made progress in my journey, but I can't seem to summit this hill. I tried subclassing NSView and implementing the drag messages.

@interface CMDroppableView : NSView <NSMenuDelegate>

I wanted to not only accept drag operations, but to provide a NSMenu when the user clicks the icon. I've managed to get the NSMenu to display properly, but the drag functionality remains elusive.

It's my understanding that I needed to register the accepted drag types which I have done here:

-(void)awakeFromNib{
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
}

Drag messages:

-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender{
    NSLog(@"Drag Enter");
    return NSDragOperationCopy;
}

-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender{
    return NSDragOperationCopy;
}

-(void)draggingExited:(id <NSDraggingInfo>)sender{
    NSLog(@"Drag Exit");
}

-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender{
     return YES;
}

-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender{
    return YES;
}

Here is the code where the custom view is set:

statusItemView = [[CMDroppableView alloc] init];
[statusItemView retain];
[statusItemView setMenu: statusMenu];

[statusItem setView: statusItemView];   

Still nothing. So where have I gone wrong?

Thanks!


Solution

  • Edit: D'oh, you're registering the drag types in -awakeFromNib, which won't be called if the view isn't being loaded from a nib. Try registering your drag types in -initWithFrame: instead!

    Old answer:

    IIRC you need to not set the menu on the status item. What I do is have my custom view manage a menu, and do something like this:

    - (void)setMenu:(NSMenu *)menu {
        [menu setDelegate:self];
        [super setMenu:menu];
    }
    
    - (void)mouseDown:(NSEvent *)event {
        [statusItem popUpStatusItemMenu:[self menu]]; // or another method that returns a menu
    }
    
    - (void)menuWillOpen:(NSMenu *)menu {
        highlight = YES;
        [self setNeedsDisplay:YES];
    }
    
    - (void)menuDidClose:(NSMenu *)menu {
        highlight = NO;
        [self setNeedsDisplay:YES];
    }
    
    - (void)drawRect:(NSRect)rect {
        NSImage *img = highlight ? [alternateImage copy] : [image copy];
        NSRect bounds = [self bounds];
        [statusItem drawStatusBarBackgroundInRect:bounds withHighlight:highlight];
        
        // rest of drawing code goes here, including drawing img where appropriate
    }
    

    in my custom view's implementation. This ensures the menu behavior is identical to the default.