1 // Copyright (c) 2010 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 "ui/base/cocoa/hover_button.h" 6 7 @implementation HoverButton 8 9 @synthesize hoverState = hoverState_; 10 11 - (id)initWithFrame:(NSRect)frameRect { 12 if ((self = [super initWithFrame:frameRect])) { 13 [self setTrackingEnabled:YES]; 14 hoverState_ = kHoverStateNone; 15 [self updateTrackingAreas]; 16 } 17 return self; 18 } 19 20 - (void)awakeFromNib { 21 [self setTrackingEnabled:YES]; 22 self.hoverState = kHoverStateNone; 23 [self updateTrackingAreas]; 24 } 25 26 - (void)dealloc { 27 [self setTrackingEnabled:NO]; 28 [super dealloc]; 29 } 30 31 - (void)mouseEntered:(NSEvent*)theEvent { 32 if (trackingArea_.get()) 33 self.hoverState = kHoverStateMouseOver; 34 } 35 36 - (void)mouseExited:(NSEvent*)theEvent { 37 if (trackingArea_.get()) 38 self.hoverState = kHoverStateNone; 39 } 40 41 - (void)mouseDown:(NSEvent*)theEvent { 42 self.hoverState = kHoverStateMouseDown; 43 // The hover button needs to hold onto itself here for a bit. Otherwise, 44 // it can be freed while |super mouseDown:| is in its loop, and the 45 // |checkImageState| call will crash. 46 // http://crbug.com/28220 47 base::scoped_nsobject<HoverButton> myself([self retain]); 48 49 [super mouseDown:theEvent]; 50 // We need to check the image state after the mouseDown event loop finishes. 51 // It's possible that we won't get a mouseExited event if the button was 52 // moved under the mouse during tab resize, instead of the mouse moving over 53 // the button. 54 // http://crbug.com/31279 55 [self checkImageState]; 56 } 57 58 - (void)setTrackingEnabled:(BOOL)enabled { 59 if (enabled) { 60 trackingArea_.reset( 61 [[CrTrackingArea alloc] initWithRect:NSZeroRect 62 options:NSTrackingMouseEnteredAndExited | 63 NSTrackingActiveAlways | 64 NSTrackingInVisibleRect 65 owner:self 66 userInfo:nil]); 67 [self addTrackingArea:trackingArea_.get()]; 68 69 // If you have a separate window that overlaps the close button, and you 70 // move the mouse directly over the close button without entering another 71 // part of the tab strip, we don't get any mouseEntered event since the 72 // tracking area was disabled when we entered. 73 // Done with a delay of 0 because sometimes an event appears to be missed 74 // between the activation of the tracking area and the call to 75 // checkImageState resulting in the button state being incorrect. 76 [self performSelector:@selector(checkImageState) 77 withObject:nil 78 afterDelay:0]; 79 } else { 80 if (trackingArea_.get()) { 81 [self removeTrackingArea:trackingArea_.get()]; 82 trackingArea_.reset(nil); 83 } 84 } 85 } 86 87 - (void)updateTrackingAreas { 88 [super updateTrackingAreas]; 89 [self checkImageState]; 90 } 91 92 - (void)checkImageState { 93 if (!trackingArea_.get()) 94 return; 95 96 // Update the button's state if the button has moved. 97 NSPoint mouseLoc = [[self window] mouseLocationOutsideOfEventStream]; 98 mouseLoc = [self convertPoint:mouseLoc fromView:nil]; 99 self.hoverState = NSPointInRect(mouseLoc, [self bounds]) ? 100 kHoverStateMouseOver : kHoverStateNone; 101 } 102 103 - (void)setHoverState:(HoverState)state { 104 hoverState_ = state; 105 [self setNeedsDisplay:YES]; 106 } 107 108 @end 109