1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
7 #include "base/mac/foundation_util.h"
8 #import "chrome/browser/ui/cocoa/autofill/autofill_bubble_controller.h"
9 #import "ui/base/cocoa/base_view.h"
10 #import "ui/base/cocoa/hover_image_button.h"
12 // Delay time before tooltip shows/hides.
13 const NSTimeInterval kTooltipDelay = 0.1;
15 // How far to inset tooltip contents.
16 CGFloat kTooltipInset = 10;
18 #pragma mark AutofillTooltipController - private methods
20 @interface AutofillTooltipController ()
22 // Sets hover state for "mouse over InfoBubble".
23 - (void)setHoveringOnBubble:(BOOL)hoveringOnBubble;
25 // Update the combined hover state - if either button or bubble is hovered,
26 // the combined state is considered "hovered". Notifies delegate if the state
28 - (void)updateTooltipDisplayState;
32 #pragma mark AutofillTooltip
34 // The actual tooltip control - based on HoverButton, which comes with free
36 @interface AutofillTooltip : HoverButton {
38 // Not owned - |tooltipController_| owns this object.
39 AutofillTooltipController* tooltipController_;
42 @property(assign, nonatomic) AutofillTooltipController* tooltipController;
47 @implementation AutofillTooltip
49 @synthesize tooltipController = tooltipController_;
51 - (void)drawRect:(NSRect)rect {
52 [[self image] drawInRect:rect
54 operation:NSCompositeSourceOver
60 - (void)setHoverState:(HoverState)state {
61 [super setHoverState:state];
62 [tooltipController_ updateTooltipDisplayState];
65 - (BOOL)acceptsFirstResponder {
71 #pragma mark AutofillTrackingView
73 // A very basic view that only tracks mouseEntered:/mouseExited: and forwards
74 // them to |tooltipController_|.
75 @interface AutofillTrackingView : BaseView {
77 // Not owned - tooltip controller owns tracking view and tooltip.
78 AutofillTooltipController* tooltipController_;
81 @property(assign, nonatomic) AutofillTooltipController* tooltipController;
85 @implementation AutofillTrackingView
87 @synthesize tooltipController = tooltipController_;
89 - (void)mouseEntered:(NSEvent*)theEvent {
90 [tooltipController_ setHoveringOnBubble:YES];
93 - (void)mouseExited:(NSEvent*)theEvent {
94 [tooltipController_ setHoveringOnBubble:NO];
99 #pragma mark AutofillTooltipController
101 @implementation AutofillTooltipController
103 @synthesize message = message_;
105 - (id)initWithArrowLocation:(info_bubble::BubbleArrowLocation)arrowLocation {
106 if ((self = [super init])) {
107 arrowLocation_ = arrowLocation;
108 view_.reset([[AutofillTooltip alloc] init]);
109 [self setView:view_];
110 [view_ setTooltipController:self];
116 [view_ setTooltipController:nil];
117 [NSObject cancelPreviousPerformRequestsWithTarget:self];
118 [[NSNotificationCenter defaultCenter]
120 name:NSWindowWillCloseNotification
121 object:[bubbleController_ window]];
125 - (void)setImage:(NSImage*)image {
126 [view_ setImage:image];
127 [view_ setFrameSize:[image size]];
130 - (void)tooltipWindowWillClose:(NSNotification*)notification {
131 bubbleController_ = nil;
134 - (void)displayHover {
135 [bubbleController_ close];
137 [[AutofillBubbleController alloc]
138 initWithParentWindow:[[self view] window]
139 message:[self message]
140 inset:NSMakeSize(kTooltipInset, kTooltipInset)
141 arrowLocation:arrowLocation_];
142 [bubbleController_ setShouldCloseOnResignKey:NO];
144 // Handle bubble self-deleting.
145 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
146 [center addObserver:self
147 selector:@selector(tooltipWindowWillClose:)
148 name:NSWindowWillCloseNotification
149 object:[bubbleController_ window]];
151 // Inject a tracking view so controller can track hover events for the bubble.
152 base::scoped_nsobject<NSView> oldContentView(
153 [[[bubbleController_ window] contentView] retain]);
154 base::scoped_nsobject<AutofillTrackingView> trackingView(
155 [[AutofillTrackingView alloc] initWithFrame:[oldContentView frame]]);
156 [trackingView setTooltipController:self];
157 [trackingView setAutoresizesSubviews:YES];
158 [oldContentView setFrame:[trackingView bounds]];
160 setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
161 [[bubbleController_ window] setContentView:trackingView];
162 [trackingView setSubviews:@[ oldContentView ]];
164 // Compute anchor point (in window coords - views might be flipped).
165 NSRect viewRect = [view_ convertRect:[view_ bounds] toView:nil];
166 NSPoint anchorPoint = NSMakePoint(NSMidX(viewRect), NSMinY(viewRect));
167 [bubbleController_ setAnchorPoint:
168 [[[self view] window] convertBaseToScreen:anchorPoint]];
169 [bubbleController_ showWindow:self];
173 [bubbleController_ close];
176 - (void)setHoveringOnBubble:(BOOL)hoveringOnBubble {
177 isHoveringOnBubble_ = hoveringOnBubble;
178 [self updateTooltipDisplayState];
181 - (void)updateTooltipDisplayState {
182 BOOL newDisplayState =
183 ([view_ hoverState] != kHoverStateNone || isHoveringOnBubble_);
185 if (newDisplayState != shouldDisplayTooltip_) {
186 shouldDisplayTooltip_ = newDisplayState;
188 // Cancel any pending visibility changes.
189 [NSObject cancelPreviousPerformRequestsWithTarget:self];
191 // If the desired visibility disagrees with current visibility, start a
192 // timer to change visibility. (Uses '!!' to force bool values)
193 if (!!bubbleController_ ^ !!shouldDisplayTooltip_) {
194 SEL sel = shouldDisplayTooltip_ ? @selector(displayHover)
195 : @selector(hideHover);
196 [self performSelector:sel withObject:nil afterDelay:kTooltipDelay];