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. 4 5#import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h" 6 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" 11 12// Delay time before tooltip shows/hides. 13const NSTimeInterval kTooltipDelay = 0.1; 14 15// How far to inset tooltip contents. 16CGFloat kTooltipInset = 10; 17 18#pragma mark AutofillTooltipController - private methods 19 20@interface AutofillTooltipController () 21 22// Sets hover state for "mouse over InfoBubble". 23- (void)setHoveringOnBubble:(BOOL)hoveringOnBubble; 24 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 27// changed. 28- (void)updateTooltipDisplayState; 29 30@end 31 32#pragma mark AutofillTooltip 33 34// The actual tooltip control - based on HoverButton, which comes with free 35// hover handling. 36@interface AutofillTooltip : HoverButton { 37 @private 38 // Not owned - |tooltipController_| owns this object. 39 AutofillTooltipController* tooltipController_; 40} 41 42@property(assign, nonatomic) AutofillTooltipController* tooltipController; 43 44@end 45 46 47@implementation AutofillTooltip 48 49@synthesize tooltipController = tooltipController_; 50 51- (void)drawRect:(NSRect)rect { 52 [[self image] drawInRect:rect 53 fromRect:NSZeroRect 54 operation:NSCompositeSourceOver 55 fraction:1.0 56 respectFlipped:YES 57 hints:nil]; 58} 59 60- (void)setHoverState:(HoverState)state { 61 [super setHoverState:state]; 62 [tooltipController_ updateTooltipDisplayState]; 63} 64 65- (BOOL)acceptsFirstResponder { 66 return NO; 67} 68 69@end 70 71#pragma mark AutofillTrackingView 72 73// A very basic view that only tracks mouseEntered:/mouseExited: and forwards 74// them to |tooltipController_|. 75@interface AutofillTrackingView : BaseView { 76 @private 77 // Not owned - tooltip controller owns tracking view and tooltip. 78 AutofillTooltipController* tooltipController_; 79} 80 81@property(assign, nonatomic) AutofillTooltipController* tooltipController; 82 83@end 84 85@implementation AutofillTrackingView 86 87@synthesize tooltipController = tooltipController_; 88 89- (void)mouseEntered:(NSEvent*)theEvent { 90 [tooltipController_ setHoveringOnBubble:YES]; 91} 92 93- (void)mouseExited:(NSEvent*)theEvent { 94 [tooltipController_ setHoveringOnBubble:NO]; 95} 96 97@end 98 99#pragma mark AutofillTooltipController 100 101@implementation AutofillTooltipController 102 103@synthesize message = message_; 104 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]; 111 } 112 return self; 113} 114 115- (void)dealloc { 116 [view_ setTooltipController:nil]; 117 [NSObject cancelPreviousPerformRequestsWithTarget:self]; 118 [[NSNotificationCenter defaultCenter] 119 removeObserver:self 120 name:NSWindowWillCloseNotification 121 object:[bubbleController_ window]]; 122 [super dealloc]; 123} 124 125- (void)setImage:(NSImage*)image { 126 [view_ setImage:image]; 127 [view_ setFrameSize:[image size]]; 128} 129 130- (void)tooltipWindowWillClose:(NSNotification*)notification { 131 bubbleController_ = nil; 132} 133 134- (void)displayHover { 135 [bubbleController_ close]; 136 bubbleController_ = 137 [[AutofillBubbleController alloc] 138 initWithParentWindow:[[self view] window] 139 message:[self message] 140 inset:NSMakeSize(kTooltipInset, kTooltipInset) 141 arrowLocation:arrowLocation_]; 142 [bubbleController_ setShouldCloseOnResignKey:NO]; 143 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]]; 150 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]]; 159 [oldContentView 160 setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; 161 [[bubbleController_ window] setContentView:trackingView]; 162 [trackingView setSubviews:@[ oldContentView ]]; 163 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]; 170} 171 172- (void)hideHover { 173 [bubbleController_ close]; 174} 175 176- (void)setHoveringOnBubble:(BOOL)hoveringOnBubble { 177 isHoveringOnBubble_ = hoveringOnBubble; 178 [self updateTooltipDisplayState]; 179} 180 181- (void)updateTooltipDisplayState { 182 BOOL newDisplayState = 183 ([view_ hoverState] != kHoverStateNone || isHoveringOnBubble_); 184 185 if (newDisplayState != shouldDisplayTooltip_) { 186 shouldDisplayTooltip_ = newDisplayState; 187 188 // Cancel any pending visibility changes. 189 [NSObject cancelPreviousPerformRequestsWithTarget:self]; 190 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]; 197 } 198 } 199} 200 201@end 202