Getting the initiator for an NSMenuItem

516 views Asked by At

I have a view with 3 images which have there own @property in their class. When I right click one of these images, I show a NSMenu with submenu which has NSMenuItems. All the items are send to the IBAction called - (IBAction)selectImage:(id)sender with each a different tag. The sender is, of course, the NSMenuItem. How can I find out which image the user right clicked upon? So I an basically looking for the caller to the parent of the sender.

Or maybe, I am building my menu all wrong?

3

There are 3 answers

0
Thomas Deniau On BEST ANSWER

You're approaching the problem the wrong way. Instead of setting an explicit target on your menu items, use the First Responder (or nilif you're doing it in code).

By default, the first responder will be the view that owns the contextual menu ("For a context menu, the search is restricted to the responder chain of the window in which the context menu was displayed, starting with the associated view" according the documentation).

Therefore, if you subclass your NSImageView and implement the actions that you need there, it will, by default, receive the messages. They can be a simple redirection to your controller object, but you will know which view was clicked on this way (because it's self in this case!)


This answer is an edit of this previous answer, which is significantly less elegant:

Start by creating a subclass of NSMenu (let's call it PPMenu) that has an additional property to store a weak reference to your image view (let's call it imageView). Set that class as your menu's class in your XIB, and set the menu as the image view's context menu there as well (or, if they don't exist in the XIB, just add an outlet).

Now, if you override -menuForEvent: is your NSImageView subclass, you'll be able to customize the context menu... Start by calling super there to retrieve the PPMenu instance (if it is set up in IB, otherwise use the outlet), and set the imageView property that you've added to self (since you're in the image view code).

Now, in your action's code, you can use ((PPMenu*)(sender.menu)).imageView to retrieve the image view associated with the menu.

0
Ken Thomases On

You would override -menuForEvent: in your view class and store a reference to the event, remember the location, or determine which image was hit and remember that. Then, in the action method, you would use that remembered event/location/image index to determine how to respond.

Of course, to get the event location in the view's coordinates, you would do:

NSPoint point = [self convertPoint:event.locationInWindow fromView:nil];

When testing if that point is in a particular part of the view, you should use:

if ([self mouse:point inRect:rectOfInterest])
    // ...
4
Daniyar On

Just remember the image you clicked in your right click event handler.

#pragma mark - mouse events

- (void)rightMouseDown:(NSEvent *)event
{
  [super rightMouseDown:event]; // if you don't want to push events to parent view - remove this line
  NSPoint eventLocation = [event locationInWindow]; // get mouse location in window coordinates
  NSPoint localLocation = [self convertPoint:eventLocation fromView:nil]; // get mouse location in view coordinates
  selectedImageView = nil; // set it nil each time right mouse down
  NSArray *images = @[imageView0, imageView1, imageView2]; // all your image view outlets, check if they aren't nil
  for (NSImageView *imageView in images)
    if (NSPointInRect(localLocation, imageView.frame))
    {
      selectedImageView = imageView; // selectedImageView should be a class member
      break;
    }
}

#pragma mark - menu actions

- (IBAction)selectImage:(id)sender
{
  if (selectedImageView)
  {
    // your code here
  }
}

This was a light version of what you need to be done. But let's think user is pushing right mouse down near the image0, then drag cursor off the image and then releases the mouse. For this you should override another mouse event handler rightMouseUp just to check the cursor still above the selectedImageView

#pragma mark - mouse events

//...

- (void)rightMouseUp:(NSEvent *)event
{
  [super rightMouseUp:event];
  if (!selectedImageView)
    return; // nothing to check here
  NSPoint eventLocation = [event locationInWindow]; 
  NSPoint localLocation = [self convertPoint:eventLocation fromView:nil]; 
  if (!NSPointInRect(localLocation, selectedImageView.frame)) // the mouse not above the selectedImageView
    selectedImageView = nil;
  else
    {
      // there may be NSMenu instance initializing
    }
}