2

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?

Richard Garside
  • 87,839
  • 11
  • 80
  • 93
  • 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 Nov 15 '20 at 18:51

1 Answers1

1

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
Richard Garside
  • 87,839
  • 11
  • 80
  • 93
Paul Patterson
  • 6,840
  • 3
  • 42
  • 56
  • This does work, but unfortunately intercepting at the mouse event level and adding cmd to all clicks over the NSCollectionView breaks another major feature of my app. There are other controls in my collection view that adding cmd to their mouse event changes their behaviour. I can only find a way to discriminate the mouse event based in it's pixel location. This is fine for the overall control, but as the sub controls move, I don't think it's practical. – Richard Garside Feb 19 '15 at 10:24
  • This solution would work for others though who don't have other controls that require mouse events in their NSCollectionView. – Richard Garside Feb 19 '15 at 10:25
  • Got it working using a tweaked version of what you suggested. Hope you don't mind, but I added it to your answer as you deserve most of the credit. Feel free to edit the answer to make it more concise and useful to future people. – Richard Garside Feb 19 '15 at 14:00
  • For some reason I've never considered tweaking events from within a standard event handler - it's a tactic I'll keep in mind. – Paul Patterson Feb 19 '15 at 16:09