1// Copyright (c) 2011 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/image_button_cell.h" 6 7#include "base/logging.h" 8#import "chrome/browser/themes/theme_service.h" 9#import "chrome/browser/ui/cocoa/nsview_additions.h" 10#import "chrome/browser/ui/cocoa/rect_path_utils.h" 11#import "chrome/browser/ui/cocoa/themed_window.h" 12#include "ui/base/resource/resource_bundle.h" 13#include "ui/gfx/image/image.h" 14#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 15 16// When the window doesn't have focus then we want to draw the button with a 17// slightly lighter color. We do this by just reducing the alpha. 18const CGFloat kImageNoFocusAlpha = 0.65; 19 20@interface ImageButtonCell (Private) 21- (void)sharedInit; 22- (image_button_cell::ButtonState)currentButtonState; 23- (NSImage*)imageForID:(NSInteger)imageID 24 controlView:(NSView*)controlView; 25@end 26 27@implementation ImageButtonCell 28 29@synthesize isMouseInside = isMouseInside_; 30 31// For nib instantiations 32- (id)initWithCoder:(NSCoder*)decoder { 33 if ((self = [super initWithCoder:decoder])) { 34 [self sharedInit]; 35 } 36 return self; 37} 38 39// For programmatic instantiations 40- (id)initTextCell:(NSString*)string { 41 if ((self = [super initTextCell:string])) { 42 [self sharedInit]; 43 } 44 return self; 45} 46 47- (void)sharedInit { 48 [self setHighlightsBy:NSNoCellMask]; 49 50 // We need to set this so that we can override |-mouseEntered:| and 51 // |-mouseExited:| to change the button image on hover states. 52 [self setShowsBorderOnlyWhileMouseInside:YES]; 53} 54 55- (NSImage*)imageForState:(image_button_cell::ButtonState)state 56 view:(NSView*)controlView{ 57 if (image_[state].imageId) 58 return [self imageForID:image_[state].imageId controlView:controlView]; 59 return image_[state].image; 60} 61 62- (void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 63 image_button_cell::ButtonState state = [self currentButtonState]; 64 BOOL windowHasFocus = [[controlView window] isMainWindow] || 65 [[controlView window] isKeyWindow]; 66 CGFloat alpha = [self imageAlphaForWindowState:[controlView window]]; 67 NSImage* image = [self imageForState:state view:controlView]; 68 69 if (!windowHasFocus) { 70 NSImage* defaultImage = [self 71 imageForState:image_button_cell::kDefaultStateBackground 72 view:controlView]; 73 NSImage* hoverImage = [self 74 imageForState:image_button_cell::kHoverStateBackground 75 view:controlView]; 76 if ([self currentButtonState] == image_button_cell::kDefaultState && 77 defaultImage) { 78 image = defaultImage; 79 alpha = 1.0; 80 } else if ([self currentButtonState] == image_button_cell::kHoverState && 81 hoverImage) { 82 image = hoverImage; 83 alpha = 1.0; 84 } 85 } 86 87 NSRect imageRect; 88 imageRect.size = [image size]; 89 imageRect.origin.x = cellFrame.origin.x + 90 roundf((NSWidth(cellFrame) - NSWidth(imageRect)) / 2.0); 91 imageRect.origin.y = cellFrame.origin.y + 92 roundf((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); 93 94 [image drawInRect:imageRect 95 fromRect:NSZeroRect 96 operation:NSCompositeSourceOver 97 fraction:alpha 98 respectFlipped:YES 99 hints:nil]; 100} 101 102- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 103 [self drawImageWithFrame:cellFrame inView:controlView]; 104 // Only draw custom focus ring if the 10.7 focus ring APIs are not available. 105 // TODO(groby): Remove once we build against the 10.7 SDK. 106 if (![self respondsToSelector:@selector(drawFocusRingMaskWithFrame:inView:)]) 107 [self drawFocusRingWithFrame:cellFrame inView:controlView]; 108} 109 110- (void)setImageID:(NSInteger)imageID 111 forButtonState:(image_button_cell::ButtonState)state { 112 DCHECK_GE(state, 0); 113 DCHECK_LT(state, image_button_cell::kButtonStateCount); 114 115 image_[state].image.reset(); 116 image_[state].imageId = imageID; 117 [[self controlView] setNeedsDisplay:YES]; 118} 119 120// Sets the image for the given button state using an image. 121- (void)setImage:(NSImage*)image 122 forButtonState:(image_button_cell::ButtonState)state { 123 DCHECK_GE(state, 0); 124 DCHECK_LT(state, image_button_cell::kButtonStateCount); 125 126 image_[state].image.reset([image retain]); 127 image_[state].imageId = 0; 128 [[self controlView] setNeedsDisplay:YES]; 129} 130 131- (CGFloat)imageAlphaForWindowState:(NSWindow*)window { 132 BOOL windowHasFocus = [window isMainWindow] || [window isKeyWindow]; 133 return windowHasFocus ? 1.0 : kImageNoFocusAlpha; 134} 135 136- (void)drawFocusRingWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 137 if (![self showsFirstResponder]) 138 return; 139 gfx::ScopedNSGraphicsContextSaveGState scoped_state; 140 const CGFloat lineWidth = [controlView cr_lineWidth]; 141 rect_path_utils::FrameRectWithInset(rect_path_utils::RoundedCornerAll, 142 NSInsetRect(cellFrame, 0, lineWidth), 143 0.0, // insetX 144 0.0, // insetY 145 3.0, // outerRadius 146 lineWidth * 2, // lineWidth 147 [controlView 148 cr_keyboardFocusIndicatorColor]); 149} 150 151- (image_button_cell::ButtonState)currentButtonState { 152 bool (^has)(image_button_cell::ButtonState) = 153 ^(image_button_cell::ButtonState state) { 154 return image_[state].image || image_[state].imageId; 155 }; 156 if (![self isEnabled] && has(image_button_cell::kDisabledState)) 157 return image_button_cell::kDisabledState; 158 if ([self isHighlighted] && has(image_button_cell::kPressedState)) 159 return image_button_cell::kPressedState; 160 if ([self isMouseInside] && has(image_button_cell::kHoverState)) 161 return image_button_cell::kHoverState; 162 return image_button_cell::kDefaultState; 163} 164 165- (NSImage*)imageForID:(NSInteger)imageID 166 controlView:(NSView*)controlView { 167 if (!imageID) 168 return nil; 169 170 ui::ThemeProvider* themeProvider = [[controlView window] themeProvider]; 171 if (!themeProvider) 172 return nil; 173 174 return themeProvider->GetNSImageNamed(imageID); 175} 176 177- (void)setIsMouseInside:(BOOL)isMouseInside { 178 if (isMouseInside_ != isMouseInside) { 179 isMouseInside_ = isMouseInside; 180 NSView<ImageButton>* control = 181 static_cast<NSView<ImageButton>*>([self controlView]); 182 if ([control respondsToSelector:@selector(mouseInsideStateDidChange:)]) { 183 [control mouseInsideStateDidChange:isMouseInside]; 184 } 185 [control setNeedsDisplay:YES]; 186 } 187} 188 189- (void)setShowsBorderOnlyWhileMouseInside:(BOOL)show { 190 VLOG_IF(1, !show) << "setShowsBorderOnlyWhileMouseInside:NO ignored"; 191} 192 193- (BOOL)showsBorderOnlyWhileMouseInside { 194 // Always returns YES so that buttons always get mouse tracking even when 195 // disabled. The reload button (and possibly others) depend on this. 196 return YES; 197} 198 199- (void)mouseEntered:(NSEvent*)theEvent { 200 [self setIsMouseInside:YES]; 201} 202 203- (void)mouseExited:(NSEvent*)theEvent { 204 [self setIsMouseInside:NO]; 205} 206 207@end 208