Re: Showing a menu after a delay...?
Re: Showing a menu after a delay...?
- Subject: Re: Showing a menu after a delay...?
- From: Graham Cox <email@hidden>
- Date: Wed, 3 Feb 2010 08:57:05 +1100
On 03/02/2010, at 2:12 AM, Eric Gorr wrote:
> This method is generally not overridden
> I have filed a bug requesting that trackMouse:inRect:ofView:untilMouseUp: call continueTracking:at:inView: and stopTracking:at:inView:mouseIsUp: ... or is there a reason why it shouldn't?
Note that it says is GENERALLY not overridden, not that it is NEVER overridden.
I've noticed that quite a few built-in cell classes override at this level, where others override the more fine-grained methods. I guess it's just a question of what makes the most sense or is easiest for a given implementation. NSButtonCell does call -startTrackingAt:inView:, but not the others, I think. It appears to run a modal loop within -startTrackingAt:...
I've only written one or two custom cell classes but have overridden at both levels. I did find that for 'button-like' tracking behaviour, overriding -trackMouse:... or -startTrackingAt:... was simplest, because you can keep control in an event-fetching loop until the mouse goes up before returning. For many controls, that's way easier than dealing with three other methods called at different times.
Back to your original question, you want to show a menu after a delay. I do this in a custom button cell (subclass of NSButtonCell). Here's my code FWIW, which unfortunately is a bit hackish. I don't bother cancelling the timer on mouse up, but I decide whether to actually show the menu based on whether the cell is still highlighted at the time, otherwise the timer callback does nothing. I also found it necessary to "fake up" a mouse up event when the menu tracking has completed to ensure the cell tracking is forced to return, because otherwise the pop-up menu tracking has swallowed the mouse-up event and so the button would otherwise remain stuck in its tracking loop waiting for a mouse-up that will never arrive. There may well be a far less hackish way to do that, but this does work:
(I note that KBPopUpToolbarItem doesn't do something similar because it implements its own tracking loop in its entirety, so has full control over when it returns. My code relies on inheriting more of NSButtonCell's standard behaviour, so the mouse-up event was needed.)
- (BOOL) startTrackingAt:(NSPoint) startPoint inView:(NSView*) controlView
{
// if there is a menu and show after delay is YES, start a timer for showing the menu
if([self menu] && [self showsMenuAfterDelay] && mMenuTimer == nil)
{
mMenuTimer = [[NSTimer timerWithTimeInterval:[self menuDelay] target:self selector:@selector(menuDelayCallback:) userInfo:nil repeats:NO] retain];
[[NSRunLoop currentRunLoop] addTimer:mMenuTimer forMode:NSEventTrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:mMenuTimer forMode:NSDefaultRunLoopMode];
// make a note of the mouse location for faking the mouse-up at the end:
mMouseDownPoint = [controlView convertPoint:startPoint toView:nil];
}
return [super startTrackingAt:startPoint inView:controlView];
}
- (void) menuDelayCallback:(NSTimer*) timer
{
#pragma unused(timer)
// show the cell's menu if the button is still highlighted (if user let go, it won't be)
if([self isHighlighted])
{
// pop up the menu and switch tracking to it.
NSMenu* menu = [self menu];
NSEvent* event = [NSApp currentEvent];
// menu tracking keeps control until a choice is made:
[NSMenu popUpContextMenu:menu withEvent:event forView:[self controlView]];
// on return, post a mouse-up so that the cell tracking completes as normal. The mouse location
// was recorded at the time it went down and the callback was scheduled
NSWindow* window = [[self controlView] window];
NSEvent* fakeMouseUp = [NSEvent mouseEventWithType:NSLeftMouseUp
location:mMouseDownPoint
modifierFlags:0
timestamp:[NSDate timeIntervalSinceReferenceDate]
windowNumber:[window windowNumber]
context:[NSGraphicsContext currentContext]
eventNumber:0
clickCount:1
pressure:0.0];
[window postEvent:fakeMouseUp atStart:YES];
// after a menu selection, ensure the button is set to its ON state:
[(NSControl*)[self controlView] selectCell:self];
[self setState:NSOnState];
}
[mMenuTimer release];
mMenuTimer = nil;
}
--Graham
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden