Reputation: 544
I have a window with a Source List (NSOutlineView). My source list has just two levels. Level one is header and level two is data. I want to have a contextual menu on some of the data cells. Not all.
First, I try to attach a menu on the table cell view who represents the data cell -> nothing happens.
Second, I attach a menu on the Outline View in IB -> the contextual menu opens on each cells (header and data). I search for stopping the opening of the menu, but I don't find anything.
Do you have some ideas ?
Thank you
OS X 10.8.2 Lion, Xcode 4.5.2, SDK 10.8
Upvotes: 6
Views: 1444
Reputation: 1242
- (void)rightMouseDown:(NSEvent *)event
An NSView will not pass this to the next view, This method looks to see that the current class has a menuForEvent:, if it does then it is called. If it does not then it is finished and nothing else will happen. This is why you will not see an NSTableCellView respond to a menuForEvent: because the table view swallows the rightMouseDown:.
You may subclass the tableview and handle the rightMouseDown: event and call the NSTableCellView's rightMouseDown: and handle displaying your menu that you have constructed in your storyboard and hooked up to your NSTableViewCell.
Here is my solution in a subclassed NSTableView:
- (void)rightMouseDown:(NSEvent *)event
{
for (NSTableRowView *rowView in self.subviews) {
for (NSView *tableCellView in [rowView subviews]) {
if (tableCellView) {
NSPoint eventPoint = [event locationInWindow];
// NSLog(@"Window Point: %@", NSStringFromPoint(eventPoint));
eventPoint = [self convertPoint:eventPoint toView:nil];
eventPoint = [self convertPoint:eventPoint toView:self];
// NSLog(@"Table View Point: %@", NSStringFromPoint(eventPoint));
NSRect newRect = [tableCellView convertRect:[tableCellView bounds] toView:self];
// NSLog(@"Rect: %@", NSStringFromRect(newRect));
BOOL rightMouseDownInTableCellView = [tableCellView mouse:eventPoint inRect:newRect];
// NSLog(@"Mouse in view: %hhd", mouseInView);
if (rightMouseDownInTableCellView) {
if (tableCellView) {
// Lets be safe and make sure that the object is going to respond.
if ([tableCellView respondsToSelector:@selector(rightMouseDown:)]) {
[tableCellView rightMouseDown:event];
}
}
}
}
}
}
}
This will find where the right mouse event occurred, check to see if we have the correct view and pass the rightMouseDown: to that view.
Please let me know if this solution works for you.
Upvotes: 0
Reputation: 537
This extension + subclass (both NSOutlineView and NSTableView) does the sensible thing of seeing whether a menu is attached to a cell view or row view. Just a general, reusable subclass!
Set the menu on the cell view in outlineView:viewForTableColumn:item:
– menu
is a NSResponder property.
(Below is in Swift)
// An extension lets us both subclass NSTableView and NSOutlineView with the same functionality
extension NSTableView {
// Find a cell view, or a row view, that has a menu. (e.g. NSResponder’s menu: NSMenu?)
func burnt_menuForEventFromCellOrRowViews(event: NSEvent) -> NSMenu? {
let point = convertPoint(event.locationInWindow, fromView: nil)
let row = rowAtPoint(point)
if row != -1 {
if let rowView = rowViewAtRow(row, makeIfNecessary: true) as? NSTableRowView {
let column = columnAtPoint(point)
if column != -1 {
if let cellView = rowView.viewAtColumn(column) as? NSTableCellView {
if let cellMenu = cellView.menuForEvent(event) {
return cellMenu
}
}
}
if let rowMenu = rowView.menuForEvent(event) {
return rowMenu
}
}
}
return nil
}
}
class OutlineView: NSOutlineView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
class TableView: NSTableView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
Upvotes: 2
Reputation: 2080
It's not clear from your question whether your outline is view based or cell based. That's important.
If you're view based, then your view instances can implement
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
and return the menu appropriate to that item -- or nil f you don't want a menu at all.
If you're cell based, or if you don't want to handle this in the view class for some reason, you'll need to subclass NSOutlineView and implement - (NSMenu *)menuForEvent:(NSEvent *)theEvent
there. Again, you'll figure out which cell is hit or active, and decide from that what menu you want.
Upvotes: 0
Reputation: 40507
If you subclass NSOutlineView, you can override menuForEvent:
to return a menu only if the user clicked on the correct row. Here's an example:
- (NSMenu *)menuForEvent:(NSEvent *)event;
{
//The event has the mouse location in window space; convert it to our (the outline view's) space so we can find which row the user clicked on.
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger row = [self rowAtPoint:point];
//If the user did not click on a row, or is not exactly one level down from the top level of hierarchy, return nil—that is, no menu.
if ( row == -1 || [self levelForRow:row] != 1 )
return nil;
//Create and populate a menu.
NSMenu *menu = [[NSMenu alloc] init];
NSMenuItem *delete = [menu addItemWithTitle:NSLocalizedString( @"Delete", @"" ) action:@selector(delete:) keyEquivalent:@""];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
//Set the Delete menu item's represented object to the clicked-on item. If the user chooses this item, we'll retrieve its represented object so we know what to delete.
[delete setRepresentedObject:[self itemAtRow:row]];
return menu;
}
This assumes we're compiling with ARC, so you don't need to autorelease the menu object being created.
Upvotes: 7