2
votes

In my Mac app I have a NSCollectionView with multi select enabled. In my app being able to select more than one item is the norm, and having to press cmd while clicking to select multiple items is frustrating some users and most don't realise they can do it (I get a lot of feature requests asking for multi select).

So, I want to change the behaviour so that:

  • when a user clicks a second item, the first item remains selected (without the need for holding cmd)
  • When a user clicks a selected item, the item is deselected

I've tried overriding setSelected on my own subclass of NSCollectionViewItem like so:

-(void)setSelected:(BOOL)flag
{
    [super setSelected:flag];
    [(MyView*)[self view] setSelected: flag];
    [(MyView*)[self view] setNeedsDisplay:YES];
}

Calling super setSelected is required to make sure the collection view functions correctly, but it also seems to be what is responsible for the default behaviour.

What should I do instead?

1
It's a bit late now, but I wonder: Why don't you support selecting with the Shift key to extend the selection, like it works nearly everywhere (TableViews, Finder)?Thomas Tempelmann

1 Answers

1
votes

You could try intercepting all left-mouse-down events using a local events monitor. Within this block you'd then work out if the click happened on your collection view. If it did, create a new event which mimics the event you intercepted but add in the command key mask if it isn't already present. Then, at the end of the block return your event rather than the one you intercepted. Your collection view will behave as if the user had pressed the command key, even though they haven't!

I had a quick go with this in a very simple demo app and it looks like a promising approach - though I expect you'll have to negotiate a few gotchas along the way.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {


    [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFromType(NSLeftMouseDown) 
            handler:^NSEvent *(NSEvent *originalEvent) {

        // Did this left down event occur on your collection view? 
        // If it did add in the command key

        NSEvent *newEvent =
            [NSEvent
                mouseEventWithType: NSLeftMouseDown
                location: originalEvent.locationInWindow
                modifierFlags: NSCommandKeyMask // I'm assuming it's not already present
                timestamp: originalEvent.timestamp
                windowNumber: originalEvent.windowNumber
                context: originalEvent.context
                eventNumber: originalEvent.eventNumber
                clickCount: originalEvent.clickCount
                pressure:0];

        return newEvent; // or originalEvent if it's nothing to do with your collection view
    }];
}

Edit (by question author):

This solution is so heavily based on the original answer that this answer deserves credit (feel free to edit)

You can also intercept the mouse event by subclassing the NSCollectionView class and overriding mousedown like this:

@implementation MyCollectionView

-(void) mouseDown:(NSEvent *)originalEvent {

    NSEvent *mouseEventWithCmd =
        [NSEvent
            mouseEventWithType: originalEvent.type
            location: originalEvent.locationInWindow
            modifierFlags: NSCommandKeyMask
            timestamp: originalEvent.timestamp
            windowNumber: originalEvent.windowNumber
            context: originalEvent.context
            eventNumber: originalEvent.eventNumber
            clickCount: originalEvent.clickCount
            pressure: originalEvent.pressure];

    [super mouseDown: mouseEventWithCmd];
}

@end