PDA

View Full Version : Right-Clicks/Control-Clicks and Contextual Menus




simX
Dec 10, 2005, 11:07 PM
I'm trying to provide a contextual menu to a table view that I have in my standard Cocoa application. Ideally, this will allow those with two-button mice or those with lightning-quick reflexes to quickly access some options based on the item selected in the list.

However, I'm having an annoying problem: when you right-click directly on an item in the NSTableView, the item under the mouse isn't automatically selected! Because how my actions are created, they rely on the selected item in the NSTableView, so right-clicking in the table view and then selecting an action will probably not (in most cases) result in the action being applied to the desired item. This negates the usefulness of the contextual menus to a great extent.

What's the easiest way to modify the mouse event so that a control-click on this NSTableView will first select the item and THEN bring up the contextual menu? (I'm even interested in "workarounds" where you would get the mouse position at the time of the control-click, and then my program would calculate which item should be selected, select it, and then bring up the menu.)



HexMonkey
Dec 11, 2005, 12:02 AM
The way I do it is to subclass NSTableView and change the table selection in the menuForEvent method. Here's the code I use (note that it requires Panther since it uses -[NSTableView selectedRowIndexes], if you need to support Jaguar use -[NSTableView selectedRowEnumerator] instead):

-(NSMenu*)menuForEvent:(NSEvent*)event
{
//Find which row is under the cursor
[[self window] makeFirstResponder:self];
NSPoint menuPoint = [self convertPoint:[event locationInWindow] fromView:nil];
int row = [self rowAtPoint:menuPoint];

/* Update the table selection before showing menu
Preserves the selection if the row under the mouse is selected (to allow for
multiple items to be selected), otherwise selects the row under the mouse */
BOOL currentRowIsSelected = [[self selectedRowIndexes] containsIndex:row];
if (!currentRowIsSelected)
[self selectRow:row byExtendingSelection:NO];

if ([self numberOfSelectedRows] <=0)
{
//No rows are selected, so the table should be displayed with all items disabled
NSMenu* tableViewMenu = [[self menu] copy];
int i;
for (i=0;i<[tableViewMenu numberOfItems];i++)
[[tableViewMenu itemAtIndex:i] setEnabled:NO];
return [tableViewMenu autorelease];
}
else
return [self menu];
}

simX
Dec 11, 2005, 05:48 AM
OMG. You rock! That worked perfectly! You solved it exactly how I would have done it... not interrupting the selection if it's already selected, and selecting it if it isn't.

Just a quick question, though -- does the menuForEvent: method handle anything else? That is, if I change the NSTableView method so that it uses your code instead, will it affect anything else that you might do with a table view? I assume, given the name of the method, that this just handles the display of a contextual menu, so all you're basically doing is adding some code so that the selection will happen first before the menu comes up. Am I right?

caveman_uk
Dec 11, 2005, 11:55 AM
Here's the code I use (note that it requires Panther since it uses -[NSTableView selectedRowIndexes], if you need to support Jaguar use -[NSTableView selectedRowEnumerator] instead):

I guess you could use selectedRowEnumerator for both as although it's now deprecated it still works.

HexMonkey
Dec 11, 2005, 03:35 PM
Just a quick question, though -- does the menuForEvent: method handle anything else? That is, if I change the NSTableView method so that it uses your code instead, will it affect anything else that you might do with a table view? I assume, given the name of the method, that this just handles the display of a contextual menu, so all you're basically doing is adding some code so that the selection will happen first before the menu comes up. Am I right?

Yes, you're correct. From the documentation:

Overridden by subclasses to return a context-sensitive pop-up menu for the mouse-down event theEvent....NSView’s implementation returns the receiver’s normal menu.

So by default it would return [self menu] and do nothing else.

I guess you could use selectedRowEnumerator for both as although it's now deprecated it still works.

Yeah, I still use it extensively in a lot of my older code. I don't expect Apple will remove it for a long time (if ever) since that would break a lot of applications.