blob: b2a66dc7c08efb0167739d9677dc27b49a38f850 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
#include "base/mac/foundation_util.h"
#import "chrome/browser/ui/cocoa/autofill/autofill_bubble_controller.h"
#import "ui/base/cocoa/base_view.h"
#import "ui/base/cocoa/hover_image_button.h"
// Delay time before tooltip shows/hides.
const NSTimeInterval kTooltipDelay = 0.1;
// How far to inset tooltip contents.
CGFloat kTooltipInset = 10;
#pragma mark AutofillTooltipController - private methods
@interface AutofillTooltipController ()
// Sets hover state for "mouse over InfoBubble".
- (void)setHoveringOnBubble:(BOOL)hoveringOnBubble;
// Update the combined hover state - if either button or bubble is hovered,
// the combined state is considered "hovered". Notifies delegate if the state
// changed.
- (void)updateTooltipDisplayState;
@end
#pragma mark AutofillTooltip
// The actual tooltip control - based on HoverButton, which comes with free
// hover handling.
@interface AutofillTooltip : HoverButton {
@private
// Not owned - |tooltipController_| owns this object.
AutofillTooltipController* tooltipController_;
}
@property(assign, nonatomic) AutofillTooltipController* tooltipController;
@end
@implementation AutofillTooltip
@synthesize tooltipController = tooltipController_;
- (void)drawRect:(NSRect)rect {
[[self image] drawInRect:rect
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
}
- (void)setHoverState:(HoverState)state {
[super setHoverState:state];
[tooltipController_ updateTooltipDisplayState];
}
- (BOOL)acceptsFirstResponder {
return NO;
}
@end
#pragma mark AutofillTrackingView
// A very basic view that only tracks mouseEntered:/mouseExited: and forwards
// them to |tooltipController_|.
@interface AutofillTrackingView : BaseView {
@private
// Not owned - tooltip controller owns tracking view and tooltip.
AutofillTooltipController* tooltipController_;
}
@property(assign, nonatomic) AutofillTooltipController* tooltipController;
@end
@implementation AutofillTrackingView
@synthesize tooltipController = tooltipController_;
- (void)mouseEntered:(NSEvent*)theEvent {
[tooltipController_ setHoveringOnBubble:YES];
}
- (void)mouseExited:(NSEvent*)theEvent {
[tooltipController_ setHoveringOnBubble:NO];
}
@end
#pragma mark AutofillTooltipController
@implementation AutofillTooltipController
@synthesize message = message_;
- (id)initWithArrowLocation:(info_bubble::BubbleArrowLocation)arrowLocation {
if ((self = [super init])) {
arrowLocation_ = arrowLocation;
view_.reset([[AutofillTooltip alloc] init]);
[self setView:view_];
[view_ setTooltipController:self];
}
return self;
}
- (void)dealloc {
[view_ setTooltipController:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:NSWindowWillCloseNotification
object:[bubbleController_ window]];
[super dealloc];
}
- (void)setImage:(NSImage*)image {
[view_ setImage:image];
[view_ setFrameSize:[image size]];
}
- (void)tooltipWindowWillClose:(NSNotification*)notification {
bubbleController_ = nil;
}
- (void)displayHover {
[bubbleController_ close];
bubbleController_ =
[[AutofillBubbleController alloc]
initWithParentWindow:[[self view] window]
message:[self message]
inset:NSMakeSize(kTooltipInset, kTooltipInset)
arrowLocation:arrowLocation_];
[bubbleController_ setShouldCloseOnResignKey:NO];
// Handle bubble self-deleting.
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(tooltipWindowWillClose:)
name:NSWindowWillCloseNotification
object:[bubbleController_ window]];
// Inject a tracking view so controller can track hover events for the bubble.
base::scoped_nsobject<NSView> oldContentView(
[[[bubbleController_ window] contentView] retain]);
base::scoped_nsobject<AutofillTrackingView> trackingView(
[[AutofillTrackingView alloc] initWithFrame:[oldContentView frame]]);
[trackingView setTooltipController:self];
[trackingView setAutoresizesSubviews:YES];
[oldContentView setFrame:[trackingView bounds]];
[oldContentView
setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[[bubbleController_ window] setContentView:trackingView];
[trackingView setSubviews:@[ oldContentView ]];
// Compute anchor point (in window coords - views might be flipped).
NSRect viewRect = [view_ convertRect:[view_ bounds] toView:nil];
NSPoint anchorPoint = NSMakePoint(NSMidX(viewRect), NSMinY(viewRect));
[bubbleController_ setAnchorPoint:
[[[self view] window] convertBaseToScreen:anchorPoint]];
[bubbleController_ showWindow:self];
}
- (void)hideHover {
[bubbleController_ close];
}
- (void)setHoveringOnBubble:(BOOL)hoveringOnBubble {
isHoveringOnBubble_ = hoveringOnBubble;
[self updateTooltipDisplayState];
}
- (void)updateTooltipDisplayState {
BOOL newDisplayState =
([view_ hoverState] != kHoverStateNone || isHoveringOnBubble_);
if (newDisplayState != shouldDisplayTooltip_) {
shouldDisplayTooltip_ = newDisplayState;
// Cancel any pending visibility changes.
[NSObject cancelPreviousPerformRequestsWithTarget:self];
// If the desired visibility disagrees with current visibility, start a
// timer to change visibility. (Uses '!!' to force bool values)
if (!!bubbleController_ ^ !!shouldDisplayTooltip_) {
SEL sel = shouldDisplayTooltip_ ? @selector(displayHover)
: @selector(hideHover);
[self performSelector:sel withObject:nil afterDelay:kTooltipDelay];
}
}
}
@end